import { useState } from 'react'; import { RestaurantTable, TableStatus } from '../types'; import { useAppStore } from '../store/useAppStore'; import { api } from '../api/client'; const STATUS_STYLES: Record = { FREE: 'border-emerald-500 bg-emerald-500/15 text-emerald-300', OCCUPIED: 'border-rose-500 bg-rose-500/15 text-rose-300', RESERVED: 'border-indigo-400 bg-indigo-400/15 text-indigo-300', BILL: 'border-sky-400 bg-sky-400/15 text-sky-300', }; const STATUS_LABEL: Record = { FREE: 'Frei', OCCUPIED: 'Belegt', RESERVED: 'Reserviert', BILL: 'Rechnung', }; let _nextId = 100; interface Props { tables: RestaurantTable[]; } export default function FloorPlan({ tables }: Props) { const { selectedTableId, selectTable, setTableStatus, openBookingModal, updateTablePosition, addTable } = useAppStore(); const [editMode, setEditMode] = useState(false); // Drag state const [dragging, setDragging] = useState<{ id: string; startX: number; startY: number; origX: number; origY: number } | null>(null); const [positions, setPositions] = useState>({}); const getPos = (t: RestaurantTable) => positions[t.id] ?? { x: t.x, y: t.y }; const handleMouseDown = (e: React.MouseEvent, t: RestaurantTable) => { if (!editMode) return; e.preventDefault(); const pos = getPos(t); setDragging({ id: t.id, startX: e.clientX, startY: e.clientY, origX: pos.x, origY: pos.y }); }; const handleMouseMove = (e: React.MouseEvent) => { if (!dragging) return; const dx = e.clientX - dragging.startX; const dy = e.clientY - dragging.startY; setPositions((prev) => ({ ...prev, [dragging.id]: { x: Math.max(0, dragging.origX + dx), y: Math.max(0, dragging.origY + dy), }, })); }; const handleMouseUp = async () => { if (!dragging) return; const pos = positions[dragging.id]; if (pos) { updateTablePosition(dragging.id, pos.x, pos.y); // Fire-and-forget PATCH to backend try { await api.updateTablePosition(dragging.id, pos.x, pos.y); } catch { /* backend might be offline */ } } setDragging(null); }; const handleTableClick = (t: RestaurantTable) => { if (editMode) return; // no selection in edit mode if (selectedTableId === t.id) selectTable(null); else selectTable(t.id); }; const handleAddTable = () => { const newTable: RestaurantTable = { id: `new_${_nextId++}`, number: `T${_nextId}`, seats: 4, minCapacity: 1, maxCapacity: 4, status: 'FREE', x: 80 + Math.random() * 200, y: 80 + Math.random() * 200, shape: 'RECT', area: 'Gastraum', }; addTable(newTable); }; const selected = tables.find((t) => t.id === selectedTableId); return (
{/* Canvas */}
{/* Grid */}
{/* Edit-Mode bar */}
{editMode && ( )}
{/* Edit mode hint */} {editMode && (
Tische verschieben via Drag & Drop
)} {/* Tables */} {tables.map((t) => { const pos = getPos(t); const isSelected = selectedTableId === t.id; const isDraggingThis = dragging?.id === t.id; return (
handleMouseDown(e, t)} onClick={() => handleTableClick(t)} style={{ position: 'absolute', left: pos.x, top: pos.y, width: 112, height: 112, cursor: editMode ? (isDraggingThis ? 'grabbing' : 'grab') : 'pointer', zIndex: isDraggingThis ? 20 : isSelected ? 10 : 1, transition: isDraggingThis ? 'none' : 'transform 0.15s, box-shadow 0.15s', transform: isSelected ? 'scale(1.07)' : isDraggingThis ? 'scale(1.05)' : 'scale(1)', boxShadow: isDraggingThis ? '0 12px 32px rgba(0,0,0,0.6)' : isSelected ? '0 0 0 3px rgba(99,102,241,0.4)' : 'none', userSelect: 'none', }} >
{t.number} {t.maxCapacity} P. {STATUS_LABEL[t.status]} {editMode && ( {Math.round(pos.x)}, {Math.round(pos.y)} )}
); })}
{/* Side Panel */}
{/* Legend */}

Legende

{(Object.entries(STATUS_LABEL) as [TableStatus, string][]).map(([s, label]) => (
{label}
))}
{/* Selected Table Detail Panel (normal mode only) */} {!editMode && selected && (

Tisch {selected.number}

{selected.area} ยท max. {selected.maxCapacity} Pers.

{(['FREE', 'OCCUPIED', 'RESERVED', 'BILL'] as TableStatus[]).map((s) => ( ))}
)}
); }