diff --git a/src/app/account/orders/page.tsx b/src/app/account/orders/page.tsx index ce5bf73..73f8828 100644 --- a/src/app/account/orders/page.tsx +++ b/src/app/account/orders/page.tsx @@ -18,7 +18,7 @@ const STATUS_FA: Record = { }; function formatMoney(amount: number) { - return new Intl.NumberFormat("fa-IR").format(amount) + " تومان"; + return new Intl.NumberFormat("fa-IR").format(amount) + " IRT"; } function formatDate(value: string) { diff --git a/src/app/blog/categories/page.tsx b/src/app/blog/categories/page.tsx index 514e618..d2573e5 100644 --- a/src/app/blog/categories/page.tsx +++ b/src/app/blog/categories/page.tsx @@ -183,6 +183,11 @@ export default function BlogCategoriesPage() { setDetailPost(null)} wide="full" footer={ @@ -423,6 +424,7 @@ export default function BlogListPage() { (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 [techHydrated, setTechHydrated] = useState(false); + const [coverPreview, setCoverPreview] = useState( + "/images/ahmad-ebadi-r5IzkiO08JU-unsplash.jpg", + ); + const [coverFile, setCoverFile] = useState(null); + const [galleryItems, setGalleryItems] = useState([]); + 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(() => { + if (!productId) return; + let cancelled = false; + (async () => { + setLoadingMeta(true); + setError(null); + try { + const [cats, brandRows, product] = await Promise.all([ + fetchCategoriesTree(), + fetchBrands(), + fetchProduct(productId), + ]); + if (cancelled) return; + setCategoryTree(cats); + setBrands(brandRows); + setCategoryId(product.categoryId || ""); + setBrandId(product.brandId || ""); + setNameFa(product.nameFa); + setNameEn(product.nameEn || ""); + setPrice(product.price ? String(product.price) : ""); + setAbstract(product.summary || ""); + setDesc(product.description || ""); + setTags(product.tags ?? []); + setCoverPreview(product.image); + setCoverFile(null); + setGalleryItems( + (product.gallery ?? []).map((preview) => ({ + preview, + file: null, + })), + ); + setTechnicalData(product.technicalData ?? {}); + setTechHydrated(true); + } catch (err) { + if (!cancelled) { + setError( + err instanceof ApiError + ? err.message + : "بارگذاری محصول ناموفق بود.", + ); + } + } finally { + if (!cancelled) setLoadingMeta(false); + } + })(); + return () => { + cancelled = true; + }; + }, [productId]); + + useEffect(() => { + if (!techHydrated) return; + setTechnicalData((prev) => { + const next: TechnicalData = {}; + for (const field of technicalFields) { + next[field.id] = prev[field.id] ?? ""; + } + return next; + }); + }, [technicalFields, techHydrated]); + + 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 (!productId) return; + 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) { + if (item.file) { + const uploaded = await uploadMedia(item.file, "gallery"); + galleryUrls.push(uploaded.url); + galleryKeys.push(uploaded.storageKey); + } else { + galleryUrls.push(item.preview); + } + } + + 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 updateProduct(productId, { + nameFa: nameFa.trim(), + nameEn: nameEn.trim() || "", + summary: abstract.trim() || "", + description: desc.trim() || "", + price: roundedPrice, + categoryId: categoryId ? Number(categoryId) : null, + brandId: brandId || null, + tags, + galleryUrls, + ...(galleryKeys.length ? { galleryKeys } : {}), + ...(imageUrl ? { imageUrl, imageKey } : {}), + technicalData: cleanedTechnical, + }); + + 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" + /> +
+
+ + +
+
+ +