diff --git a/src/app/api/portofolio/[id]/route.ts b/src/app/api/portofolio/[id]/route.ts
index d6d46d28..0760c303 100644
--- a/src/app/api/portofolio/[id]/route.ts
+++ b/src/app/api/portofolio/[id]/route.ts
@@ -13,6 +13,18 @@ async function GET(request: Request, { params }: { params: { id: string } }) {
id: id,
},
include: {
+ Portofolio_BidangDanSubBidangBisnis: {
+ select: {
+ id: true,
+ MasterSubBidangBisnis: {
+ select: {
+ id: true,
+ name: true,
+ masterBidangBisnisId: true,
+ },
+ },
+ },
+ },
MasterBidangBisnis: {
select: {
id: true,
@@ -84,10 +96,26 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
const { id } = params;
const { data } = await request.json();
- const udpateData = await prisma.portofolio.update({
- where: {
- id: id,
+ const checkData = await prisma.portofolio.findUnique({
+ where: { id },
+ include: {
+ Portofolio_BidangDanSubBidangBisnis: true,
},
+ });
+
+ if (!checkData) {
+ return NextResponse.json(
+ {
+ success: false,
+ message: "Data tidak ditemukan",
+ },
+ { status: 404 }
+ );
+ }
+
+ // Update data utama portofolio
+ const updatePortofolio = await prisma.portofolio.update({
+ where: { id },
data: {
namaBisnis: data.namaBisnis,
alamatKantor: data.alamatKantor,
@@ -97,25 +125,77 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
});
- if (!udpateData)
- return NextResponse.json(
- {
- success: false,
- message: "Gagal update data",
- },
- { status: 400 }
+ const bidangBerubah =
+ checkData.masterBidangBisnisId !== data.masterBidangBisnisId;
+
+ if (bidangBerubah) {
+ // Bidang berubah → hapus semua sub bidang lama
+ await prisma.portofolio_BidangDanSubBidangBisnis.deleteMany({
+ where: { portofolioId: id },
+ });
+
+ // Tambahkan sub bidang baru
+ for (const sub of data.subBidang) {
+ await prisma.portofolio_BidangDanSubBidangBisnis.create({
+ data: {
+ portofolioId: id,
+ masterBidangBisnisId: data.masterBidangBisnisId,
+ masterSubBidangBisnisId: sub.MasterSubBidangBisnis.id,
+ },
+ });
+ }
+ } else {
+ // Bidang tidak berubah → sinkronisasi sub bidang
+
+ const existingSub = checkData.Portofolio_BidangDanSubBidangBisnis;
+
+ const incomingIds = data.subBidang.map(
+ (sub: any) => sub.MasterSubBidangBisnis.id
);
+ const existingIds = existingSub.map(
+ (item) => item.masterSubBidangBisnisId
+ );
+
+ // 1. Hapus sub bidang yang sudah tidak dipilih
+ const toDelete = existingSub.filter(
+ (item) => !incomingIds.includes(item.masterSubBidangBisnisId)
+ );
+
+ await prisma.portofolio_BidangDanSubBidangBisnis.deleteMany({
+ where: {
+ id: {
+ in: toDelete.map((item) => item.id),
+ },
+ },
+ });
+
+ // 2. Tambahkan sub bidang baru yang belum ada di DB
+ const toCreate = data.subBidang.filter(
+ (sub: any) => !existingIds.includes(sub.MasterSubBidangBisnis.id)
+ );
+
+ for (const sub of toCreate) {
+ await prisma.portofolio_BidangDanSubBidangBisnis.create({
+ data: {
+ portofolioId: id,
+ masterBidangBisnisId: data.masterBidangBisnisId,
+ masterSubBidangBisnisId: sub.MasterSubBidangBisnis.id,
+ },
+ });
+ }
+ }
+
return NextResponse.json(
{
success: true,
- message: "Berhasil mendapatkan data",
- data: udpateData,
+ message: "Berhasil update data",
+ data: updatePortofolio,
},
-
{ status: 200 }
);
} catch (error) {
+ backendLogger.error("Error update data portofolio", error);
return NextResponse.json(
{
success: false,
@@ -127,6 +207,8 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
}
}
+
+
async function POST(request: Request, { params }: { params: { id: string } }) {
if (request.method !== "POST") {
return NextResponse.json(
@@ -156,7 +238,6 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
});
for (let i of data.subBidang) {
- // console.log("sub bidang", i.id)
const createSubBidang =
await prisma.portofolio_BidangDanSubBidangBisnis.create({
data: {
@@ -166,7 +247,6 @@ async function POST(request: Request, { params }: { params: { id: string } }) {
},
});
-
if (!createSubBidang)
return NextResponse.json(
{
diff --git a/src/app/dev/(user)/portofolio/edit/data/[id]/page.tsx b/src/app/dev/(user)/portofolio/edit/data/[id]/page.tsx
index 625d8ee4..23e0b3c4 100644
--- a/src/app/dev/(user)/portofolio/edit/data/[id]/page.tsx
+++ b/src/app/dev/(user)/portofolio/edit/data/[id]/page.tsx
@@ -1,9 +1,11 @@
import { Portofolio_EditDataBisnis } from "@/app_modules/katalog/portofolio";
+import Portofolio_V3_EditDataBisnis from "@/app_modules/katalog/portofolio/edit/data/ui_new_edit_data";
export default async function Page() {
return (
<>
-
+ {/* */}
+
>
);
}
diff --git a/src/app_modules/katalog/portofolio/create/new_create.tsx b/src/app_modules/katalog/portofolio/create/new_create.tsx
index 1bb10804..37ee476b 100644
--- a/src/app_modules/katalog/portofolio/create/new_create.tsx
+++ b/src/app_modules/katalog/portofolio/create/new_create.tsx
@@ -36,6 +36,7 @@ import { Portofolio_ComponentButtonSelanjutnya } from "../component";
import { apiGetSubBidangBisnis } from "../lib/api_portofolio";
import { MODEL_PORTOFOLIO_BIDANG_BISNIS } from "../model/interface";
+
export default function Portofolio_V3_Create() {
const params = useParams<{ id: string }>();
const profileId = params.id;
@@ -172,9 +173,6 @@ export default function Portofolio_V3_Create() {
setDataPortofolio({
...dataPortofolio,
masterBidangBisnisId: val,
- // masterSubBidangBisnisId: isSameBidang
- // ? dataPortofolio.masterSubBidangBisnisId
- // : "",
});
// Jika berbeda bidang, reset sub bidang ke satu input kosong
diff --git a/src/app_modules/katalog/portofolio/edit/data/ui_new_edit_data.tsx b/src/app_modules/katalog/portofolio/edit/data/ui_new_edit_data.tsx
new file mode 100644
index 00000000..47a178e5
--- /dev/null
+++ b/src/app_modules/katalog/portofolio/edit/data/ui_new_edit_data.tsx
@@ -0,0 +1,610 @@
+"use client";
+
+import { MainColor } from "@/app_modules/_global/color/color_pallet";
+import ComponentGlobal_ErrorInput from "@/app_modules/_global/component/error_input";
+import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown";
+import { apiGetMasterBidangBisnis } from "@/app_modules/_global/lib/api_fetch_master";
+import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global";
+import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil";
+import { ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global/notifikasi_gagal";
+import { clientLogger } from "@/util/clientLogger";
+import {
+ ActionIcon,
+ Box,
+ Button,
+ Center,
+ Grid,
+ Select,
+ Stack,
+ Styles,
+ Text,
+ TextInput,
+ Textarea,
+} from "@mantine/core";
+import { useShallowEffect } from "@mantine/hooks";
+import _ from "lodash";
+import { useParams, useRouter } from "next/navigation";
+import { useState, useEffect } from "react";
+import { PhoneInput } from "react-international-phone";
+import {
+ apiGetPortofolioById,
+ apiUpdatePortofolioById,
+} from "../../component/api_fetch_portofolio";
+import { Portofolio_SkeletonEditDataBisnis } from "../../component/skeleton_view";
+import {
+ MODEL_PORTOFOLIO,
+ MODEL_PORTOFOLIO_BIDANG_BISNIS,
+} from "../../model/interface";
+import { ISUB_BIDANG_BISNIS } from "@/app_modules/model_global/portofolio";
+import { apiGetSubBidangBisnis } from "../../lib/api_portofolio";
+import { BaseSelectStylesNames } from "@mantine/core/lib/Select/types";
+import { IconPlus, IconRowRemove, IconTrash } from "@tabler/icons-react";
+import Component_V3_Label_TextInput from "@/app_modules/_global/component/new/comp_V3_label_text_input";
+
+interface SubBidangSelected {
+ id: string;
+ MasterSubBidangBisnis: {
+ id: string;
+ name: string;
+ };
+}
+
+interface IUpdatePortofoli {
+ namaBisnis: string;
+ alamatKantor: string;
+ tlpn: string;
+ deskripsi: string;
+ masterBidangBisnisId: string;
+ subBidang: SubBidangSelected[];
+}
+
+export default function Portofolio_EditDataBisnis() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+
+ const params = useParams<{ id: string }>();
+ const portofolioId = params.id;
+ const [data, setData] = useState(null);
+ const [listBidang, setListBidang] = useState<
+ MODEL_PORTOFOLIO_BIDANG_BISNIS[]
+ >([]);
+
+ const [listSubBidang, setListSubBidang] = useState<
+ ISUB_BIDANG_BISNIS[] | null
+ >(null);
+
+ const [selectedSubBidang, setSelectedSubBidang] = useState<
+ ISUB_BIDANG_BISNIS[] | null
+ >(null);
+
+ const [listSubBidangSelected, setListSubBidangSelected] = useState<
+ SubBidangSelected[]
+ >([
+ {
+ id: "",
+ MasterSubBidangBisnis: {
+ id: "",
+ name: "",
+ },
+ },
+ ]);
+
+ useShallowEffect(() => {
+ onLoadData();
+ onLoadBidang();
+ onLoadMasterSubBidangBisnis();
+ }, []);
+
+ const onLoadData = async () => {
+ try {
+ const respone = await apiGetPortofolioById({
+ id: portofolioId,
+ });
+
+ if (respone.success) {
+ setData(respone.data);
+
+ // Cek apakah ada sub bidang bisnis yang terpilih
+ const subBidangData = respone.data.Portofolio_BidangDanSubBidangBisnis;
+
+ // Jika ada sub bidang, gunakan data tersebut
+ if (subBidangData && subBidangData.length > 0) {
+ setListSubBidangSelected(subBidangData);
+ } else {
+ // Jika tidak ada sub bidang yang terpilih sebelumnya, tetap inisialisasi dengan array kosong
+ setListSubBidangSelected([
+ {
+ id: "",
+ MasterSubBidangBisnis: {
+ id: "",
+ name: "",
+ },
+ },
+ ]);
+ }
+
+ const bisnisId = respone.data.MasterBidangBisnis.id;
+ if (bisnisId) {
+ handlerLoadSelectedSubBidang({ id: bisnisId });
+ }
+ } else {
+ setData(null);
+ }
+ } catch (error) {
+ clientLogger.error("Error get data portofolio", error);
+ }
+ };
+
+ const onLoadBidang = async () => {
+ try {
+ const respone = await apiGetMasterBidangBisnis();
+
+ if (respone.success) {
+ setListBidang(respone.data);
+ } else {
+ setListBidang([]);
+ }
+ } catch (error) {
+ clientLogger.error("Error get data master bidang bisnis", error);
+ }
+ };
+
+ async function onLoadMasterSubBidangBisnis() {
+ try {
+ const response = await apiGetSubBidangBisnis({});
+
+ if (response.success) {
+ setListSubBidang(response.data);
+ }
+ } catch (error) {
+ console.error("Error on load master sub bidang bisnis", error);
+ }
+ }
+
+ const validateData = async (data: any) => {
+ if (_.values(data).includes("")) {
+ return "Lengkapi data";
+ }
+
+ if (data?.tlpn?.length < 8) {
+ return "Nomor telepon minimal 8 digit";
+ }
+
+ // Validasi sub bidang bisnis yang dipilih
+ // Pastikan setidaknya satu sub bidang dipilih
+ const validSubBidangCount = listSubBidangSelected.filter(
+ (item) => item.MasterSubBidangBisnis.id
+ ).length;
+
+ if (validSubBidangCount === 0) {
+ return "Pilih minimal satu sub bidang bisnis";
+ }
+
+ return null;
+ };
+
+ const hanldeUpadteData = async (data: any) => {
+ try {
+ // Filter list sub bidang yang dipilih (hanya ambil yang memiliki id)
+ const validSubBidang = listSubBidangSelected.filter(
+ (item) => item.MasterSubBidangBisnis.id
+ );
+
+ const newData: IUpdatePortofoli = {
+ namaBisnis: data?.namaBisnis,
+ alamatKantor: data?.alamatKantor,
+ tlpn: data?.tlpn,
+ deskripsi: data?.deskripsi,
+ masterBidangBisnisId: data?.MasterBidangBisnis.id,
+ subBidang: validSubBidang, // Hanya kirim sub bidang yang valid
+ };
+
+ const respone = await apiUpdatePortofolioById({
+ data: newData,
+ id: portofolioId,
+ });
+
+ return respone;
+ } catch (error) {
+ console.error("Error update data portofolio", error);
+ return null;
+ }
+ };
+
+ const submitUpdate = async () => {
+ const validate = await validateData(data);
+ if (validate) {
+ ComponentGlobal_NotifikasiPeringatan(validate);
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const updateData = await hanldeUpadteData(data);
+
+ if (updateData.success) {
+ ComponentGlobal_NotifikasiBerhasil(updateData.message);
+ router.back();
+ } else {
+ setLoading(false);
+ ComponentGlobal_NotifikasiGagal(updateData.message);
+ }
+ } catch (error) {
+ setLoading(false);
+ console.error("Error update data portofolio", error);
+ }
+ };
+
+ const baseStyles: Styles> = {
+ label: {
+ color: MainColor.white,
+ },
+ input: {
+ backgroundColor: MainColor.white,
+ },
+ };
+
+ // Handler untuk perubahan bidang bisnis
+ const handleBidangBisnisChange = (val: string) => {
+ const isSameBidang = data?.MasterBidangBisnis?.id === val;
+
+ setData({
+ ...(data as any),
+ MasterBidangBisnis: {
+ id: val,
+ },
+ });
+
+ // Reset sub bidang jika ganti bidang
+ if (!isSameBidang) {
+ setListSubBidangSelected([
+ {
+ id: "",
+ MasterSubBidangBisnis: { id: "", name: "" },
+ },
+ ]);
+ }
+
+ handlerLoadSelectedSubBidang({ id: val });
+ };
+
+ // Handler untuk saat komponen pertama kali load
+ const handlerLoadSelectedSubBidang = ({ id }: { id: string }) => {
+ if (!listSubBidang) return;
+
+ const filteredSubBidang = listSubBidang.filter(
+ (item) => item.masterBidangBisnisId === id
+ );
+
+ setSelectedSubBidang(filteredSubBidang);
+ };
+
+ // Handler untuk menambah sub bidang bisnis
+ const handleAddSubBidang = () => {
+ setListSubBidangSelected([
+ ...listSubBidangSelected,
+ {
+ id: "",
+ MasterSubBidangBisnis: { id: "", name: "" },
+ },
+ ]);
+ };
+
+ // Handler untuk menghapus sub bidang bisnis
+ const handleRemoveSubBidang = (index: number) => {
+ if (listSubBidangSelected.length <= 1) return;
+
+ const updatedList = [...listSubBidangSelected];
+ updatedList.splice(index, 1);
+ setListSubBidangSelected(updatedList);
+ };
+
+ // Handler untuk update sub bidang
+ const handleSubBidangChange = (val: string, index: number) => {
+ const selected = selectedSubBidang?.find((s) => s.id === val);
+ const list = _.cloneDeep(listSubBidangSelected);
+
+ list[index] = {
+ id: "",
+ MasterSubBidangBisnis: selected || {
+ id: val,
+ name: "",
+ },
+ };
+
+ setListSubBidangSelected(list);
+ };
+
+ // Effect untuk mengupdate selectedSubBidang saat listSubBidang berubah
+ useShallowEffect(() => {
+ if (data?.MasterBidangBisnis?.id && listSubBidang) {
+ handlerLoadSelectedSubBidang({ id: data.MasterBidangBisnis.id });
+ }
+ }, [listSubBidang, data?.MasterBidangBisnis?.id]);
+
+ if (!data || !listBidang || !listSubBidang)
+ return ;
+
+ return (
+ <>
+
+
+
+ ) : (
+ ""
+ )
+ }
+ onChange={(val) => {
+ setData({
+ ...data,
+ namaBisnis: val.target.value,
+ });
+ }}
+ />
+
+ {/* Select Bidang dan Sub Bidang */}
+
+
+
+
+ >
+ );
+}
diff --git a/src/app_modules/katalog/portofolio/model/interface.ts b/src/app_modules/katalog/portofolio/model/interface.ts
index 6c3a62f3..404a6f07 100644
--- a/src/app_modules/katalog/portofolio/model/interface.ts
+++ b/src/app_modules/katalog/portofolio/model/interface.ts
@@ -2,6 +2,8 @@ import { MODEL_USER } from "@/app_modules/home/model/interface";
import { MODEL_IMAGES } from "@/app_modules/model_global/interface";
import { MODEL_PROFILE } from "../../profile/model/interface";
import { MODEL_MAP } from "@/app_modules/map/lib/interface";
+import { ISUB_BIDANG_BISNIS } from "@/app_modules/model_global/portofolio";
+import { Prisma } from "@prisma/client";
export interface MODEL_PORTOFOLIO {
id: string;
@@ -12,6 +14,7 @@ export interface MODEL_PORTOFOLIO {
active: boolean;
MasterBidangBisnis: MODEL_PORTOFOLIO_BIDANG_BISNIS;
masterBidangBisnisId: string;
+ Portofolio_BidangDanSubBidangBisnis: Portofolio_BidangDanSubBidangBisnis;
profileId: string;
Logo: MODEL_LOGO;
logoId: string;
@@ -34,7 +37,7 @@ export interface MODEL_PORTOFOLIO_BIDANG_BISNIS {
id: string;
name: string;
active: boolean;
- slug: string
+ slug: string;
}
export interface MODEL_PORTOFOLIO_MEDSOS {
@@ -49,3 +52,29 @@ export interface MODEL_PORTOFOLIO_MEDSOS {
updatedAt: Date;
portofolioId: string;
}
+
+export type Portofolio_BidangDanSubBidangBisnis =
+ Prisma.Portofolio_BidangDanSubBidangBisnisGetPayload<{
+ select: {
+ id: true,
+ masterBidangBisnisId?: true,
+ masterSubBidangBisnisId?: true,
+ isActive: true
+ MasterBidangBisnis: {
+ select: {
+ id?: true;
+ name?: true;
+ };
+ };
+ MasterSubBidangBisnis: {
+ select: {
+ id?: true;
+ name?: true;
+ };
+ };
+
+ };
+ // include: {
+
+ // };
+ }>;
diff --git a/types/env.d.ts b/types/env.d.ts
new file mode 100644
index 00000000..d85d4b56
--- /dev/null
+++ b/types/env.d.ts
@@ -0,0 +1,15 @@
+declare namespace NodeJS {
+ interface ProcessEnv {
+ DATABASE_URL?: string;
+ WIBU_PWD?: string;
+ Client_KEY?: string;
+ Server_KEY?: string;
+ MAPBOX_TOKEN?: string;
+ WS_APIKEY?: string;
+ NEXT_PUBLIC_WIBU_REALTIME_TOKEN?: string;
+ NEXT_PUBLIC_BASE_TOKEN_KEY?: string;
+ NEXT_PUBLIC_BASE_SESSION_KEY?: string;
+ NEXT_PUBLIC_API_URL?: string;
+ }
+}
+