diff --git a/app/(app)/equipment/manage/equipment-manage-client.tsx b/app/(app)/equipment/manage/equipment-manage-client.tsx new file mode 100644 index 0000000..459f863 --- /dev/null +++ b/app/(app)/equipment/manage/equipment-manage-client.tsx @@ -0,0 +1,253 @@ +'use client' + +import { useState } from 'react' +import { toast } from 'sonner' +import { Hammer, Plus } from 'lucide-react' +import { PageHeader, PageTitle } from '@/components/ui/page-title' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty' +import { useConfirm } from '@/components/ui/confirm' +import { createEquipment, updateEquipment, deleteEquipment } from '@/lib/actions' +import { EQUIPMENT_STATUS_LABEL } from '@/lib/equipment-logic' + +type Equip = { + id: string + name: string + description: string | null + location: string | null + status: string + required_certification_id: string | null + asset_tag: string | null + is_active: boolean +} +type Cert = { id: string; name: string } + +const inputCls = + 'w-full bg-background border border-border text-foreground font-sans text-sm rounded px-3 py-2 focus:outline-none focus:border-primary' + +function emptyDraft() { + return { name: '', description: '', location: '', status: 'available', required_certification_id: '', asset_tag: '' } +} + +export function EquipmentManageClient({ initial, certs }: { initial: Equip[]; certs: Cert[] }) { + const confirm = useConfirm() + const [items, setItems] = useState(initial) + const [showNew, setShowNew] = useState(false) + const [draft, setDraft] = useState(emptyDraft()) + const [editId, setEditId] = useState(null) + const [editDraft, setEditDraft] = useState(emptyDraft()) + const [busy, setBusy] = useState(false) + + const certName = (id: string | null) => (id ? certs.find(c => c.id === id)?.name ?? null : null) + + async function onCreate(e: React.FormEvent) { + e.preventDefault() + if (!draft.name.trim()) return toast.error('Name is required') + setBusy(true) + const res = await createEquipment({ + name: draft.name.trim(), + description: draft.description.trim() || null, + location: draft.location.trim() || null, + status: draft.status, + required_certification_id: draft.required_certification_id || null, + asset_tag: draft.asset_tag.trim() || null, + }) + setBusy(false) + if ('error' in res && res.error) return toast.error(res.error) + const id = (res as { data: { id: string } }).data.id + setItems(prev => [ + { + id, + name: draft.name.trim(), + description: draft.description.trim() || null, + location: draft.location.trim() || null, + status: draft.status, + required_certification_id: draft.required_certification_id || null, + asset_tag: draft.asset_tag.trim() || null, + is_active: true, + }, + ...prev, + ]) + setDraft(emptyDraft()) + setShowNew(false) + toast.success('Equipment added') + } + + function startEdit(it: Equip) { + setEditId(it.id) + setEditDraft({ + name: it.name, + description: it.description ?? '', + location: it.location ?? '', + status: it.status, + required_certification_id: it.required_certification_id ?? '', + asset_tag: it.asset_tag ?? '', + }) + } + + async function onSaveEdit(id: string) { + if (!editDraft.name.trim()) return toast.error('Name is required') + setBusy(true) + const res = await updateEquipment({ + equipmentId: id, + name: editDraft.name.trim(), + description: editDraft.description.trim() || null, + location: editDraft.location.trim() || null, + status: editDraft.status, + required_certification_id: editDraft.required_certification_id || null, + asset_tag: editDraft.asset_tag.trim() || null, + }) + setBusy(false) + if ('error' in res && res.error) return toast.error(res.error) + setItems(prev => + prev.map(x => + x.id === id + ? { + ...x, + name: editDraft.name.trim(), + description: editDraft.description.trim() || null, + location: editDraft.location.trim() || null, + status: editDraft.status, + required_certification_id: editDraft.required_certification_id || null, + asset_tag: editDraft.asset_tag.trim() || null, + } + : x, + ), + ) + setEditId(null) + toast.success('Saved') + } + + async function onToggleActive(it: Equip) { + const res = await updateEquipment({ equipmentId: it.id, is_active: !it.is_active }) + if ('error' in res && res.error) return toast.error(res.error) + setItems(prev => prev.map(x => (x.id === it.id ? { ...x, is_active: !x.is_active } : x))) + toast.success(it.is_active ? 'Archived' : 'Restored') + } + + async function onDelete(it: Equip) { + const ok = await confirm({ + title: 'Delete equipment', + description: `"${it.name}" will be removed. Only allowed if it has no reservations.`, + confirmText: 'Delete', + destructive: true, + }) + if (!ok) return + const res = await deleteEquipment({ equipmentId: it.id }) + if ('error' in res && res.error) return toast.error(res.error) + setItems(prev => prev.filter(x => x.id !== it.id)) + toast.success('Deleted') + } + + return ( + <> + + Manage equipment + + + +
+ {showNew && ( +
+ setDraft({ ...draft, name: e.target.value })} /> +