feat: add shared validation for chainage and coordinates
This commit is contained in:
@@ -74,7 +74,11 @@ export function PackageDialog({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<form noValidate onSubmit={onSubmit} className="flex min-h-0 flex-1 flex-col">
|
<form
|
||||||
|
noValidate
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
|
>
|
||||||
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -120,7 +124,7 @@ export function PackageDialog({
|
|||||||
<FormField id="package-region" label="Region">
|
<FormField id="package-region" label="Region">
|
||||||
<Input
|
<Input
|
||||||
id="package-region"
|
id="package-region"
|
||||||
placeholder="North Zone"
|
placeholder="Enter region"
|
||||||
{...register('region')}
|
{...register('region')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
@@ -131,21 +135,22 @@ export function PackageDialog({
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h3 className="text-sm font-semibold">Chainage range</h3>
|
<h3 className="text-sm font-semibold">Chainage range</h3>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
Optional start and end kilometre values.
|
start and end kilometre values.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
id="package-start"
|
id="package-start"
|
||||||
label="Start (km)"
|
label="Start Chainage"
|
||||||
error={startErrorMessage}
|
error={startErrorMessage}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="package-start"
|
id="package-start"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="1"
|
||||||
placeholder="0.00"
|
min="0"
|
||||||
|
placeholder="Enter start chainage"
|
||||||
aria-invalid={!!startErrorMessage}
|
aria-invalid={!!startErrorMessage}
|
||||||
{...register('chainage_start_km')}
|
{...register('chainage_start_km')}
|
||||||
/>
|
/>
|
||||||
@@ -153,14 +158,15 @@ export function PackageDialog({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
id="package-end"
|
id="package-end"
|
||||||
label="End (km)"
|
label="End Chainage"
|
||||||
error={endErrorMessage}
|
error={endErrorMessage}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="package-end"
|
id="package-end"
|
||||||
type="number"
|
type="number"
|
||||||
step="0.01"
|
step="1"
|
||||||
placeholder="0.00"
|
min="0"
|
||||||
|
placeholder="Enter end chainage"
|
||||||
aria-invalid={!!endErrorMessage}
|
aria-invalid={!!endErrorMessage}
|
||||||
{...register('chainage_end_km')}
|
{...register('chainage_end_km')}
|
||||||
/>
|
/>
|
||||||
@@ -179,7 +185,9 @@ export function PackageDialog({
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={isSaving || !canSubmit}>
|
<Button type="submit" disabled={isSaving || !canSubmit}>
|
||||||
{isSaving ? <Loader2 className="mr-2 size-4 animate-spin" /> : null}
|
{isSaving ? (
|
||||||
|
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
{packageId ? 'Update Package' : 'Create Package'}
|
{packageId ? 'Update Package' : 'Create Package'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -7,29 +7,25 @@ import { useForm, useWatch } from 'react-hook-form';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import {
|
||||||
|
optionalIntegerNumber,
|
||||||
|
validateChainageRange,
|
||||||
|
} from '@/lib/validation/numberField';
|
||||||
import { packageService } from '@/services/api';
|
import { packageService } from '@/services/api';
|
||||||
import type { Package, PackageCreate, PackageUpdate } from '@/types';
|
import type { Package, PackageCreate, PackageUpdate } from '@/types';
|
||||||
|
|
||||||
import { packageKeys } from '../queries/packageKeys';
|
import { packageKeys } from '../queries/packageKeys';
|
||||||
|
|
||||||
const optionalNumber = z.string().refine(
|
const packageFormSchema = z
|
||||||
(value) => {
|
.object({
|
||||||
if (!value.trim()) {
|
id: z.string().optional(),
|
||||||
return true;
|
project_id: z.string().trim().min(1, 'Project is required'),
|
||||||
}
|
name: z.string().trim().min(1, 'Package name is required'),
|
||||||
return Number.isFinite(Number(value));
|
region: z.string().optional(),
|
||||||
},
|
chainage_start_km: optionalIntegerNumber,
|
||||||
{ message: 'Enter a valid number' },
|
chainage_end_km: optionalIntegerNumber,
|
||||||
);
|
})
|
||||||
|
.superRefine(validateChainageRange);
|
||||||
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>;
|
export type PackageFormValues = z.infer<typeof packageFormSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,11 @@ export function ProjectDialog({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<form noValidate onSubmit={onSubmit} className="flex min-h-0 flex-1 flex-col">
|
<form
|
||||||
|
noValidate
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
className="flex min-h-0 flex-1 flex-col"
|
||||||
|
>
|
||||||
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
<div className="max-h-[58vh] space-y-6 overflow-y-auto px-6 py-4">
|
||||||
<section className="space-y-4">
|
<section className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -94,14 +98,14 @@ export function ProjectDialog({
|
|||||||
<FormField id="project-state" label="State">
|
<FormField id="project-state" label="State">
|
||||||
<Input
|
<Input
|
||||||
id="project-state"
|
id="project-state"
|
||||||
placeholder="Maharashtra"
|
placeholder="Enter state"
|
||||||
{...register('state')}
|
{...register('state')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
<FormField id="project-corridor" label="Corridor Name">
|
<FormField id="project-corridor" label="Corridor Name">
|
||||||
<Input
|
<Input
|
||||||
id="project-corridor"
|
id="project-corridor"
|
||||||
placeholder="Mumbai-Goa Highway"
|
placeholder="Enter corridor name"
|
||||||
{...register('corridor_name')}
|
{...register('corridor_name')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
@@ -127,7 +131,9 @@ export function ProjectDialog({
|
|||||||
id="project-start-lat"
|
id="project-start-lat"
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
min="-90"
|
||||||
|
max="90"
|
||||||
|
placeholder="Enter start latitude"
|
||||||
aria-invalid={!!startLatErrorMessage}
|
aria-invalid={!!startLatErrorMessage}
|
||||||
{...register('start_lat')}
|
{...register('start_lat')}
|
||||||
/>
|
/>
|
||||||
@@ -141,7 +147,9 @@ export function ProjectDialog({
|
|||||||
id="project-start-lng"
|
id="project-start-lng"
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
min="-180"
|
||||||
|
max="180"
|
||||||
|
placeholder="Enter start longitude"
|
||||||
aria-invalid={!!startLngErrorMessage}
|
aria-invalid={!!startLngErrorMessage}
|
||||||
{...register('start_lng')}
|
{...register('start_lng')}
|
||||||
/>
|
/>
|
||||||
@@ -158,7 +166,9 @@ export function ProjectDialog({
|
|||||||
id="project-end-lat"
|
id="project-end-lat"
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
min="-90"
|
||||||
|
max="90"
|
||||||
|
placeholder="Enter end latitude"
|
||||||
aria-invalid={!!endLatErrorMessage}
|
aria-invalid={!!endLatErrorMessage}
|
||||||
{...register('end_lat')}
|
{...register('end_lat')}
|
||||||
/>
|
/>
|
||||||
@@ -172,7 +182,9 @@ export function ProjectDialog({
|
|||||||
id="project-end-lng"
|
id="project-end-lng"
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="any"
|
||||||
placeholder="0.0000"
|
min="-180"
|
||||||
|
max="180"
|
||||||
|
placeholder="Enter end longitude"
|
||||||
aria-invalid={!!endLngErrorMessage}
|
aria-invalid={!!endLngErrorMessage}
|
||||||
{...register('end_lng')}
|
{...register('end_lng')}
|
||||||
/>
|
/>
|
||||||
@@ -192,7 +204,9 @@ export function ProjectDialog({
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={isSaving || !canSubmit}>
|
<Button type="submit" disabled={isSaving || !canSubmit}>
|
||||||
{isSaving ? <Loader2 className="mr-2 size-4 animate-spin" /> : null}
|
{isSaving ? (
|
||||||
|
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
{projectId ? 'Update Project' : 'Create Project'}
|
{projectId ? 'Update Project' : 'Create Project'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -7,30 +7,24 @@ import { useForm, useWatch } from 'react-hook-form';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import {
|
||||||
|
optionalLatitude,
|
||||||
|
optionalLongitude,
|
||||||
|
} from '@/lib/validation/numberField';
|
||||||
import { projectService } from '@/services/api';
|
import { projectService } from '@/services/api';
|
||||||
import type { Project, ProjectCreate, ProjectUpdate } from '@/types';
|
import type { Project, ProjectCreate, ProjectUpdate } from '@/types';
|
||||||
|
|
||||||
import { projectKeys } from '../queries/projectKeys';
|
import { projectKeys } from '../queries/projectKeys';
|
||||||
|
|
||||||
const optionalCoordinate = z.string().refine(
|
|
||||||
(value) => {
|
|
||||||
if (!value.trim()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return Number.isFinite(Number(value));
|
|
||||||
},
|
|
||||||
{ message: 'Enter a valid number' },
|
|
||||||
);
|
|
||||||
|
|
||||||
const projectFormSchema = z.object({
|
const projectFormSchema = z.object({
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().trim().min(1, 'Project name is required'),
|
name: z.string().trim().min(1, 'Project name is required'),
|
||||||
state: z.string().optional(),
|
state: z.string().optional(),
|
||||||
corridor_name: z.string().optional(),
|
corridor_name: z.string().optional(),
|
||||||
start_lat: optionalCoordinate,
|
start_lat: optionalLatitude,
|
||||||
start_lng: optionalCoordinate,
|
start_lng: optionalLongitude,
|
||||||
end_lat: optionalCoordinate,
|
end_lat: optionalLatitude,
|
||||||
end_lng: optionalCoordinate,
|
end_lng: optionalLongitude,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ProjectFormValues = z.infer<typeof projectFormSchema>;
|
export type ProjectFormValues = z.infer<typeof projectFormSchema>;
|
||||||
|
|||||||
@@ -182,16 +182,16 @@ export function SegmentDialog({
|
|||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<FormField
|
<FormField
|
||||||
id="segment-start-km"
|
id="segment-start-km"
|
||||||
label="Start (km)"
|
label="Start Chainage"
|
||||||
required
|
required
|
||||||
error={getError('chainage_start_km')}
|
error={getError('chainage_start_km')}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="segment-start-km"
|
id="segment-start-km"
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
placeholder="0.0"
|
placeholder="Enter start chainage"
|
||||||
aria-invalid={!!getError('chainage_start_km')}
|
aria-invalid={!!getError('chainage_start_km')}
|
||||||
{...register('chainage_start_km')}
|
{...register('chainage_start_km')}
|
||||||
/>
|
/>
|
||||||
@@ -199,16 +199,16 @@ export function SegmentDialog({
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
id="segment-end-km"
|
id="segment-end-km"
|
||||||
label="End (km)"
|
label="End Chainage"
|
||||||
required
|
required
|
||||||
error={getError('chainage_end_km')}
|
error={getError('chainage_end_km')}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="segment-end-km"
|
id="segment-end-km"
|
||||||
type="number"
|
type="number"
|
||||||
step="any"
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
placeholder="1.0"
|
placeholder="Enter end chainage"
|
||||||
aria-invalid={!!getError('chainage_end_km')}
|
aria-invalid={!!getError('chainage_end_km')}
|
||||||
{...register('chainage_end_km')}
|
{...register('chainage_end_km')}
|
||||||
/>
|
/>
|
||||||
@@ -312,7 +312,9 @@ export function SegmentDialog({
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" disabled={isSaving || !canSubmit}>
|
<Button type="submit" disabled={isSaving || !canSubmit}>
|
||||||
{isSaving ? <Loader2 className="mr-2 size-4 animate-spin" /> : null}
|
{isSaving ? (
|
||||||
|
<Loader2 className="mr-2 size-4 animate-spin" />
|
||||||
|
) : null}
|
||||||
{segmentId ? 'Update Segment' : 'Create Segment'}
|
{segmentId ? 'Update Segment' : 'Create Segment'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -7,49 +7,32 @@ import { useForm, useWatch } from 'react-hook-form';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import {
|
||||||
|
requiredIntegerNumber,
|
||||||
|
requiredLatitude,
|
||||||
|
requiredLongitude,
|
||||||
|
validateChainageRange,
|
||||||
|
} from '@/lib/validation/numberField';
|
||||||
import { chainageService } from '@/services/api';
|
import { chainageService } from '@/services/api';
|
||||||
import type { Chainage, ChainageCreate, ChainageUpdate } from '@/types';
|
import type { Chainage, ChainageCreate, ChainageUpdate } from '@/types';
|
||||||
|
|
||||||
import { segmentKeys } from '../queries/segmentKeys';
|
import { segmentKeys } from '../queries/segmentKeys';
|
||||||
|
|
||||||
const requiredNumber = (message: string) =>
|
const segmentFormSchema = z
|
||||||
z
|
.object({
|
||||||
.string()
|
id: z.string().optional(),
|
||||||
.trim()
|
project_id: z.string().trim().min(1, 'Project is required'),
|
||||||
.min(1, message)
|
package_id: z.string().trim().min(1, 'Package is required'),
|
||||||
.refine((value) => Number.isFinite(Number(value)), {
|
segment_name: z.string().trim().min(1, 'Segment name is required'),
|
||||||
message: 'Enter a valid number',
|
chainage_start_km: requiredIntegerNumber('Start km is required'),
|
||||||
});
|
chainage_end_km: requiredIntegerNumber('End km is required'),
|
||||||
|
start_lat: requiredLatitude,
|
||||||
const latitude = requiredNumber('Latitude is required').refine(
|
start_lng: requiredLongitude,
|
||||||
(value) => {
|
end_lat: requiredLatitude,
|
||||||
const numberValue = Number(value);
|
end_lng: requiredLongitude,
|
||||||
return numberValue >= -90 && numberValue <= 90;
|
direction: z.enum(['UP', 'DOWN']),
|
||||||
},
|
})
|
||||||
{ message: 'Latitude must be between -90 and 90' },
|
.superRefine(validateChainageRange);
|
||||||
);
|
|
||||||
|
|
||||||
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>;
|
export type SegmentFormValues = z.infer<typeof segmentFormSchema>;
|
||||||
|
|
||||||
|
|||||||
127
src/lib/validation/numberField.ts
Normal file
127
src/lib/validation/numberField.ts
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
interface ChainageRangeValues {
|
||||||
|
chainage_start_km: string;
|
||||||
|
chainage_end_km: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNonNegativeWholeNumberText(value: string) {
|
||||||
|
return /^\d+$/.test(value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFiniteNumberText(value: string) {
|
||||||
|
return Number.isFinite(Number(value.trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function coordinateSchema({
|
||||||
|
requiredMessage,
|
||||||
|
rangeMessage,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
}: {
|
||||||
|
requiredMessage?: string;
|
||||||
|
rangeMessage: string;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}) {
|
||||||
|
const schema = requiredMessage
|
||||||
|
? z.string().trim().min(1, requiredMessage)
|
||||||
|
: z.string();
|
||||||
|
|
||||||
|
return schema
|
||||||
|
.refine(
|
||||||
|
(value) => {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
return !trimmedValue || isFiniteNumberText(trimmedValue);
|
||||||
|
},
|
||||||
|
{ message: 'Enter a valid number' },
|
||||||
|
)
|
||||||
|
.refine(
|
||||||
|
(value) => {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
if (!trimmedValue || !isFiniteNumberText(trimmedValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberValue = Number(trimmedValue);
|
||||||
|
return numberValue >= min && numberValue <= max;
|
||||||
|
},
|
||||||
|
{ message: rangeMessage },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const optionalLatitude = coordinateSchema({
|
||||||
|
min: -90,
|
||||||
|
max: 90,
|
||||||
|
rangeMessage: 'Latitude must be between -90 and 90',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const optionalLongitude = coordinateSchema({
|
||||||
|
min: -180,
|
||||||
|
max: 180,
|
||||||
|
rangeMessage: 'Longitude must be between -180 and 180',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const requiredLatitude = coordinateSchema({
|
||||||
|
requiredMessage: 'Latitude is required',
|
||||||
|
min: -90,
|
||||||
|
max: 90,
|
||||||
|
rangeMessage: 'Latitude must be between -90 and 90',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const requiredLongitude = coordinateSchema({
|
||||||
|
requiredMessage: 'Longitude is required',
|
||||||
|
min: -180,
|
||||||
|
max: 180,
|
||||||
|
rangeMessage: 'Longitude must be between -180 and 180',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const optionalIntegerNumber = z.string().refine(
|
||||||
|
(value) => {
|
||||||
|
const trimmedValue = value.trim();
|
||||||
|
|
||||||
|
if (!trimmedValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNonNegativeWholeNumberText(trimmedValue);
|
||||||
|
},
|
||||||
|
{ message: 'Enter a whole number 0 or above' },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const requiredIntegerNumber = (message: string) =>
|
||||||
|
z.string().trim().min(1, message).refine(isNonNegativeWholeNumberText, {
|
||||||
|
message: 'Enter a whole number 0 or above',
|
||||||
|
});
|
||||||
|
|
||||||
|
export function validateChainageRange(
|
||||||
|
values: ChainageRangeValues,
|
||||||
|
ctx: z.RefinementCtx,
|
||||||
|
) {
|
||||||
|
const startValue = values.chainage_start_km.trim();
|
||||||
|
const endValue = values.chainage_end_km.trim();
|
||||||
|
|
||||||
|
if (!startValue || !endValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Number(startValue);
|
||||||
|
const end = Number(endValue);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Number.isFinite(start) ||
|
||||||
|
!Number.isFinite(end) ||
|
||||||
|
!isNonNegativeWholeNumberText(startValue) ||
|
||||||
|
!isNonNegativeWholeNumberText(endValue) ||
|
||||||
|
start <= end
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: 'Start km cannot be greater than end km',
|
||||||
|
path: ['chainage_start_km'],
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user