refactor: Align form validation UX across management dialogs
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import type { FieldErrors, UseFormRegister } from 'react-hook-form';
|
||||
import { Layers, Loader2, MapPin, Route } from 'lucide-react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
import { FormField } from '@/components/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -54,144 +54,135 @@ export function ProjectDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-w-2xl"
|
||||
className="flex max-h-[calc(100vh-2rem)] flex-col gap-0 p-0 sm:max-w-2xl"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<DialogHeader className="gap-2">
|
||||
<DialogTitle className="flex items-center gap-3">
|
||||
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
||||
<Layers className="size-5" />
|
||||
</div>
|
||||
{projectId ? 'Edit Project Details' : 'Create New Project'}
|
||||
<DialogHeader className="shrink-0 border-b px-6 py-4 pr-12">
|
||||
<DialogTitle className="text-lg leading-none font-semibold tracking-tight">
|
||||
{projectId ? 'Edit Project' : 'Create Project'}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{projectId
|
||||
? 'Update the technical specifications for your road infrastructure project.'
|
||||
: 'Provide the essential road data to establish a new analysis project.'}
|
||||
<DialogDescription className="text-sm">
|
||||
Manage road project identity and optional coordinate boundaries.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
<FormField
|
||||
id="project-name"
|
||||
label="Project Name"
|
||||
required
|
||||
error={nameErrorMessage}
|
||||
>
|
||||
<Input
|
||||
id="project-name"
|
||||
placeholder="Enter a descriptive project name"
|
||||
aria-invalid={!!nameErrorMessage}
|
||||
{...register('name')}
|
||||
/>
|
||||
</FormField>
|
||||
<form 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">
|
||||
<section className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold">Basic details</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Project name and corridor information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<FormField id="project-state" label="State">
|
||||
<Input
|
||||
id="project-state"
|
||||
placeholder="e.g. Maharashtra"
|
||||
{...register('state')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
id="project-corridor"
|
||||
label={
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Route className="size-3.5 opacity-60" />
|
||||
Corridor Name
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="project-corridor"
|
||||
placeholder="e.g. Mumbai-Goa Highway"
|
||||
{...register('corridor_name')}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField
|
||||
id="project-name"
|
||||
label="Project Name"
|
||||
required
|
||||
error={nameErrorMessage}
|
||||
>
|
||||
<Input
|
||||
id="project-name"
|
||||
placeholder="Enter project name"
|
||||
aria-invalid={!!nameErrorMessage}
|
||||
{...register('name')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<div className="grid gap-8 pt-2 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<p className="flex items-center gap-2 text-muted-foreground">
|
||||
<MapPin className="size-3.5 opacity-60" />
|
||||
START POINT
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
id="project-start-lat"
|
||||
label="Lat"
|
||||
error={startLatErrorMessage}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<FormField id="project-state" label="State">
|
||||
<Input
|
||||
id="project-state"
|
||||
placeholder="Maharashtra"
|
||||
{...register('state')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField id="project-corridor" label="Corridor Name">
|
||||
<Input
|
||||
id="project-corridor"
|
||||
placeholder="Mumbai-Goa Highway"
|
||||
{...register('corridor_name')}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold">Coordinates</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Optional start and end points for the project boundary.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
id="project-start-lat"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
className="font-mono"
|
||||
aria-invalid={!!startLatErrorMessage}
|
||||
{...register('start_lat')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
id="project-start-lng"
|
||||
label="Lng"
|
||||
error={startLngErrorMessage}
|
||||
>
|
||||
<Input
|
||||
label="Start Latitude"
|
||||
error={startLatErrorMessage}
|
||||
>
|
||||
<Input
|
||||
id="project-start-lat"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
aria-invalid={!!startLatErrorMessage}
|
||||
{...register('start_lat')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
id="project-start-lng"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
className="font-mono"
|
||||
aria-invalid={!!startLngErrorMessage}
|
||||
{...register('start_lng')}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
label="Start Longitude"
|
||||
error={startLngErrorMessage}
|
||||
>
|
||||
<Input
|
||||
id="project-start-lng"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
aria-invalid={!!startLngErrorMessage}
|
||||
{...register('start_lng')}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="flex items-center gap-2 text-muted-foreground">
|
||||
<MapPin className="size-3.5 opacity-60" />
|
||||
END POINT
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
id="project-end-lat"
|
||||
label="Lat"
|
||||
error={endLatErrorMessage}
|
||||
>
|
||||
<Input
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
id="project-end-lat"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
className="font-mono"
|
||||
aria-invalid={!!endLatErrorMessage}
|
||||
{...register('end_lat')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
id="project-end-lng"
|
||||
label="Lng"
|
||||
error={endLngErrorMessage}
|
||||
>
|
||||
<Input
|
||||
label="End Latitude"
|
||||
error={endLatErrorMessage}
|
||||
>
|
||||
<Input
|
||||
id="project-end-lat"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
aria-invalid={!!endLatErrorMessage}
|
||||
{...register('end_lat')}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
id="project-end-lng"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
className="font-mono"
|
||||
aria-invalid={!!endLngErrorMessage}
|
||||
{...register('end_lng')}
|
||||
/>
|
||||
</FormField>
|
||||
label="End Longitude"
|
||||
error={endLngErrorMessage}
|
||||
>
|
||||
<Input
|
||||
id="project-end-lng"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="0.0000"
|
||||
aria-invalid={!!endLngErrorMessage}
|
||||
{...register('end_lng')}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogFooter className="shrink-0 border-t px-6 py-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
@@ -201,7 +192,7 @@ export function ProjectDialog({
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSaving || !canSubmit}>
|
||||
{isSaving ? <Loader2 className="size-4 animate-spin" /> : null}
|
||||
{isSaving ? <Loader2 className="mr-2 size-4 animate-spin" /> : null}
|
||||
{projectId ? 'Update Project' : 'Create Project'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
Reference in New Issue
Block a user