"use client"; import { useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { Upload, Plus, UploadCloud } from "lucide-react"; import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; import { Label } from "@radix-ui/react-dropdown-menu"; import Image from "next/image"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { createProduct } from "@/service/product"; import withReactContent from "sweetalert2-react-content"; import Swal from "sweetalert2"; import { useRouter } from "next/navigation"; import { loading } from "@/config/swal"; const formSchema = z.object({ name: z.string().min(1, "Nama produk wajib diisi"), variant: z.string().min(1, "Tipe varian wajib diisi"), price: z.coerce.number().min(1, "Harga produk wajib diisi"), banner: z.instanceof(FileList).optional(), }); export default function AddProductForm() { const [colors, setColors] = useState< { id: number; name: string; file: File | null }[] >([{ id: 1, name: "", file: null }]); const [selectedColor, setSelectedColor] = useState(null); const [specs, setSpecs] = useState< { id: number; title: string; images: string[]; files: File[] }[] >([{ id: 1, title: "", images: [], files: [] }]); const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false); const [uploadTarget, setUploadTarget] = useState<{ type: "spec"; index: number; } | null>(null); const fileInputId = "spec-upload-input"; const fileInputRef = useRef(null); const isUploadingRef = useRef(false); 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]; if (selected) setFile(selected); }; const handleAddSpec = () => { setSpecs((prev) => [ ...prev, { id: prev.length + 1, title: "", images: [], files: [] }, ]); }; const { register, handleSubmit, setValue, formState: { errors }, } = useForm>({ resolver: zodResolver(formSchema), }); const handleSpecTitleChange = (index: number, value: string) => { const updated = [...specs]; updated[index].title = value; setSpecs(updated); }; const handleFileSelected = (e: React.ChangeEvent) => { const input = e.target; const selectedFile = input.files?.[0]; if (!selectedFile || !uploadTarget) return; // 🔒 CEGAH DOUBLE EVENT if (isUploadingRef.current) return; isUploadingRef.current = true; setSpecs((prev) => { const updated = [...prev]; const spec = updated[uploadTarget.index]; // max 5 gambar if (spec.files.length >= 5) { isUploadingRef.current = false; return prev; } // cegah file sama if ( spec.files.some( (f) => f.name === selectedFile.name && f.size === selectedFile.size, ) ) { isUploadingRef.current = false; return prev; } const previewUrl = URL.createObjectURL(selectedFile); spec.images = [...spec.images, previewUrl]; spec.files = [...spec.files, selectedFile]; return updated; }); // reset input input.value = ""; setIsUploadDialogOpen(false); // release lock setTimeout(() => { isUploadingRef.current = false; }, 0); }; const onSubmit = async (data: z.infer) => { try { loading(); const formData = new FormData(); formData.append("title", data.name); formData.append("variant", data.variant); formData.append("price", data.price.toString()); formData.append("status", "1"); formData.append("is_active", "1"); // banner if (file) { formData.append("file", file); } // 🔥 colors JSON (object) const colorsPayload = colors.map((c) => ({ name: c.name, })); formData.append("colors", JSON.stringify(colorsPayload)); // 🔥 color images colors.forEach((c) => { if (c.file) { formData.append("color_images", c.file); } }); // 🔥 specifications JSON (include image count for backend processing) const specificationsPayload = specs.map((s) => ({ title: s.title, imageCount: s.files.length, })); formData.append("specifications", JSON.stringify(specificationsPayload)); // 🔥 specification images (multiple files per spec) specs.forEach((s) => { s.files.forEach((file) => { formData.append("specification_images", file); }); }); await createProduct(formData); successSubmit("/admin/product"); } catch (err) { console.error(err); alert("Gagal mengirim produk"); } }; function successSubmit(redirect: any) { MySwal.fire({ title: "Sukses", icon: "success", confirmButtonColor: "#3085d6", confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { router.push(redirect); } }); } 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, name: "", file: null }, ]); }; const handleColorFileChange = ( index: number, e: React.ChangeEvent, ) => { const file = e.target.files?.[0] || null; const updated = [...colors]; updated[index].file = file; setColors(updated); }; const formatRupiah = (value: string) => { const number = value.replace(/\D/g, ""); return number ? `Rp ${Number(number).toLocaleString("id-ID")}` : ""; }; return ( Form Tambah Produk Baru
{errors.name && (

{errors.name.message}

)}
{errors.variant && (

{errors.variant.message}

)}
{errors.price && (

{errors.price.message}

)}
{/* Upload Produk */}
{colors.map((color, index) => (
{ const updated = [...colors]; updated[index].name = e.target.value; setColors(updated); }} /> {/* Pilihan Warna */}
{[ "#1E4E52", "#597E8D", "#6B6B6B", "#BEBEBE", "#E2E2E2", "#F4F4F4", "#FFFFFF", "#F9360A", "#9A2A00", "#7A1400", "#4B0200", "#B48B84", "#FFA598", ].map((colorCode) => (
{/* Upload Foto Warna */}
{color.file && (

{color.file.name}

)}
))}
{specs.map((spec, index) => (
handleSpecTitleChange(index, e.target.value)} />
{spec.images.map((img, i) => (
spec
))}
))}
Upload File

Klik tombol di bawah untuk memilih file

PNG, JPG (max 2 MB)

); }