// Table Card — 3 variations
// All cards share the same fixed dimensions and field positions so the grid
// stays visually aligned even when fields are empty.
const STATUS = {
open: { label: 'Open', tint: 'var(--open-50)', tintStrong: 'var(--open-100)', accent: 'var(--open-500)', ink: 'var(--open-700)' },
occupied:{ label: 'Occupied', tint: 'var(--occ-50)', tintStrong: 'var(--occ-100)', accent: 'var(--occ-500)', ink: 'var(--occ-700)' },
reserved:{ label: 'Reserved', tint: 'var(--res-50)', tintStrong: 'var(--res-100)', accent: 'var(--res-500)', ink: 'var(--res-700)' },
alert: { label: 'Needs attention', tint: 'var(--alert-50)', tintStrong: 'var(--alert-100)', accent: 'var(--alert-500)', ink: 'var(--alert-700)' },
dirty: { label: 'Needs cleaning', tint: 'var(--dirty-50)', tintStrong: 'var(--dirty-100)', accent: 'var(--dirty-500)', ink: 'var(--dirty-700)' },
};
// ----- Shared helpers -----
function formatEuro(n) {
if (n == null) return null;
return '€' + n.toFixed(2).replace(/\.00$/, '.00');
}
function formatDuration(mins) {
if (mins == null) return null;
if (mins < 60) return `${mins}m`;
const h = Math.floor(mins / 60);
const m = mins % 60;
return m === 0 ? `${h}h` : `${h}h ${m}m`;
}
function avatarColor(name) {
const palette = ['#3758c9', '#7a44c9', '#2f9e5e', '#d94b26', '#8a6d2b', '#0d7a8a', '#c93775'];
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return palette[h % palette.length];
}
function Initials({ name, size = 28 }) {
const parts = name.split(' ');
const initials = (parts[0][0] + (parts[1]?.[0] || '')).toUpperCase();
return (
{initials}
);
}
function Flag({ kind }) {
const map = {
VIP: { bg: '#fff4d6', fg: '#8a6d0b', label: 'VIP' },
Allergy: { bg: '#fde3dc', fg: '#a5361b', label: 'Allergy' },
Birthday: { bg: '#fbe2ee', fg: '#a8276b', label: 'Birthday' },
};
const s = map[kind] || { bg: '#eceff2', fg: '#3a4049', label: kind };
return (
{s.label}
);
}
// Placeholder dashes so empty fields keep their footprint but visually disappear
function EmptyDash({ width = 40 }) {
return — —;
}
// ===========================================================================
// VARIATION 1 — Left accent border + tinted background
// Stacked: header row, stats row, waiter row. Clean and quiet.
// ===========================================================================
function TableCardV1({ name, status, amount, occupiedMins, waiters = [], flags = [] }) {
const s = STATUS[status];
const [hover, setHover] = React.useState(false);
const [pressed, setPressed] = React.useState(false);
// Waiter display rules
const showMulti = waiters.length >= 3;
return (
);
}
// ===========================================================================
// VARIATION 2 — Top stripe + large name on left, stats on right
// More "at-a-glance" — big name dominates.
// ===========================================================================
function TableCardV2({ name, status, amount, occupiedMins, waiters = [], flags = [] }) {
const s = STATUS[status];
const [hover, setHover] = React.useState(false);
const [pressed, setPressed] = React.useState(false);
const showMulti = waiters.length >= 3;
return (
);
}
// ===========================================================================
// VARIATION 3 — Bold name badge, stats right-aligned
// Name lives in a colored badge in the top-left like a table plaque.
// Good for tablet thumb reach — name is easy to tap to open.
// ===========================================================================
function TableCardV3({ name, status, amount, occupiedMins, waiters = [], flags = [] }) {
const s = STATUS[status];
const [hover, setHover] = React.useState(false);
const [pressed, setPressed] = React.useState(false);
const showMulti = waiters.length >= 3;
return (
);
}
// Export to window
Object.assign(window, { TableCardV1, TableCardV2, TableCardV3, STATUS });