// Table cards at 5 densities. All share the same data model — each card type
// just renders a subset, sized for fast reading at-a-glance.
const { TABLE_STATUS, TABLE_BADGES } = window;
// ---------- shared bits ----------------------------------------------------
function fmtAmount(n) {
if (n == null || n === 0) return '0.00';
return n.toFixed(2);
}
// Splits "12.34" into ["12", ".34"] so we can typeset cents smaller
function splitAmount(n) {
const s = fmtAmount(n);
const [whole, cents] = s.split('.');
return [whole, '.' + cents];
}
function avatarHash(name) {
const palette = ['#3758c9', '#7a44c9', '#2f9e5e', '#d94b26', '#8a6d2b', '#0d7a8a', '#c93775', '#1d6f3a'];
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return palette[h % palette.length];
}
function WaiterDot({ name, size = 22, ring }) {
const initials = name.split(' ').map(p => p[0]).slice(0, 2).join('').toUpperCase();
return (
{initials}
);
}
function StackedAvatars({ waiters, size = 22, ring }) {
if (!waiters?.length) return null;
if (waiters.length >= 3) {
return (
{waiters.slice(0, 2).map((w, i) => (
))}
+{waiters.length - 2}
);
}
return (
{waiters.map((w, i) => (
))}
);
}
function StatusPill({ status, size = 'md' }) {
const s = TABLE_STATUS[status];
const sizes = {
sm: { h: 18, px: 7, fs: 10 },
md: { h: 22, px: 9, fs: 11 },
lg: { h: 26, px: 11, fs: 12 },
};
const z = sizes[size];
return (
{s.label}
);
}
function BadgeChip({ kind, size = 'md' }) {
const b = TABLE_BADGES[kind];
if (!b) return null;
const sizes = {
sm: { h: 20, fs: 11, ic: 12 },
md: { h: 24, fs: 12, ic: 14 },
lg: { h: 28, fs: 13, ic: 16 },
};
const z = sizes[size];
return (
{b.icon}
{b.label}
);
}
function BadgeDot({ kind, size = 16 }) {
const b = TABLE_BADGES[kind];
if (!b) return null;
return (
{b.icon}
);
}
function Amount({ value, size = 22, color }) {
const [w, c] = splitAmount(value);
return (
{w}
{c}€
);
}
// ---------- card shell -----------------------------------------------------
// All densities share this shell — just different content + dimensions.
function CardShell({ status, w, h, children, padding }) {
const s = TABLE_STATUS[status];
return (
{children}
);
}
// ===========================================================================
// 1×1 — tiniest. Just NAME. Status is purely the card color.
// ===========================================================================
function Card1x1({ table, w, h }) {
const t = table;
// Show one badge dot if present (very subtle, top-right)
const badge = t.badges[0];
return (
{t.name}
{badge && (
)}
);
}
// ===========================================================================
// 2×1 — wider. NAME + status PILL + maybe one badge dot.
// ===========================================================================
function Card2x1({ table, w, h }) {
const t = table;
return (
{t.name}
{t.badges.length > 0 && (
{t.badges.slice(0, 2).map(b => )}
)}
);
}
// ===========================================================================
// 2×2 — square. NAME big + status pill + amount + waiter dots + badges
// ===========================================================================
function Card2x2({ table, w, h }) {
const t = table;
const showAmount = t.amount > 0 || t.status === 'paid' || t.status === 'partial';
return (
{/* left column: name + pill (top), amount (bottom) */}
{/* right column: badges stacked vertically, bottom-aligned */}
{t.badges.length > 0 && (
{t.badges.slice(0, 3).map(b => )}
)}
);
}
// ===========================================================================
// 4×1 — wide horizontal. NAME · AMOUNT · status pill + waiter dots
// ===========================================================================
function Card4x1({ table, w, h }) {
const t = table;
const showAmount = t.amount > 0 || t.status === 'paid' || t.status === 'partial';
return (
{/* name */}
{t.name}
{/* amount (or spacer) */}
{/* badges */}
{t.badges.length > 0 && (
{t.badges.slice(0, 2).map(b => )}
)}
{/* status pill */}
);
}
// ===========================================================================
// 4×2 — full detail. Name + section + status pill + amount + badges + waiters with names
// ===========================================================================
function Card4x2({ table, w, h }) {
const t = table;
const s = TABLE_STATUS[t.status];
const showAmount = t.amount > 0 || t.status === 'paid' || t.status === 'partial';
// First waiter name (or "Multiple")
const waiterCaption = t.waiters.length === 0
? 'Unassigned'
: t.waiters.length >= 3
? `${t.waiters.length} waiters`
: t.waiters.map(w => w.split(' ')[0]).join(', ');
return (
{/* top row: name + section + status pill | amount */}
{/* badges block — right-aligned, up to 4 in 2×2 grid, sits above waiter line */}
{t.badges.length > 0 && (
{t.badges.slice(0, 4).map(b => (
))}
)}
{/* bottom: waiters with names */}
{t.waiters.length === 0 ? (
Unassigned
) : (
<>
{waiterCaption}
>
)}
);
}
window.TableCards = { Card1x1, Card2x1, Card2x2, Card4x1, Card4x2 };