Files

218 lines
12 KiB
TypeScript

import { useState, useMemo } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/Components/ui/dialog';
import { Input } from '@/Components/ui/input';
import { Badge } from '@/Components/ui/badge';
import { Search, Wrench, Check } from 'lucide-react';
interface EquipmentSpecification {
id: number;
ulid: string;
name: string;
}
interface Equipment {
id: number;
ulid: string;
name: string;
owner_name: string | null;
hourly_rate: string;
specifications: EquipmentSpecification[];
}
interface Props {
open: boolean;
onOpenChange: (open: boolean) => void;
equipments: Equipment[];
onSelectEquipment: (equipment: Equipment) => void;
selectedEquipmentUlid?: string;
}
export default function EquipmentLookupModal({
open,
onOpenChange,
equipments,
onSelectEquipment,
selectedEquipmentUlid
}: Props) {
const [search, setSearch] = useState('');
const [ownerFilter, setOwnerFilter] = useState<string>('all');
const formatCurrency = (v: string | number) => {
return new Intl.NumberFormat('en-PH', { style: 'currency', currency: 'PHP' }).format(Number(v));
};
// Dynamically discover all unique owner names in the equipment list for filtering
const uniqueOwners = useMemo(() => {
const owners = new Set<string>();
equipments.forEach(e => {
if (e.owner_name) {
owners.add(e.owner_name);
}
});
return Array.from(owners).sort();
}, [equipments]);
const filteredEquipments = useMemo(() => {
return equipments.filter(eq => {
// Owner filter
if (ownerFilter !== 'all') {
if (ownerFilter === 'unspecified') {
if (eq.owner_name !== null && eq.owner_name !== '') return false;
} else {
if (eq.owner_name !== ownerFilter) return false;
}
}
// Search filter
if (!search) return true;
const query = search.toLowerCase();
const matchesName = eq.name.toLowerCase().includes(query);
const matchesOwner = eq.owner_name ? eq.owner_name.toLowerCase().includes(query) : false;
const matchesSpecs = eq.specifications && eq.specifications.some(spec =>
spec.name.toLowerCase().includes(query)
);
return matchesName || matchesOwner || matchesSpecs;
});
}, [equipments, search, ownerFilter]);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[70vw] max-w-[95vw] max-h-[85vh] flex flex-col p-0 gap-0 border-slate-200 bg-white shadow-2xl overflow-hidden">
<DialogHeader className="px-6 py-4 border-b border-slate-100">
<DialogTitle className="flex items-center gap-2 text-lg font-bold text-slate-800">
<Wrench className="h-5 w-5 text-emerald-600" />
Select Equipment / Tool
</DialogTitle>
<DialogDescription className="text-xs text-slate-500">
Search and select a piece of machinery or equipment to allocate to the project task.
</DialogDescription>
</DialogHeader>
<div className="px-6 py-3 border-b border-slate-100 bg-slate-50 flex flex-col sm:flex-row gap-3 justify-between items-center">
{/* Owner Filter Badges */}
<div className="flex flex-wrap gap-1.5 w-full sm:w-auto">
<button
type="button"
onClick={() => setOwnerFilter('all')}
className={`px-3 py-1.5 text-xs font-semibold rounded-md border transition-all ${
ownerFilter === 'all'
? 'bg-slate-900 text-white border-slate-900 shadow-sm'
: 'bg-slate-100 text-slate-700 border-slate-300 hover:bg-slate-200'
}`}
>
All Owners
</button>
{uniqueOwners.map(owner => (
<button
key={owner}
type="button"
onClick={() => setOwnerFilter(owner)}
className={`px-3 py-1.5 text-xs font-semibold rounded-md border transition-all ${
ownerFilter === owner
? 'bg-slate-900 text-white border-slate-900 shadow-sm'
: 'bg-slate-100 text-slate-700 border-slate-300 hover:bg-slate-200'
}`}
>
{owner}
</button>
))}
</div>
{/* Search Input */}
<div className="relative w-full sm:w-72">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-slate-400" />
<Input
type="search"
placeholder="Search by name, specs, owner..."
className="h-9 pl-9 bg-white text-xs border-slate-200 focus-visible:ring-emerald-500 focus-visible:border-emerald-500"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
</div>
<div className="flex-1 overflow-y-auto p-6 min-h-0">
{filteredEquipments.length === 0 ? (
<div className="text-center py-12 text-slate-400 text-xs">
No equipment profiles found matching "{search}".
</div>
) : (
<div className="border border-slate-200/80 rounded-xl overflow-hidden shadow-sm">
<table className="w-full text-xs text-left border-collapse">
<thead className="bg-slate-55 border-b border-slate-100 text-slate-600 font-semibold">
<tr>
<th className="px-4 py-3 font-semibold">Equipment / Tool Name</th>
<th className="px-4 py-3 font-semibold w-36">Owner</th>
<th className="px-4 py-3 font-semibold">Specifications</th>
<th className="px-4 py-3 font-semibold text-right w-32">Hourly Rate</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 bg-white">
{filteredEquipments.map(eq => {
const isSelected = selectedEquipmentUlid === eq.ulid;
return (
<tr
key={eq.id}
className={`transition-all hover:bg-slate-50/50 cursor-pointer group ${
isSelected ? 'bg-emerald-50/40 hover:bg-emerald-50/60' : ''
}`}
onClick={() => onSelectEquipment(eq)}
>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<span className="font-semibold text-slate-800 group-hover:text-emerald-700 transition-colors">
{eq.name}
</span>
{isSelected && (
<Badge className="bg-emerald-100 text-emerald-800 border-none text-[10px] font-medium flex items-center gap-0.5">
<Check className="h-2.5 w-2.5" /> Selected
</Badge>
)}
</div>
</td>
<td className="px-4 py-3">
{eq.owner_name ? (
<Badge
variant="outline"
className="bg-slate-50 text-slate-700 border-slate-200 font-normal text-[10px]"
>
{eq.owner_name}
</Badge>
) : (
<span className="text-slate-400 italic text-[10px]">Unspecified</span>
)}
</td>
<td className="px-4 py-3">
<div className="flex flex-wrap gap-1">
{eq.specifications && eq.specifications.length > 0 ? (
eq.specifications.map(spec => (
<Badge
key={spec.id}
variant="secondary"
className="bg-slate-100 text-slate-650 hover:bg-slate-200 text-[10px] font-normal"
>
{spec.name}
</Badge>
))
) : (
<span className="text-slate-400 italic">No specs listed</span>
)}
</div>
</td>
<td className="px-4 py-3 text-right font-mono font-medium text-slate-700">
{formatCurrency(eq.hourly_rate)}/hr
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}