From 256a3ece8988ba31a6e501af7e5db6f36948e5b8 Mon Sep 17 00:00:00 2001 From: Anang Yusman Date: Tue, 18 Nov 2025 14:56:39 +0800 Subject: [PATCH] update --- app/(admin)/admin/agent/create/page.tsx | 11 + app/(admin)/admin/agent/page.tsx | 42 ++ app/(admin)/admin/agent/update/[id]/page.tsx | 15 + app/(admin)/admin/banner/create/page.tsx | 9 + app/(admin)/admin/banner/detail/[id]/page.tsx | 22 + app/(admin)/admin/banner/edit/[id]/page.tsx | 22 + app/(admin)/admin/banner/page.tsx | 73 +++ app/(admin)/admin/galery/page.tsx | 51 ++ app/(admin)/admin/product/create/page.tsx | 10 + app/(admin)/admin/product/page.tsx | 41 ++ app/(admin)/admin/product/update/page.tsx | 9 + app/(admin)/admin/promotion/create/page.tsx | 12 + app/(admin)/admin/promotion/page.tsx | 43 ++ components/dialog/agent-dialog.tsx | 114 ++++ components/dialog/galery-detail-dialog.tsx | 96 +++ components/dialog/galery-dialog.tsx | 141 +++++ components/dialog/galery-update-dialog.tsx | 173 ++++++ components/dialog/promo-dialog.tsx | 173 ++++++ components/form/agent/agent-form.tsx | 248 ++++++++ components/form/agent/update-agent-form.tsx | 268 +++++++++ components/form/banner-dialog.tsx | 191 ++++++ components/form/banner-edit-dialog.tsx | 221 +++++++ .../form/product/create-product-form.tsx | 299 ++++++++++ .../form/product/update-product-form.tsx | 348 +++++++++++ .../form/promotion/create-promo-form.tsx | 129 ++++ components/landing-page/option.tsx | 52 +- .../landing-page/retracting-sidedar.tsx | 76 +-- .../main/dashboard/dashboard-container.tsx | 559 ++++++++++++----- components/table/agent-table.tsx | 563 ++++++++++++++++++ components/table/article-table.tsx | 385 +++++++----- components/table/galery.tsx | 208 +++++++ components/table/product-table.tsx | 562 +++++++++++++++++ components/table/promotion-table.tsx | 539 +++++++++++++++++ components/ui/button.tsx | 9 +- components/ui/form.tsx | 167 ++++++ next.config.ts | 2 +- package-lock.json | 162 ++++- package.json | 10 +- service/agent.ts | 37 ++ service/article.ts | 19 +- service/banner.ts | 44 ++ service/galery.ts | 46 ++ service/http-config/axios-base-instance.ts | 4 +- .../http-config/axios-interceptor-instance.ts | 4 +- service/product.ts | 36 ++ service/promotion.ts | 39 ++ 46 files changed, 5908 insertions(+), 376 deletions(-) create mode 100644 app/(admin)/admin/agent/create/page.tsx create mode 100644 app/(admin)/admin/agent/page.tsx create mode 100644 app/(admin)/admin/agent/update/[id]/page.tsx create mode 100644 app/(admin)/admin/banner/create/page.tsx create mode 100644 app/(admin)/admin/banner/detail/[id]/page.tsx create mode 100644 app/(admin)/admin/banner/edit/[id]/page.tsx create mode 100644 app/(admin)/admin/banner/page.tsx create mode 100644 app/(admin)/admin/galery/page.tsx create mode 100644 app/(admin)/admin/product/create/page.tsx create mode 100644 app/(admin)/admin/product/page.tsx create mode 100644 app/(admin)/admin/product/update/page.tsx create mode 100644 app/(admin)/admin/promotion/create/page.tsx create mode 100644 app/(admin)/admin/promotion/page.tsx create mode 100644 components/dialog/agent-dialog.tsx create mode 100644 components/dialog/galery-detail-dialog.tsx create mode 100644 components/dialog/galery-dialog.tsx create mode 100644 components/dialog/galery-update-dialog.tsx create mode 100644 components/dialog/promo-dialog.tsx create mode 100644 components/form/agent/agent-form.tsx create mode 100644 components/form/agent/update-agent-form.tsx create mode 100644 components/form/banner-dialog.tsx create mode 100644 components/form/banner-edit-dialog.tsx create mode 100644 components/form/product/create-product-form.tsx create mode 100644 components/form/product/update-product-form.tsx create mode 100644 components/form/promotion/create-promo-form.tsx create mode 100644 components/table/agent-table.tsx create mode 100644 components/table/galery.tsx create mode 100644 components/table/product-table.tsx create mode 100644 components/table/promotion-table.tsx create mode 100644 components/ui/form.tsx create mode 100644 service/agent.ts create mode 100644 service/banner.ts create mode 100644 service/galery.ts create mode 100644 service/product.ts create mode 100644 service/promotion.ts diff --git a/app/(admin)/admin/agent/create/page.tsx b/app/(admin)/admin/agent/create/page.tsx new file mode 100644 index 0000000..df48b03 --- /dev/null +++ b/app/(admin)/admin/agent/create/page.tsx @@ -0,0 +1,11 @@ +import AddAgentForm from "@/components/form/agent/agent-form"; +import CreateArticleForm from "@/components/form/article/create-article-form"; +import AddProductForm from "@/components/form/product/create-product-form"; + +export default function CreateAgent() { + return ( +
+ +
+ ); +} diff --git a/app/(admin)/admin/agent/page.tsx b/app/(admin)/admin/agent/page.tsx new file mode 100644 index 0000000..d2c2b67 --- /dev/null +++ b/app/(admin)/admin/agent/page.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useState } from "react"; +import ArticleTable from "@/components/table/article-table"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; +import { BannerDialog } from "@/components/form/banner-dialog"; +import Link from "next/link"; +import ProductTable from "@/components/table/product-table"; +import AgentTable from "@/components/table/agent-table"; + +export default function AgentPage() { + const [openDialog, setOpenDialog] = useState(false); + + const handleSubmitBanner = (data: any) => { + console.log("Banner Data:", data); + // TODO: kirim data ke API di sini + }; + + return ( +
+
+
+
+

Agent

+

Kelola Agent JAECOO

+
+ +
+ + + + +
+
+
+
+ ); +} diff --git a/app/(admin)/admin/agent/update/[id]/page.tsx b/app/(admin)/admin/agent/update/[id]/page.tsx new file mode 100644 index 0000000..41814da --- /dev/null +++ b/app/(admin)/admin/agent/update/[id]/page.tsx @@ -0,0 +1,15 @@ +import UpdateAgentForm from "@/components/form/agent/update-agent-form"; + +export default async function EditAgentPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + + return ( +
+ +
+ ); +} diff --git a/app/(admin)/admin/banner/create/page.tsx b/app/(admin)/admin/banner/create/page.tsx new file mode 100644 index 0000000..6e62d24 --- /dev/null +++ b/app/(admin)/admin/banner/create/page.tsx @@ -0,0 +1,9 @@ +import CreateArticleForm from "@/components/form/article/create-article-form"; + +export default function CreateArticle() { + return ( +
+ +
+ ); +} diff --git a/app/(admin)/admin/banner/detail/[id]/page.tsx b/app/(admin)/admin/banner/detail/[id]/page.tsx new file mode 100644 index 0000000..62d5a75 --- /dev/null +++ b/app/(admin)/admin/banner/detail/[id]/page.tsx @@ -0,0 +1,22 @@ +import EditArticleForm from "@/components/form/article/edit-article-form"; + +export default function DetailArticlePage() { + return ( +
+ {/*
+
+

Article

+

Article

+
+ + + + + +
*/} +
+ +
+
+ ); +} diff --git a/app/(admin)/admin/banner/edit/[id]/page.tsx b/app/(admin)/admin/banner/edit/[id]/page.tsx new file mode 100644 index 0000000..6c06089 --- /dev/null +++ b/app/(admin)/admin/banner/edit/[id]/page.tsx @@ -0,0 +1,22 @@ +import EditArticleForm from "@/components/form/article/edit-article-form"; + +export default function UpdateArticlePage() { + return ( +
+ {/*
+
+

Article

+

Article

+
+ + + + + +
*/} +
+ +
+
+ ); +} diff --git a/app/(admin)/admin/banner/page.tsx b/app/(admin)/admin/banner/page.tsx new file mode 100644 index 0000000..caa9b07 --- /dev/null +++ b/app/(admin)/admin/banner/page.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useState } from "react"; +import ArticleTable from "@/components/table/article-table"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; +import { BannerDialog } from "@/components/form/banner-dialog"; +import { createBanner } from "@/service/banner"; +import router from "next/router"; +import { useRouter } from "next/navigation"; +import withReactContent from "sweetalert2-react-content"; +import Swal from "sweetalert2"; + +export default function BasicPage() { + const [openDialog, setOpenDialog] = useState(false); + const router = useRouter(); + const MySwal = withReactContent(Swal); + const [refreshKey, setRefreshKey] = useState(0); + const handleSubmitBanner = async (formData: FormData) => { + try { + const response = await createBanner(formData); + console.log("Banner created:", response); + } catch (error) { + console.error("Error creating banner:", error); + } + }; + + const successSubmit = () => { + MySwal.fire({ + title: "Sukses", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then((result) => { + if (result.isConfirmed) { + setRefreshKey((prev) => prev + 1); // ⬅️ trigger refresh + } + }); + }; + + return ( +
+
+
+
+

Banner

+

Kelola gambar banner yang tampil di halaman Utama website

+
+ +
+ + + +
+
+
+ + {/* Dialog Tambah Banner */} + +
+ ); +} diff --git a/app/(admin)/admin/galery/page.tsx b/app/(admin)/admin/galery/page.tsx new file mode 100644 index 0000000..0c37957 --- /dev/null +++ b/app/(admin)/admin/galery/page.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; +import Galery from "@/components/table/galery"; +import { GaleriDialog } from "@/components/dialog/galery-dialog"; + +export default function GaleryPage() { + const [openDialog, setOpenDialog] = useState(false); + + const handleSubmitGaleri = () => { + console.log("Submit galeri..."); + setOpenDialog(false); + }; + + return ( +
+
+
+
+

Galeri

+

Kelola Galeri JAECOO

+
+ +
+ {/* Tombol buka dialog */} + + + {/* Komponen Galeri */} + +
+
+
+ + {/* Dialog Tambah Galeri */} + setOpenDialog(false)} + onSubmit={handleSubmitGaleri} + /> +
+ ); +} diff --git a/app/(admin)/admin/product/create/page.tsx b/app/(admin)/admin/product/create/page.tsx new file mode 100644 index 0000000..7f336a7 --- /dev/null +++ b/app/(admin)/admin/product/create/page.tsx @@ -0,0 +1,10 @@ +import CreateArticleForm from "@/components/form/article/create-article-form"; +import AddProductForm from "@/components/form/product/create-product-form"; + +export default function CreateProduct() { + return ( +
+ +
+ ); +} diff --git a/app/(admin)/admin/product/page.tsx b/app/(admin)/admin/product/page.tsx new file mode 100644 index 0000000..2db1ecf --- /dev/null +++ b/app/(admin)/admin/product/page.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useState } from "react"; +import ArticleTable from "@/components/table/article-table"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; +import { BannerDialog } from "@/components/form/banner-dialog"; +import Link from "next/link"; +import ProductTable from "@/components/table/product-table"; + +export default function ProductPage() { + const [openDialog, setOpenDialog] = useState(false); + + const handleSubmitBanner = (data: any) => { + console.log("Banner Data:", data); + // TODO: kirim data ke API di sini + }; + + return ( +
+
+
+
+

Product

+

Kelola Informasi Product Kendaraan JAECOO

+
+ +
+ + + + +
+
+
+
+ ); +} diff --git a/app/(admin)/admin/product/update/page.tsx b/app/(admin)/admin/product/update/page.tsx new file mode 100644 index 0000000..62f73c3 --- /dev/null +++ b/app/(admin)/admin/product/update/page.tsx @@ -0,0 +1,9 @@ +import UpdateProductForm from "@/components/form/product/update-product-form"; + +export default function CreateProduct() { + return ( +
+ +
+ ); +} diff --git a/app/(admin)/admin/promotion/create/page.tsx b/app/(admin)/admin/promotion/create/page.tsx new file mode 100644 index 0000000..f79502b --- /dev/null +++ b/app/(admin)/admin/promotion/create/page.tsx @@ -0,0 +1,12 @@ +import AddAgentForm from "@/components/form/agent/agent-form"; +import CreateArticleForm from "@/components/form/article/create-article-form"; +import AddProductForm from "@/components/form/product/create-product-form"; +import AddPromoForm from "@/components/form/promotion/create-promo-form"; + +export default function CreatePromo() { + return ( +
+ +
+ ); +} diff --git a/app/(admin)/admin/promotion/page.tsx b/app/(admin)/admin/promotion/page.tsx new file mode 100644 index 0000000..3c3d5b5 --- /dev/null +++ b/app/(admin)/admin/promotion/page.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { useState } from "react"; +import ArticleTable from "@/components/table/article-table"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; +import { BannerDialog } from "@/components/form/banner-dialog"; +import Link from "next/link"; +import ProductTable from "@/components/table/product-table"; +import AgentTable from "@/components/table/agent-table"; +import PromotionTable from "@/components/table/promotion-table"; + +export default function PromotionPage() { + const [openDialog, setOpenDialog] = useState(false); + + const handleSubmitBanner = (data: any) => { + console.log("Banner Data:", data); + // TODO: kirim data ke API di sini + }; + + return ( +
+
+
+
+

Promo

+

Kelola Promo JAECOO

+
+ +
+ + + + +
+
+
+
+ ); +} diff --git a/components/dialog/agent-dialog.tsx b/components/dialog/agent-dialog.tsx new file mode 100644 index 0000000..a2b8bec --- /dev/null +++ b/components/dialog/agent-dialog.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import Image from "next/image"; +import { CheckCircle2 } from "lucide-react"; + +type AgentDetailProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + data: { + name: string; + position: string; + phone: string; + status: "Aktif" | "Nonaktif"; + roles: string[]; + imageUrl: string; + } | null; +}; + +export default function AgentDetailDialog({ + open, + onOpenChange, + data, +}: AgentDetailProps) { + if (!data) return null; + + return ( + + + {/* HEADER */} +
+ {/* CLOSE BUTTON */} + {/* */} + + + + Detail Agen + + + + {/* STATUS BADGE */} +
+ + {data.status} +
+
+ + {/* BODY */} +
+ {/* FOTO PROFIL */} +
+ {data.name} +
+ + {/* NAMA */} +

{data.name}

+ + {/* JABATAN */} +

{data.position}

+ + {/* NOMOR TELEPON */} +

{data.phone}

+ + {/* JENIS AGEN */} +
+

Jenis Agen

+ +
+ {data.roles.map((role) => ( +
+ + {role} +
+ ))} +
+
+
+ + {/* FOOTER */} +
+ +
+
+
+ ); +} diff --git a/components/dialog/galery-detail-dialog.tsx b/components/dialog/galery-detail-dialog.tsx new file mode 100644 index 0000000..b539570 --- /dev/null +++ b/components/dialog/galery-detail-dialog.tsx @@ -0,0 +1,96 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import Image from "next/image"; +import { CheckCircle } from "lucide-react"; + +export function DialogDetailGaleri({ open, onClose, data }: any) { + return ( + + + {/* Header */} +
+ Detail Galeri +
+ +
+ {/* Images */} +
+
+ Galery Image +
+
+ + {/* Title */} +

+ {data.title} +

+ + {/* Deskripsi */} +
+

Deskripsi

+

{data.desc}

+
+ + {/* 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")} +

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

Disetujui oleh Approver

+

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

+
+
+ )} +
+
+
+ + {/* Footer */} + + + +
+
+ ); +} diff --git a/components/dialog/galery-dialog.tsx b/components/dialog/galery-dialog.tsx new file mode 100644 index 0000000..4e0c0dd --- /dev/null +++ b/components/dialog/galery-dialog.tsx @@ -0,0 +1,141 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Upload } from "lucide-react"; +import { useState, useRef } from "react"; +import { createGalery } from "@/service/galery"; + +export function GaleriDialog({ open, onClose, onSubmit }: any) { + const [title, setTitle] = useState(""); + const [desc, setDesc] = useState(""); + const [file, setFile] = useState(null); + + const fileRef = useRef(null); + + const handleSubmit = async () => { + if (!file) { + alert("File wajib diupload!"); + return; + } + + const form = new FormData(); + form.append("gallery_id", "1"); // nilai default (bisa dinamis) + form.append("title", title); + form.append("desc", desc); + form.append("file", file); + + try { + const res = await createGalery(form); + + console.log("Upload Success:", res?.data); + + onSubmit(); // tutup dialog + } catch (error: any) { + console.error("Upload failed:", error); + alert("Gagal mengupload file"); + } + }; + + const handleFileChange = (e: any) => { + const selected = e.target.files[0]; + if (selected) setFile(selected); + }; + + return ( + + + {/* Header */} +
+ Tambah Galeri +
+ +
+ {/* Judul */} +
+ + setTitle(e.target.value)} + /> +
+ + {/* Deskripsi */} +
+ + setDesc(e.target.value)} + /> +
+ + {/* Upload */} +
+ + +
fileRef.current?.click()} + > + + +

