Files
Ota-dashboard/components/GroupManager.tsx
2025-12-11 20:58:10 +01:00

183 lines
7.8 KiB
TypeScript

'use client';
import { useState } from 'react';
import { VehicleGroup, Vehicle, createGroup, updateGroup, assignVehicleToGroup } from '@/lib/api';
import { Users, Plus, Edit2, X, Check } from 'lucide-react';
interface GroupManagerProps {
groups: VehicleGroup[];
vehicles: Vehicle[];
onGroupChanged: () => void;
}
export function GroupManager({ groups, vehicles, onGroupChanged }: GroupManagerProps) {
const [isCreating, setIsCreating] = useState(false);
const [editingId, setEditingId] = useState<number | null>(null);
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const [loading, setLoading] = useState(false);
// Vehicle Assignment State
const [assignVin, setAssignVin] = useState('');
const startCreate = () => {
setIsCreating(true);
setEditingId(null);
setName('');
setDescription('');
setAssignVin('');
};
const startEdit = (group: VehicleGroup) => {
setIsCreating(false);
setEditingId(group.id);
setName(group.name);
setDescription(group.description || '');
setAssignVin('');
};
const cancel = () => {
setIsCreating(false);
setEditingId(null);
setName('');
setDescription('');
setAssignVin('');
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!name) return;
setLoading(true);
try {
if (isCreating) {
await createGroup(name, description);
} else if (editingId) {
await updateGroup(editingId, name, description);
}
onGroupChanged();
cancel();
} catch (err) {
console.error(err);
alert('Operation failed');
} finally {
setLoading(false);
}
};
const handleAssignVehicle = async () => {
if (!editingId || !assignVin) return;
setLoading(true);
try {
await assignVehicleToGroup(editingId, assignVin);
onGroupChanged();
alert('Vehicle assigned');
setAssignVin('');
} catch (err) {
console.error(err);
alert('Failed to assign vehicle');
} finally {
setLoading(false);
}
};
return (
<div className="bg-zinc-900/50 backdrop-blur-md border border-zinc-800 rounded-xl p-6 shadow-xl">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-white flex items-center gap-2">
<Users className="w-5 h-5 text-orange-400" />
Vehicle Groups
</h2>
<button
onClick={startCreate}
className="text-xs bg-zinc-800 hover:bg-zinc-700 text-white px-3 py-1.5 rounded-lg transition-colors flex items-center gap-1"
>
<Plus className="w-3 h-3" /> New Group
</button>
</div>
{(isCreating || editingId) && (
<div className="mb-4 bg-zinc-950/50 p-4 rounded-lg border border-zinc-800/50">
<form onSubmit={handleSubmit} className="space-y-3 mb-4">
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Group Name"
className="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-white text-sm outline-none focus:ring-1 focus:ring-orange-500"
required
/>
<input
type="text"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Description (optional)"
className="w-full bg-zinc-900 border border-zinc-700 rounded-lg px-3 py-2 text-white text-sm outline-none focus:ring-1 focus:ring-orange-500"
/>
<div className="flex items-center gap-2 pt-1">
<button
type="submit"
disabled={loading}
className="flex-1 bg-orange-500 hover:bg-orange-600 text-white text-xs font-medium py-1.5 rounded transition-colors flex items-center justify-center gap-1"
>
<Check className="w-3 h-3" /> Save
</button>
<button
type="button"
onClick={cancel}
className="flex-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs font-medium py-1.5 rounded transition-colors flex items-center justify-center gap-1"
>
<X className="w-3 h-3" /> Cancel
</button>
</div>
</form>
{editingId && (
<div className="border-t border-zinc-800 pt-3">
<h4 className="text-xs font-medium text-zinc-400 mb-2">Assign Vehicle</h4>
<div className="flex gap-2">
<select
value={assignVin}
onChange={e => setAssignVin(e.target.value)}
className="flex-1 bg-zinc-900 border border-zinc-700 rounded px-2 py-1 text-xs text-white outline-none"
>
<option value="">Select...</option>
{vehicles.map(v => (
<option key={v.vin} value={v.vin}>{v.vin} {v.groupId === editingId ? '(Already in group)' : ''}</option>
))}
</select>
<button
onClick={handleAssignVehicle}
disabled={loading || !assignVin}
className="bg-zinc-800 hover:bg-zinc-700 text-white px-2 py-1 rounded text-xs"
>
<Plus className="w-3 h-3" />
</button>
</div>
</div>
)}
</div>
)}
<div className="space-y-2 max-h-[300px] overflow-y-auto pr-1">
{groups.map(group => (
<div key={group.id} className="group flex items-center justify-between p-3 bg-zinc-900/30 border border-zinc-800/30 rounded-lg hover:border-zinc-700 transition-colors">
<div>
<h3 className="text-zinc-200 font-medium text-sm">{group.name}</h3>
<p className="text-zinc-500 text-xs">{group.vehicles?.length || 0} vehicles</p>
</div>
<button
onClick={() => startEdit(group)}
className="opacity-0 group-hover:opacity-100 p-1.5 text-zinc-500 hover:text-white hover:bg-zinc-800 rounded transition-all"
>
<Edit2 className="w-3.5 h-3.5" />
</button>
</div>
))}
{groups.length === 0 && <p className="text-center text-zinc-500 text-sm py-4">No groups created.</p>}
</div>
</div>
);
}