import { useRef, useState } from 'react' import { createFileRoute, Link, redirect, useRouter } from '@tanstack/react-router' import { ConfirmDialog } from '#/components/confirm-dialog' import { StaffTabs } from '#/components/staff-tabs' import { TableDetailDialog } from '#/components/table-detail-dialog' import { getCurrentStaffUser } from '#/lib/auth' import { authClient } from '#/lib/auth-client' import { createTable, deleteTable, getTables, updateTable } from '#/lib/reservations' import { ACCEPTED_IMAGE_TYPES, MAX_TABLE_IMAGE_BYTES, readImageAsDataUrl } from '#/lib/table-image' export const Route = createFileRoute('/staff/tables')({ component: StaffTables, beforeLoad: async () => { const staffUser = await getCurrentStaffUser() if (!staffUser) { throw redirect({ to: '/staff/login' }) } if (staffUser.role !== 'admin') { throw redirect({ to: '/staff' }) } return { staffUser } }, loader: async ({ context }) => { const tables = await getTables() return { staffUser: context.staffUser, tables } }, }) type TableRow = Awaited>[number] type FormState = { name: string tableNumber: number seats: number imageUrl: string | null } function emptyForm(): FormState { return { name: '', tableNumber: 1, seats: 2, imageUrl: null } } const MAX_IMAGE_MB = Math.floor(MAX_TABLE_IMAGE_BYTES / 1_000_000) function StaffTables() { const router = useRouter() const { staffUser, tables } = Route.useLoaderData() const [editingId, setEditingId] = useState(null) const [form, setForm] = useState(emptyForm) const [error, setError] = useState(null) const [success, setSuccess] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) const [deletingId, setDeletingId] = useState(null) const [pendingDelete, setPendingDelete] = useState(null) const [viewingTable, setViewingTable] = useState(null) const fileInputRef = useRef(null) function startEdit(table: TableRow) { setEditingId(table.id) setForm({ name: table.name, tableNumber: table.tableNumber ?? 0, seats: table.seats, imageUrl: table.imageUrl, }) setError(null) setSuccess(null) if (fileInputRef.current) fileInputRef.current.value = '' } function cancelEdit() { setEditingId(null) setForm(emptyForm()) setError(null) if (fileInputRef.current) fileInputRef.current.value = '' } async function handleImageChange(event: React.ChangeEvent) { const file = event.target.files?.[0] event.target.value = '' if (!file) return if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) { setError('Bild muss PNG, JPEG oder WebP sein') return } if (file.size > MAX_TABLE_IMAGE_BYTES) { setError(`Bild ist zu groß (max. ${MAX_IMAGE_MB} MB)`) return } setError(null) const dataUrl = await readImageAsDataUrl(file) setForm((f) => ({ ...f, imageUrl: dataUrl })) } async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) setSuccess(null) setIsSubmitting(true) try { const payload = { name: form.name, tableNumber: Number(form.tableNumber), seats: Number(form.seats), imageUrl: form.imageUrl, } if (editingId) { await updateTable({ data: { id: editingId, ...payload } }) setSuccess('Tisch wurde aktualisiert.') } else { await createTable({ data: payload }) setSuccess('Tisch wurde angelegt.') } cancelEdit() await router.invalidate() } catch (err) { setError(err instanceof Error ? err.message : 'Etwas ist schiefgelaufen') } finally { setIsSubmitting(false) } } async function confirmDelete() { const table = pendingDelete if (!table) return setPendingDelete(null) setDeletingId(table.id) setError(null) setSuccess(null) try { await deleteTable({ data: { id: table.id } }) if (editingId === table.id) cancelEdit() setSuccess('Tisch wurde gelöscht.') await router.invalidate() } catch (err) { setError(err instanceof Error ? err.message : 'Etwas ist schiefgelaufen') } finally { setDeletingId(null) } } async function handleLogout() { await authClient.signOut() await router.navigate({ to: '/staff/login' }) await router.invalidate() } return (
Steakhaus bei Mirko
Angemeldet als {staffUser.name}

Tische

{editingId ? 'Tisch bearbeiten' : 'Neuer Tisch'}

Die Tischnummer wird auf dem Marker angezeigt; der Name ist nur für die Verwaltung hier.

Zeigt Gästen im Buchungsformular, wie der Tisch aussieht. PNG, JPEG oder WebP, max.{' '} {MAX_IMAGE_MB} MB.

{editingId && ( )}
{error &&

{error}

} {success &&

{success}

}
{tables.length === 0 ? (

Noch keine Tische vorhanden.

) : (
{tables.map((table) => ( setViewingTable(table)} > ))}
Nr. Name Plätze
{table.imageUrl ? ( ) : ( {table.tableNumber ?? '—'} {table.name} {table.seats} event.stopPropagation()}>
)}
Tisch {pendingDelete.name} wirklich löschen?} warning="Diese Aktion kann nicht rückgängig gemacht werden." confirmLabel="Löschen" danger onConfirm={confirmDelete} onCancel={() => setPendingDelete(null)} /> setViewingTable(null)} />
) }