'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 { chainageService } from '@/services/api'; import type { Chainage, ChainageCreate, ChainageUpdate } from '@/types'; import { segmentKeys } from '../queries/segmentKeys'; const requiredNumber = (message: string) => z .string() .trim() .min(1, message) .refine((value) => Number.isFinite(Number(value)), { message: 'Enter a valid number' }); const latitude = requiredNumber('Latitude is required').refine( (value) => { const numberValue = Number(value); return numberValue >= -90 && numberValue <= 90; }, { message: 'Latitude must be between -90 and 90' }, ); const longitude = requiredNumber('Longitude is required').refine( (value) => { const numberValue = Number(value); return numberValue >= -180 && numberValue <= 180; }, { message: 'Longitude must be between -180 and 180' }, ); const segmentFormSchema = z.object({ id: z.string().optional(), project_id: z.string().trim().min(1, 'Project is required'), package_id: z.string().trim().min(1, 'Package is required'), segment_name: z.string().trim().min(1, 'Segment name is required'), chainage_start_km: requiredNumber('Start km is required'), chainage_end_km: requiredNumber('End km is required'), start_lat: latitude, start_lng: longitude, end_lat: latitude, end_lng: longitude, direction: z.enum(['UP', 'DOWN']), }); export type SegmentFormValues = z.infer; const defaultValues: SegmentFormValues = { project_id: '', package_id: '', segment_name: '', chainage_start_km: '', chainage_end_km: '', start_lat: '', start_lng: '', end_lat: '', end_lng: '', direction: 'UP', }; function toSegmentPayload(values: SegmentFormValues): ChainageCreate | ChainageUpdate { return { package_id: values.package_id, segment_name: values.segment_name.trim(), chainage_start_km: Number(values.chainage_start_km), chainage_end_km: Number(values.chainage_end_km), start_lat: Number(values.start_lat), start_lng: Number(values.start_lng), end_lat: Number(values.end_lat), end_lng: Number(values.end_lng), direction: values.direction, }; } export function useSegmentForm({ onSaved }: { onSaved: () => void }) { const queryClient = useQueryClient(); const { register, handleSubmit, reset, setValue, control, formState: { errors, isSubmitting, touchedFields }, } = useForm({ defaultValues, mode: 'onTouched', reValidateMode: 'onChange', resolver: zodResolver(segmentFormSchema), }); const segmentId = useWatch({ control, name: 'id' }); const projectId = useWatch({ control, name: 'project_id' }) || ''; const packageId = useWatch({ control, name: 'package_id' }) || ''; const direction = useWatch({ control, name: 'direction' }) || 'UP'; const segmentName = useWatch({ control, name: 'segment_name' }) || ''; const chainageStartKm = useWatch({ control, name: 'chainage_start_km' }) || ''; const chainageEndKm = useWatch({ control, name: 'chainage_end_km' }) || ''; const startLat = useWatch({ control, name: 'start_lat' }) || ''; const startLng = useWatch({ control, name: 'start_lng' }) || ''; const endLat = useWatch({ control, name: 'end_lat' }) || ''; const endLng = useWatch({ control, name: 'end_lng' }) || ''; const canSubmit = projectId.trim().length > 0 && packageId.trim().length > 0 && segmentName.trim().length > 0 && chainageStartKm.trim().length > 0 && chainageEndKm.trim().length > 0 && startLat.trim().length > 0 && startLng.trim().length > 0 && endLat.trim().length > 0 && endLng.trim().length > 0; const saveMutation = useMutation({ mutationFn: (values: SegmentFormValues) => { const payload = toSegmentPayload(values); return values.id ? chainageService.updateChainage(values.id, payload) : chainageService.createChainage(payload as ChainageCreate); }, onSuccess: (_data, values) => { toast.success(values.id ? 'Segment updated' : 'Segment created'); reset(defaultValues); onSaved(); queryClient.invalidateQueries({ queryKey: segmentKeys.all }); }, onError: (_error, values) => { toast.error(values.id ? 'Failed to update segment' : 'Failed to create segment'); }, }); const onSubmit = handleSubmit( (values) => saveMutation.mutate(values), (formErrors) => { const firstMessage = Object.values(formErrors).find((error) => error?.message)?.message; if (firstMessage) { toast.error(String(firstMessage)); } }, ); const openCreate = useCallback(() => { reset(defaultValues); }, [reset]); const openEdit = useCallback( (segment: Chainage, projectIdForSegment: string) => { reset({ id: segment.id, project_id: projectIdForSegment, package_id: segment.package_id, segment_name: segment.segment_name || '', chainage_start_km: segment.chainage_start_km?.toString() || '', chainage_end_km: segment.chainage_end_km?.toString() || '', start_lat: segment.start_lat?.toString() || '', start_lng: segment.start_lng?.toString() || '', end_lat: segment.end_lat?.toString() || '', end_lng: segment.end_lng?.toString() || '', direction: segment.direction || 'UP', }); }, [reset], ); const setProjectId = useCallback( (value: string) => { setValue('project_id', value, { shouldDirty: true, shouldValidate: true }); setValue('package_id', '', { shouldDirty: true, shouldValidate: true }); }, [setValue], ); const setPackageId = useCallback( (value: string) => setValue('package_id', value, { shouldDirty: true, shouldValidate: true }), [setValue], ); const setDirection = useCallback( (value: 'UP' | 'DOWN') => setValue('direction', value, { shouldDirty: true, shouldValidate: true }), [setValue], ); return { register, onSubmit, openCreate, openEdit, resetForm: () => reset(defaultValues), segmentId, projectId, setProjectId, packageId, setPackageId, direction, setDirection, errors, touchedFields, canSubmit, isSaving: isSubmitting || saveMutation.isPending, }; }