376 lines
18 KiB
PHP
376 lines
18 KiB
PHP
<?php
|
||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||
header('Pragma: no-cache');
|
||
header('Expires: 0');
|
||
|
||
require_once __DIR__ . '/includes/asset_version.php';
|
||
require_once __DIR__ . '/../api/request_ip.php';
|
||
$menuConfig = require __DIR__ . '/../config/menu.php';
|
||
$publicDir = __DIR__;
|
||
$vCss = publicAssetVersion($publicDir, 'assets/css/app.css');
|
||
$vJs = publicJsBundleVersion($publicDir, 'assets/js/app.js');
|
||
|
||
$languagesForClient = [];
|
||
foreach (($menuConfig['languages'] ?? []) as $code => $meta) {
|
||
$languagesForClient[] = [
|
||
'code' => (string) $code,
|
||
'label' => (string) ($meta['label'] ?? strtoupper((string) $code)),
|
||
'flag' => (string) ($meta['flag'] ?? '🏳️'),
|
||
];
|
||
}
|
||
?><!doctype html>
|
||
<html lang="pl">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||
<meta http-equiv="Pragma" content="no-cache" />
|
||
<!-- build: css=<?= assetVersionAttr($vCss) ?> js=<?= assetVersionAttr($vJs) ?> -->
|
||
<title>Karczma Biesiada – Twoje Zamówienie</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=Plus+Jakarta+Sans:wght@300;400;600;700&family=Playfair+Display:wght@700&display=swap"
|
||
rel="stylesheet">
|
||
<link rel="stylesheet" href="assets/css/app.css?v=<?= assetVersionAttr($vCss) ?>">
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<div id="loadingScreen">
|
||
<div class="loader-icon"></div>
|
||
<div class="loader-text">
|
||
<h2>Karczma Biesiada</h2>
|
||
<div class="loader-msg" id="loaderMsg" data-i18n="loader.connecting">Łączenie z kuchnią...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="geoScreen" class="hidden">
|
||
<div class="geo-shell">
|
||
<div class="geo-lang-row">
|
||
<div id="geoLangPickerMount"></div>
|
||
</div>
|
||
<div class="geo-icon">📍</div>
|
||
<h2 class="geo-title" data-i18n="geo.title">Witamy w Karcznie</h2>
|
||
<p class="geo-lead" id="geoLead">
|
||
Przeglądaj menu od razu — albo potwierdź, że jesteś u nas, aby wezwać kelnera, śledzić zamówienie i poprosić o rachunek.
|
||
</p>
|
||
<p class="geo-status" id="geoMsg"></p>
|
||
|
||
<div class="geo-actions" id="geoActions">
|
||
<button type="button" id="geoMenuOnlyBtn" class="geo-btn geo-btn-menu">
|
||
<span class="geo-btn-main">Przejdź do menu</span>
|
||
<span class="geo-btn-sub">bez lokalizacji</span>
|
||
</button>
|
||
<button type="button" id="geoActionBtn" class="geo-btn geo-btn-locate btn btn-primary">
|
||
<span class="geo-btn-main">Zgoda, sprawdź lokalizację</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="geo-instructions hidden" id="geoInstructions" aria-live="polite"></div>
|
||
|
||
<div class="geo-wifi-callout">
|
||
<p class="geo-wifi-callout-title" data-i18n="geo.wifi.title">📶 Wejdź bez zgody na lokalizację</p>
|
||
<p data-i18n="geo.wifi.connect">Połącz telefon z otwartą siecią Wi‑Fi restauracji:</p>
|
||
<p class="geo-wifi-network"><strong data-i18n="geo.wifi.ssid">HotSpot</strong></p>
|
||
<p class="geo-wifi-open" data-i18n="geo.wifi.open">Sieć publiczna — bez hasła.</p>
|
||
<p data-i18n="geo.wifi.after" data-i18n-html>Po połączeniu <strong>odśwież stronę</strong> (lub zamknij i otwórz ponownie kod QR). Aplikacja wpuści Cię <strong>bez pytania o lokalizację</strong> — z pełnym dostępem do:</p>
|
||
<ul class="geo-wifi-list">
|
||
<li data-i18n="geo.wifi.li1">wezwania kelnera</li>
|
||
<li data-i18n="geo.wifi.li2">prośby o rachunek</li>
|
||
<li data-i18n="geo.wifi.li3">statusu zamówienia</li>
|
||
<li data-i18n="geo.wifi.li4">całej aplikacji przy stoliku</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="app-shell" id="appShell">
|
||
<div class="app-scroll" id="appScroll">
|
||
<div class="container">
|
||
<header id="mainHeader">
|
||
<div class="header-top">
|
||
<h1 class="logo-text">Karczma Biesiada</h1>
|
||
<div id="headerLangPickerMount"></div>
|
||
</div>
|
||
<div id="tableLabel" class="table-badge" data-i18n="table.choose">Wybierz stolik</div>
|
||
</header>
|
||
|
||
<main id="mainContent">
|
||
<div id="statusView" class="view-section active">
|
||
|
||
<section class="status-card">
|
||
<div class="status-header">
|
||
<div>
|
||
<span class="status-title" data-i18n="status.title">Aktualny status</span>
|
||
<div id="prepStatus" class="status-value" data-i18n="status.waiting">Oczekiwanie...</div>
|
||
</div>
|
||
<div id="statusIcon" style="font-size: 28px;">⏳</div>
|
||
</div>
|
||
|
||
<div class="progress-container">
|
||
<div id="progressBar" class="progress-bar"></div>
|
||
</div>
|
||
|
||
<div id="statusMeta" style="font-size: 12px; color: var(--text-muted);" data-i18n="status.checking">
|
||
Sprawdzamy co pysznego się przygotowuje...
|
||
</div>
|
||
</section>
|
||
|
||
<section class="items-container" id="ordersContainer">
|
||
<h3 id="ordersTitle" data-i18n="orders.title">Twoje zamówione dania</h3>
|
||
<div id="emptyState" class="empty-state hidden">
|
||
<div class="empty-icon">📖</div>
|
||
<p style="color: var(--text-muted)" data-i18n="orders.empty">Jeśli właśnie złożyłeś zamówienie, daj nam chwilkę na jego
|
||
przetworzenie.</p>
|
||
<button class="btn btn-primary" style="margin-top: 15px; padding: 12px 20px; font-size: 15px;"
|
||
onclick="switchTab('menu')" data-i18n="orders.browse_menu">Przeglądaj menu</button>
|
||
</div>
|
||
<div id="itemsList"></div>
|
||
</section>
|
||
|
||
<section id="historySection" class="items-container history-section hidden">
|
||
<h3 data-i18n="history.title">Twoje poprzednie zamówienia</h3>
|
||
<p class="history-note" data-i18n="history.note">To są pozycje z innych wizyt, które były widoczne na tym telefonie po zeskanowaniu
|
||
kodów QR. Jeśli kiedyś byłeś w restauracji bez skanowania kodu, tych pozycji tu nie będzie 🙂</p>
|
||
<div id="historyList"></div>
|
||
<div style="text-align: center; margin-top: 15px;">
|
||
<a href="#" onclick="clearGlobalHistory(event)"
|
||
style="font-size: 12px; color: var(--text-muted); text-decoration: underline;" data-i18n="history.clear">Usuń historię</a>
|
||
</div>
|
||
</section>
|
||
|
||
<div id="metaFooter" class="meta-footer"></div>
|
||
</div>
|
||
|
||
<div id="menuView" class="view-section hidden">
|
||
<div id="menuOnlyBanner" class="menu-only-banner is-hidden">
|
||
<span data-i18n="menu.banner">Potwierdź lokalizację, aby wezwać kelnera lub poprosić o rachunek.</span>
|
||
<button type="button" class="menu-only-banner-btn" onclick="promptGeoForFullAccess()" data-i18n="menu.banner_btn">Sprawdź teraz</button>
|
||
</div>
|
||
<div class="menu-search-container">
|
||
<input type="text" id="menuSearchInput" data-i18n-placeholder="menu.search" placeholder="Szukaj dania..." oninput="filterMenu()" />
|
||
<div id="menuLangPickerMount"></div>
|
||
</div>
|
||
|
||
<div class="restaurant-menu-container">
|
||
<nav class="menu-categories-nav">
|
||
<ul>
|
||
<!-- Dynamic categories -->
|
||
</ul>
|
||
</nav>
|
||
|
||
<div class="restaurant-menu-scroll" id="menuContainer">
|
||
<!-- Dynamiczne menu załaduje się tutaj -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<footer class="app-footer">
|
||
<a href="https://magico.pl" target="_blank">© Magico Software</a>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="bottom-nav" id="bottomNav" hidden>
|
||
<div class="nav-item active" onclick="switchTab('status')" id="navStatus">
|
||
<span class="nav-icon">🍽️</span>
|
||
<span class="nav-label" data-i18n="nav.order">Zamówienie</span>
|
||
</div>
|
||
<div class="nav-item" onclick="switchTab('menu')" id="navMenu">
|
||
<span class="nav-icon">📖</span>
|
||
<span class="nav-label" data-i18n="nav.menu">Menu</span>
|
||
</div>
|
||
<div class="nav-item action-call" onclick="openWaiterDialog()" id="navWaiter">
|
||
<span class="nav-icon">🛎️</span>
|
||
<span class="nav-label" data-i18n="nav.waiter">Kelner</span>
|
||
</div>
|
||
<div class="nav-item action-bill" onclick="openBillDialog()" id="navBill">
|
||
<span class="nav-icon">💳</span>
|
||
<span class="nav-label" data-i18n="nav.bill">Rachunek</span>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="waiterModal">
|
||
<div class="modal-content" style="text-align: center;">
|
||
<div style="font-size: 48px; margin-bottom: 15px;">🛎️</div>
|
||
<h3 style="margin-top: 0; color: var(--text-main); font-family: 'Playfair Display', serif; font-size: 24px;" data-i18n="waiter.title">
|
||
Przywołać obsługę?</h3>
|
||
<p style="color: var(--text-muted); font-size: 15px; margin-bottom: 25px; line-height: 1.5;" data-i18n="waiter.body">
|
||
Kelner otrzyma natychmiastowe powiadomienie na swoim panelu i podejdzie do Twojego stolika najszybciej jak to
|
||
możliwe.
|
||
</p>
|
||
<div style="display: flex; gap: 12px; flex-direction: column;">
|
||
<button class="btn btn-primary" onclick="confirmCallWaiter()" style="padding: 14px; font-size: 16px;" data-i18n="waiter.confirm">Tak,
|
||
poproś kelnera</button>
|
||
<button class="btn btn-secondary" onclick="closeWaiterDialog()" style="padding: 14px;" data-i18n="waiter.cancel">Anuluj</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="itemModal">
|
||
<div class="modal-content item-modal-content" id="itemModalContent">
|
||
<button type="button" class="close-btn item-modal-close" onclick="closeItemModal()" data-i18n-aria="menu.close" aria-label="Zamknij">×</button>
|
||
<div class="item-modal-pane" id="itemModalPane">
|
||
<div class="item-modal-image-wrap">
|
||
<img id="itemModalImage" class="item-modal-image" src="" alt="">
|
||
<div id="itemModalImagePlaceholder" class="menu-image-placeholder" aria-hidden="true">
|
||
<svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
|
||
<circle cx="12" cy="13" r="4"/>
|
||
</svg>
|
||
<span data-i18n="menu.no_image">Brak zdjęcia</span>
|
||
</div>
|
||
</div>
|
||
<div class="item-modal-body">
|
||
<div class="item-modal-meta">
|
||
<span id="itemModalCounter" class="item-modal-counter"></span>
|
||
</div>
|
||
<h3 id="itemModalTitle" class="item-modal-title"></h3>
|
||
<div id="itemModalPrice" class="item-modal-price"></div>
|
||
<p id="itemModalDesc" class="item-modal-desc"></p>
|
||
<button type="button" class="btn btn-secondary item-modal-close-btn" onclick="closeItemModal()" data-i18n="menu.close">Zamknij</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="billModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3 id="modalTitle" data-i18n="bill.title">Rozliczenie</h3>
|
||
<button class="close-btn" onclick="closeBillDialog()">×</button>
|
||
</div>
|
||
|
||
<div class="step active" id="stepBillList">
|
||
<div id="billLoading" style="text-align:center; padding: 20px;" data-i18n="bill.loading">
|
||
⏳ Pobieranie rachunków...
|
||
</div>
|
||
<div id="billListContainer" class="hidden">
|
||
<p style="margin-top:0; color:var(--text-muted); font-size:14px; margin-bottom: 20px;" data-i18n="bill.multi">Mamy kilka otwartych
|
||
rachunków na tym stoliku. Który chcesz opłacić?</p>
|
||
<div id="billListItems" style="display:flex; flex-direction:column; gap:10px;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="step" id="stepBillReview">
|
||
<p style="margin-top:0; color:var(--text-muted); font-size:14px; margin-bottom: 20px;" data-i18n="bill.review">Podsumowanie rachunku:
|
||
</p>
|
||
<div id="billReviewContent"
|
||
style="background: var(--surface-light); padding: 15px; border-radius: 12px; max-height: 40vh; overflow-y: auto; margin-bottom: 15px;">
|
||
</div>
|
||
<div style="display:flex; justify-content:space-between; font-weight:700; font-size:18px; margin-bottom: 20px;">
|
||
<span data-i18n="bill.total">Do zapłaty:</span>
|
||
<span id="billTotalAmount" style="color:var(--primary);">0.00 PLN</span>
|
||
</div>
|
||
<div style="display:flex; gap:12px;">
|
||
<button class="btn btn-secondary" style="flex:1;" onclick="goBackToBillList()"
|
||
id="btnBackToBills" data-i18n="bill.back">Wróć</button>
|
||
<button class="btn btn-primary" style="flex:2;" onclick="proceedToBillPayment()" data-i18n="bill.request">Poproś rachunek</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="step" id="stepPayment">
|
||
<p style="margin-top:0; color:var(--text-muted); font-size:14px; margin-bottom: 20px;" data-i18n="bill.payment">Wybierz preferowaną formę
|
||
płatności:</p>
|
||
<div class="option-grid">
|
||
<div class="option-card" onclick="selectPayment('karta')">
|
||
<span class="option-icon">💳</span>
|
||
<span class="option-label" data-i18n="bill.card">Karta</span>
|
||
</div>
|
||
<div class="option-card" onclick="selectPayment('gotówka')">
|
||
<span class="option-icon">💵</span>
|
||
<span class="option-label" data-i18n="bill.cash">Gotówka</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-secondary" onclick="goToStep('stepBillReview')" style="margin-top: 15px;" data-i18n="bill.back_summary">Wróć do
|
||
podsumowania</button>
|
||
</div>
|
||
|
||
<div class="step" id="stepDocument">
|
||
<p style="margin-top:0; color:var(--text-muted); font-size:14px; margin-bottom: 20px;" data-i18n="bill.doc">Jakiego dokumentu
|
||
potrzebujesz?</p>
|
||
<div class="option-grid">
|
||
<div class="option-card" onclick="selectDocument('paragon')">
|
||
<span class="option-icon">🧾</span>
|
||
<span class="option-label" data-i18n="bill.receipt">Paragon</span>
|
||
</div>
|
||
<div class="option-card" onclick="selectDocument('faktura')">
|
||
<span class="option-icon">📄</span>
|
||
<span class="option-label" data-i18n="bill.invoice">Faktura</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-secondary" onclick="goToStep('stepPayment')" style="margin-top: 8px;" data-i18n="bill.back">Wróć</button>
|
||
</div>
|
||
|
||
<div class="step" id="stepNIP">
|
||
<p style="margin-top:0; color:var(--text-muted); font-size:14px; margin-bottom: 20px;" data-i18n="bill.nip_intro">Wprowadź NIP firmy,
|
||
abyśmy mogli automatycznie pobrać dane.</p>
|
||
<div class="input-group">
|
||
<label class="input-label" data-i18n="bill.nip_label">Numer NIP</label>
|
||
<input type="number" id="nipInput" class="input-field" placeholder="np. 1234567890" autocomplete="off" />
|
||
</div>
|
||
<div style="display:flex; gap:12px; margin-top: 24px;">
|
||
<button class="btn btn-secondary" style="flex:1;" onclick="goToStep('stepDocument')" data-i18n="bill.back">Wróć</button>
|
||
<button class="btn btn-primary" style="flex:2;" onclick="fetchGUS()" id="btnGUS" data-i18n="bill.gus">Pobierz z GUS</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="step" id="stepVerify">
|
||
<p style="margin-top:0; color:var(--text-muted); font-size:14px; margin-bottom: 20px;" data-i18n="bill.verify">Czy poniższe dane do
|
||
faktury są prawidłowe?</p>
|
||
|
||
<div class="company-details">
|
||
<input type="text" id="cmpName" class="company-input" style="font-weight:700; margin-bottom:4px;" readonly />
|
||
<input type="text" id="cmpStreet" class="company-input" data-i18n-placeholder="bill.street" placeholder="Ulica i numer" style="margin-bottom:4px;"
|
||
readonly />
|
||
<div style="display: flex; gap: 8px; margin-bottom: 4px;">
|
||
<input type="text" id="cmpZip" class="company-input" data-i18n-placeholder="bill.zip" placeholder="Kod" style="flex: 1;" readonly />
|
||
<input type="text" id="cmpCity" class="company-input" data-i18n-placeholder="bill.city" placeholder="Miasto" style="flex: 2;" readonly />
|
||
</div>
|
||
<input type="text" id="cmpNip" class="company-input muted" readonly />
|
||
</div>
|
||
|
||
<div style="display:flex; gap:12px; flex-direction:column;">
|
||
<button class="btn btn-primary" onclick="confirmInvoice()" data-i18n="bill.confirm_invoice">Tak, poproszę fakturę!</button>
|
||
<div style="display:flex; gap:12px;">
|
||
<button class="btn btn-secondary" onclick="goToStep('stepNIP')" data-i18n="bill.change_nip">Zmień NIP</button>
|
||
<button class="btn btn-secondary" onclick="editCompanyData()" id="btnEditCompany" data-i18n="bill.edit_company">Popraw ręcznie</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toastMsg">
|
||
<span style="font-size:20px;">✓</span> <span id="toastText" data-i18n="toast.sent">Wysłano!</span>
|
||
</div>
|
||
|
||
<script>
|
||
window.APP_JS_VERSION = <?= json_encode($vJs, JSON_UNESCAPED_UNICODE) ?>;
|
||
window.APP_CONFIG = <?= json_encode([
|
||
'geoBypassHosts' => getGeoBypassTrustedHosts(),
|
||
'languages' => $languagesForClient,
|
||
'endpoints' => [
|
||
'analytics' => '../api/analytics.php',
|
||
'guestActionQueue' => '../api/guest_action_queue.php',
|
||
'geoBypass' => '../api/geo_bypass.php',
|
||
'gusLookup' => '../api/gus_lookup.php',
|
||
'kds' => '../api/kds.php',
|
||
'bills' => '../api/bills.php',
|
||
'menu' => '../api/menu.php',
|
||
],
|
||
'loaderMinMs' => 10000,
|
||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||
</script>
|
||
<script type="module" src="assets/js/app.js?v=<?= assetVersionAttr($vJs) ?>"></script>
|
||
</body>
|
||
|
||
</html>
|