import { useState, useEffect, useRef } from "react"; import { Button } from "../ui/button"; import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerFooter, } from "../ui/drawer"; import { toast } from "../../hooks/use-toast"; import { ColorSelector } from "./ColorSelector"; import { SizeSelector } from "./SizeSelector"; import { useColorAttributes } from "~/requestHandler/use-product-hooks"; import AppInput from "../AppInput"; interface ProductVariant { color: string; size: string; quantity: number; } interface ProductVariantDrawerProps { isOpen: boolean; onClose: () => void; onSave: (variants: ProductVariant[]) => void; initialVariants?: ProductVariant[]; } export const ProductVariantDrawer = ({ isOpen, onClose, onSave, initialVariants = [], }: ProductVariantDrawerProps) => { const { data: colorAttributes } = useColorAttributes(); const [step, setStep] = useState<"attributes" | "quantities">("attributes"); const [selectedColors, setSelectedColors] = useState([]); const [selectedSizes, setSelectedSizes] = useState([]); const [bulkQuantity, setBulkQuantity] = useState("1"); const [customQuantities, setCustomQuantities] = useState< Record >({}); const [colorUnselectedMode, setColorUnselectedMode] = useState(false); const initializationDoneRef = useRef(false); const prevIsOpenRef = useRef(false); // Initialize state from initial variants when drawer opens useEffect(() => { // Only initialize when drawer opens for the first time (not on re-renders) if (isOpen && !prevIsOpenRef.current) { if (initialVariants.length > 0) { // Color still supports the "no color" escape hatch. const hasUnselectedColor = initialVariants.some( (v) => v.color === "unselected" ); setColorUnselectedMode(hasUnselectedColor); // Extract unique colors and sizes from initial variants (excluding unselected). // Legacy variants with size="unselected" are dropped here so the seller is // forced to pick a real size (FreeSize covers one-size products). const colors = [ ...new Set( initialVariants .map((v) => v.color) .filter((c) => c !== "unselected") ), ]; const sizes = [ ...new Set( initialVariants.map((v) => v.size).filter((s) => s !== "unselected") ), ]; setSelectedColors(colors); setSelectedSizes(sizes); // Set individual quantities const quantities: Record = {}; initialVariants.forEach((variant) => { const key = `${variant.color}-${variant.size}`; quantities[key] = variant.quantity; }); setCustomQuantities(quantities); // Set bulk quantity to the most common quantity or first quantity const quantityValues = initialVariants.map((v) => v.quantity); const mostCommonQuantity = quantityValues.length > 0 ? quantityValues[0] : 1; setBulkQuantity(mostCommonQuantity.toString()); } else { // Reset state when opening without initial variants setStep("attributes"); setSelectedColors([]); setSelectedSizes([]); setBulkQuantity("1"); setCustomQuantities({}); setColorUnselectedMode(false); } initializationDoneRef.current = true; } // Reset when drawer closes if (!isOpen && prevIsOpenRef.current) { setStep("attributes"); setSelectedColors([]); setSelectedSizes([]); setBulkQuantity("1"); setCustomQuantities({}); setColorUnselectedMode(false); initializationDoneRef.current = false; } prevIsOpenRef.current = isOpen; }, [isOpen]); // Separate effect for initialVariants changes (when drawer is already open) useEffect(() => { if (isOpen && initializationDoneRef.current && initialVariants.length > 0) { // Only update if variants actually changed const currentVariantKeys = selectedColors .flatMap((color) => selectedSizes.map((size) => `${color}-${size}`)) .sort(); const newVariantKeys = initialVariants .map((v) => `${v.color}-${v.size}`) .sort(); if ( JSON.stringify(currentVariantKeys) !== JSON.stringify(newVariantKeys) ) { // Color still supports the "no color" escape hatch. const hasUnselectedColor = initialVariants.some( (v) => v.color === "unselected" ); setColorUnselectedMode(hasUnselectedColor); // Extract unique colors and sizes from initial variants (excluding unselected). // Legacy "unselected" size variants are ignored so the seller must pick. const colors = [ ...new Set( initialVariants .map((v) => v.color) .filter((c) => c !== "unselected") ), ]; const sizes = [ ...new Set( initialVariants.map((v) => v.size).filter((s) => s !== "unselected") ), ]; setSelectedColors(colors); setSelectedSizes(sizes); // Set individual quantities const quantities: Record = {}; initialVariants.forEach((variant) => { const key = `${variant.color}-${variant.size}`; quantities[key] = variant.quantity; }); setCustomQuantities(quantities); } } }, [initialVariants, isOpen]); // Generate all possible variants const generateVariants = (): ProductVariant[] => { const variants: ProductVariant[] = []; getQuantityVariants().forEach((variant) => { const { color, size, key } = variant; let quantity: number; if (key in customQuantities) { // Use custom quantity if exists quantity = customQuantities[key]; } else { // Use bulk quantity for variants without custom quantity quantity = parseInt(bulkQuantity) || 0; } variants.push({ color, size, quantity }); }); return variants; }; const toggleColor = (colorValue: string) => { setSelectedColors((prev) => prev.includes(colorValue) ? prev.filter((c) => c !== colorValue) : [...prev, colorValue] ); }; const removeColor = (colorValue: string) => { setSelectedColors((prev) => prev.filter((c) => c !== colorValue)); }; const toggleSize = (size: string) => { setSelectedSizes((prev) => prev.includes(size) ? prev.filter((s) => s !== size) : [...prev, size] ); }; const removeSize = (size: string) => { setSelectedSizes((prev) => prev.filter((s) => s !== size)); }; const handleQuantityChange = ( color: string, size: string, quantity: string ) => { const key = `${color}-${size}`; // Allow empty string or parse to number (including 0) const quantityNumber = quantity === "" ? 0 : parseInt(quantity); setCustomQuantities((prev) => ({ ...prev, [key]: isNaN(quantityNumber) ? 0 : quantityNumber, })); }; const applyBulkQuantityToAll = () => { const bulkQuantityNumber = parseInt(bulkQuantity) || 0; const newQuantities: Record = {}; getQuantityVariants().forEach((variant) => { const { key } = variant; newQuantities[key] = bulkQuantityNumber; }); setCustomQuantities((prev) => ({ ...prev, ...newQuantities })); }; // Get variants for quantity display. Size is always required (FreeSize covers // one-size products); color still has an unselected escape hatch. const getQuantityVariants = () => { const variants: { color: string; size: string; key: string }[] = []; if (colorUnselectedMode) { selectedSizes.forEach((size) => { variants.push({ color: "unselected", size: size, key: `unselected-${size}`, }); }); } else { selectedColors.forEach((color) => { selectedSizes.forEach((size) => { variants.push({ color: color, size: size, key: `${color}-${size}`, }); }); }); } return variants; }; const handleNext = () => { // Size is always required; use "فری سایز" for one-size products. if (selectedSizes.length === 0) { toast({ title: "خطا", description: "لطفاً حداقل یک سایز انتخاب کنید. اگر محصول تک‌سایز است، «فری سایز» را انتخاب کنید.", }); return; } // Color is optional (colorUnselectedMode covers no-color products). if (!colorUnselectedMode && selectedColors.length === 0) { toast({ title: "خطا", description: "لطفاً حداقل یک رنگ انتخاب کنید یا حالت بدون رنگ را فعال کنید", }); return; } setStep("quantities"); }; const handleSave = () => { const variants = generateVariants(); onSave(variants); onClose(); }; const handleClose = () => { onClose(); }; return ( {step === "attributes" ? "انتخاب رنگ و سایز" : "تعیین موجودی"}
{step === "attributes" ? (
{/* Color Selection */} {/* Size Selection */}
) : (
{/* Bulk Quantity Setting */}

تعیین موجودی عمومی

این مقدار به تمام موارد اعمال می‌شود

setBulkQuantity(e.target.value)} inputPlaceholder="0" type="number" className="text-center font-semibold text-lg" />
{/* Individual Variant Quantities */}

موجودی اختصاصی:

{getQuantityVariants().map((variant) => { const { color, size, key } = variant; const quantity = key in customQuantities ? customQuantities[key] : parseInt(bulkQuantity) || 0; const colorInfo = colorAttributes?.find( (c) => c.value === color ); // Helper function to get display text — colorless variants // roll up under the size label; sized/colored variants show // both. const getDisplayText = () => { if (colorUnselectedMode) { return `موجودی سایز ${size}`; } return null; // Normal mode — render color + size below }; const displayText = getDisplayText(); return (
{displayText ? ( // Special unselected modes {displayText} ) : ( // Normal mode - show color and size <>
{colorInfo?.info.persian || color}
سایز: {size}
)}
handleQuantityChange( color, size, e.target.value ) } inputPlaceholder="0" type="number" className="text-center text-sm" />
); })}
)}
{step === "attributes" ? ( ) : ( <> )}
); };