"use client" import { useState } from "react" import { AlertTriangle, Plus, Save, Trash2 } from "lucide-react" import { useFieldArray } from "react-hook-form" import { Button } from "@/shared/components/ui/button" import { Input } from "@/shared/components/ui/input" import { Label } from "@/shared/components/ui/label" import { Alert, AlertTitle } from "@/shared/components/ui/alert" import { FieldGroup } from "@/shared/components/ui/field" import { Rhform, RhfTextField, RhfTextareaField, RhfAsyncSelectField, RhfAutoGenerateField, RhfSelectField, type InlineCreateFormProps, } from "@/shared/components/form" import { toast } from "sonner" import { useAuthApi } from "@/shared/useApi" import { useResourceForm } from "@/shared/hooks/use-resource-form" import { useFormMutation } from "@/shared/hooks/use-form-mutation" import { toRelation, toId } from "@/shared/lib/utils" import { inventoryAdjustmentFormSchema, type InventoryAdjustmentFormValues, } from "./inventory-adjustment.schema" import { INVENTORY_ADJUSTMENT_ROUTES, REASON_ROUTES, PARTS_ROUTES } from "@garage/api" // ── Props ── export type InventoryAdjustmentFormProps = { resourceId?: string | null initialData?: unknown onSuccess?: () => void } // ── Default values ── const OPERATION_OPTIONS = [ { value: "add", label: "Increase" }, { value: "subtract", label: "Decrease" }, ] const DEFAULT_VALUES: InventoryAdjustmentFormValues = { reference_number: "", date: "", chart_of_account: undefined, reason: null, notes: "", parts: [{ part: null, quantity: 1, operation: "add", rate: 0 }], } // ── Mapping helpers ── function mapToFormValues(data: unknown): InventoryAdjustmentFormValues { const d = (data as any)?.data ?? data ?? {} // Laravel serializes the `inventoryAdjustmentParts` relation as // `inventory_adjustment_parts` in JSON; fall back through the other // shapes for resilience. const rawParts: any[] = (Array.isArray(d.inventory_adjustment_parts) && d.inventory_adjustment_parts) || (Array.isArray(d.inventoryAdjustmentParts) && d.inventoryAdjustmentParts) || (Array.isArray(d.parts) && d.parts) || [] // Backend casts `date` to Carbon → serializes as an ISO string // ("2026-05-18T00:00:00.000000Z"). HTML only accepts // "YYYY-MM-DD", so slice the first 10 chars; pass through plain dates. const rawDate = d.date ?? "" const date = typeof rawDate === "string" && rawDate.length >= 10 ? rawDate.slice(0, 10) : "" return { reference_number: d.reference_number || "", date, chart_of_account: d.chart_of_account != null && d.chart_of_account !== "" ? Number(d.chart_of_account) : undefined, reason: toRelation(d.reason_id, d.reason?.title ?? d.reason?.name ?? d.reason_name), notes: d.notes || "", parts: rawParts.length > 0 ? rawParts.map((p: any) => ({ part: toRelation(p.part_id, p.part?.title ?? p.part?.name ?? p.part_name), quantity: p.quantity ?? 1, operation: (p.operation === "subtract" ? "subtract" : "add") as "add" | "subtract", rate: p.rate ?? 0, })) : [{ part: null, quantity: 1, operation: "add", rate: 0 }], } } function mapFormToPayload(values: InventoryAdjustmentFormValues) { return { reference_number: values.reference_number || undefined, date: values.date || undefined, chart_of_account: values.chart_of_account ?? undefined, reason_id: toId(values.reason) ? Number(toId(values.reason)) : undefined, notes: values.notes || undefined, parts: values.parts.map((p) => ({ part_id: toId(p.part) ? Number(toId(p.part)) : undefined, quantity: p.quantity, operation: p.operation, rate: p.rate, })), } } const mapLookupOption = (item: any) => ({ value: String(item.id), label: item.name ?? item.title }) const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label } function ReasonInlineCreateForm({ onSuccess }: InlineCreateFormProps) { const api = useAuthApi() const [title, setTitle] = useState("") const [isSaving, setIsSaving] = useState(false) const handleSave = async () => { const trimmed = title.trim() if (!trimmed) { toast.error("Reason title is required.") return } setIsSaving(true) try { const response = (await api.reasons.create({ title: trimmed } as never)) as any const created = response?.data ?? response toast.success("Reason added.") onSuccess(created?.id ? { value: String(created.id), label: created.title ?? trimmed } : undefined) } catch { toast.error("Failed to add reason.") } finally { setIsSaving(false) } } return (
setTitle(e.target.value)} placeholder="e.g. Stock count correction" autoFocus />
) } // ── Component ── export function InventoryAdjustmentForm({ resourceId, initialData, onSuccess }: InventoryAdjustmentFormProps) { const api = useAuthApi() const { form, isEditing } = useResourceForm({ schema: inventoryAdjustmentFormSchema, defaultValues: DEFAULT_VALUES, resourceId, initialData, mapToFormValues, }) const { fields, append, remove } = useFieldArray({ control: form.control, name: "parts", }) const { mutate, error, isPending } = useFormMutation(form, { mutationFn: (values: InventoryAdjustmentFormValues) => { const payload = mapFormToPayload(values) const promise = isEditing && resourceId ? api.inventoryAdjustments.update(resourceId, payload as never) : api.inventoryAdjustments.create(payload as never) return promise }, onSuccess: () => { toast.success(isEditing ? "Adjustment updated." : "Adjustment created.") onSuccess?.() }, }) return ( {error && ( {isEditing ? "Failed to update adjustment" : "Failed to create adjustment"} {error.message} )}
{/* TODO: replace with chart-of-accounts async select once the module ships. Locked as integer + disabled for now. */} api.reasons.list()} mapOption={mapLookupOption} createLabel="Reason" createForm={(props) => } {...STORE_OBJECT} />
Parts
{fields.length === 0 && (

No parts added. Click "Add Part" to begin.

)} {fields.map((field, index) => (
api.parts.list()} mapOption={mapLookupOption} {...STORE_OBJECT} />
))}
) }