"use client" import { useState } from "react" import { useForm, type Resolver } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Button } from "@/components/ui/button" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Switch } from "@/components/ui/switch" import { useRouter } from "next/navigation" import { toast } from "sonner" import { Loader2, X, UploadCloud } from "lucide-react" import imageCompression from 'browser-image-compression' import { createClient } from "@/lib/supabase-browser" import Image from "next/image" import { createProduct, updateProduct, deleteProductImage } from "@/app/(dashboard)/dashboard/products/actions" const productSchema = z.object({ name: z.string().min(2, "Ürün adı en az 2 karakter olmalıdır"), product_code: z.string().optional(), category: z.string().min(1, "Kategori seçiniz"), description: z.string().optional(), price: z.coerce.number().min(0, "Fiyat 0'dan küçük olamaz"), image_url: z.string().optional(), is_active: z.boolean().default(true), images: z.array(z.string()).optional() }) type ProductFormValues = z.infer // Define the shape of data coming from Supabase interface Product { id: number name: string product_code?: string | null category: string description: string | null price: number image_url: string | null created_at: string is_active?: boolean // images? we might need to fetch them separately if they are in another table, // but for now let's assume update passes them if fetched, or we can handle it later. // Ideally the server component fetches relation. } interface ProductFormProps { initialData?: Product } export function ProductForm({ initialData }: ProductFormProps) { const router = useRouter() const [loading, setLoading] = useState(false) const [uploading, setUploading] = useState(false) const [previewImages, setPreviewImages] = useState( initialData?.image_url ? [initialData.image_url] : [] ) // Note: initialData probably only has single image_url field unless we updated the fetch query. // For MVP phase 1, we just sync with image_url or expect 'images' prop if we extended it. // I will add a local state for images. const form = useForm({ resolver: zodResolver(productSchema) as Resolver, defaultValues: initialData ? { name: initialData.name, product_code: initialData.product_code || "", category: initialData.category, description: initialData.description || "", price: initialData.price, image_url: initialData.image_url || "", is_active: initialData.is_active ?? true, images: initialData.image_url ? [initialData.image_url] : [] } : { name: "", product_code: "", category: "", description: "", price: 0, image_url: "", is_active: true, images: [] }, }) const handleImageUpload = async (event: React.ChangeEvent) => { const files = event.target.files if (!files || files.length === 0) return setUploading(true) const supabase = createClient() const uploadedUrls: string[] = [...form.getValues("images") || []] try { for (let i = 0; i < files.length; i++) { const file = files[i] // Compression const options = { maxSizeMB: 1, // Max 1MB maxWidthOrHeight: 1920, useWebWorker: true } let compressedFile = file try { compressedFile = await imageCompression(file, options) } catch (error) { console.error("Compression error:", error) // Fallback to original } // Upload const fileExt = file.name.split('.').pop() const fileName = `${Math.random().toString(36).substring(2)}_${Date.now()}.${fileExt}` const filePath = `products/${fileName}` const { error: uploadError } = await supabase.storage .from('products') // Assuming 'products' bucket exists .upload(filePath, compressedFile) if (uploadError) { console.error(uploadError) toast.error(`Resim yüklenemedi: ${file.name}`) continue } // Get URL const { data } = supabase.storage.from('products').getPublicUrl(filePath) uploadedUrls.push(data.publicUrl) } // Update form form.setValue("images", uploadedUrls) // Set first image as main if (uploadedUrls.length > 0) { form.setValue("image_url", uploadedUrls[0]) } setPreviewImages(uploadedUrls) } catch { toast.error("Yükleme sırasında hata oluştu") } finally { setUploading(false) } } const removeImage = async (index: number) => { const imageToDelete = previewImages[index] // If editing an existing product and image is from server (starts with http/https usually) if (initialData && imageToDelete.startsWith("http")) { const result = await deleteProductImage(imageToDelete, initialData.id) if (!result.success) { toast.error("Resim silinirken hata oluştu") return } toast.success("Resim silindi") } const currentImages = [...form.getValues("images") || []] // Filter out the deleted image URL if it matches const newImages = currentImages.filter(url => url !== imageToDelete) form.setValue("images", newImages) if (newImages.length > 0) { form.setValue("image_url", newImages[0]) } else { form.setValue("image_url", "") } setPreviewImages(newImages) } async function onSubmit(data: ProductFormValues) { try { setLoading(true) let result if (initialData) { result = await updateProduct(initialData.id, data) } else { result = await createProduct(data) } if (!result.success) { toast.error(result.error || "Bir hata oluştu") return } toast.success(initialData ? "Ürün güncellendi" : "Ürün başarıyla oluşturuldu") router.push("/dashboard/products") router.refresh() } catch { toast.error("Bir aksilik oldu") } finally { setLoading(false) } } return (
(
Aktif Durum Ürün sitede görüntülensin mi?
)} />
( Ürün Adı )} /> ( Ürün Kodu )} />
( Kategori )} />
( Fiyat (₺) )} />
Ürün Görselleri
{previewImages.map((url, i) => (
Preview
))}
Birden fazla resim seçebilirsiniz. Resimler otomatik olarak sıkıştırılacaktır.
{/* Hidden input for main image url fallback if needed */} ( Açıklama