+ Klik untuk upload atau drag & drop +

+

PNG, JPG (max 2MB)

+ + +
+ + {file && ( +

+ File dipilih: {file.name} +

+ )} +
+
+ + {/* Footer */} + + + + + +
+
+ ); +} diff --git a/components/dialog/galery-update-dialog.tsx b/components/dialog/galery-update-dialog.tsx new file mode 100644 index 0000000..bb74335 --- /dev/null +++ b/components/dialog/galery-update-dialog.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { useState } from "react"; +import Image from "next/image"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { X } from "lucide-react"; +import { updateGalery } from "@/service/galery"; + +export function DialogUpdateGaleri({ open, onClose, data, onUpdated }: any) { + const [title, setTitle] = useState(data.title); + const [desc, setDesc] = useState(data.desc); + const [files, setFiles] = useState(data.files || []); + const [newFiles, setNewFiles] = useState([]); + const [loading, setLoading] = useState(false); + + const handleUpload = (e: React.ChangeEvent) => { + const uploaded = Array.from(e.target.files || []) as File[]; + setNewFiles((prev) => [...prev, ...uploaded]); + }; + + const removeOldFile = (id: number) => { + setFiles(files.filter((f: any) => f.id !== id)); + }; + + const removeNewFile = (index: number) => { + setNewFiles(newFiles.filter((_, i) => i !== index)); + }; + + const handleSubmit = async () => { + try { + setLoading(true); + + const form = new FormData(); + form.append("title", title); + form.append("desc", desc); + + // file lama yang masih dipakai + form.append("old_files", JSON.stringify(files.map((f: any) => f.id))); + + // file baru + newFiles.forEach((file) => { + form.append("files", file); + }); + + const res = await updateGalery(data.id, form); + + setLoading(false); + onClose(); + if (onUpdated) onUpdated(); // refresh list + } catch (error) { + setLoading(false); + console.error("Error update:", error); + } + }; + + return ( + + + + Edit Banner + + + {/* Form */} +
+ {/* Title */} +
+ + setTitle(e.target.value)} + required + /> +
+ + {/* Desc */} +
+ +