300 lines
10 KiB
TypeScript
300 lines
10 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"), // ⬅️ PRICE NUMBER
|
||
|
|
banner: z.instanceof(FileList).optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export default function AddProductForm() {
|
||
|
|
const [colors, setColors] = useState([{ id: 1 }]);
|
||
|
|
const [selectedColor, setSelectedColor] = useState<string | null>(null);
|
||
|
|
const [specs, setSpecs] = useState([{ id: 1 }]);
|
||
|
|
const [file, setFile] = useState<File | null>(null);
|
||
|
|
const router = useRouter();
|
||
|
|
const MySwal = withReactContent(Swal);
|
||
|
|
|
||
|
|
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,
|
||
|
|
formState: { errors },
|
||
|
|
} = useForm<z.infer<typeof formSchema>>({
|
||
|
|
resolver: zodResolver(formSchema),
|
||
|
|
});
|
||
|
|
|
||
|
|
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());
|
||
|
|
// if (data.banner && data.banner.length > 0) {
|
||
|
|
// formData.append("thumbnail_path", data.banner[0]);
|
||
|
|
// }
|
||
|
|
if (file) {
|
||
|
|
formData.append("file", file);
|
||
|
|
}
|
||
|
|
const colorsArray = colors.map((c) => selectedColor || "");
|
||
|
|
formData.append("colors", JSON.stringify(colorsArray));
|
||
|
|
|
||
|
|
const res = await createProduct(formData);
|
||
|
|
|
||
|
|
console.log("API Success:", res);
|
||
|
|
successSubmit("/admin/product");
|
||
|
|
} catch (error) {
|
||
|
|
console.error("Submit Error:", error);
|
||
|
|
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 handleAddColor = () => {
|
||
|
|
setColors((prev) => [...prev, { id: prev.length + 1 }]);
|
||
|
|
};
|
||
|
|
|
||
|
|
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">
|
||
|
|
{/* 3 Input Field */}
|
||
|
|
<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"
|
||
|
|
{...register("price")}
|
||
|
|
/>
|
||
|
|
{errors.price && (
|
||
|
|
<p className="text-sm text-red-500 mt-1">
|
||
|
|
{errors.price.message}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Upload Banner */}
|
||
|
|
<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 placeholder="Contoh: Silver or #E2E2E2" />
|
||
|
|
|
||
|
|
{/* 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={() => 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>Foto Warna {index + 1}</Label>
|
||
|
|
<div 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
|
||
|
|
type="file"
|
||
|
|
accept="image/png,image/jpeg"
|
||
|
|
className="hidden"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
|
||
|
|
{/* Tambah Warna Baru */}
|
||
|
|
<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">
|
||
|
|
{/* Judul Spesifikasi */}
|
||
|
|
<Label className="text-sm font-semibold">
|
||
|
|
Judul Spesifikasi {index + 1}
|
||
|
|
</Label>
|
||
|
|
<Input
|
||
|
|
placeholder="Contoh: Mesin Turbo 1.6L, Interior Premium, Safety Features"
|
||
|
|
className="mt-1"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Foto Spesifikasi */}
|
||
|
|
<Label className="text-sm font-semibold mt-4 block">
|
||
|
|
Foto Spesifikasi {index + 1}
|
||
|
|
</Label>
|
||
|
|
<div 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
|
||
|
|
type="file"
|
||
|
|
accept="image/png,image/jpeg"
|
||
|
|
className="hidden"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
|
||
|
|
{/* Tambah spesifikasi baru */}
|
||
|
|
<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>
|
||
|
|
);
|
||
|
|
}
|