"use client"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ChangeEvent, FormEvent, useEffect, useMemo, useRef, useState } from "react"; import ClothesShell from "@/components/clothes/ClothesShell"; import CategoryTreeSelect from "@/components/ui/CategoryTreeSelect"; import ImageCropper from "@/components/ui/ImageCropper"; import PriceInput from "@/components/ui/PriceInput"; import type { Brand, CategoryNode, TechnicalData } from "@/data/clothes"; import { parsePriceInput, resolveCategoryTechnicalFields, } from "@/data/clothes"; import { ApiError, uploadMedia } from "@/lib/api"; import { createProduct, fetchBrands, fetchCategoriesTree, } from "@/lib/clothes-api"; export default function AddProductPage() { const router = useRouter(); const coverRef = useRef(null); const galleryRef = useRef(null); const [categoryTree, setCategoryTree] = useState([]); const [brands, setBrands] = useState([]); const [categoryId, setCategoryId] = useState(""); const [brandId, setBrandId] = useState(""); const [nameFa, setNameFa] = useState(""); const [nameEn, setNameEn] = useState(""); const [price, setPrice] = useState(""); const [abstract, setAbstract] = useState(""); const [desc, setDesc] = useState(""); const [tags, setTags] = useState([]); const [tagInput, setTagInput] = useState(""); const [technicalData, setTechnicalData] = useState({}); const [coverPreview, setCoverPreview] = useState( "/images/ahmad-ebadi-r5IzkiO08JU-unsplash.jpg", ); const [coverFile, setCoverFile] = useState(null); const [galleryItems, setGalleryItems] = useState< Array<{ preview: string; file: File }> >([]); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [loadingMeta, setLoadingMeta] = useState(true); const technicalFields = useMemo( () => categoryId ? resolveCategoryTechnicalFields(categoryId, categoryTree) : [], [categoryId, categoryTree], ); useEffect(() => { setTechnicalData((prev) => { const next: TechnicalData = {}; for (const field of technicalFields) { next[field.id] = prev[field.id] ?? ""; } return next; }); }, [technicalFields]); useEffect(() => { let cancelled = false; (async () => { try { const [cats, brandRows] = await Promise.all([ fetchCategoriesTree(), fetchBrands(), ]); if (cancelled) return; setCategoryTree(cats); setBrands(brandRows); } catch (err) { if (!cancelled) { setError( err instanceof ApiError ? err.message : "بارگذاری دسته‌ها و برندها ناموفق بود.", ); } } finally { if (!cancelled) setLoadingMeta(false); } })(); return () => { cancelled = true; }; }, []); function addTag() { const next = tagInput.trim(); if (!next || tags.includes(next)) return; setTags((prev) => [...prev, next]); setTagInput(""); } function handleCoverChange(event: ChangeEvent) { const file = event.target.files?.[0]; if (!file) return; setCoverFile(file); setCoverPreview(URL.createObjectURL(file)); } function handleGalleryChange(event: ChangeEvent) { const files = event.target.files; if (!files?.length) return; const next = Array.from(files).map((file) => ({ preview: URL.createObjectURL(file), file, })); setGalleryItems((prev) => [...prev, ...next]); event.target.value = ""; } async function handleSubmit(event: FormEvent) { event.preventDefault(); if (!nameFa.trim()) { setError("نام فارسی الزامی است."); return; } const priceTrimmed = parsePriceInput(price); const priceNum = priceTrimmed ? Number(priceTrimmed) : 0; if (priceTrimmed && (!Number.isFinite(priceNum) || priceNum < 0)) { setError("قیمت معتبر وارد کنید."); return; } const missingRequired = technicalFields.find( (field) => field.required && !(technicalData[field.id] ?? "").trim(), ); if (missingRequired) { setError(`فیلد «${missingRequired.labelFa}» الزامی است.`); return; } setSaving(true); setError(null); try { let imageUrl: string | undefined; let imageKey: string | undefined; if (coverFile) { const uploaded = await uploadMedia(coverFile, "main"); imageUrl = uploaded.url; imageKey = uploaded.storageKey; } const galleryUrls: string[] = []; const galleryKeys: string[] = []; for (const item of galleryItems) { const uploaded = await uploadMedia(item.file, "gallery"); galleryUrls.push(uploaded.url); galleryKeys.push(uploaded.storageKey); } const cleanedTechnical: TechnicalData = {}; for (const field of technicalFields) { const value = (technicalData[field.id] ?? "").trim(); if (value) cleanedTechnical[field.id] = value; } const roundedPrice = Math.round(priceNum); await createProduct({ nameFa: nameFa.trim(), nameEn: nameEn.trim() || undefined, summary: abstract.trim() || undefined, description: desc.trim() || undefined, price: roundedPrice, categoryId: categoryId ? Number(categoryId) : undefined, brandId: brandId || undefined, tags, imageUrl, imageKey, galleryUrls, galleryKeys, published: true, technicalData: cleanedTechnical, ...(priceTrimmed ? { variants: [ { colorId: "", sizeId: "", packageId: "", price: roundedPrice, }, ], } : {}), }); router.push("/clothes/list"); router.refresh(); } catch (err) { setError( err instanceof ApiError ? err.message : "ثبت محصول ناموفق بود.", ); } finally { setSaving(false); } } return (
{error ? (

{error}

) : null} {loadingMeta ? (

در حال بارگذاری...

) : null}
setNameFa(e.target.value)} placeholder="مثلاً مانتو مشکی کلاسیک" required />
setNameEn(e.target.value)} placeholder="Classic Black Manteau" />