208 lines
6.4 KiB
TypeScript
208 lines
6.4 KiB
TypeScript
'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<typeof segmentFormSchema>;
|
|
|
|
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<SegmentFormValues>({
|
|
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,
|
|
};
|
|
}
|