Files
hackerspace.sh/components/equipment/equipment-reservations.tsx
T
Moheeb ZaraandClaude Opus 4.8 b1edb5c460 types: clear remaining tsc errors across 41 files (76→14, behavior-preserving)
Per-file type-only fixes surfaced after the Supabase client was correctly
typed. All fixes preserve runtime behavior; no any/ts-ignore/ts-expect-error.

- lib/supabase/client.ts: annotate the browser client as SupabaseClient<Database>
  (same ssr/js drift as server.ts; fixes it centrally for all callers).
- Supabase embedded-select joins whose FK metadata is missing from the
  generated types are bridged with `as unknown as <target>` + a TODO to remove
  after regenerating types/database.ts.
- setState optimistic-update variance and raw <select> enum values cast to the
  precise element/union type; .insert payload fields (Json, enums) given the
  precise column type via satisfies/field casts.
- Added narrowing guards where a value was legitimately nullable (comms channel,
  public form page) without changing the intended path.

The 14 remaining errors are confined to the four files fixed in Phase B (real
bugs + the tautological actions.test rewrite). 639 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 18:56:11 -07:00

92 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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<Reservation[]>([])
const [busy, setBusy] = useState<string | null>(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
}
// TODO(types): remove after regenerating types/database.ts (missing FK relationship metadata)
setRows(((res as unknown 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 (
<div className="mt-3 rounded border border-border bg-background p-3">
<p className="font-mono text-[10px] uppercase tracking-widest text-muted-foreground mb-2">Reservations</p>
{loading ? (
<p className="font-mono text-[10px] text-muted-foreground">Loading…</p>
) : rows.length === 0 ? (
<p className="font-mono text-[10px] text-muted-foreground">No reservations.</p>
) : (
<ul className="divide-y divide-border">
{rows.map(r => (
<li key={r.id} className="py-2 flex items-center justify-between gap-3 flex-wrap">
<div className="min-w-0">
<span className="font-sans text-sm text-foreground">{who(r)}</span>
<span className="font-mono text-[10px] text-muted-foreground ml-2">
{new Date(r.starts_at).toLocaleString()} – {new Date(r.ends_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
{' · '}
<span className={r.status === 'cancelled' ? 'text-red-500' : 'text-primary'}>{r.status}</span>
</span>
{r.notes && <p className="font-sans text-xs text-muted-foreground mt-0.5">{r.notes}</p>}
</div>
{r.status !== 'cancelled' && (
<button
onClick={() => onCancel(r)}
disabled={busy === r.id}
className="font-mono text-[10px] border border-border px-3 py-2 rounded hover:border-red-500 hover:text-red-500 transition disabled:opacity-60"
>
Cancel
</button>
)}
</li>
))}
</ul>
)}
</div>
)
}