From 13f76dc8b50b71b84f1ef99a2a61de8a752b20d9 Mon Sep 17 00:00:00 2001 From: Anang Yusman Date: Mon, 19 Jan 2026 19:25:14 +0800 Subject: [PATCH] update --- app/(admin)/admin/agent/detail/[id]/page.tsx | 12 + app/(admin)/admin/banner/detail/[id]/page.tsx | 11 - .../admin/product/detail/[id]/page.tsx | 12 + components/dialog/galery-detail-dialog.tsx | 301 +++++--- components/dialog/promo-dialog.tsx | 358 +++++++-- components/form/agent/detail-agent-form.tsx | 275 +++++++ components/form/agent/update-agent-form.tsx | 186 ++--- .../form/product/create-product-form.tsx | 17 +- .../form/product/detail-product-form.tsx | 700 ++++++++++++++++++ .../landing-page/retracting-sidedar.tsx | 26 +- components/table/agent-table.tsx | 18 +- components/table/article-table.tsx | 154 +++- components/table/product-table.tsx | 62 +- components/table/promotion-table.tsx | 18 +- service/product.ts | 8 + 15 files changed, 1792 insertions(+), 366 deletions(-) create mode 100644 app/(admin)/admin/agent/detail/[id]/page.tsx create mode 100644 app/(admin)/admin/product/detail/[id]/page.tsx create mode 100644 components/form/agent/detail-agent-form.tsx create mode 100644 components/form/product/detail-product-form.tsx diff --git a/app/(admin)/admin/agent/detail/[id]/page.tsx b/app/(admin)/admin/agent/detail/[id]/page.tsx new file mode 100644 index 0000000..e9a4b5c --- /dev/null +++ b/app/(admin)/admin/agent/detail/[id]/page.tsx @@ -0,0 +1,12 @@ +import DetailAgentForm from "@/components/form/agent/detail-agent-form"; +import DetailProductForm from "@/components/form/product/detail-product-form"; + +export default function DetailAgentPage() { + return ( +
+
+ +
+
+ ); +} diff --git a/app/(admin)/admin/banner/detail/[id]/page.tsx b/app/(admin)/admin/banner/detail/[id]/page.tsx index 62d5a75..a502c0a 100644 --- a/app/(admin)/admin/banner/detail/[id]/page.tsx +++ b/app/(admin)/admin/banner/detail/[id]/page.tsx @@ -3,17 +3,6 @@ import EditArticleForm from "@/components/form/article/edit-article-form"; export default function DetailArticlePage() { return (
- {/*
-
-

Article

-

Article

-
- - - - - -
*/}
diff --git a/app/(admin)/admin/product/detail/[id]/page.tsx b/app/(admin)/admin/product/detail/[id]/page.tsx new file mode 100644 index 0000000..7c756df --- /dev/null +++ b/app/(admin)/admin/product/detail/[id]/page.tsx @@ -0,0 +1,12 @@ +import EditArticleForm from "@/components/form/article/edit-article-form"; +import DetailProductForm from "@/components/form/product/detail-product-form"; + +export default function DetailProductPage() { + return ( +
+
+ +
+
+ ); +} diff --git a/components/dialog/galery-detail-dialog.tsx b/components/dialog/galery-detail-dialog.tsx index 41416ef..37e47cf 100644 --- a/components/dialog/galery-detail-dialog.tsx +++ b/components/dialog/galery-detail-dialog.tsx @@ -14,6 +14,7 @@ import { getGaleryFileData } from "@/service/galery"; export function DialogDetailGaleri({ open, onClose, data }: any) { const [images, setImages] = useState([]); + const [openApproverHistory, setOpenApproverHistory] = useState(false); const fetchImages = async () => { try { @@ -21,7 +22,7 @@ export function DialogDetailGaleri({ open, onClose, data }: any) { const allImages = res?.data?.data ?? []; const filteredImages = allImages.filter( - (img: any) => img.gallery_id === data.id + (img: any) => img.gallery_id === data.id, ); setImages(filteredImages); @@ -39,108 +40,232 @@ export function DialogDetailGaleri({ open, onClose, data }: any) { const openFile = (url: string) => { window.open(url, "_blank"); }; + const handleOpenApproverHistory = () => { + setOpenApproverHistory(true); + }; return ( - - - {/* Header */} -
- Detail Galeri -
- -
- {/* Images List */} -
-

Daftar Gambar

- -
- {images.length === 0 && ( -

- Tidak ada gambar. -

- )} - - {images.map((img) => ( -
openFile(img.image_url)} - > - {img.title} - -
- Lihat File -
-
- ))} -
+ <> + + + {/* Header */} +
+ Detail Galeri
- {/* Title */} -

- {data.title} -

- - {/* Deskripsi */} -
-

Deskripsi

-

{data.description}

-
- - {/* Tanggal Upload */} -
-

Tanggal Upload

-

- {new Date(data.created_at).toLocaleDateString("id-ID")} -

-
- - {/* Timeline */} -
-

- Status Timeline -

- -
-
- -
-

Upload Berhasil

-

- {new Date(data.created_at).toLocaleString("id-ID")} -

-
+
+ {/* Images List */} +
+

+ {data.title} +

+
+

Deskripsi

+

{data.description}

+
+ {images.length === 0 && ( +

+ Tidak ada gambar. +

+ )} - {data.approved_at && ( + {images.map((img) => ( +
openFile(img.image_url)} + > + {img.title} + +
+ Lihat File +
+
+ ))} +
+
+ + {/* Title */} + + {/* Deskripsi */} + + {/* Tanggal Upload */} +
+

+ Tanggal Upload +

+

+ {new Date(data.created_at).toLocaleDateString("id-ID")} +

+
+ + {/* Timeline */} +
+

+ Status Timeline +

+ +
-

Disetujui oleh Approver

+

Diupload Oleh :

- {new Date(data.approved_at).toLocaleString("id-ID")} + {new Date(data.created_at).toLocaleString("id-ID")}

- )} +
+ +
+

Disetujui Oleh :

+

+ {new Date(data.created_at).toLocaleString("id-ID")} +

+
+
+ + {data.approved_at && ( +
+ +
+

Disetujui oleh Approver

+

+ {new Date(data.approved_at).toLocaleString("id-ID")} +

+
+
+ )} +
+
+
+

Comment :

+
+ +

Jaecoo - Approver | 10/11/2026

+
+
+
+ + {/* + + */} + +
+ {openApproverHistory && ( +
setOpenApproverHistory(false)} + > +
e.stopPropagation()} + > + {/* HEADER */} +
+ + +

Approver History

+ +
+ + Menunggu + + + Banner + + + 1 + +
+
+ + {/* BODY */} +
+ {/* LEFT TIMELINE */} +
+ {/* Upload */} +
+ + Upload + +
+
+ + {/* Diterima */} +
+

Diterima

+ + Direview oleh: approver-jaecoo1 + +
+ +
+ + {/* Pending */} +
+

Pending

+ + Direview oleh: approver-jaecoo1 + +
+
+ + {/* ARROW */} +
+ > + > +
+ + {/* RIGHT NOTES */} +
+
+
+ Catatan: +
+
+ +
+
+ Catatan: +
+
+
+
+ + {/* FOOTER */} +
+
- - - - - -
+ )} + ); } diff --git a/components/dialog/promo-dialog.tsx b/components/dialog/promo-dialog.tsx index ea2693b..d6ebb9b 100644 --- a/components/dialog/promo-dialog.tsx +++ b/components/dialog/promo-dialog.tsx @@ -23,6 +23,7 @@ export default function PromoDetailDialog({ }: PromoDetailDialogProps) { const [loading, setLoading] = useState(false); const [promo, setPromo] = useState(null); + const [openApproverHistory, setOpenApproverHistory] = useState(false); // FORMAT TANGGAL → DD-MM-YYYY const formatDate = (dateStr: string) => { @@ -33,7 +34,9 @@ export default function PromoDetailDialog({ return `${day}-${month}-${year}`; }; - // FETCH API PROMO BY ID + const handleOpenApproverHistory = () => { + setOpenApproverHistory(true); + }; useEffect(() => { if (!promoId || !open) return; @@ -54,7 +57,7 @@ export default function PromoDetailDialog({ date: formatDate(res?.data?.data?.created_at), time: new Date(res?.data?.data?.created_at).toLocaleTimeString( "id-ID", - { hour: "2-digit", minute: "2-digit" } + { hour: "2-digit", minute: "2-digit" }, ), }, { @@ -62,7 +65,7 @@ export default function PromoDetailDialog({ date: formatDate(res?.data?.data?.updated_at), time: new Date(res?.data?.data?.updated_at).toLocaleTimeString( "id-ID", - { hour: "2-digit", minute: "2-digit" } + { hour: "2-digit", minute: "2-digit" }, ), }, ], @@ -82,92 +85,301 @@ export default function PromoDetailDialog({ if (!open) return null; return ( - - - {/* HEADER */} -
- - - Detail Promo - - + <> + + + {/* HEADER */} +
+ + + Detail Promo + + - {/* STATUS BADGE */} - {promo && ( -
- - {promo.status} + {/* STATUS BADGE */} + {promo && ( +
+ + {promo.status} +
+ )} +
+ + {/* BODY */} +
+ {loading ? ( +

Memuat data...

+ ) : promo ? ( + <> + {/*
+
+ +
+
*/} + +

+ {promo.title} +

+ +
+
+

Ukuran File

+

{promo.fileSize}

+
+ +
+

Tanggal Upload

+

{promo.uploadDate}

+
+ + {/* TIMELINE */} +
+

+ Status Timeline +

+ +
+ {promo.timeline.map((item: any, idx: number) => ( +
+ +
+

{item.label}

+

+ {item.date} • {item.time} WIB +

+
+
+ ))} +
+
+

Comment :

+
+ +

Jaecoo - Approver | 10/11/2026

+
+
+
+
+ + ) : ( +

Data tidak ditemukan

+ )} +
+ {openApproverHistory && ( +
setOpenApproverHistory(false)} + > +
e.stopPropagation()} + > + {/* HEADER */} +
+ + +

Approver History

+ +
+ + Menunggu + + + Banner + + + 1 + +
+
+ + {/* BODY */} +
+ {/* LEFT TIMELINE */} +
+ {/* Upload */} +
+ + Upload + +
+
+ + {/* Diterima */} +
+

Diterima

+ + Direview oleh: approver-jaecoo1 + +
+ +
+ + {/* Pending */} +
+

Pending

+ + Direview oleh: approver-jaecoo1 + +
+
+ + {/* ARROW */} +
+ > + > +
+ + {/* RIGHT NOTES */} +
+
+
+ Catatan: +
+
+ +
+
+ Catatan: +
+
+
+
+ + {/* FOOTER */} +
+ +
+
)} -
+ {/* FOOTER */} +
+ +
+ +
+ {openApproverHistory && ( +
setOpenApproverHistory(false)} + > +
e.stopPropagation()} + > + {/* HEADER */} +
+ - {/* BODY */} -
- {loading ? ( -

Memuat data...

- ) : promo ? ( - <> -
-
- +

Approver History

+ +
+ + Menunggu + + + Banner + + + 1 + +
+
+ + {/* BODY */} +
+ {/* LEFT TIMELINE */} +
+ {/* Upload */} +
+ + Upload + +
+
+ + {/* Diterima */} +
+

Diterima

+ + Direview oleh: approver-jaecoo1 + +
+ +
+ + {/* Pending */} +
+

Pending

+ + Direview oleh: approver-jaecoo1 +
-

- {promo.title} -

+ {/* ARROW */} +
+ > + > +
-
+ {/* RIGHT NOTES */} +
-

Ukuran File

-

{promo.fileSize}

+
+ Catatan: +
-

Tanggal Upload

-

{promo.uploadDate}

-
- - {/* TIMELINE */} -
-

- Status Timeline -

- -
- {promo.timeline.map((item: any, idx: number) => ( -
- -
-

{item.label}

-

- {item.date} • {item.time} WIB -

-
-
- ))} +
+ Catatan:
- - ) : ( -

Data tidak ditemukan

- )} -
+
- {/* FOOTER */} -
- + {/* FOOTER */} +
+ +
+
- -
+ )} + ); } diff --git a/components/form/agent/detail-agent-form.tsx b/components/form/agent/detail-agent-form.tsx new file mode 100644 index 0000000..23fb1c6 --- /dev/null +++ b/components/form/agent/detail-agent-form.tsx @@ -0,0 +1,275 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Upload, Plus, Settings, CheckCheck } from "lucide-react"; +import Image from "next/image"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardContent, CardTitle } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { loading } from "@/config/swal"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useDropzone } from "react-dropzone"; +import z from "zod"; +import dynamic from "next/dynamic"; +import { useParams } from "next/navigation"; +import Cookies from "js-cookie"; +import { getProductDataById } from "@/service/product"; +import { getAgentById } from "@/service/agent"; +import { Checkbox } from "@/components/ui/checkbox"; + +const ViewEditor = dynamic( + () => { + return import("@/components/editor/view-editor"); + }, + { ssr: false }, +); +const CustomEditor = dynamic( + () => { + return import("@/components/editor/custom-editor"); + }, + { ssr: false }, +); + +interface FileWithPreview extends File { + preview: string; +} + +interface CategoryType { + id: number; + label: string; + value: number; +} +const categorySchema = z.object({ + id: z.number(), + label: z.string(), + value: z.number(), +}); + +const AGENT_TYPES = [ + { key: "after-sales", label: "After Sales" }, + { key: "sales", label: "Sales" }, + { key: "spv", label: "SPV" }, + { key: "branch_manager", label: "Branch Manager" }, +]; + +const createArticleSchema = z.object({ + title: z.string().min(2, { + message: "Judul harus diisi", + }), + variant: z.string().min(2, { + message: "variant diisi", + }), + price: z.string().min(2, { + message: "Price harus diisi", + }), + category: z.array(categorySchema).nonempty({ + message: "Kategori harus memiliki setidaknya satu item", + }), + tags: z.array(z.string()).nonempty({ + message: "Minimal 1 tag", + }), // Array berisi string +}); + +export default function DetailAgentForm(props: { isDetail: boolean }) { + const { isDetail } = props; + const params = useParams(); + const id = params?.id; + const [data, setData] = useState(null); + const [openApproverHistory, setOpenApproverHistory] = useState(false); + + useEffect(() => { + fetchData(); + }, []); + + async function fetchData() { + const res = await getAgentById(id); + setData(res?.data?.data); + } + + if (!data) return null; + + const handleOpenApproverHistory = () => { + setOpenApproverHistory(true); + }; + + return ( + <> + + + + Detail Agen + + + + + {/* FORM */} +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {/* JENIS AGEN */} +
+ +
+ {AGENT_TYPES.map((item) => ( +
+ + {item.label} +
+ ))} +
+
+ + {/* FOTO PROFILE */} +
+ +
+ Profile +
+
+
+

Comment :

+
+ +

Jaecoo - Approver | 10/11/2026

+
+
+ {/* */} +
+
+ {openApproverHistory && ( +
setOpenApproverHistory(false)} + > +
e.stopPropagation()} + > + {/* HEADER */} +
+ + +

Approver History

+ +
+ + Menunggu + + + Banner + + + 1 + +
+
+ + {/* BODY */} +
+ {/* LEFT TIMELINE */} +
+ {/* Upload */} +
+ + Upload + +
+
+ + {/* Diterima */} +
+

Diterima

+ + Direview oleh: approver-jaecoo1 + +
+ +
+ + {/* Pending */} +
+

Pending

+ + Direview oleh: approver-jaecoo1 + +
+
+ + {/* ARROW */} +
+ > + > +
+ + {/* RIGHT NOTES */} +
+
+
+ Catatan: +
+
+ +
+
+ Catatan: +
+
+
+
+ + {/* FOOTER */} +
+ +
+
+
+ )} + + ); +} diff --git a/components/form/agent/update-agent-form.tsx b/components/form/agent/update-agent-form.tsx index 6de47bf..3c676c5 100644 --- a/components/form/agent/update-agent-form.tsx +++ b/components/form/agent/update-agent-form.tsx @@ -8,7 +8,6 @@ import { FormField, FormItem, FormLabel, - FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; @@ -24,55 +23,49 @@ type AgentFormValues = { position: string; phone: string; roles: string[]; - profileImage: File | null; }; -const agentTypes = ["After Sales", "Sales", "Spv", "Branch Manager"]; +const AGENT_TYPES = ["after-Sales", "sales", "spv", "branch_manager"]; export default function UpdateAgentForm({ id }: { id: number }) { - const [loading, setLoading] = useState(true); - const [previewImg, setPreviewImg] = useState(null); - const [agentData, setAgentData] = useState(null); - const [file, setFile] = useState(null); - const [preview, setPreview] = useState(null); - const router = useRouter(); const MySwal = withReactContent(Swal); + const [loading, setLoading] = useState(true); + const [file, setFile] = useState(null); + const [preview, setPreview] = useState(null); + const [agentData, setAgentData] = useState(null); + const form = useForm({ defaultValues: { fullName: "", position: "", phone: "", roles: [], - profileImage: null, }, }); + /* ================= FETCH DATA ================= */ useEffect(() => { async function fetchData() { try { const res = await getAgentById(id); + const agent = res?.data?.data; + if (!agent) return; - if (!res || !res.data) { - console.error("DATA AGENT TIDAK DITEMUKAN"); - return; - } - - setAgentData(res.data); + setAgentData(agent); form.reset({ - fullName: res?.data?.data?.name, - position: res?.data?.data?.job_title, - phone: res?.data?.data?.phone, - roles: res?.data?.data?.agent_type || [], - profileImage: null, + fullName: agent.name, + position: agent.job_title, + phone: agent.phone, + roles: agent.job_title ? [agent.job_title] : [], }); - console.log("name", res?.data?.data?.name); - setPreviewImg(res.data.data.profile_picture_path || null); - } catch (err) { - console.error("ERROR FETCH DATA AGENT:", err); + // ✅ FOTO DARI API + setPreview(agent.profile_picture_url ?? null); + } catch (error) { + console.error("FETCH AGENT ERROR:", error); } finally { setLoading(false); } @@ -81,154 +74,133 @@ export default function UpdateAgentForm({ id }: { id: number }) { fetchData(); }, [id, form]); - const handleFileChange = (e: any) => { - const selected = e.target.files[0]; - if (selected) { - setFile(selected); - setPreview(URL.createObjectURL(selected)); - } + /* ================= FILE HANDLER ================= */ + const handleFileChange = (e: React.ChangeEvent) => { + const selected = e.target.files?.[0]; + if (!selected) return; + + setFile(selected); + setPreview(URL.createObjectURL(selected)); }; const handleRemoveFile = () => { setFile(null); - setPreview(null); + setPreview(agentData?.profile_picture_url ?? null); }; + /* ================= SUBMIT ================= */ const onSubmit = async (data: AgentFormValues) => { try { const formData = new FormData(); - formData.append("name", data.fullName); - formData.append("job_title", data.position); + formData.append("job_title", data.roles[0]); // single role formData.append("phone", data.phone); - data.roles.forEach((role) => { - formData.append("agent_type", role); - }); - if (file) { formData.append("file", file); } await updateAgent(id, formData); - successSubmit("/admin/agent"); - } catch (err) { - console.error("ERROR UPDATE AGENT:", err); + MySwal.fire({ + title: "Berhasil", + text: "Data agen berhasil diperbarui", + icon: "success", + }).then(() => router.push("/admin/agent")); + } catch (error) { + console.error("UPDATE AGENT ERROR:", error); } }; - function successSubmit(redirect: string) { - MySwal.fire({ - title: "Data Berhasil Diupdate", - icon: "success", - confirmButtonColor: "#3085d6", - confirmButtonText: "OK", - }).then((res) => { - if (res.isConfirmed) router.push(redirect); - }); - } - - if (loading) { - return
Loading data...
; - } + if (loading) return

Loading...

; return ( -
+

Edit Agen

-
+ {/* BASIC INFO */} +
( - Nama Lengkap * + Nama Lengkap - + )} /> - {/* Jabatan */} ( - Jabatan * + Jabatan - + )} /> - {/* Telepon */} ( - No Telp * + No Telp - + )} />
- {/* ROLES */} + {/* JENIS AGEN */}
- Pilih Jenis Agen * - -
- {agentTypes.map((role) => ( + Jenis Agen +
+ {AGENT_TYPES.map((role) => ( { - const selected = field.value || []; - return ( - - - { - const updated = checked - ? [...selected, role] - : selected.filter((i) => i !== role); - field.onChange(updated); - }} - /> - - {role} - - ); - }} + render={({ field }) => ( + + + + field.onChange(checked ? [role] : []) + } + /> + + + {role.replace("_", " ")} + + + )} /> ))}
{/* FOTO */} -
+
Foto Agen -
+
{preview && ( -
+
Preview @@ -252,31 +224,29 @@ export default function UpdateAgentForm({ id }: { id: number }) {
{/* BUTTON */} -
+
-
- -
+
diff --git a/components/form/product/create-product-form.tsx b/components/form/product/create-product-form.tsx index e3ed83f..389e5b6 100644 --- a/components/form/product/create-product-form.tsx +++ b/components/form/product/create-product-form.tsx @@ -30,6 +30,7 @@ export default function AddProductForm() { const [file, setFile] = useState(null); const router = useRouter(); const MySwal = withReactContent(Swal); + const [priceDisplay, setPriceDisplay] = useState(""); const handleFileChange = (e: any) => { const selected = e.target.files[0]; @@ -42,6 +43,7 @@ export default function AddProductForm() { const { register, handleSubmit, + setValue, formState: { errors }, } = useForm>({ resolver: zodResolver(formSchema), @@ -86,10 +88,21 @@ export default function AddProductForm() { }); } + const handlePriceChange = (e: React.ChangeEvent) => { + const rawValue = e.target.value.replace(/\D/g, ""); + setPriceDisplay(formatRupiah(rawValue)); + setValue("price", rawValue ? Number(rawValue) : 0); + }; + const handleAddColor = () => { setColors((prev) => [...prev, { id: prev.length + 1 }]); }; + const formatRupiah = (value: string) => { + const number = value.replace(/\D/g, ""); + return number ? `Rp ${Number(number).toLocaleString("id-ID")}` : ""; + }; + return ( @@ -127,8 +140,10 @@ export default function AddProductForm() { + {errors.price && (

{errors.price.message} diff --git a/components/form/product/detail-product-form.tsx b/components/form/product/detail-product-form.tsx new file mode 100644 index 0000000..7776afa --- /dev/null +++ b/components/form/product/detail-product-form.tsx @@ -0,0 +1,700 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Upload, Plus, Settings, CheckCheck } from "lucide-react"; +import Image from "next/image"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Button } from "@/components/ui/button"; +import { Card, CardHeader, CardContent, CardTitle } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { loading } from "@/config/swal"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useDropzone } from "react-dropzone"; +import z from "zod"; +import dynamic from "next/dynamic"; +import { useParams } from "next/navigation"; +import Cookies from "js-cookie"; +import { getProductDataById } from "@/service/product"; + +const ViewEditor = dynamic( + () => { + return import("@/components/editor/view-editor"); + }, + { ssr: false }, +); +const CustomEditor = dynamic( + () => { + return import("@/components/editor/custom-editor"); + }, + { ssr: false }, +); + +interface FileWithPreview extends File { + preview: string; +} + +interface CategoryType { + id: number; + label: string; + value: number; +} +const categorySchema = z.object({ + id: z.number(), + label: z.string(), + value: z.number(), +}); + +const createArticleSchema = z.object({ + title: z.string().min(2, { + message: "Judul harus diisi", + }), + variant: z.string().min(2, { + message: "variant diisi", + }), + price: z.string().min(2, { + message: "Price harus diisi", + }), + category: z.array(categorySchema).nonempty({ + message: "Kategori harus memiliki setidaknya satu item", + }), + tags: z.array(z.string()).nonempty({ + message: "Minimal 1 tag", + }), // Array berisi string +}); + +export default function DetailProductForm(props: { isDetail: boolean }) { + const { isDetail } = props; + const params = useParams(); + const id = params?.id; + const username = Cookies.get("username"); + const userId = Cookies.get("uie"); + const [files, setFiles] = useState([]); + const [useAi, setUseAI] = useState(false); + const [listCategory, setListCategory] = useState([]); + const [tag, setTag] = useState(""); + const [detailfiles, setDetailFiles] = useState([]); + const [mainImage, setMainImage] = useState(0); + const [thumbnail, setThumbnail] = useState(""); + const [diseId, setDiseId] = useState(0); + const [thumbnailImg, setThumbnailImg] = useState([]); + const [selectedMainImage, setSelectedMainImage] = useState( + null, + ); + const [thumbnailValidation, setThumbnailValidation] = useState(""); + // const { isOpen, onOpen, onOpenChange } = useDisclosure(); + const [isOpen, setIsOpen] = useState(false); + const onOpen = () => setIsOpen(true); + const onOpenChange = () => setIsOpen((prev) => !prev); + const [approvalStatus, setApprovalStatus] = useState(2); + const [approvalMessage, setApprovalMessage] = useState(""); + const [detailData, setDetailData] = useState(); + const [startDateValue, setStartDateValue] = useState(null); + const [timeValue, setTimeValue] = useState("00:00"); + const [openApproverHistory, setOpenApproverHistory] = useState(false); + + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFiles((prevFiles) => [ + ...prevFiles, + ...acceptedFiles.map((file) => Object.assign(file)), + ]); + }, + multiple: true, + accept: { + "image/*": [], + }, + }); + + const formOptions = { + resolver: zodResolver(createArticleSchema), + defaultValues: { title: "", description: "", category: [], tags: [] }, + }; + type UserSettingSchema = z.infer; + const { + register, + control, + handleSubmit, + formState: { errors }, + setValue, + getValues, + watch, + setError, + clearErrors, + } = useForm(formOptions); + const [specs, setSpecs] = useState([ + { + id: 1, + title: "Jaecoo 7 SHS Teknologi dan Exterior", + images: ["/spec1.jpg", "/spec2.jpg", "/spec3.jpg", "/spec4.jpg"], + }, + ]); + + type ColorType = { + id: number; + name: string; + preview: string; + colorSelected: string | null; + }; + + const [colors, setColors] = useState([ + { + id: 1, + name: "", + preview: "/car-1.png", + colorSelected: null, + }, + { + id: 2, + name: "", + preview: "/car-2.png", + colorSelected: null, + }, + ]); + + const palette = [ + "#1E4E52", + "#597E8D", + "#6B6B6B", + "#BEBEBE", + "#E2E2E2", + "#F4F4F4", + "#FFFFFF", + "#F9360A", + "#9A2A00", + "#7A1400", + "#4B0200", + "#B48B84", + "#FFA598", + ]; + + const handleAddSpec = () => { + setSpecs((prev) => [ + ...prev, + { + id: prev.length + 1, + title: "", + images: [], + }, + ]); + }; + + const handleAddColor = () => { + setColors((p) => [ + ...p, + { + id: p.length + 1, + name: "", + preview: "/car-default.png", + colorSelected: null, + }, + ]); + }; + + const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false); + const [uploadTarget, setUploadTarget] = useState<{ + type: "spec" | "color"; + index: number; + } | null>(null); + + const fileInputId = "file-upload-input"; + + const handleFileSelected = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file || !uploadTarget) return; + + const reader = new FileReader(); + reader.onload = () => { + const fileUrl = reader.result as string; + + if (uploadTarget.type === "spec") { + setSpecs((prev) => { + const updated = [...prev]; + updated[uploadTarget.index].images.push(fileUrl); + return updated; + }); + } + + if (uploadTarget.type === "color") { + setColors((prev) => { + const updated = [...prev]; + updated[uploadTarget.index].preview = fileUrl; + return updated; + }); + } + }; + + reader.readAsDataURL(file); + setIsUploadDialogOpen(false); + }; + + useEffect(() => { + initState(); + }, [listCategory]); + + async function initState() { + const res = await getProductDataById(id); + const data = res?.data?.data; + + if (!data) return; + + setDetailData(data); + + // form + setValue("title", data.title); + setValue("variant", data.variant); + setValue("price", formatRupiah(data.price)); + + // thumbnail + setThumbnail(data.thumbnail_url); + + // colors + if (data.colors?.length) { + setColors( + data.colors.map((color: string, index: number) => ({ + id: index + 1, + name: color, + preview: data.thumbnail_url, + colorSelected: color, + })), + ); + } + } + + const handleOpenApproverHistory = () => { + setOpenApproverHistory(true); + }; + + const formatRupiah = (value: string) => + "Rp " + Number(value).toLocaleString("id-ID"); + + return ( + <> + + + + Edit Produk + + + + +

+
+ ( +
+ + +
+ )} + /> +
+ +
+ ( +
+ + +
+ )} + /> +
+ +
+ ( +
+ + +
+ )} + /> +
+
+
+ +
+ {thumbnail ? ( + Banner + ) : ( +
+ No Image +
+ )} +
+
+ +
+ + + {colors.map((item, index) => ( +
+ + + +
+
+ +
+ + {palette.map((colorCode) => ( +
+ +
+ + +
+ car color + + +
+
+
+ ))} + + {/* */} +
+ +
+ + + {specs.map((spec, index) => ( +
+ + + + + +
+ {spec.images.map((img, i) => ( + spec + ))} + + {/* */} +
+
+ ))} + + {/* */} +
+
+

Status Timeline

+
+
+ +
+
+

Diupload oleh

+
+
+
+
+ +
+
+

Disetujui oleh

+
+
+
+
+

Comment :

+
+ +

Jaecoo - Approver | 10/11/2026

+
+
+ {/* */} + +
+ {openApproverHistory && ( +
setOpenApproverHistory(false)} + > +
e.stopPropagation()} + > + {/* HEADER */} +
+ + +

Approver History

+ +
+ + Menunggu + + + Banner + + + 1 + +
+
+ + {/* BODY */} +
+ {/* LEFT TIMELINE */} +
+ {/* Upload */} +
+ + Upload + +
+
+ + {/* Diterima */} +
+

Diterima

+ + Direview oleh: approver-jaecoo1 + +
+ +
+ + {/* Pending */} +
+

Pending

+ + Direview oleh: approver-jaecoo1 + +
+
+ + {/* ARROW */} +
+ > + > +
+ + {/* RIGHT NOTES */} +
+
+
+ Catatan: +
+
+ +
+
+ Catatan: +
+
+
+
+ + {/* FOOTER */} +
+ +
+
+
+ )} + + + + + Upload File + + + +
document.getElementById(fileInputId)?.click()} + > + +

+ Klik untuk upload atau drag & drop +

+

PNG, JPG (max 2 MB)

+ + +
+ + + + + + +
+
+ + ); +} diff --git a/components/landing-page/retracting-sidedar.tsx b/components/landing-page/retracting-sidedar.tsx index bbed730..6221c4e 100644 --- a/components/landing-page/retracting-sidedar.tsx +++ b/components/landing-page/retracting-sidedar.tsx @@ -11,6 +11,7 @@ import { usePathname } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; import { useTheme } from "../layout/theme-context"; import Option from "./option"; +import { LogOut } from "lucide-react"; interface RetractingSidebarProps { sidebarData: boolean; @@ -56,7 +57,7 @@ const sidebarSections = [ // link: "/admin/magazine", // }, { - title: "Agen", + title: "Agent", icon: () => , link: "/admin/agent", }, @@ -365,10 +366,10 @@ const SidebarContent = ({ } p-3 rounded-xl bg-gradient-to-r from-slate-50 to-slate-100/50 hover:from-slate-100 hover:to-slate-200/50 transition-all duration-200 cursor-pointer group`} >
-
+ {/*
A
-
+
*/}
{open && ( -

- admin-mabes -

- -

- Sign out -

- + {/*

+ Jaecoo +

*/} +
+ + +

+ Log Out +

+ +
)}
diff --git a/components/table/agent-table.tsx b/components/table/agent-table.tsx index d3e7134..2abfc8c 100644 --- a/components/table/agent-table.tsx +++ b/components/table/agent-table.tsx @@ -294,7 +294,7 @@ export default function AgentTable() { return cellValue; } }, - [article, page] + [article, page], ); let typingTimer: NodeJS.Timeout; @@ -386,19 +386,9 @@ export default function AgentTable() { {/* STATUS */} - {item.is_active === "true" ? ( - - Published - - ) : item.publishedStatus === "On Schedule" ? ( - - On Schedule - - ) : ( - - Cancel - - )} + + Menunggu + {/* AKSI */} diff --git a/components/table/article-table.tsx b/components/table/article-table.tsx index 679e1c0..d192629 100644 --- a/components/table/article-table.tsx +++ b/components/table/article-table.tsx @@ -48,6 +48,7 @@ import CustomPagination from "../layout/custom-pagination"; import { EditBannerDialog } from "../form/banner-edit-dialog"; import { deleteBanner, getBannerData, updateBanner } from "@/service/banner"; import { CheckCheck, Eye } from "lucide-react"; +import { useRouter } from "next/navigation"; const columns = [ { name: "No", uid: "no" }, @@ -90,6 +91,15 @@ export default function ArticleTable() { startDate: null, endDate: null, }); + const [userLevelId, setUserLevelId] = useState(null); + + const router = useRouter(); + + // 🔹 Ambil userlevelId dari cookies + useEffect(() => { + const ulne = Cookies.get("ulne"); // contoh: "3" + setUserLevelId(ulne ?? null); + }, []); useEffect(() => { initState(); @@ -173,6 +183,17 @@ export default function ArticleTable() { const [openViewDialog, setOpenViewDialog] = useState(false); const [viewBanner, setViewBanner] = useState(null); const [openApproverHistory, setOpenApproverHistory] = useState(false); + const [openCommentModal, setOpenCommentModal] = useState(false); + const [commentValue, setCommentValue] = useState(""); + + const handleSubmitComment = async () => { + // await api.post("/banner/comment", { + // bannerId: viewBanner.id, + // comment: commentValue, + // }); + + setOpenCommentModal(false); + }; const handleView = (item: any) => { setViewBanner(item); @@ -305,7 +326,7 @@ export default function ArticleTable() { return cellValue; } }, - [article, page] + [article, page], ); let typingTimer: NodeJS.Timeout; @@ -402,19 +423,19 @@ export default function ArticleTable() { {/* STATUS */} - {item.status === "Disetujui" ? ( + {/* {item.status === "Disetujui" ? ( Disetujui - ) : item.status === "Menunggu" ? ( - - Menunggu - - ) : ( + ) : item.status === "Menunggu" ? ( */} + + Menunggu + + {/* ) : ( Ditolak - )} + )} */} {/* AKSI */} @@ -429,15 +450,17 @@ export default function ArticleTable() { Lihat - + {userLevelId !== "3" && ( + + )}
{/* FOOTER */} -
- + {userLevelId !== "2" && ( +
+ -
- - +
-
+ )}
)} @@ -790,6 +824,76 @@ export default function ArticleTable() {
)} + {openCommentModal && viewBanner && ( +
setOpenCommentModal(false)} + > +
e.stopPropagation()} + > + {/* HEADER */} +
+ + +

Beri Tanggapan

+ + {/* Badge */} +
+ + Menunggu + + + + Banner + + + + {viewBanner.position} + +
+
+ + {/* BODY */} +
+
+ +