refactor: modularize road management modules
This commit is contained in:
170
src/app/(modules)/package/components/PackageDialog.tsx
Normal file
170
src/app/(modules)/package/components/PackageDialog.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
'use client';
|
||||
|
||||
import type { ComponentProps } from 'react';
|
||||
import type { FieldErrors, UseFormRegister, UseFormReturn } from 'react-hook-form';
|
||||
import { Globe, Loader2, Package as PackageIcon } from 'lucide-react';
|
||||
|
||||
import { FormField } from '@/components/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import type { Project } from '@/types';
|
||||
|
||||
import type { PackageFormValues } from '../hooks/usePackageForm';
|
||||
|
||||
interface PackageDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
packageId?: string;
|
||||
projects: Project[];
|
||||
isProjectsLoading: boolean;
|
||||
projectId: string;
|
||||
onProjectChange: (projectId: string) => void;
|
||||
register: UseFormRegister<PackageFormValues>;
|
||||
errors: FieldErrors<PackageFormValues>;
|
||||
touchedFields: UseFormReturn<PackageFormValues>['formState']['touchedFields'];
|
||||
onSubmit: ComponentProps<'form'>['onSubmit'];
|
||||
canSubmit: boolean;
|
||||
isSaving: boolean;
|
||||
}
|
||||
|
||||
export function PackageDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
packageId,
|
||||
projects,
|
||||
isProjectsLoading,
|
||||
projectId,
|
||||
onProjectChange,
|
||||
register,
|
||||
errors,
|
||||
touchedFields,
|
||||
onSubmit,
|
||||
canSubmit,
|
||||
isSaving,
|
||||
}: PackageDialogProps) {
|
||||
const projectErrorMessage = touchedFields.project_id ? errors.project_id?.message : undefined;
|
||||
const nameErrorMessage = touchedFields.name ? errors.name?.message : undefined;
|
||||
const startErrorMessage = touchedFields.chainage_start_km
|
||||
? errors.chainage_start_km?.message
|
||||
: undefined;
|
||||
const endErrorMessage = touchedFields.chainage_end_km
|
||||
? errors.chainage_end_km?.message
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl" onOpenAutoFocus={(event) => event.preventDefault()}>
|
||||
<DialogHeader className="gap-2">
|
||||
<DialogTitle className="flex items-center gap-3 text-xl">
|
||||
<div className="rounded-lg bg-primary p-2 text-primary-foreground shadow-sm">
|
||||
<PackageIcon className="size-5" />
|
||||
</div>
|
||||
{packageId ? 'Edit Package Details' : 'Create New Package'}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-sm">
|
||||
{packageId
|
||||
? 'Update the technical specifications for your road infrastructure package.'
|
||||
: 'Select a project and provide the essential data to establish a new package.'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-6">
|
||||
{!packageId ? (
|
||||
<FormField label="Project" required error={projectErrorMessage}>
|
||||
<Select value={projectId} onValueChange={onProjectChange}>
|
||||
<SelectTrigger aria-invalid={!!projectErrorMessage}>
|
||||
{isProjectsLoading ? (
|
||||
<span className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
Loading...
|
||||
</span>
|
||||
) : (
|
||||
<SelectValue placeholder="Choose a project" />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{projects.map((project) => (
|
||||
<SelectItem key={project.id} value={project.id}>
|
||||
{project.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<input type="hidden" {...register('project_id')} value={projectId} readOnly />
|
||||
</FormField>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<FormField id="package-name" label="Package Name" required error={nameErrorMessage}>
|
||||
<Input
|
||||
id="package-name"
|
||||
placeholder="e.g. Package 01"
|
||||
aria-invalid={!!nameErrorMessage}
|
||||
{...register('name')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
id="package-region"
|
||||
label={
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Globe className="size-3.5 opacity-60" />
|
||||
Region
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input id="package-region" placeholder="e.g. North Zone" {...register('region')} />
|
||||
</FormField>
|
||||
|
||||
<FormField id="package-start" label="Segment Start (km)" error={startErrorMessage}>
|
||||
<Input
|
||||
id="package-start"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
aria-invalid={!!startErrorMessage}
|
||||
{...register('chainage_start_km')}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField id="package-end" label="Segment End (km)" error={endErrorMessage}>
|
||||
<Input
|
||||
id="package-end"
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0.00"
|
||||
aria-invalid={!!endErrorMessage}
|
||||
{...register('chainage_end_km')}
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSaving || !canSubmit}>
|
||||
{isSaving ? <Loader2 className="size-4 animate-spin" /> : null}
|
||||
{packageId ? 'Update Package' : 'Create Package'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user