@@ -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 ? (
+
+ ) : (
+
+ No Image
+
+ )}
+
+
+
+
+
+
+ {colors.map((item, index) => (
+
+
+
+
+
+
+
+
+
+ {palette.map((colorCode) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ {/*
*/}
+
+
+
+
+
+ {specs.map((spec, index) => (
+
+
+
+
+
+
+
+ {spec.images.map((img, i) => (
+
+ ))}
+
+ {/* */}
+
+
+ ))}
+
+ {/*
*/}
+
+
+
+
Comment :
+
+
+
Jaecoo - Approver | 10/11/2026
+
+
+ {/* */}
+
+
+ {openApproverHistory && (
+ setOpenApproverHistory(false)}
+ >
+
e.stopPropagation()}
+ >
+ {/* HEADER */}
+
+
+
+
Approver History
+
+
+
+ Menunggu
+
+
+ Banner
+
+
+ 1
+
+
+
+
+ {/* BODY */}
+
+ {/* LEFT TIMELINE */}
+
+ {/* Upload */}
+
+
+ {/* Diterima */}
+
+
Diterima
+
+ Direview oleh: approver-jaecoo1
+
+
+
+
+
+ {/* Pending */}
+
+
Pending
+
+ Direview oleh: approver-jaecoo1
+
+
+
+
+ {/* ARROW */}
+
+ >
+ >
+
+
+ {/* RIGHT NOTES */}
+
+
+
+ {/* FOOTER */}
+
+
+
+
+
+ )}
+
+ >
+ );
+}
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`}
>
-
{open && (
-
- admin-mabes
-
-
-
- Sign out
-
-
+ {/*
+ Jaecoo
+
*/}
+
)}
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" && (
+
+ )}