diff --git a/app/(app)/equipment/manage/equipment-manage-client.tsx b/app/(app)/equipment/manage/equipment-manage-client.tsx index 459f863..6f404cc 100644 --- a/app/(app)/equipment/manage/equipment-manage-client.tsx +++ b/app/(app)/equipment/manage/equipment-manage-client.tsx @@ -10,6 +10,7 @@ import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/ import { useConfirm } from '@/components/ui/confirm' import { createEquipment, updateEquipment, deleteEquipment } from '@/lib/actions' import { EQUIPMENT_STATUS_LABEL } from '@/lib/equipment-logic' +import { EquipmentReservations } from '@/components/equipment/equipment-reservations' type Equip = { id: string @@ -37,6 +38,7 @@ export function EquipmentManageClient({ initial, certs }: { initial: Equip[]; ce const [draft, setDraft] = useState(emptyDraft()) const [editId, setEditId] = useState(null) const [editDraft, setEditDraft] = useState(emptyDraft()) + const [resFor, setResFor] = useState(null) const [busy, setBusy] = useState(false) const certName = (id: string | null) => (id ? certs.find(c => c.id === id)?.name ?? null : null) @@ -237,12 +239,16 @@ export function EquipmentManageClient({ initial, certs }: { initial: Equip[]; ce

+
)} + {editId !== it.id && resFor === it.id && } ))} diff --git a/components/equipment/equipment-reservations.tsx b/components/equipment/equipment-reservations.tsx new file mode 100644 index 0000000..bed9f59 --- /dev/null +++ b/components/equipment/equipment-reservations.tsx @@ -0,0 +1,90 @@ +'use client' + +import { useEffect, useState, useCallback } from 'react' +import { toast } from 'sonner' +import { listEquipmentReservations, cancelReservation } from '@/lib/actions' + +type Reservation = { + id: string + member_id: string + starts_at: string + ends_at: string + status: string + notes: string | null + space_members: { display_name: string | null; email: string | null } | null +} + +function who(r: Reservation) { + return r.space_members?.display_name ?? r.space_members?.email ?? 'Member' +} + +// Manager view of who reserved a piece of equipment, with a manager cancel. +// listEquipmentReservations is equipment.manage-gated server-side. +export function EquipmentReservations({ equipmentId }: { equipmentId: string }) { + const [loading, setLoading] = useState(false) + const [rows, setRows] = useState([]) + const [busy, setBusy] = useState(null) + + const load = useCallback(async () => { + setLoading(true) + const res = await listEquipmentReservations({ equipmentId }) + setLoading(false) + if ('error' in res && res.error) { + toast.error(res.error) + return + } + setRows(((res as { data: Reservation[] }).data) ?? []) + }, [equipmentId]) + + useEffect(() => { + load() + }, [load]) + + async function onCancel(r: Reservation) { + setBusy(r.id) + const res = await cancelReservation({ reservationId: r.id }) + setBusy(null) + if ('error' in res && res.error) { + toast.error(res.error) + return + } + setRows(prev => prev.map(x => (x.id === r.id ? { ...x, status: 'cancelled' } : x))) + toast.success('Reservation cancelled') + } + + return ( +
+

Reservations

+ {loading ? ( +

Loading…

+ ) : rows.length === 0 ? ( +

No reservations.

+ ) : ( +
    + {rows.map(r => ( +
  • +
    + {who(r)} + + {new Date(r.starts_at).toLocaleString()} – {new Date(r.ends_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} + {' · '} + {r.status} + + {r.notes &&

    {r.notes}

    } +
    + {r.status !== 'cancelled' && ( + + )} +
  • + ))} +
+ )} +
+ ) +}