refactor: modularize road management modules
This commit is contained in:
151
src/app/(modules)/package/hooks/usePackageForm.ts
Normal file
151
src/app/(modules)/package/hooks/usePackageForm.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
'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 { packageService } from '@/services/api';
|
||||
import type { Package, PackageCreate, PackageUpdate } from '@/types';
|
||||
|
||||
import { packageKeys } from '../queries/packageKeys';
|
||||
|
||||
const optionalNumber = z.string().refine(
|
||||
(value) => {
|
||||
if (!value.trim()) {
|
||||
return true;
|
||||
}
|
||||
return Number.isFinite(Number(value));
|
||||
},
|
||||
{ message: 'Enter a valid number' },
|
||||
);
|
||||
|
||||
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: optionalNumber,
|
||||
chainage_end_km: optionalNumber,
|
||||
});
|
||||
|
||||
export type PackageFormValues = z.infer<typeof packageFormSchema>;
|
||||
|
||||
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, touchedFields },
|
||||
} = useForm<PackageFormValues>({
|
||||
defaultValues,
|
||||
mode: 'onTouched',
|
||||
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),
|
||||
(formErrors) => {
|
||||
if (formErrors.project_id?.message) {
|
||||
toast.error(formErrors.project_id.message);
|
||||
return;
|
||||
}
|
||||
if (formErrors.name?.message) {
|
||||
toast.error(formErrors.name.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
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,
|
||||
touchedFields,
|
||||
canSubmit,
|
||||
isSaving: isSubmitting || saveMutation.isPending,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user