Files
karczma-aplikacja-stoliki/public/app.php
T

376 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 siecią Wi‑Fi restauracji:</p>
<p class="geo-wifi-network"><strong>HotSpot Karczmy</strong></p>
<p class="geo-wifi-password">Hasło: <strong>karczmabiesiada</strong></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">&copy; 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">&times;</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()">&times;</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>