"use client" import React from "react" import { AlertTriangle, Plus, Save } from "lucide-react" import { z } from "zod" import { Button } from "@/shared/components/ui/button" import { Alert, AlertTitle } from "@/shared/components/ui/alert" import { FieldGroup } from "@/shared/components/ui/field" import { Rhform, RhfTextField, RhfTextareaField, RhfSelectField, RhfAsyncSelectField, } from "@/shared/components/form" import { DepartmentInlineForm } from "@/modules/services/inline-forms/department-inline-form" import { toast } from "sonner" import { useAuthApi } from "@/shared/useApi" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { RateType, DEPARTMENT_ROUTES, TAX_ROUTES } from "@garage/api" import { useJobCard } from "./job-card-context" // ── Schema ── const relationFieldSchema = z.object({ value: z.string(), label: z.string() }).nullable() const jobCardServiceFormSchema = z.object({ service: relationFieldSchema, department: z.object({ value: z.string(), label: z.string() }), tax: relationFieldSchema.optional(), rate_type: z.string().optional(), labor_rate: relationFieldSchema.optional(), quantity: z.coerce.number().min(1, "Quantity is required"), rate: z.coerce.number().min(0, "Rate is required"), working_hours: z.coerce.number().min(0).optional(), labor_hours: z.coerce.number().min(0).optional(), discount_amount: z.coerce.number().min(0).optional(), chart_of_account: z.string().optional(), description: z.string().optional(), }) type JobCardServiceFormValues = z.infer // ── Props ── export type JobCardServiceFormProps = { jobCardId: string jobCardServiceId?: number | null initialData?: unknown onSuccess?: () => void onCancel?: () => void } const DEFAULT_VALUES: JobCardServiceFormValues = { service: null, department: undefined as any, tax: null, rate_type: "flat_rate", labor_rate: null, quantity: 1, rate: 0, working_hours: 0, labor_hours: 0, discount_amount: undefined, chart_of_account: "", description: "", } const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label } function mapToFormValues(data: unknown): JobCardServiceFormValues { const d = (data as any) ?? {} return { service: d.service ? { value: String(d.service.id), label: d.service.labor_name ?? String(d.service.id) } : null, department: d.department ? { value: String(d.department.id), label: d.department.name ?? String(d.department.id) } : undefined as any, tax: d.tax_id != null ? { value: String(d.tax_id), label: d.tax ? `${d.tax.title} (${d.tax.rate}%)` : String(d.tax_id) } : null, rate_type: d.rate_type ?? "flat_rate", labor_rate: d.labor_rate ? { value: String(d.labor_rate.id), label: d.labor_rate.title ?? String(d.labor_rate.id) } : null, quantity: d.quantity ?? 1, rate: d.rate != null ? Number(d.rate) : 0, working_hours: d.working_hours != null ? Number(d.working_hours) : 0, labor_hours: d.labor_hours != null ? Number(d.labor_hours) : 0, discount_amount: d.discount_amount != null ? Number(d.discount_amount) : undefined, chart_of_account: d.chart_of_account ?? "", description: d.description ?? "", } } const RATE_TYPE_OPTIONS = RateType.map((v) => ({ value: v, label: v === "flat_rate" ? "Flat Rate" : "Hourly", })) // ── Component ── export function JobCardServiceForm({ jobCardId, jobCardServiceId, initialData, onSuccess, onCancel, }: JobCardServiceFormProps) { const api = useAuthApi() const jobCard = useJobCard() const isEditing = !!jobCardServiceId const isLineItemDiscount = (jobCard as any)?.discount_type === "line_item_level" const form = useForm({ resolver: zodResolver(jobCardServiceFormSchema) as any, defaultValues: initialData ? mapToFormValues(initialData) : DEFAULT_VALUES, }) const rateType = form.watch("rate_type") const isHourly = rateType === "hourly" const [error, setError] = React.useState(null) const [isPending, setIsPending] = React.useState(false) async function handleSubmit(values: JobCardServiceFormValues) { setError(null) setIsPending(true) try { if (isEditing && jobCardServiceId) { await toast.promise( api.jobCards.updateService(jobCardId, { job_card_service_id: jobCardServiceId, quantity: values.quantity, rate: values.rate, discount_amount: isLineItemDiscount ? (values.discount_amount ?? 0) : undefined, description: values.description || undefined, }), { loading: "Updating service...", success: "Service updated successfully", error: "Failed to update service", } ) } else { await toast.promise( api.jobCards.addService(jobCardId, { service_id: values.service ? Number(values.service.value) : undefined, department_id: values.department ? Number(values.department.value) : undefined, tax_id: values.tax ? Number(values.tax.value) : undefined, rate_type: values.rate_type || undefined, labor_rate_id: values.labor_rate ? Number(values.labor_rate.value) : undefined, quantity: values.quantity, rate: values.rate, working_hours: values.working_hours || undefined, labor_hours: values.labor_hours || undefined, discount_amount: isLineItemDiscount ? (values.discount_amount ?? 0) : undefined, chart_of_account: values.chart_of_account || undefined, description: values.description || undefined, }), { loading: "Adding service...", success: "Service added successfully", error: "Failed to add service", } ) } form.reset() onSuccess?.() } catch (err: any) { setError(err?.message ?? "An unexpected error occurred") } finally { setIsPending(false) } } return ( {error && ( {isEditing ? "Failed to update service" : "Failed to add service"} {error} )} {!isEditing && ( api.services.list()} mapOption={(item: any) => ({ value: String(item.id), label: item.labor_name ?? String(item.id), })} {...STORE_OBJECT} /> )}
{isLineItemDiscount && ( )} {!isEditing && ( <>
api.inventory.listLaborRates()} mapOption={(item: any) => ({ value: String(item.id), label: item.title ?? String(item.id), })} {...STORE_OBJECT} />
{isHourly && (
)}
api.departments.list()} mapOption={(item: any) => ({ value: String(item.id), label: item.name ?? String(item.id), })} createForm={(props) => } createLabel="Department" {...STORE_OBJECT} /> api.taxes.list()} mapOption={(item: any) => ({ value: String(item.id), label: item.title ? `${item.title} (${item.rate}%)` : `#${item.id}`, })} {...STORE_OBJECT} />
)}
{onCancel && ( )}
) }