'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; import { optionalIntegerNumber, validateChainageRange, } from '@/lib/validation/numberField'; import { packageService } from '@/services/api'; import type { Package, PackageCreate, PackageUpdate } from '@/types'; import { packageKeys } from '../queries/packageKeys'; const packageFormSchema = z .object({ id: z.string().optional(), project_id: z.string().trim().min(1, 'Project is required'), name: z.string().trim().min(1, 'Package name is required'), region: z.string().optional(), chainage_start_km: optionalIntegerNumber, chainage_end_km: optionalIntegerNumber, }) .superRefine(validateChainageRange); export type PackageFormValues = z.infer; const defaultValues: PackageFormValues = { project_id: '', name: '', region: '', chainage_start_km: '', chainage_end_km: '', }; function trimOptional(value?: string) { return value?.trim() || null; } function toNumberOrZero(value: string) { return value.trim() ? Number(value) : 0; } function toPackagePayload( values: PackageFormValues, ): PackageCreate | PackageUpdate { return { project_id: values.project_id, name: values.name.trim(), region: trimOptional(values.region), chainage_start_km: toNumberOrZero(values.chainage_start_km), chainage_end_km: toNumberOrZero(values.chainage_end_km), }; } export function usePackageForm({ onSaved }: { onSaved: () => void }) { const queryClient = useQueryClient(); const { register, handleSubmit, reset, setValue, control, formState: { errors, isSubmitting, isSubmitted }, } = useForm({ defaultValues, mode: 'onSubmit', reValidateMode: 'onChange', resolver: zodResolver(packageFormSchema), }); const packageId = useWatch({ control, name: 'id' }); const projectId = useWatch({ control, name: 'project_id' }) || ''; const packageName = useWatch({ control, name: 'name' }) || ''; const canSubmit = projectId.trim().length > 0 && packageName.trim().length > 0; const saveMutation = useMutation({ mutationFn: (values: PackageFormValues) => { const payload = toPackagePayload(values); return values.id ? packageService.updatePackage(values.id, payload) : packageService.createPackage(payload as PackageCreate); }, onSuccess: (_data, values) => { toast.success(values.id ? 'Package updated' : 'Package created'); reset(defaultValues); onSaved(); queryClient.invalidateQueries({ queryKey: packageKeys.all }); }, onError: (_error, values) => { toast.error( values.id ? 'Failed to update package' : 'Failed to create package', ); }, }); const onSubmit = handleSubmit((values) => saveMutation.mutate(values)); const openCreate = useCallback(() => { reset(defaultValues); }, [reset]); const openEdit = useCallback( (pkg: Package) => { reset({ id: pkg.id, project_id: pkg.project_id, name: pkg.name || '', region: pkg.region || '', chainage_start_km: pkg.chainage_start_km?.toString() || '', chainage_end_km: pkg.chainage_end_km?.toString() || '', }); }, [reset], ); const setProjectId = useCallback( (value: string) => setValue('project_id', value, { shouldDirty: true, shouldValidate: true, }), [setValue], ); return { register, onSubmit, openCreate, openEdit, resetForm: () => reset(defaultValues), packageId, projectId, setProjectId, errors, isSubmitted, canSubmit, isSaving: isSubmitting || saveMutation.isPending, }; }