import { useEffect, useState } from "react"; import { useCreateItem, useItems, useUpdateItem } from "../hooks/useItems"; import { useUIStore } from "../stores/uiStore"; import { CategoryPicker } from "./CategoryPicker"; import { ImageUpload } from "./ImageUpload"; interface ItemFormProps { mode: "add" | "edit"; itemId?: number | null; } interface FormData { name: string; weightGrams: string; priceDollars: string; quantity: number; categoryId: number; notes: string; productUrl: string; imageFilename: string | null; } const INITIAL_FORM: FormData = { name: "", weightGrams: "", priceDollars: "", quantity: 1, categoryId: 1, notes: "", productUrl: "", imageFilename: null, }; export function ItemForm({ mode, itemId }: ItemFormProps) { const { data: items } = useItems(); const createItem = useCreateItem(); const updateItem = useUpdateItem(); const closePanel = useUIStore((s) => s.closePanel); const openConfirmDelete = useUIStore((s) => s.openConfirmDelete); const [form, setForm] = useState(INITIAL_FORM); const [errors, setErrors] = useState>({}); // Pre-fill form when editing useEffect(() => { if (mode === "edit" && itemId != null && items) { const item = items.find((i) => i.id === itemId); if (item) { setForm({ name: item.name, weightGrams: item.weightGrams != null ? String(item.weightGrams) : "", priceDollars: item.priceCents != null ? (item.priceCents / 100).toFixed(2) : "", quantity: item.quantity ?? 1, categoryId: item.categoryId, notes: item.notes ?? "", productUrl: item.productUrl ?? "", imageFilename: item.imageFilename, }); } } else if (mode === "add") { setForm(INITIAL_FORM); } }, [mode, itemId, items]); function validate(): boolean { const newErrors: Record = {}; if (!form.name.trim()) { newErrors.name = "Name is required"; } if ( form.weightGrams && (Number.isNaN(Number(form.weightGrams)) || Number(form.weightGrams) < 0) ) { newErrors.weightGrams = "Must be a positive number"; } if ( form.priceDollars && (Number.isNaN(Number(form.priceDollars)) || Number(form.priceDollars) < 0) ) { newErrors.priceDollars = "Must be a positive number"; } if ( form.productUrl && form.productUrl.trim() !== "" && !form.productUrl.match(/^https?:\/\//) ) { newErrors.productUrl = "Must be a valid URL (https://...)"; } setErrors(newErrors); return Object.keys(newErrors).length === 0; } function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!validate()) return; const payload = { name: form.name.trim(), weightGrams: form.weightGrams ? Number(form.weightGrams) : undefined, priceCents: form.priceDollars ? Math.round(Number(form.priceDollars) * 100) : undefined, quantity: form.quantity, categoryId: form.categoryId, notes: form.notes.trim() || undefined, productUrl: form.productUrl.trim() || undefined, imageFilename: form.imageFilename ?? undefined, }; if (mode === "add") { createItem.mutate(payload, { onSuccess: () => { setForm(INITIAL_FORM); closePanel(); }, }); } else if (itemId != null) { updateItem.mutate( { id: itemId, ...payload }, { onSuccess: () => closePanel() }, ); } } const isPending = createItem.isPending || updateItem.isPending; return (
{/* Image */} setForm((f) => ({ ...f, imageFilename: filename })) } /> {/* Name */}
setForm((f) => ({ ...f, name: e.target.value }))} className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" placeholder="e.g. Osprey Talon 22" /> {errors.name && (

{errors.name}

)}
{/* Weight */}
setForm((f) => ({ ...f, weightGrams: e.target.value })) } className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" placeholder="e.g. 680" /> {errors.weightGrams && (

{errors.weightGrams}

)}
{/* Price */}
setForm((f) => ({ ...f, priceDollars: e.target.value })) } className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" placeholder="e.g. 129.99" /> {errors.priceDollars && (

{errors.priceDollars}

)}
{/* Quantity */}
setForm((f) => ({ ...f, quantity: Math.max(1, Number(e.target.value) || 1), })) } className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent" />
{/* Category */}
setForm((f) => ({ ...f, categoryId: id }))} />
{/* Notes */}