732 lines
20 KiB
HTML
732 lines
20 KiB
HTML
<!doctype html>
|
||
<html lang="pl">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||
<title>Karczma Biesiada</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
|
||
|
||
<style>
|
||
:root {
|
||
--ink: #1a1410;
|
||
--ink-2: #3d342b;
|
||
--ink-3: #7a6d63;
|
||
--cream: #faf7f2;
|
||
--cream-2: #f2ede4;
|
||
--cream-3: #e8e0d4;
|
||
--gold: #c9a84c;
|
||
--gold-lt: #f0d89a;
|
||
--gold-dk: #8c6b22;
|
||
--green: #2d5a3d;
|
||
--green-lt: #e8f2eb;
|
||
--amber: #b85c1a;
|
||
--amber-lt: #faeee5;
|
||
--r: 16px;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
html {
|
||
font-family: 'DM Sans', sans-serif;
|
||
font-size: 16px;
|
||
color: var(--ink);
|
||
background: var(--cream);
|
||
}
|
||
|
||
body {
|
||
min-height: 100dvh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background:
|
||
radial-gradient(ellipse 80% 40% at 50% 0%, rgba(201,168,76,.10) 0%, transparent 70%),
|
||
var(--cream);
|
||
}
|
||
|
||
/* ── HEADER ── */
|
||
|
||
header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20px 20px 0;
|
||
animation: fadeDown .5s ease both;
|
||
}
|
||
|
||
.brand {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
}
|
||
|
||
.brand-name {
|
||
font-family: 'Playfair Display', serif;
|
||
font-size: 20px;
|
||
font-weight: 400;
|
||
color: var(--ink);
|
||
line-height: 1;
|
||
}
|
||
|
||
.brand-tagline {
|
||
font-size: 11px;
|
||
font-weight: 300;
|
||
color: var(--ink-3);
|
||
letter-spacing: .06em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.table-pill {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
background: var(--ink);
|
||
color: var(--cream);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
padding: 6px 13px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.table-pill svg { opacity: .65; }
|
||
|
||
/* ── HERO ── */
|
||
|
||
.hero {
|
||
padding: 36px 20px 28px;
|
||
text-align: center;
|
||
animation: fadeUp .55s .1s ease both;
|
||
}
|
||
|
||
.hero-icon {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 50%;
|
||
background: var(--cream-2);
|
||
border: 1px solid var(--cream-3);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0 auto 18px;
|
||
transition: background .4s, border-color .4s;
|
||
}
|
||
|
||
.hero-icon svg {
|
||
width: 26px; height: 26px;
|
||
transition: opacity .3s;
|
||
}
|
||
|
||
.hero-title {
|
||
font-family: 'Playfair Display', serif;
|
||
font-size: 26px;
|
||
font-weight: 400;
|
||
color: var(--ink);
|
||
margin-bottom: 8px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.hero-sub {
|
||
font-size: 14px;
|
||
font-weight: 300;
|
||
color: var(--ink-3);
|
||
line-height: 1.6;
|
||
max-width: 280px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* ── STATUS BADGE ── */
|
||
|
||
.status-wrap {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
padding: 7px 15px;
|
||
border-radius: 999px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
border: 1px solid transparent;
|
||
transition: all .35s ease;
|
||
}
|
||
|
||
.status-badge.s-idle {
|
||
background: var(--cream-2);
|
||
border-color: var(--cream-3);
|
||
color: var(--ink-3);
|
||
}
|
||
.status-badge.s-cooking {
|
||
background: var(--amber-lt);
|
||
border-color: rgba(184,92,26,.25);
|
||
color: var(--amber);
|
||
}
|
||
.status-badge.s-partial {
|
||
background: #e8f0fa;
|
||
border-color: rgba(59,100,180,.22);
|
||
color: #2a5cb8;
|
||
}
|
||
.status-badge.s-done {
|
||
background: var(--green-lt);
|
||
border-color: rgba(45,90,61,.22);
|
||
color: var(--green);
|
||
}
|
||
|
||
.status-dot {
|
||
width: 7px; height: 7px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
background: currentColor;
|
||
}
|
||
|
||
.status-dot.pulse { animation: dotpulse 1.6s ease-in-out infinite; }
|
||
|
||
@keyframes dotpulse {
|
||
0%, 100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: .4; transform: scale(.65); }
|
||
}
|
||
|
||
/* ── PROGRESS ── */
|
||
|
||
.progress-section {
|
||
padding: 0 20px;
|
||
margin-bottom: 8px;
|
||
animation: fadeUp .5s .15s ease both;
|
||
}
|
||
|
||
.progress-meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
color: var(--ink-3);
|
||
margin-bottom: 7px;
|
||
}
|
||
|
||
.progress-track {
|
||
height: 4px;
|
||
background: var(--cream-3);
|
||
border-radius: 999px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: var(--green);
|
||
border-radius: 999px;
|
||
transition: width .6s cubic-bezier(.4, 0, .2, 1);
|
||
}
|
||
|
||
/* ── LOADER ── */
|
||
|
||
.loader-section {
|
||
margin: 8px 20px 0;
|
||
padding: 28px 20px;
|
||
border-radius: var(--r);
|
||
border: 1px solid var(--cream-3);
|
||
background: var(--cream-2);
|
||
text-align: center;
|
||
animation: fadeUp .5s .2s ease both;
|
||
}
|
||
|
||
.loader-ring {
|
||
width: 36px; height: 36px;
|
||
margin: 0 auto 14px;
|
||
border-radius: 50%;
|
||
border: 2px solid var(--cream-3);
|
||
border-top-color: var(--gold);
|
||
animation: spin .85s linear infinite;
|
||
}
|
||
|
||
@keyframes spin { to { transform: rotate(360deg); } }
|
||
|
||
.loader-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--ink-2);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.loader-sub {
|
||
font-size: 13px;
|
||
font-weight: 300;
|
||
color: var(--ink-3);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* ── ITEMS CARD ── */
|
||
|
||
.items-card {
|
||
margin: 14px 20px 0;
|
||
border-radius: var(--r);
|
||
border: 1px solid var(--cream-3);
|
||
background: #fff;
|
||
overflow: hidden;
|
||
animation: fadeUp .5s .2s ease both;
|
||
}
|
||
|
||
.items-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 14px 16px 12px;
|
||
border-bottom: 1px solid var(--cream-3);
|
||
}
|
||
|
||
.items-label {
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
color: var(--ink-3);
|
||
text-transform: uppercase;
|
||
letter-spacing: .07em;
|
||
}
|
||
|
||
.items-count {
|
||
font-size: 12px;
|
||
color: var(--ink-3);
|
||
background: var(--cream-2);
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--cream-3);
|
||
}
|
||
|
||
.order-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 13px 16px;
|
||
border-bottom: 1px solid var(--cream-3);
|
||
transition: background .15s;
|
||
}
|
||
|
||
.order-row:last-child { border-bottom: none; }
|
||
.order-row:active { background: var(--cream-2); }
|
||
|
||
.row-dot {
|
||
width: 6px; height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--gold);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.row-name {
|
||
flex: 1;
|
||
font-size: 15px;
|
||
font-weight: 400;
|
||
color: var(--ink);
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.row-qty {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--ink-3);
|
||
background: var(--cream-2);
|
||
border: 1px solid var(--cream-3);
|
||
border-radius: 6px;
|
||
padding: 3px 9px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* ── META TAGS ── */
|
||
|
||
.meta-row {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-wrap: wrap;
|
||
padding: 14px 20px 0;
|
||
animation: fadeUp .5s .3s ease both;
|
||
}
|
||
|
||
.meta-tag {
|
||
font-size: 12px;
|
||
font-weight: 300;
|
||
color: var(--ink-3);
|
||
background: var(--cream-2);
|
||
border: 1px solid var(--cream-3);
|
||
border-radius: 6px;
|
||
padding: 3px 9px;
|
||
}
|
||
|
||
/* ── FOOTER ── */
|
||
|
||
footer {
|
||
margin-top: auto;
|
||
padding: 28px 20px 24px;
|
||
text-align: center;
|
||
animation: fadeUp .5s .35s ease both;
|
||
}
|
||
|
||
.footer-line {
|
||
font-size: 12px;
|
||
font-weight: 300;
|
||
color: var(--cream-3);
|
||
}
|
||
|
||
.footer-divider {
|
||
width: 32px;
|
||
height: 1px;
|
||
background: var(--cream-3);
|
||
margin: 14px auto;
|
||
}
|
||
|
||
.ws-status {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: 11px;
|
||
color: var(--ink-3);
|
||
}
|
||
|
||
.ws-dot {
|
||
width: 6px; height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--ink-3);
|
||
}
|
||
|
||
.ws-dot.connected { background: var(--green); }
|
||
.ws-dot.error { background: var(--amber); }
|
||
|
||
/* ── HELPERS ── */
|
||
|
||
.hidden { display: none !important; }
|
||
|
||
@keyframes fadeUp {
|
||
from { opacity: 0; transform: translateY(14px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
@keyframes fadeDown {
|
||
from { opacity: 0; transform: translateY(-8px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
@keyframes itemIn {
|
||
from { opacity: 0; transform: translateX(-10px); }
|
||
to { opacity: 1; transform: translateX(0); }
|
||
}
|
||
|
||
.order-row { animation: itemIn .35s ease both; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<div class="brand">
|
||
<span class="brand-name">Karczma Biesiada</span>
|
||
<span class="brand-tagline">Status zamówienia</span>
|
||
</div>
|
||
<div class="table-pill" id="tablePill">
|
||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none">
|
||
<rect x="1" y="7" width="14" height="2" rx="1" fill="currentColor"/>
|
||
<rect x="3" y="9" width="2" height="5" rx="1" fill="currentColor"/>
|
||
<rect x="11" y="9" width="2" height="5" rx="1" fill="currentColor"/>
|
||
</svg>
|
||
<span id="tableLabel">Stolik —</span>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- HERO -->
|
||
<div class="hero">
|
||
<div class="hero-icon" id="heroIcon">
|
||
<!-- icon swapped by JS -->
|
||
<svg id="iconIdle" viewBox="0 0 24 24" fill="none" stroke="var(--ink-3)" stroke-width="1.5">
|
||
<circle cx="12" cy="12" r="9"/>
|
||
<path d="M12 7v5l3 2" stroke-linecap="round"/>
|
||
</svg>
|
||
<svg id="iconCooking" class="hidden" viewBox="0 0 24 24" fill="none" stroke="var(--amber)" stroke-width="1.5">
|
||
<path d="M3 12c0-5 2-8 9-8s9 3 9 8" stroke-linecap="round"/>
|
||
<path d="M3 12h18M12 12v6" stroke-linecap="round"/>
|
||
<path d="M9 18h6" stroke-linecap="round"/>
|
||
<path d="M8 5.5c0-1 .5-2 1-2.5M12 5c0-1 .5-2 1-2.5M16 5.5c0-1-.5-2-1-2.5" stroke-linecap="round"/>
|
||
</svg>
|
||
<svg id="iconDone" class="hidden" viewBox="0 0 24 24" fill="none" stroke="var(--green)" stroke-width="1.5">
|
||
<circle cx="12" cy="12" r="9"/>
|
||
<path d="M8 12l3 3 5-5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</div>
|
||
<h1 class="hero-title" id="heroTitle">Sprawdzamy…</h1>
|
||
<p class="hero-sub" id="heroSub">Łączymy się z kuchnią. Chwileczkę.</p>
|
||
<div class="status-wrap">
|
||
<span class="status-badge s-idle" id="statusBadge">
|
||
<span class="status-dot pulse" id="statusDot"></span>
|
||
<span id="statusText">Łączenie</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PROGRESS -->
|
||
<div class="progress-section hidden" id="progressSection">
|
||
<div class="progress-meta">
|
||
<span>Postęp przygotowania</span>
|
||
<span id="progressPct">0%</span>
|
||
</div>
|
||
<div class="progress-track">
|
||
<div class="progress-fill" id="progressFill" style="width:0%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LOADER -->
|
||
<div class="loader-section" id="loaderSection">
|
||
<div class="loader-ring"></div>
|
||
<div class="loader-title">Szukamy Twojego zamówienia</div>
|
||
<div class="loader-sub">Łączymy się z kuchnią.<br>Może chwilkę potrwać.</div>
|
||
</div>
|
||
|
||
<!-- ITEMS -->
|
||
<div class="items-card hidden" id="itemsCard">
|
||
<div class="items-header">
|
||
<span class="items-label">Na rachunku</span>
|
||
<span class="items-count" id="itemsCount">0 pozycji</span>
|
||
</div>
|
||
<div id="itemsList"></div>
|
||
</div>
|
||
|
||
<!-- META -->
|
||
<div class="meta-row hidden" id="metaRow"></div>
|
||
|
||
<footer>
|
||
<div class="footer-divider"></div>
|
||
<div class="ws-status">
|
||
<span class="ws-dot" id="wsDot"></span>
|
||
<span id="wsLabel">Rozłączono</span>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
/* ── HELPERS ── */
|
||
|
||
const $ = id => document.getElementById(id);
|
||
const params = new URLSearchParams(location.search);
|
||
const tableParam = (params.get("table") || "").trim();
|
||
|
||
// Set table label
|
||
$("tableLabel").textContent = tableParam ? `Stolik\u00a0${tableParam}` : "Brak stolika";
|
||
|
||
function parseNum(v) {
|
||
const n = parseFloat(String(v || "0").replace(",", "."));
|
||
return Number.isFinite(n) ? n : 0;
|
||
}
|
||
|
||
function detectTable(bill) {
|
||
const t = `${bill?.Remark || ""} ${bill?.Description || ""}`;
|
||
const m = t.match(/STOLIK\s*([0-9A-Z]+)/i);
|
||
return m ? m[1] : "";
|
||
}
|
||
|
||
function minutesAgo(iso) {
|
||
const diff = Math.max(0, Math.floor((Date.now() - new Date(iso || Date.now())) / 60000));
|
||
if (diff < 1) return "przed chwilą";
|
||
if (diff === 1) return "1 min temu";
|
||
if (diff < 5) return `${diff} min temu`;
|
||
return `${diff} min temu`;
|
||
}
|
||
|
||
/* ── RENDER STATES ── */
|
||
|
||
function setIcon(name) {
|
||
["Idle","Cooking","Done"].forEach(n => $("icon"+n).classList.add("hidden"));
|
||
$("icon"+name).classList.remove("hidden");
|
||
}
|
||
|
||
function setHeroIcon(state) {
|
||
const icon = $("heroIcon");
|
||
icon.style.background = state === "done" ? "var(--green-lt)"
|
||
: state === "cooking" || state === "partial" ? "var(--amber-lt)"
|
||
: "var(--cream-2)";
|
||
icon.style.borderColor = state === "done" ? "rgba(45,90,61,.2)"
|
||
: state === "cooking" || state === "partial" ? "rgba(184,92,26,.2)"
|
||
: "var(--cream-3)";
|
||
if (state === "done") setIcon("Done");
|
||
else if (state === "cooking" || state === "partial") setIcon("Cooking");
|
||
else setIcon("Idle");
|
||
}
|
||
|
||
function setStatus(state, text) {
|
||
const badge = $("statusBadge");
|
||
const dot = $("statusDot");
|
||
badge.className = "status-badge";
|
||
dot.className = "status-dot";
|
||
if (state === "cooking") { badge.classList.add("s-cooking"); dot.classList.add("pulse"); }
|
||
else if (state === "partial") { badge.classList.add("s-partial"); dot.classList.add("pulse"); }
|
||
else if (state === "done") badge.classList.add("s-done");
|
||
else badge.classList.add("s-idle");
|
||
$("statusText").textContent = text;
|
||
}
|
||
|
||
function setProgress(pct) {
|
||
const p = Math.max(0, Math.min(100, pct));
|
||
$("progressFill").style.width = p + "%";
|
||
$("progressPct").textContent = Math.round(p) + "%";
|
||
}
|
||
|
||
function showEl(id, show) {
|
||
$(id).classList.toggle("hidden", !show);
|
||
}
|
||
|
||
/* ── RENDER BILLS ── */
|
||
|
||
function renderBills(bills) {
|
||
// Hide loader
|
||
showEl("loaderSection", false);
|
||
|
||
if (!bills.length) {
|
||
setHeroIcon("idle");
|
||
$("heroTitle").textContent = "Cisza w kuchni";
|
||
$("heroSub").textContent = "Aktualnie nie mamy aktywnego zamówienia dla tego stolika.";
|
||
setStatus("idle", "Brak zamówienia");
|
||
showEl("progressSection", false);
|
||
showEl("itemsCard", false);
|
||
showEl("metaRow", false);
|
||
return;
|
||
}
|
||
|
||
const allArticles = bills.flatMap(b => Array.isArray(b?.Articles) ? b.Articles : []);
|
||
|
||
if (!allArticles.length) {
|
||
setHeroIcon("cooking");
|
||
$("heroTitle").textContent = "Gotujemy dla Ciebie";
|
||
$("heroSub").textContent = "Zamówienie przyjęte — kuchnia właśnie zaczyna.";
|
||
setStatus("cooking", "W trakcie przygotowywania");
|
||
showEl("progressSection", true);
|
||
setProgress(0);
|
||
showEl("itemsCard", false);
|
||
showEl("metaRow", false);
|
||
return;
|
||
}
|
||
|
||
// Group items
|
||
const grouped = new Map();
|
||
let sumTodo = 0, sumDone = 0;
|
||
|
||
for (const a of allArticles) {
|
||
const name = (a.Name || "Pozycja").trim();
|
||
const qty = parseNum(a.QuantityToDo || a.QuantitySet || "1");
|
||
const done = parseNum(a.QuantityDone || "0");
|
||
sumTodo += qty;
|
||
sumDone += done;
|
||
grouped.set(name, (grouped.get(name) || 0) + qty);
|
||
}
|
||
|
||
const progress = sumTodo > 0 ? (sumDone / sumTodo) * 100 : 0;
|
||
|
||
// Render list
|
||
const list = $("itemsList");
|
||
list.innerHTML = "";
|
||
[...grouped.entries()].forEach(([name, qty], i) => {
|
||
const row = document.createElement("div");
|
||
row.className = "order-row";
|
||
row.style.animationDelay = (i * 0.05) + "s";
|
||
row.innerHTML = `
|
||
<span class="row-dot"></span>
|
||
<span class="row-name">${name}</span>
|
||
<span class="row-qty">× ${qty % 1 === 0 ? qty.toFixed(0) : qty.toFixed(2)}</span>
|
||
`;
|
||
list.appendChild(row);
|
||
});
|
||
|
||
const count = grouped.size;
|
||
$("itemsCount").textContent = count + (count === 1 ? " pozycja" : count < 5 ? " pozycje" : " pozycji");
|
||
|
||
// Status
|
||
if (sumDone >= sumTodo && sumTodo > 0) {
|
||
setHeroIcon("done");
|
||
$("heroTitle").textContent = "Smacznego!";
|
||
$("heroSub").textContent = "Wszystko gotowe. Kelner zaraz do Was dotrze.";
|
||
setStatus("done", "Gotowe do podania");
|
||
} else if (sumDone > 0) {
|
||
setHeroIcon("partial");
|
||
$("heroTitle").textContent = "Część już jedzie!";
|
||
$("heroSub").textContent = "Kilka pozycji gotowych — reszta zaraz będzie.";
|
||
setStatus("partial", "Część gotowa do podania");
|
||
} else {
|
||
setHeroIcon("cooking");
|
||
$("heroTitle").textContent = "Gotujemy dla Ciebie";
|
||
$("heroSub").textContent = "Kuchnia pracuje nad Twoim zamówieniem.";
|
||
setStatus("cooking", "W trakcie przygotowywania");
|
||
}
|
||
|
||
showEl("progressSection", true);
|
||
setProgress(progress);
|
||
showEl("itemsCard", true);
|
||
|
||
// Meta
|
||
const sorted = [...bills].sort((a, b) => new Date(a?.Date || 0) - new Date(b?.Date || 0));
|
||
const first = sorted[0];
|
||
const nums = bills.map(b => b?.Description).filter(Boolean).join(", ");
|
||
const metaRow = $("metaRow");
|
||
metaRow.innerHTML = [
|
||
`Stolik ${tableParam}`,
|
||
nums ? `Nr: ${nums}` : null,
|
||
first?.Date ? `Zamówiono ${minutesAgo(first.Date)}` : null,
|
||
first?.Date ? `Godz. ${new Date(first.Date).toLocaleTimeString("pl-PL", {hour:"2-digit", minute:"2-digit"})}` : null,
|
||
].filter(Boolean).map(t => `<span class="meta-tag">${t}</span>`).join("");
|
||
showEl("metaRow", true);
|
||
}
|
||
|
||
/* ── WEBSOCKET ── */
|
||
|
||
function setWS(state) {
|
||
const dot = $("wsDot");
|
||
const lbl = $("wsLabel");
|
||
dot.className = "ws-dot";
|
||
if (state === "connected") { dot.classList.add("connected"); lbl.textContent = "Połączono z kuchnią"; }
|
||
else if (state === "error") { dot.classList.add("error"); lbl.textContent = "Błąd połączenia"; }
|
||
else { lbl.textContent = "Rozłączono"; }
|
||
}
|
||
|
||
function handleMessage(parsed) {
|
||
if (!parsed || parsed.Type !== "bills" || !Array.isArray(parsed.Bills)) return;
|
||
|
||
if (!tableParam) {
|
||
showEl("loaderSection", false);
|
||
$("heroTitle").textContent = "Brak numeru stolika";
|
||
$("heroSub").textContent = "Użyj adresu z parametrem ?table=9";
|
||
setStatus("idle", "Brak stolika");
|
||
return;
|
||
}
|
||
|
||
const norm = tableParam.toLowerCase();
|
||
const matches = parsed.Bills.filter(b => detectTable(b).toLowerCase() === norm)
|
||
.sort((a, b) => new Date(b?.Date || 0) - new Date(a?.Date || 0));
|
||
|
||
renderBills(matches);
|
||
}
|
||
|
||
if (tableParam) {
|
||
const proto = location.protocol === "https:" ? "wss" : "ws";
|
||
const ws = new WebSocket(`${proto}://${location.host}/ws`);
|
||
|
||
ws.onopen = () => setWS("connected");
|
||
|
||
ws.onmessage = evt => {
|
||
let data;
|
||
try { data = JSON.parse(evt.data); } catch { return; }
|
||
if (data.event === "snapshot" || data.event === "message") handleMessage(data.parsed);
|
||
};
|
||
|
||
ws.onerror = () => {
|
||
setWS("error");
|
||
showEl("loaderSection", false);
|
||
$("heroTitle").textContent = "Błąd połączenia";
|
||
$("heroSub").textContent = "Nie udało się połączyć z kuchnią. Odśwież stronę.";
|
||
setStatus("idle", "Błąd");
|
||
};
|
||
|
||
ws.onclose = () => setWS("disconnected");
|
||
|
||
} else {
|
||
// No table param
|
||
showEl("loaderSection", false);
|
||
$("heroTitle").textContent = "Brak numeru stolika";
|
||
$("heroSub").textContent = "Zeskanuj kod QR przy swoim stoliku, aby zobaczyć zamówienie.";
|
||
setStatus("idle", "Brak stolika");
|
||
$("tableLabel").textContent = "Brak stolika";
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|