"use client"; import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { ImageUpload } from "@/components/forms/image-upload"; import { PricingTiers } from "@/components/forms/pricing-tiers"; import type { ProductModalProps, ProductData } from "@/lib/types"; import { toast } from "sonner"; import type React from "react"; import { Plus } from "lucide-react"; import { clientFetch } from "@/lib/api"; import { Switch } from "@/components/ui/switch"; type CategorySelectProps = { categories: { _id: string; name: string; parentId?: string }[]; value: string; setProductData: React.Dispatch>; onAddCategory: (newCategory: { _id: string; name: string; parentId?: string }) => void; }; export const ProductModal: React.FC = ({ open, onClose, onSave, productData, categories, editing, handleChange, handleRemoveTier, setProductData, }) => { /** * 1) Store the selected file *separately* from productData.image */ const [selectedFile, setSelectedFile] = useState(null); const [imagePreview, setImagePreview] = useState(null); const [imageDimensions, setImageDimensions] = useState({ width: 300, height: 200 }); const [localCategories, setLocalCategories] = useState(categories); // If productData.image is a *URL* (string), show it as a default preview useEffect(() => { if (productData.image && typeof productData.image === "string" && productData._id) { setImagePreview(`${process.env.NEXT_PUBLIC_API_URL}/products/${productData._id}/image`); } else if (productData.image && typeof productData.image === "string") { // Image exists but no ID, this is probably a new product setImagePreview(null); } }, [productData.image, productData._id]); useEffect(() => { setLocalCategories(categories); }, [categories]); // Reset image state when modal is closed useEffect(() => { if (!open) { setSelectedFile(null); setImagePreview(null); } }, [open]); const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) { // no file selected setSelectedFile(null); setImagePreview(null); return; } // For preview const objectUrl = URL.createObjectURL(file); setSelectedFile(file); setImagePreview(objectUrl); const image = new Image(); image.onload = () => { const aspectRatio = image.naturalWidth / image.naturalHeight; const width = aspectRatio > 1 ? 300 : 200 * aspectRatio; const height = aspectRatio > 1 ? 300 / aspectRatio : 200; setImageDimensions({ width, height }); }; image.src = objectUrl; }; const handleAddTier = () => { setProductData((prev) => ({ ...prev, pricing: [ ...prev.pricing, { minQuantity: 0, pricePerUnit: 0, // tempId ensures stable identity before backend assigns _id // Using crypto.randomUUID if available, otherwise a timestamp fallback tempId: (typeof crypto !== "undefined" && (crypto as any).randomUUID ? (crypto as any).randomUUID() : `${Date.now()}-${Math.random()}`), }, ], })); }; const handleSave = async () => { try { // Validate required fields if (!productData.name || !productData.category || !productData.unitType) { toast.error("Please fill in all required fields"); return; } // Validate pricing tiers if (!productData.pricing || productData.pricing.length === 0) { toast.error("At least one pricing tier is required"); return; } // Make sure stock values are numbers let stockData = { ...productData }; if (stockData.stockTracking) { stockData.currentStock = Number(stockData.currentStock) || 0; stockData.lowStockThreshold = Number(stockData.lowStockThreshold) || 10; } await onSave(stockData, selectedFile); onClose(); } catch (error) { console.error("Error saving product:", error); toast.error("Failed to save product"); } }; const handleAddCategory = (newCategory: { _id: string; name: string; parentId?: string }) => { setLocalCategories((prev) => [...prev, newCategory]); }; const handleTierChange = (e: React.ChangeEvent, index: number) => { const { name, value } = e.target; setProductData((prev) => ({ ...prev, pricing: prev.pricing.map((tier, i) => i === index ? { ...tier, [name]: value === "" ? 0 : parseFloat(value) || 0 } : tier ), })); }; return ( {editing ? "Edit Product" : "Add Product"}
); }; const ProductBasicInfo: React.FC<{ productData: ProductData; handleChange: (e: React.ChangeEvent) => void; categories: { _id: string; name: string; parentId?: string }[]; setProductData: React.Dispatch>; onAddCategory: (newCategory: { _id: string; name: string; parentId?: string }) => void; }> = ({ productData, handleChange, categories, setProductData, onAddCategory }) => (