setup husky prettier eslint

This commit is contained in:
2026-03-18 19:39:52 +05:30
parent 42624cae94
commit b675dd857b
93 changed files with 11269 additions and 6011 deletions

View File

@@ -1,470 +1,513 @@
"use client"
'use client';
import { useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Loader2, CheckCircle2, Package, FolderKanban, Globe } from "lucide-react"
import { DataTable } from "@/components/data-table"
import { PageHeader } from "@/components/page-header"
import { projectService, packageService } from "@/services/api"
import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Project,
Package as PackageType,
PackageCreate,
PackageUpdate
} from "@/types"
import { ColumnDef } from "@tanstack/react-table"
import { toast } from "sonner"
import { Badge } from "@/components/ui/badge"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { PoweredBy } from "@/components/powered-by"
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Loader2, CheckCircle2, Package, FolderKanban, Globe } from 'lucide-react';
import { DataTable } from '@/components/data-table';
import { PageHeader } from '@/components/page-header';
import { projectService, packageService } from '@/services/api';
import { Project, Package as PackageType, PackageCreate, PackageUpdate } from '@/types';
import { ColumnDef } from '@tanstack/react-table';
import { toast } from 'sonner';
import { Badge } from '@/components/ui/badge';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { PoweredBy } from '@/components/powered-by';
export default function PackagePage() {
const [packages, setPackages] = useState<PackageType[]>([])
const [totalItems, setTotalItems] = useState(0)
const [projects, setProjects] = useState<Project[]>([])
const [isLoading, setIsLoading] = useState(true)
const [isModalOpen, setIsModalOpen] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
const [loadingProjects, setLoadingProjects] = useState(false)
const [packages, setPackages] = useState<PackageType[]>([]);
const [totalItems, setTotalItems] = useState(0);
const [projects, setProjects] = useState<Project[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [loadingProjects, setLoadingProjects] = useState(false);
// Pagination state
const [skip, setSkip] = useState(0)
const [limit, setLimit] = useState(10)
// Pagination state
const [skip, setSkip] = useState(0);
const [limit, setLimit] = useState(10);
// Editing state
const [isEditing, setIsEditing] = useState(false)
const [currentPackage, setCurrentPackage] = useState<PackageType | null>(null)
// Editing state
const [isEditing, setIsEditing] = useState(false);
const [currentPackage, setCurrentPackage] = useState<PackageType | null>(null);
// Form fields
const [selectedProjectId, setSelectedProjectId] = useState("")
const [name, setName] = useState("")
const [region, setRegion] = useState("")
const [chainageStartKm, setChainageStartKm] = useState<string>("")
const [chainageEndKm, setChainageEndKm] = useState<string>("")
// Form fields
const [selectedProjectId, setSelectedProjectId] = useState('');
const [name, setName] = useState('');
const [region, setRegion] = useState('');
const [chainageStartKm, setChainageStartKm] = useState<string>('');
const [chainageEndKm, setChainageEndKm] = useState<string>('');
// Load packages and projects
const loadPackages = async (currentSkip = skip, currentLimit = limit) => {
try {
setIsLoading(true)
setError(null)
const data = await packageService.getPackages({ skip: currentSkip, limit: currentLimit })
setPackages(data.items)
setTotalItems(data.totalItems)
} catch (err) {
setError("Failed to load packages. Please check if the backend is running.")
} finally {
// Add a small delay for animation stability
setTimeout(() => {
setIsLoading(false)
}, 800)
}
// Load packages and projects
const loadPackages = async (currentSkip = skip, currentLimit = limit) => {
try {
setIsLoading(true);
setError(null);
const data = await packageService.getPackages({ skip: currentSkip, limit: currentLimit });
setPackages(data.items);
setTotalItems(data.totalItems);
} catch (err) {
setError('Failed to load packages. Please check if the backend is running.');
} finally {
// Add a small delay for animation stability
setTimeout(() => {
setIsLoading(false);
}, 800);
}
};
const loadProjects = async () => {
try {
setLoadingProjects(true);
const data = await projectService.getProjects({ skip: 0, limit: 1000 }); // Load all projects for selector
setProjects(data.items);
} catch (err) {
setError('Failed to load projects.');
} finally {
setLoadingProjects(false);
}
};
useEffect(() => {
loadPackages(skip, limit);
loadProjects();
}, [skip, limit]);
const resetForm = () => {
setSelectedProjectId('');
setName('');
setRegion('');
setChainageStartKm('');
setChainageEndKm('');
setError(null);
setIsEditing(false);
setCurrentPackage(null);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedProjectId) {
setError('Please select a project first');
return;
}
if (!name.trim()) {
setError('Package name is required');
return;
}
const loadProjects = async () => {
try {
setLoadingProjects(true)
const data = await projectService.getProjects({ skip: 0, limit: 1000 }) // Load all projects for selector
setProjects(data.items)
} catch (err) {
setError("Failed to load projects.")
} finally {
setLoadingProjects(false)
}
setIsSubmitting(true);
setError(null);
try {
if (isEditing && currentPackage) {
const data: PackageUpdate = {
name: name.trim(),
region: region.trim() || null,
chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : 0,
chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : 0,
};
await packageService.updatePackage(currentPackage.id, data);
toast.success('Package Updated', {
description: `${name} has been updated successfully at ${new Date().toLocaleTimeString()}`,
});
} else {
const data: PackageCreate = {
project_id: selectedProjectId,
name: name.trim(),
region: region.trim() || null,
chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : 0,
chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : 0,
};
await packageService.createPackage(data);
toast.success('Package Created', {
description: `${name} has been established successfully at ${new Date().toLocaleTimeString()}`,
});
}
// Refresh packages list
await loadPackages();
// Close modal and reset form immediately
setIsModalOpen(false);
resetForm();
} catch (err) {
const message =
err instanceof Error ? err.message : `Failed to ${isEditing ? 'update' : 'create'} package`;
setError(message);
toast.error('Operation Failed', {
description: message,
});
} finally {
setIsSubmitting(false);
}
};
useEffect(() => {
loadPackages(skip, limit)
loadProjects()
}, [skip, limit])
const handleEdit = (pkg: PackageType) => {
setIsEditing(true);
setCurrentPackage(pkg);
setSelectedProjectId(pkg.project_id);
setName(pkg.name || '');
setRegion(pkg.region || '');
setChainageStartKm(pkg.chainage_start_km?.toString() || '0');
setChainageEndKm(pkg.chainage_end_km?.toString() || '0');
setIsModalOpen(true);
};
const resetForm = () => {
setSelectedProjectId("")
setName("")
setRegion("")
setChainageStartKm("")
setChainageEndKm("")
setError(null)
setIsEditing(false)
setCurrentPackage(null)
const handleDelete = async (pkg: PackageType) => {
if (!confirm(`Are you sure you want to delete package "${pkg.name}"?`)) return;
try {
setIsLoading(true);
await packageService.deletePackage(pkg.id);
toast.success('Package Deleted', {
description: `${pkg.name} has been removed from the system.`,
});
await loadPackages();
} catch (err) {
setError('Failed to delete package');
toast.error('Deletion Failed', {
description: 'The package could not be removed. Please try again.',
});
} finally {
setIsLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!selectedProjectId) {
setError("Please select a project first")
return
}
if (!name.trim()) {
setError("Package name is required")
return
}
const getProjectName = (projectId: string) => {
return projects.find((p) => p.id === projectId)?.name || projectId;
};
setIsSubmitting(true)
setError(null)
const columns: ColumnDef<PackageType>[] = [
{
accessorKey: 'name',
header: 'Package Name',
cell: ({ row }) => (
<div className="font-semibold text-gray-900 dark:text-gray-100">{row.original.name}</div>
),
},
{
accessorKey: 'project_id',
header: 'Project',
cell: ({ row }) => getProjectName(row.original.project_id),
},
{
accessorKey: 'project_state',
header: 'Project State',
cell: ({ row }) => {
const pkg = row.original;
const project = projects.find((p) => p.id === pkg.project_id);
if (!project?.state) return <span className="text-gray-400"></span>;
try {
if (isEditing && currentPackage) {
const data: PackageUpdate = {
name: name.trim(),
region: region.trim() || null,
chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : 0,
chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : 0,
}
await packageService.updatePackage(currentPackage.id, data)
toast.success("Package Updated", {
description: `${name} has been updated successfully at ${new Date().toLocaleTimeString()}`,
})
} else {
const data: PackageCreate = {
project_id: selectedProjectId,
name: name.trim(),
region: region.trim() || null,
chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : 0,
chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : 0,
}
await packageService.createPackage(data)
toast.success("Package Created", {
description: `${name} has been established successfully at ${new Date().toLocaleTimeString()}`,
})
const states = project.state
.split(',')
.map((s) => s.trim())
.filter(Boolean);
if (states.length === 0) return <span className="text-gray-400"></span>;
const firstState = states[0];
const remainingStates = states.slice(1);
return (
<div className="flex items-center gap-1.5">
<Badge variant="secondary">{firstState}</Badge>
{remainingStates.length > 0 && (
<Popover>
<PopoverTrigger asChild>
<button className="flex items-center justify-center rounded-full bg-muted/50 hover:bg-muted px-1.5 py-0.5 text-[10px] font-bold text-muted-foreground transition-colors border border-border/40">
+{remainingStates.length}
</button>
</PopoverTrigger>
<PopoverContent className="w-auto p-2" align="start">
<div className="flex flex-col gap-1.5">
<p className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-0.5 px-1">
Other States
</p>
{remainingStates.map((item, idx) => (
<Badge key={idx} variant="secondary">
{item}
</Badge>
))}
</div>
</PopoverContent>
</Popover>
)}
</div>
);
},
},
{
accessorKey: 'region',
header: 'Region',
},
{
accessorKey: 'chainage_start_km',
header: 'Start (km)',
cell: ({ row }) => row.original.chainage_start_km?.toFixed(2) ?? '0.00',
},
{
accessorKey: 'chainage_end_km',
header: 'End (km)',
cell: ({ row }) => row.original.chainage_end_km?.toFixed(2) ?? '0.00',
},
];
return (
<>
<main className="relative z-10">
{/* Refined Header */}
<div className="mb-8">
<PageHeader
title="Package Management"
description="Manage project packages"
icon={Package}
actions={
<Button
onClick={() => {
setIsEditing(false);
setIsModalOpen(true);
}}
>
<Package className="mr-2 h-4 w-4" />
Add New Package
</Button>
}
/>
</div>
// Refresh packages list
await loadPackages()
{/* Data Table */}
<div>
<DataTable
title="Packages"
data={packages}
columns={columns}
onEdit={handleEdit}
onDelete={handleDelete}
isLoading={isLoading}
pagination={{
skip,
limit,
totalItems,
onPageChange: setSkip,
onLimitChange: (newLimit) => {
setLimit(newLimit);
setSkip(0); // Reset skip when limit changes
},
}}
/>
</div>
// Close modal and reset form immediately
setIsModalOpen(false)
resetForm()
} catch (err) {
const message = err instanceof Error ? err.message : `Failed to ${isEditing ? 'update' : 'create'} package`
setError(message)
toast.error("Operation Failed", {
description: message,
})
} finally {
setIsSubmitting(false)
}
}
<PoweredBy />
</main>
const handleEdit = (pkg: PackageType) => {
setIsEditing(true)
setCurrentPackage(pkg)
setSelectedProjectId(pkg.project_id)
setName(pkg.name || "")
setRegion(pkg.region || "")
setChainageStartKm(pkg.chainage_start_km?.toString() || "0")
setChainageEndKm(pkg.chainage_end_km?.toString() || "0")
setIsModalOpen(true)
}
{/* Modal Dialog */}
<Dialog
open={isModalOpen}
onOpenChange={(open) => {
if (!open) {
setIsModalOpen(false);
resetForm();
} else {
setIsModalOpen(true);
}
}}
>
<DialogContent className="max-w-2xl" onOpenAutoFocus={(e) => e.preventDefault()}>
<DialogHeader className="gap-2">
<DialogTitle className="flex items-center gap-3 text-xl">
<div className="p-2 rounded-lg bg-primary text-primary-foreground shadow-sm">
<Package className="h-5 w-5" />
</div>
{isEditing ? 'Edit Package Details' : 'Create New Package'}
</DialogTitle>
<DialogDescription className="text-sm">
{isEditing
? 'Update the technical specifications for your road infrastructure package.'
: 'Select a project and provide the essential data to establish a new package.'}
</DialogDescription>
</DialogHeader>
const handleDelete = async (pkg: PackageType) => {
if (!confirm(`Are you sure you want to delete package "${pkg.name}"?`)) return
<form onSubmit={handleSubmit} className="space-y-8">
{/* Step 1: Select Project */}
{!isEditing && (
<div className="space-y-3">
<div className="flex items-center gap-2.5">
<div className="w-6 h-6 rounded-full bg-primary/10 text-primary flex items-center justify-center text-[10px] font-bold uppercase">
01
</div>
<p className="text-[11px] font-black text-muted-foreground uppercase tracking-widest">
Select Parent Project
</p>
</div>
<Select value={selectedProjectId} onValueChange={setSelectedProjectId}>
<SelectTrigger className="h-11 bg-muted/10 border-border/40 focus:ring-primary/20">
{loadingProjects ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin text-primary" />
<span className="text-muted-foreground text-sm">Loading...</span>
</div>
) : (
<SelectValue placeholder="Choose a project..." />
)}
</SelectTrigger>
<SelectContent>
{projects.map((project) => (
<SelectItem key={project.id} value={project.id} className="py-3">
<span className="font-semibold">{project.name}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
try {
setIsLoading(true)
await packageService.deletePackage(pkg.id)
toast.success("Package Deleted", {
description: `${pkg.name} has been removed from the system.`,
})
await loadPackages()
} catch (err) {
setError("Failed to delete package")
toast.error("Deletion Failed", {
description: "The package could not be removed. Please try again.",
})
} finally {
setIsLoading(false)
}
}
{/* Step 2: Package Info */}
<div
className={`space-y-4 ${selectedProjectId || isEditing ? 'opacity-100' : 'opacity-40 pointer-events-none'}`}
>
{!isEditing && (
<div className="flex items-center gap-2.5">
<div
className={`w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold uppercase ${selectedProjectId ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'}`}
>
02
</div>
<p className="text-[11px] font-black text-muted-foreground uppercase tracking-widest">
Package Information
</p>
</div>
)}
const getProjectName = (projectId: string) => {
return projects.find(p => p.id === projectId)?.name || projectId
}
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<div className="space-y-2">
<Label
htmlFor="pkg-name"
className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider"
>
Package Name <span className="text-destructive">*</span>
</Label>
<Input
id="pkg-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g. Package 01"
className="h-11 bg-muted/20 border-border/60"
required
/>
</div>
const columns: ColumnDef<PackageType>[] = [
{
accessorKey: "name",
header: "Package Name",
cell: ({ row }) => (
<div className="font-semibold text-gray-900 dark:text-gray-100">{row.original.name}</div>
)
},
{
accessorKey: "project_id",
header: "Project",
cell: ({ row }) => getProjectName(row.original.project_id)
},
{
accessorKey: "project_state",
header: "Project State",
cell: ({ row }) => {
const pkg = row.original
const project = projects.find(p => p.id === pkg.project_id)
if (!project?.state) return <span className="text-gray-400"></span>
const states = project.state.split(',').map(s => s.trim()).filter(Boolean)
if (states.length === 0) return <span className="text-gray-400"></span>
const firstState = states[0]
const remainingStates = states.slice(1)
<div className="space-y-2">
<Label
htmlFor="region"
className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider flex items-center gap-2"
>
<Globe className="h-3.5 w-3.5 opacity-60" />
Region{' '}
<span className="text-[10px] lowercase font-normal opacity-70">(Optional)</span>
</Label>
<Input
id="region"
value={region}
onChange={(e) => setRegion(e.target.value)}
placeholder="e.g. North Zone"
className="h-11 bg-muted/20 border-border/60"
/>
</div>
return (
<div className="flex items-center gap-1.5">
<Badge variant="secondary">
{firstState}
</Badge>
{remainingStates.length > 0 && (
<Popover>
<PopoverTrigger asChild>
<button className="flex items-center justify-center rounded-full bg-muted/50 hover:bg-muted px-1.5 py-0.5 text-[10px] font-bold text-muted-foreground transition-colors border border-border/40">
+{remainingStates.length}
</button>
</PopoverTrigger>
<PopoverContent className="w-auto p-2" align="start">
<div className="flex flex-col gap-1.5">
<p className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider mb-0.5 px-1">Other States</p>
{remainingStates.map((item, idx) => (
<Badge key={idx} variant="secondary">
{item}
</Badge>
))}
</div>
</PopoverContent>
</Popover>
)}
</div>
)
}
},
{
accessorKey: "region",
header: "Region",
},
{
accessorKey: "chainage_start_km",
header: "Start (km)",
cell: ({ row }) => row.original.chainage_start_km?.toFixed(2) ?? "0.00"
},
{
accessorKey: "chainage_end_km",
header: "End (km)",
cell: ({ row }) => row.original.chainage_end_km?.toFixed(2) ?? "0.00"
},
]
<div className="space-y-2">
<Label
htmlFor="chainage-start"
className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider"
>
Chainage Start (km)
</Label>
<Input
id="chainage-start"
type="number"
step="0.01"
value={chainageStartKm}
onChange={(e) => setChainageStartKm(e.target.value)}
placeholder="0.00"
className="h-11 bg-muted/20 border-border/60"
/>
</div>
return (
<>
<main className="relative z-10">
{/* Refined Header */}
<div className="mb-8">
<PageHeader
title="Package Management"
description="Manage project packages"
icon={Package}
actions={
<Button
onClick={() => { setIsEditing(false); setIsModalOpen(true); }}
>
<Package className="mr-2 h-4 w-4" />
Add New Package
</Button>
}
/>
</div>
<div className="space-y-2">
<Label
htmlFor="chainage-end"
className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider"
>
Chainage End (km)
</Label>
<Input
id="chainage-end"
type="number"
step="0.01"
value={chainageEndKm}
onChange={(e) => setChainageEndKm(e.target.value)}
placeholder="0.00"
className="h-11 bg-muted/20 border-border/60"
/>
</div>
</div>
</div>
{/* Data Table */}
<div>
<DataTable
title="Packages"
data={packages}
columns={columns}
onEdit={handleEdit}
onDelete={handleDelete}
isLoading={isLoading}
pagination={{
skip,
limit,
totalItems,
onPageChange: setSkip,
onLimitChange: (newLimit) => {
setLimit(newLimit);
setSkip(0); // Reset skip when limit changes
}
}}
/>
</div>
<PoweredBy />
</main>
{/* Modal Dialog */}
<Dialog open={isModalOpen} onOpenChange={(open) => { if (!open) { setIsModalOpen(false); resetForm(); } else { setIsModalOpen(true); } }}>
<DialogContent
className="max-w-2xl"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<DialogHeader className="gap-2">
<DialogTitle className="flex items-center gap-3 text-xl">
<div className="p-2 rounded-lg bg-primary text-primary-foreground shadow-sm">
<Package className="h-5 w-5" />
</div>
{isEditing ? 'Edit Package Details' : 'Create New Package'}
</DialogTitle>
<DialogDescription className="text-sm">
{isEditing ? '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={handleSubmit} className="space-y-8">
{/* Step 1: Select Project */}
{!isEditing && (
<div className="space-y-3">
<div className="flex items-center gap-2.5">
<div className="w-6 h-6 rounded-full bg-primary/10 text-primary flex items-center justify-center text-[10px] font-bold uppercase">
01
</div>
<p className="text-[11px] font-black text-muted-foreground uppercase tracking-widest">Select Parent Project</p>
</div>
<Select value={selectedProjectId} onValueChange={setSelectedProjectId}>
<SelectTrigger className="h-11 bg-muted/10 border-border/40 focus:ring-primary/20">
{loadingProjects ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin text-primary" />
<span className="text-muted-foreground text-sm">Loading...</span>
</div>
) : (
<SelectValue placeholder="Choose a project..." />
)}
</SelectTrigger>
<SelectContent>
{projects.map(project => (
<SelectItem key={project.id} value={project.id} className="py-3">
<span className="font-semibold">{project.name}</span>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{/* Step 2: Package Info */}
<div className={`space-y-4 ${selectedProjectId || isEditing ? 'opacity-100' : 'opacity-40 pointer-events-none'}`}>
{!isEditing && (
<div className="flex items-center gap-2.5">
<div className={`w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold uppercase ${selectedProjectId ? 'bg-primary/10 text-primary' : 'bg-muted text-muted-foreground'}`}>
02
</div>
<p className="text-[11px] font-black text-muted-foreground uppercase tracking-widest">Package Information</p>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
<div className="space-y-2">
<Label htmlFor="pkg-name" className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider">
Package Name <span className="text-destructive">*</span>
</Label>
<Input
id="pkg-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g. Package 01"
className="h-11 bg-muted/20 border-border/60"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="region" className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider flex items-center gap-2">
<Globe className="h-3.5 w-3.5 opacity-60" />
Region <span className="text-[10px] lowercase font-normal opacity-70">(Optional)</span>
</Label>
<Input
id="region"
value={region}
onChange={(e) => setRegion(e.target.value)}
placeholder="e.g. North Zone"
className="h-11 bg-muted/20 border-border/60"
/>
</div>
<div className="space-y-2">
<Label htmlFor="chainage-start" className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider">
Chainage Start (km)
</Label>
<Input
id="chainage-start"
type="number"
step="0.01"
value={chainageStartKm}
onChange={(e) => setChainageStartKm(e.target.value)}
placeholder="0.00"
className="h-11 bg-muted/20 border-border/60"
/>
</div>
<div className="space-y-2">
<Label htmlFor="chainage-end" className="text-[11px] font-bold text-muted-foreground uppercase tracking-wider">
Chainage End (km)
</Label>
<Input
id="chainage-end"
type="number"
step="0.01"
value={chainageEndKm}
onChange={(e) => setChainageEndKm(e.target.value)}
placeholder="0.00"
className="h-11 bg-muted/20 border-border/60"
/>
</div>
</div>
</div>
{/* Submit Button */}
<div className="flex gap-4 pt-4">
<Button
type="button"
variant="outline"
onClick={() => {
setIsModalOpen(false)
resetForm()
}}
disabled={isSubmitting}
className="flex-1"
>
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting || !name.trim() || !selectedProjectId}
className="flex-1"
>
{isSubmitting ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span>{isEditing ? 'Updating...' : 'Creating...'}</span>
</div>
) : (
<div className="flex items-center gap-2">
{isEditing ? <CheckCircle2 className="h-4 w-4" /> : <Package className="h-4 w-4" />}
<span>{isEditing ? 'Update Package' : 'Create Package'}</span>
</div>
)}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
</>
)
{/* Submit Button */}
<div className="flex gap-4 pt-4">
<Button
type="button"
variant="outline"
onClick={() => {
setIsModalOpen(false);
resetForm();
}}
disabled={isSubmitting}
className="flex-1"
>
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting || !name.trim() || !selectedProjectId}
className="flex-1"
>
{isSubmitting ? (
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span>{isEditing ? 'Updating...' : 'Creating...'}</span>
</div>
) : (
<div className="flex items-center gap-2">
{isEditing ? (
<CheckCircle2 className="h-4 w-4" />
) : (
<Package className="h-4 w-4" />
)}
<span>{isEditing ? 'Update Package' : 'Create Package'}</span>
</div>
)}
</Button>
</div>
</form>
</DialogContent>
</Dialog>
</>
);
}