jaecoo-cihampelas/components/form/product/create-product-form.tsx

402 lines
13 KiB
TypeScript

"use client";
import { 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 { 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";
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<string | null>(null);
const [specs, setSpecs] = useState<
{ id: number; title: string; file: File | null }[]
>([{ id: 1, title: "", file: null }]);
const [file, setFile] = useState<File | null>(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 }]);
// };
const {
register,
handleSubmit,
setValue,
formState: { errors },
} = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
});
const handleSpecTitleChange = (index: number, value: string) => {
const updated = [...specs];
updated[index].title = value;
setSpecs(updated);
};
const handleSpecFileChange = (
index: number,
e: React.ChangeEvent<HTMLInputElement>,
) => {
const file = e.target.files?.[0] || null;
const updated = [...specs];
updated[index].file = file;
setSpecs(updated);
};
const onSubmit = async (data: z.infer<typeof formSchema>) => {
try {
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
const specificationsPayload = specs.map((s) => ({
title: s.title,
}));
formData.append("specifications", JSON.stringify(specificationsPayload));
// 🔥 imagespecification_url (files)
specs.forEach((s) => {
if (s.file) {
formData.append("imagespecification_url", s.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<HTMLInputElement>) => {
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<HTMLInputElement>,
) => {
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 (
<Card className="w-full max-w-full mx-auto shadow-md border-none">
<CardHeader>
<CardTitle className="text-xl font-bold text-teal-900">
Form Tambah Produk Baru
</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<div className="grid md:grid-cols-3 gap-4">
<div>
<Label>Nama Produk *</Label>
<Input placeholder="Masukkan Nama Produk" {...register("name")} />
{errors.name && (
<p className="text-sm text-red-500 mt-1">
{errors.name.message}
</p>
)}
</div>
<div>
<Label>Tipe Varian *</Label>
<Input
placeholder="Contoh: AWD, SHS, EV"
{...register("variant")}
/>
{errors.variant && (
<p className="text-sm text-red-500 mt-1">
{errors.variant.message}
</p>
)}
</div>
<div>
<Label>Harga Produk *</Label>
<Input
placeholder="Masukkan Harga Produk"
value={priceDisplay}
onChange={handlePriceChange}
/>
{errors.price && (
<p className="text-sm text-red-500 mt-1">
{errors.price.message}
</p>
)}
</div>
</div>
<div>
<Label className="text-gray-700">
Upload Banner <span className="text-red-500">*</span>
</Label>
<label
htmlFor="uploadFile"
className="mt-1 border-2 border-dashed border-gray-300 rounded-xl p-6 flex flex-col items-center justify-center text-gray-500 cursor-pointer hover:border-[#1F6779]/50 transition"
>
<UploadCloud className="w-10 h-10 text-[#1F6779] mb-2" />
<p className="text-sm font-medium">
Klik untuk upload atau drag & drop
</p>
<p className="text-xs text-gray-400 mt-1">PNG, JPG (max 2 MB)</p>
<input
id="uploadFile"
type="file"
accept="image/png, image/jpeg"
className="hidden"
onChange={handleFileChange}
/>
{file && (
<p className="mt-2 text-xs text-[#1F6779] font-medium">
{file.name}
</p>
)}
</label>
</div>
{/* Upload Produk */}
<div>
<Label>Upload Produk *</Label>
{colors.map((color, index) => (
<div
key={color.id}
className="border p-4 rounded-lg mt-4 space-y-4"
>
<Label className="text-sm font-semibold">
Pilih Warna {index + 1}
</Label>
<Input
value={color.name}
onChange={(e) => {
const updated = [...colors];
updated[index].name = e.target.value;
setColors(updated);
}}
/>
{/* Pilihan Warna */}
<div className="flex flex-wrap gap-2">
{[
"#1E4E52",
"#597E8D",
"#6B6B6B",
"#BEBEBE",
"#E2E2E2",
"#F4F4F4",
"#FFFFFF",
"#F9360A",
"#9A2A00",
"#7A1400",
"#4B0200",
"#B48B84",
"#FFA598",
].map((colorCode) => (
<button
key={colorCode}
type="button"
onClick={() => {
const updated = [...colors];
updated[index].name = colorCode; // 🔥 INI KUNCINYA
setColors(updated);
setSelectedColor(colorCode);
}}
className={`w-8 h-8 rounded-full border-2 transition ${
selectedColor === colorCode
? "border-teal-700 scale-110"
: "border-gray-200"
}`}
style={{ backgroundColor: colorCode }}
/>
))}
</div>
{/* Upload Foto Warna */}
<div>
<label
htmlFor={`color-file-${index}`}
className="border-2 border-dashed rounded-lg flex flex-col items-center justify-center py-6 cursor-pointer hover:bg-gray-50 transition"
>
<Upload className="w-6 h-6 text-gray-400 mb-2" />
<p className="text-sm text-gray-500 text-center">
Klik untuk upload foto mobil warna ini
</p>
<p className="text-xs text-gray-400">PNG, JPG (max 5 MB)</p>
<input
id={`color-file-${index}`}
type="file"
accept="image/png,image/jpeg"
className="hidden"
onChange={(e) => handleColorFileChange(index, e)}
/>
</label>
{color.file && (
<p className="text-xs text-teal-700 mt-2">
{color.file.name}
</p>
)}
</div>
</div>
))}
<Button
type="button"
onClick={handleAddColor}
className="w-full bg-teal-800 hover:bg-teal-900 text-white mt-4"
>
<Plus className="w-4 h-4 mr-2" /> Tambah Warna Baru
</Button>
</div>
<div className="mt-8">
<Label className="font-semibold text-lg text-teal-900">
Spesifikasi Produk <span className="text-red-500">*</span>
</Label>
{specs.map((spec, index) => (
<div key={spec.id} className="mt-4">
<Label className="text-sm font-semibold">
Judul Spesifikasi {index + 1}
</Label>
<Input
placeholder="Contoh: Mesin Turbo 1.6L"
className="mt-1"
value={spec.title}
onChange={(e) => handleSpecTitleChange(index, e.target.value)}
/>
<Label className="text-sm font-semibold mt-4 block">
Foto Spesifikasi {index + 1}
</Label>
<label
htmlFor={`spec-file-${index}`}
className="border-2 border-dashed rounded-lg flex flex-col items-center justify-center py-10 cursor-pointer hover:bg-gray-50 transition mt-1"
>
<Upload className="w-8 h-8 text-gray-400 mb-2" />
<p className="text-sm text-gray-500 text-center">
Klik untuk upload gambar spesifikasi
</p>
<p className="text-xs text-gray-400">PNG, JPG (max 5 MB)</p>
<input
id={`spec-file-${index}`}
type="file"
accept="image/png,image/jpeg"
className="hidden"
onChange={(e) => handleSpecFileChange(index, e)}
/>
</label>
{spec.file && (
<p className="text-xs text-teal-700 mt-2">{spec.file.name}</p>
)}
</div>
))}
{/* <button
type="button"
onClick={handleAddSpec}
className="w-full bg-teal-800 hover:bg-teal-900 text-white py-3 rounded-lg mt-6 flex items-center justify-center gap-2"
>
<Plus className="w-4 h-4" />
Tambahkan Spesifikasi Baru
</button> */}
</div>
<Button
type="submit"
className=" bg-teal-800 hover:bg-teal-900 text-white mt-6"
>
Submit
</Button>
</form>
</CardContent>
</Card>
);
}