window.kitchenAnimations = [ `
🔥
🍳
`, `
🐓
👨‍🍳
`, `
🐓
🍲
`, `
👨‍🍳
🍕
`, `
🐷
💦
🍴
` ]; window.selectedAnimationHtml = null; const params = new URLSearchParams(location.search); let hashParam = (params.get("h") || "").trim(); // Jeśli brak hasha w URL – zapytaj użytkownika (np. do testów) if (!hashParam) { const input = prompt("Podaj bezpieczny hash stolika (wymagane):"); const trimmed = (input || "").trim(); if (trimmed) { const newUrl = new URL(location.href); newUrl.searchParams.set("h", trimmed); location.replace(newUrl.toString()); } } let tableParam = ""; // Puste, zostanie uzupełnione przez backend // USER PROFILE LOGIC const userProfileKey = "karczma_user_profile"; const USER_PROFILE_EXPIRE_MS = 180 * 24 * 60 * 60 * 1000; // ~6 months function initUserProfile() { return; // Funkcja tymczasowo wyłączona try { const raw = localStorage.getItem(userProfileKey); let profile = null; if (raw) { profile = JSON.parse(raw); } const now = Date.now(); // Check if profile exists and is valid if (profile) { if (profile.declined) { // User declined in the past, don't ask again. return; } // If expired, maybe we want to ask again or we could just keep it if they didn't decline. // The user said: "trzymamy przez wiele miesięcy (odnawiamy datę przy każdej wizycie)" if (now - profile.lastVisit > USER_PROFILE_EXPIRE_MS) { // Profile expired. Ask again. showNameDialog(); } else { // Profile valid, renew date and show greeting profile.lastVisit = now; localStorage.setItem(userProfileKey, JSON.stringify(profile)); showGreeting(profile.name, profile.firstVisit || profile.lastVisit); } } else { // No profile, ask for name showNameDialog(); } } catch (err) { // If error parsing, ask again showNameDialog(); } } function showNameDialog() { const modal = document.getElementById("nameModal"); if (modal) { modal.classList.add("active"); document.body.style.overflow = 'hidden'; } } function hideNameDialog() { const modal = document.getElementById("nameModal"); if (modal) { modal.classList.remove("active"); document.body.style.overflow = ''; } } window.saveUserName = function () { const input = document.getElementById("userNameInput").value.trim(); if (input) { const now = Date.now(); const profile = { name: input, firstVisit: now, lastVisit: now, declined: false }; localStorage.setItem(userProfileKey, JSON.stringify(profile)); hideNameDialog(); showGreeting(profile.name, profile.firstVisit); } }; window.declineUserName = function () { const profile = { name: null, firstVisit: Date.now(), lastVisit: Date.now(), declined: true }; localStorage.setItem(userProfileKey, JSON.stringify(profile)); hideNameDialog(); }; function showGreeting(name, firstVisitTime) { const banner = document.getElementById("greetingBanner"); if (banner && name) { const isToday = new Date(firstVisitTime).toDateString() === new Date().toDateString(); if (isToday) { banner.innerHTML = `Cześć ${name}, życzymy pysznego posiłku!`; } else { banner.innerHTML = `Witaj ${name}, super że do nas wracasz!`; } banner.style.display = "block"; } } // Call init is now delayed until geolocation succeeds // initUserProfile(); // UI Elements const loadingScreen = document.getElementById("loadingScreen"); const loaderMsg = document.getElementById("loaderMsg"); const tableLabel = document.getElementById("tableLabel"); const prepStatus = document.getElementById("prepStatus"); const progressBar = document.getElementById("progressBar"); const statusMeta = document.getElementById("statusMeta"); const itemsList = document.getElementById("itemsList"); const emptyState = document.getElementById("emptyState"); const metaFooter = document.getElementById("metaFooter"); const statusIcon = document.getElementById("statusIcon"); const storageKey = `stolik2_state_${(tableParam || "unknown").toLowerCase()}`; const historyKey = "stolik2_global_history"; const historySection = document.getElementById("historySection"); const historyList = document.getElementById("historyList"); const SIX_MONTHS_MS = 180 * 24 * 60 * 60 * 1000; const HOT_WINDOW_MS = 5 * 60 * 60 * 1000; // Dynamic Loader Messages const LOADER_MIN_MS = 10_000; const loadStartTime = Date.now(); const msgs = ["Rozgrzewamy piece...", "Szef kuchni sprawdza składniki...", "Łączenie z sercem restauracji...", "Prawie gotowe..."]; let msgIdx = 0; const msgInterval = setInterval(() => { msgIdx = (msgIdx + 1) % msgs.length; loaderMsg.textContent = msgs[msgIdx]; }, 4000); // Initial State if (tableParam) { tableLabel.textContent = tableParam.toUpperCase().startsWith("STOLIK") ? tableParam : `Stolik ${tableParam}`; } function hideLoader() { const elapsed = Date.now() - loadStartTime; const remaining = Math.max(0, LOADER_MIN_MS - elapsed); setTimeout(() => { loadingScreen.classList.add("hidden"); clearInterval(msgInterval); const bottomNav = document.getElementById("bottomNav"); if (bottomNav) { bottomNav.style.display = ""; } }, remaining); } function updateUI(bills) { // Hide loader after minimum display time hideLoader(); const allArticles = bills.flatMap(b => Array.isArray(b?.Articles) ? b.Articles : []); const items = mergeWithPersistedItems(allArticles); renderGlobalHistory(); if (items.length === 0) { showEmptyState(); return; } renderItems(items); updateStatus(bills, items); } function detectTableCandidates(bill) { const remark = String(bill?.Remark || ""); const description = String(bill?.Description || "").trim(); // 1) klasyczny format: "STOLIK 9", "STOLIK 11A" const stolikMatch = remark.match(/STOLIK\s*([0-9A-Z]+)/i); const fromRemark = stolikMatch ? stolikMatch[1].toLowerCase() : ""; // 2) czasem numer bywa w samym Description const fromDescription = description.toLowerCase(); return { fromRemark, fromDescription, remark: remark.toLowerCase() }; } function showEmptyState() { const title = document.getElementById("ordersTitle"); if (title) title.classList.add("hidden"); emptyState.classList.remove("hidden"); itemsList.innerHTML = ""; prepStatus.textContent = "Brak aktywnych zamówień"; statusIcon.textContent = "🍃"; progressBar.style.width = "0%"; statusMeta.textContent = "Zapraszamy do sprawdzenia naszego menu."; // Historia może istnieć nawet gdy brak bieżących pozycji renderGlobalHistory(); } function normalizeArticleName(rawName) { const name = String(rawName || "Pozycja"); // Usuwa gramatury typu: "300G", "250 G", "500/200/150G". const withoutWeight = name.replace( /\b\d+(?:[.,]\d+)?(?:\s*\/\s*\d+(?:[.,]\d+)?)*\s*[gG]\b/g, "" ); return withoutWeight .replace(/\s{2,}/g, " ") .replace(/\s+([,.;:!?])/g, "$1") .trim() || "Pozycja"; } function loadPersistedItems() { try { const raw = localStorage.getItem(storageKey); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } function savePersistedItems(items) { try { localStorage.setItem(storageKey, JSON.stringify(items)); } catch { // brak miejsca/tryb prywatny } } function loadGlobalHistory() { try { const raw = localStorage.getItem(historyKey); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } function saveGlobalHistory(entries) { const now = Date.now(); const cleaned = entries .filter((e) => e && e.name && Number.isFinite(e.archivedAt)) .filter((e) => (now - e.archivedAt) <= SIX_MONTHS_MS); try { localStorage.setItem(historyKey, JSON.stringify(cleaned)); } catch { // ignore storage errors } } function addItemsToGlobalHistory(items, sourceTable) { if (!items.length) return; const now = Date.now(); const existing = loadGlobalHistory(); // zapobiegamy dokładnym duplikatom (ta sama nazwa + qty + stół blisko czasu) const dedupWindowMs = 2 * 60 * 1000; const toAdd = items.filter((item) => { return !existing.some((h) => h.name === item.name && Number(h.qty) === Number(item.qty) && String(h.sourceTable || "") === String(sourceTable || "") && Math.abs((h.archivedAt || 0) - now) <= dedupWindowMs ); }).map((item) => ({ name: item.name, qty: item.qty, sourceTable: sourceTable || "?", archivedAt: now })); if (!toAdd.length) return; saveGlobalHistory([...existing, ...toAdd]); } function renderGlobalHistory() { const now = Date.now(); const history = loadGlobalHistory() .filter((e) => (now - (e.archivedAt || 0)) <= SIX_MONTHS_MS) .sort((a, b) => (b.archivedAt || 0) - (a.archivedAt || 0)); saveGlobalHistory(history); if (!history.length) { historySection.classList.add("hidden"); historyList.innerHTML = ""; return; } historySection.classList.remove("hidden"); historyList.innerHTML = ""; history.forEach((entry) => { const dt = new Date(entry.archivedAt || Date.now()); const div = document.createElement("div"); div.className = "item-card archived ready"; div.innerHTML = `
${entry.name} Stolik ${entry.sourceTable || "?"} • ${dt.toLocaleDateString("pl-PL")} ${dt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
x${entry.qty}
`; historyList.appendChild(div); }); } window.clearGlobalHistory = function (e) { if (e) e.preventDefault(); if (confirm("Czy na pewno chcesz usunąć historię swoich poprzednich zamówień?")) { localStorage.removeItem(historyKey); renderGlobalHistory(); } }; function mergeWithPersistedItems(articles) { const current = new Map(); articles.forEach(a => { const name = normalizeArticleName(a.Name); const todo = parseFloat(String(a.QuantityToDo || a.QuantitySet || "0").replace(",", ".")); const done = parseFloat(String(a.QuantityDone || "0").replace(",", ".")); if (!current.has(name)) { current.set(name, { name, qty: 0, done: 0, present: true, completedByDisappear: false }); } const curr = current.get(name); curr.qty += Number.isFinite(todo) ? todo : 0; curr.done += Number.isFinite(done) ? done : 0; }); const persisted = loadPersistedItems(); const persistedMap = new Map(persisted.map(i => [i.name, i])); const merged = []; // Aktualnie obecne pozycje z WS current.forEach((item) => { merged.push({ ...item, present: true, completedByDisappear: false, updatedAt: Date.now() }); }); // Pozycje, które były wcześniej, ale zniknęły z API -> przesyłamy od razu do historii globalnej persistedMap.forEach((oldItem, name) => { if (current.has(name)) return; const qty = Number.isFinite(oldItem?.qty) ? oldItem.qty : 0; // Zniknęło z bieżącego rachunku (np. rachunek został zamknięty), od razu leci do osobnego bloku historii! addItemsToGlobalHistory([{ name, qty }], tableParam); }); // Aktywne na górze, gotowe (zniknięte) na dole merged.sort((a, b) => { if (a.present !== b.present) return a.present ? -1 : 1; return a.name.localeCompare(b.name, "pl"); }); savePersistedItems(merged); return merged; } function renderItems(items) { const title = document.getElementById("ordersTitle"); if (title) title.classList.remove("hidden"); emptyState.classList.add("hidden"); itemsList.innerHTML = ""; items.forEach((item) => { const isReady = item.done >= item.qty && item.qty > 0; const div = document.createElement("div"); div.className = `item-card ${isReady ? 'ready' : ''} ${item.present ? '' : 'archived'}`; let meta = "🔥 W przygotowaniu"; if (isReady && item.completedByDisappear) { meta = "✅ Gotowe (zrealizowane)"; } else if (isReady) { meta = "✅ Gotowe"; } div.innerHTML = `
${item.name} ${meta}
x${item.qty}
`; itemsList.appendChild(div); }); renderGlobalHistory(); } function updateStatus(bills, items) { let total = 0; let done = 0; items.forEach(i => { total += Number.isFinite(i.qty) ? i.qty : 0; done += Number.isFinite(i.done) ? i.done : 0; }); const pct = total > 0 ? (done / total) * 100 : 0; progressBar.style.width = `${pct}%`; if (pct >= 100) { prepStatus.textContent = "Gotowe do podania!"; statusIcon.innerHTML = "😋"; statusMeta.textContent = "Wszystkie Twoje dania opuściły już kuchnię."; } else if (pct > 0) { prepStatus.textContent = "Częściowo gotowe"; statusIcon.innerHTML = "🍳"; statusMeta.textContent = "Pierwsze pyszności już na Ciebie czekają!"; } else { prepStatus.textContent = "W przygotowaniu"; if (!window.selectedAnimationHtml) { window.selectedAnimationHtml = window.kitchenAnimations[Math.floor(Math.random() * window.kitchenAnimations.length)]; } statusIcon.innerHTML = window.selectedAnimationHtml; statusMeta.textContent = "Twoje zamówienie jest właśnie tworzone przez naszych kucharzy."; } // Footer meta const newest = [...bills].sort((a, b) => new Date(b?.Date || 0) - new Date(a?.Date || 0))[0]; const time = newest?.Date ? new Date(newest.Date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : "--:--"; metaFooter.textContent = `Zamówienie złożone o godzinie ${time} • Stolik ${tableParam}`; } // API Fetch Logic async function fetchOrders() { try { if (!hashParam) { updateUI([]); return; } const response = await fetch(`../api/kds.php?h=${encodeURIComponent(hashParam)}`); const result = await response.json(); if (result.status === 'success') { if (result.tableName && result.tableName !== '') { tableLabel.textContent = result.tableName.toUpperCase().startsWith("STOLIK") ? result.tableName : `Stolik ${result.tableName}`; tableParam = result.tableName; // Aktualizacja do właściwej nazwy na poczet innych zapytań } // API teraz samo filtruje i zwraca tylko to co nas interesuje (za pomocą mocnego wyrażenia regularnego) const matches = result.data; // Grupowanie składników w główne dania const groups = {}; matches.forEach(item => { const groupId = item.GrupaZestawuID || item.PozycjaID; if (!groups[groupId]) { groups[groupId] = { Name: item.NazwaZestawu || item.NazwaTowaru, QuantitySet: item.GrupaZestawuID ? 1 : parseFloat(item.Ilosc), Done: 0 }; } // StatusRealizacji >= 2 oznacza, że kucharz wcisnął "Gotowe" na swoim ekranie if (parseInt(item.StatusRealizacji, 10) >= 2) { groups[groupId].Done = groups[groupId].QuantitySet; } }); const transformedArticles = Object.values(groups).map(g => ({ Name: g.Name, QuantitySet: g.QuantitySet, QuantityDone: g.Done })); // Najnowszy czas dodania (do pokazania w stopce) const latestDate = matches.length > 0 ? matches.sort((a, b) => new Date(b.DataDodania) - new Date(a.DataDodania))[0].DataDodania : null; // Przekazanie do dotychczasowej logiki aktualizującej UI (w odpowiednim formacie) updateUI([{ Articles: transformedArticles, Date: latestDate }]); } else { loaderMsg.textContent = "Błąd API: " + result.message; } } catch (err) { loaderMsg.textContent = "Problem z połączeniem. Próbujemy ponownie..."; } } // fetchOrders(); // setInterval(fetchOrders, 10000); // --- CALL WAITER LOGIC --- let billState = { payment: '', doc: '', nip: '', company: null }; function showToast(msg) { const t = document.getElementById("toastMsg"); document.getElementById("toastText").textContent = msg; t.classList.remove("active"); void t.offsetWidth; // trigger reflow t.classList.add("active"); setTimeout(() => t.classList.remove("active"), 3500); } function sendApiSimulated(actionName, details) { console.log(`[SYMULACJA API] Akcja: ${actionName}`, details); // Przykładowe wysłanie docelowo: // if (window.socket && window.socket.readyState === WebSocket.OPEN) { // window.socket.send(JSON.stringify({ action: "sendUpstream", payload: { type: actionName, table: tableParam, ...details } })); // } } window.callWaiter = function (type) { if (type === 'order') { sendApiSimulated("CallWaiter_Order", { table: tableParam }); showToast("Kelner wkrótce do Ciebie podejdzie!"); } }; window.openWaiterDialog = function () { document.getElementById("waiterModal").classList.add("active"); document.body.style.overflow = 'hidden'; }; window.closeWaiterDialog = function () { document.getElementById("waiterModal").classList.remove("active"); document.body.style.overflow = ''; }; window.confirmCallWaiter = function () { closeWaiterDialog(); callWaiter('order'); }; window.openBillDialog = async function () { billState = { payment: '', doc: '', nip: '', company: null, selectedBillId: null }; document.getElementById("billModal").classList.add("active"); document.body.style.overflow = 'hidden'; // Zablokuj scroll tła document.getElementById("billLoading").classList.remove("hidden"); document.getElementById("billListContainer").classList.add("hidden"); goToStep("stepBillList"); try { const res = await fetch(`../api/bills.php?h=${encodeURIComponent(hashParam)}`); const result = await res.json(); if (result.status === 'success' && result.data.length > 0) { const bills = result.data; if (bills.length === 1) { showBillReview(bills[0]); document.getElementById("btnBackToBills").style.display = 'none'; } else { renderBillList(bills); document.getElementById("btnBackToBills").style.display = 'block'; } } else { document.getElementById("billLoading").innerHTML = "Brak otwartych rachunków do opłacenia."; } } catch (err) { document.getElementById("billLoading").innerHTML = "Błąd pobierania rachunków."; } }; function renderBillList(bills) { document.getElementById("billLoading").classList.add("hidden"); document.getElementById("billListContainer").classList.remove("hidden"); const container = document.getElementById("billListItems"); container.innerHTML = ""; bills.forEach(b => { const div = document.createElement("div"); div.className = "option-card"; div.style.flexDirection = "row"; div.style.justifyContent = "space-between"; div.style.padding = "15px"; div.onclick = () => showBillReview(b); const numerFormat = b.numer ? `#${b.numer}` : "Rachunek"; div.innerHTML = `
${numerFormat}
${b.opis}
${b.suma.toFixed(2)} PLN
`; container.appendChild(div); }); } window.goBackToBillList = function () { goToStep("stepBillList"); }; window.showBillReview = function (bill) { billState.selectedBillId = bill.id; const content = document.getElementById("billReviewContent"); content.innerHTML = ""; bill.pozycje.forEach(p => { const div = document.createElement("div"); div.style.display = "flex"; div.style.justifyContent = "space-between"; div.style.marginBottom = "8px"; div.style.borderBottom = "1px solid rgba(255,255,255,0.05)"; div.style.paddingBottom = "8px"; div.innerHTML = `
${p.nazwa}
${p.ilosc} x ${p.cena.toFixed(2)} PLN
${p.wartosc.toFixed(2)} PLN
`; content.appendChild(div); }); document.getElementById("billTotalAmount").textContent = bill.suma.toFixed(2) + " PLN"; goToStep("stepBillReview"); }; window.closeBillDialog = function () { document.getElementById("billModal").classList.remove("active"); document.body.style.overflow = ''; // Odblokuj scroll tła }; window.goToStep = function (stepId) { document.querySelectorAll('.step').forEach(el => el.classList.remove('active')); document.getElementById(stepId).classList.add('active'); }; // --- SPA NAVIGATION LOGIC --- window.switchTab = function (tabName) { // 1. Zdejmij .active z widoków i ikonek nav document.querySelectorAll('.view-section').forEach(el => el.classList.add('hidden')); document.querySelectorAll('.view-section').forEach(el => el.classList.remove('active')); document.getElementById('navStatus').classList.remove('active'); document.getElementById('navMenu').classList.remove('active'); const header = document.getElementById('mainHeader'); const greetingBanner = document.getElementById('greetingBanner'); // 2. Nadaj .active wybranym elementom if (tabName === 'status') { const view = document.getElementById('statusView'); view.classList.remove('hidden'); view.classList.add('active'); document.getElementById('navStatus').classList.add('active'); if (header) header.style.display = ''; // Show greeting banner only if it was rendered initially or has content if (greetingBanner && greetingBanner.innerHTML.trim() !== '') greetingBanner.style.display = ''; } else if (tabName === 'menu') { const view = document.getElementById('menuView'); view.classList.remove('hidden'); view.classList.add('active'); document.getElementById('navMenu').classList.add('active'); if (header) header.style.display = 'none'; if (greetingBanner) greetingBanner.style.display = 'none'; } window.scrollTo(0, 0); }; // --- MENU LOGIC --- window.filterMenu = function () { const query = document.getElementById('menuSearchInput').value.toLowerCase(); const categories = document.querySelectorAll('.rm-category'); categories.forEach(category => { let hasVisibleItems = false; const items = category.querySelectorAll('.rmc-position'); items.forEach(item => { const title = item.querySelector('.rmc-title h4').textContent.toLowerCase(); if (title.includes(query)) { item.style.display = ''; hasVisibleItems = true; } else { item.style.display = 'none'; } }); if (hasVisibleItems) { category.style.display = ''; } else { category.style.display = 'none'; } }); }; window.showCategory = function (categoryId) { document.querySelectorAll('.menu-categories-nav a').forEach(a => a.classList.remove('active')); const clickedLink = document.querySelector(`.menu-categories-nav a[data-category-badge="${categoryId}"]`); if (clickedLink) { clickedLink.classList.add('active'); clickedLink.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } const categories = document.querySelectorAll('.rm-category'); categories.forEach(cat => { if (categoryId === 0) { cat.style.display = ''; } else { const catId = parseInt(cat.getAttribute('data-cat-id'), 10); if (catId === categoryId) { cat.style.display = ''; } else { cat.style.display = 'none'; } } }); }; window.selectPayment = function (method) { billState.payment = method; goToStep("stepDocument"); }; window.selectDocument = function (docType) { billState.doc = docType; if (docType === 'paragon') { closeBillDialog(); sendApiSimulated("CallWaiter_Bill", { table: tableParam, billId: billState.selectedBillId, payment: billState.payment, doc: 'paragon' }); showToast("Kelner przyniesie paragon do opłacenia!"); } else { goToStep("stepNIP"); document.getElementById("nipInput").value = ''; setTimeout(() => document.getElementById("nipInput").focus(), 100); } }; window.fetchGUS = async function () { const nip = document.getElementById("nipInput").value.replace(/[\s-]/g, ''); if (nip.length < 10) { alert("Wprowadź poprawny numer NIP."); return; } const btn = document.getElementById("btnGUS"); btn.textContent = "Szukam..."; btn.disabled = true; try { const apiKey = "d8bdc252-e0f1-4863-97a1-826faddbc49c"; const response = await fetch(`https://api.magico.pro/v1/gus/${nip}?api_key=${apiKey}`); const result = await response.json(); if (result.status && result.data) { const data = result.data; let fullStreet = data.street || ''; if (data.propertyNumber) fullStreet += ' ' + data.propertyNumber; if (data.apartmentNumber) fullStreet += '/' + data.apartmentNumber; billState.nip = nip; billState.company = { name: data.name, street: fullStreet.trim(), zip: data.zipCode, city: data.city, nip: data.nip }; document.getElementById("cmpName").value = billState.company.name; document.getElementById("cmpStreet").value = billState.company.street; document.getElementById("cmpZip").value = billState.company.zip; document.getElementById("cmpCity").value = billState.company.city; document.getElementById("cmpNip").value = "NIP: " + billState.company.nip; // reset do readonly document.getElementById("cmpName").readOnly = true; document.getElementById("cmpStreet").readOnly = true; document.getElementById("cmpZip").readOnly = true; document.getElementById("cmpCity").readOnly = true; document.getElementById("btnEditCompany").textContent = "Popraw ręcznie"; goToStep("stepVerify"); } else { alert("Nie udało się pobrać danych z GUS dla podanego NIP-u."); } } catch (error) { console.error("Błąd pobierania danych z GUS:", error); alert("Błąd połączenia z API GUS."); } finally { btn.textContent = "Pobierz z GUS"; btn.disabled = false; } }; // --- DYNAMIC MENU LOADING --- async function loadMenu() { try { const response = await fetch('menu.json'); if (!response.ok) throw new Error('Nie udało się załadować menu'); const menuData = await response.json(); window.menuDataRaw = menuData; const container = document.getElementById('menuContainer'); if (!container) return; container.innerHTML = ''; menuData.forEach(category => { const catId = category.items.length > 0 ? category.items[0].categoryId : ''; const catDiv = document.createElement('div'); catDiv.className = 'rm-category'; catDiv.setAttribute('data-cat-id', catId); let html = `
${category.categoryName}
`; category.items.forEach(item => { html += `

${item.title}${item.description}

${item.price}
`; }); html += `
`; catDiv.innerHTML = html; container.appendChild(catDiv); }); } catch (err) { console.error('Błąd ładowania menu:', err); const container = document.getElementById('menuContainer'); if (container) { container.innerHTML = '

Nie udało się załadować menu.

'; } } } // Inicjalizacja ładowania menu loadMenu(); window.openItemModal = function(categoryId, position) { if (!window.menuDataRaw) return; let foundItem = null; for (const cat of window.menuDataRaw) { for (const item of cat.items) { if (item.categoryId == categoryId && item.position == position) { foundItem = item; break; } } if (foundItem) break; } if (foundItem) { document.getElementById('itemModalImage').src = foundItem.image; document.getElementById('itemModalTitle').textContent = foundItem.title; document.getElementById('itemModalDesc').textContent = foundItem.description; document.getElementById('itemModalPrice').textContent = foundItem.price; const modal = document.getElementById('itemModal'); if (modal) { modal.classList.add('active'); document.body.style.overflow = 'hidden'; } } }; window.closeItemModal = function() { const modal = document.getElementById('itemModal'); if (modal) { modal.classList.remove('active'); document.body.style.overflow = ''; } }; window.editCompanyData = function () { const n = document.getElementById("cmpName"); const s = document.getElementById("cmpStreet"); const z = document.getElementById("cmpZip"); const c = document.getElementById("cmpCity"); const btn = document.getElementById("btnEditCompany"); if (n.readOnly) { n.readOnly = false; s.readOnly = false; z.readOnly = false; c.readOnly = false; n.focus(); btn.textContent = "Zakończ edycję"; } else { n.readOnly = true; s.readOnly = true; z.readOnly = true; c.readOnly = true; btn.textContent = "Popraw ręcznie"; } }; window.confirmInvoice = function () { billState.company.name = document.getElementById("cmpName").value; billState.company.street = document.getElementById("cmpStreet").value; billState.company.zip = document.getElementById("cmpZip").value; billState.company.city = document.getElementById("cmpCity").value; closeBillDialog(); sendApiSimulated("CallWaiter_Bill", { table: tableParam, billId: billState.selectedBillId, payment: billState.payment, doc: 'faktura', nip: billState.nip, company: billState.company }); showToast("Dziękujemy! Prośba o fakturę została wysłana."); }; // --- GEOLOCATION LOGIC --- const RESTAURANT_LAT = 50.5624963; const RESTAURANT_LNG = 22.0608059; const MAX_DISTANCE_METERS = 200; function haversineDistance(lat1, lon1, lat2, lon2) { const R = 6371e3; const p1 = lat1 * Math.PI / 180; const p2 = lat2 * Math.PI / 180; const dp = (lat2 - lat1) * Math.PI / 180; const dl = (lon2 - lon1) * Math.PI / 180; const a = Math.sin(dp / 2) * Math.sin(dp / 2) + Math.cos(p1) * Math.cos(p2) * Math.sin(dl / 2) * Math.sin(dl / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function startApp() { document.getElementById("geoScreen").classList.add("hidden"); document.getElementById("loadingScreen").classList.remove("hidden"); initUserProfile(); fetchOrders(); if (!window.ordersInterval) { window.ordersInterval = setInterval(fetchOrders, 10000); } // Fallback: If no data after 25s, show empty state anyway setTimeout(() => { if (!document.getElementById("loadingScreen").classList.contains("hidden")) { updateUI([]); } }, 25000); } function showGeoConsentScreen() { const geoScreen = document.getElementById("geoScreen"); const loadingScreen = document.getElementById("loadingScreen"); const geoMsg = document.getElementById("geoMsg"); const geoActionBtn = document.getElementById("geoActionBtn"); loadingScreen.classList.add("hidden"); geoScreen.classList.remove("hidden"); if (geoMsg) { geoMsg.innerHTML = `Aby zapewnić bezpieczeństwo Twojego zamówienia, musimy upewnić się, że znajdujesz się na terenie restauracji.

Prosimy o udzielenie zgody na dostęp do lokalizacji w przeglądarce.`; } if (geoActionBtn) { geoActionBtn.disabled = false; geoActionBtn.textContent = "Udziel zgody / Sprawdź"; } } function isIOSDevice() { return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); } function shouldBypassGeolocationHost() { const bypassHosts = ['82.160.190.247']; return bypassHosts.includes(window.location.hostname); } window.initGeolocation = function () { if (shouldBypassGeolocationHost()) { console.warn("Bypassing geolocation for trusted host."); startApp(); return; } const geoScreen = document.getElementById("geoScreen"); const loadingScreen = document.getElementById("loadingScreen"); const geoMsg = document.getElementById("geoMsg"); const geoActionBtn = document.getElementById("geoActionBtn"); const bypassHosts = ['localhost', '127.0.0.1', '192.168.20.84']; if (window.location.protocol === 'http:' && bypassHosts.includes(window.location.hostname)) { console.warn("Bypassing geolocation on local HTTP environment."); startApp(); return; } if (!window.isSecureContext) { geoMsg.innerHTML = `Ta strona nie jest uruchomiona w bezpiecznym trybie HTTPS.

Przeglądarki mobilne blokują geolokalizację bez pytania, jeśli adres nie zaczyna się od https://.

Otwórz aplikację przez HTTPS i spróbuj ponownie.`; if (geoActionBtn) { geoActionBtn.disabled = false; geoActionBtn.textContent = "Spróbuj ponownie"; } return; } loadingScreen.classList.add("hidden"); geoScreen.classList.remove("hidden"); if (!navigator.geolocation) { geoMsg.innerHTML = "Twoja przeglądarka nie wspiera geolokalizacji. Aplikacja wymaga nowszej przeglądarki."; return; } geoMsg.innerHTML = "Sprawdzamy Twoją lokalizację..."; if (geoActionBtn) { geoActionBtn.disabled = true; geoActionBtn.textContent = "Sprawdzanie..."; } navigator.geolocation.getCurrentPosition( (position) => { const dist = haversineDistance( RESTAURANT_LAT, RESTAURANT_LNG, position.coords.latitude, position.coords.longitude ); const accuracy = position.coords.accuracy; console.log(`[GEO] Lat: ${position.coords.latitude}, Lng: ${position.coords.longitude}, Dist: ${dist}m, Accuracy: ${accuracy}m`); if (dist <= MAX_DISTANCE_METERS) { startApp(); // setTimeout(() => showToast(`Lokalizacja zweryfikowana (Dystans: ${Math.round(dist)}m, Dokładność: ${Math.round(accuracy)}m)`), 2000); } else { if (geoActionBtn) { geoActionBtn.disabled = false; geoActionBtn.textContent = "Spróbuj ponownie"; } geoMsg.innerHTML = `Wydaje się, że jesteś poza restauracją (ok. ${Math.round(dist)}m od nas).
Nasza aplikacja działa tylko na miejscu.

Debug: Twoja odległość: ${Math.round(dist)}m, Dokładność sygnału: ${Math.round(accuracy)}m

Jeśli to błąd GPS lub słaby sygnał, spróbuj ponownie za chwilę.`; } }, (error) => { if (geoActionBtn) { geoActionBtn.disabled = false; geoActionBtn.textContent = "Spróbuj ponownie"; } const deniedBecauseInsecure = /secure origins|only secure|https/i.test(String(error.message || "")); if (deniedBecauseInsecure) { geoMsg.innerHTML = `Przeglądarka zablokowała lokalizację z powodu braku HTTPS.

Geolokalizacja działa tylko na bezpiecznym adresie https:// (lub localhost).
Otwórz aplikację przez HTTPS i spróbuj ponownie.`; } else if (error.code === error.PERMISSION_DENIED) { const isIOS = isIOSDevice(); let instructions = ''; if (isIOS) { instructions = `Instrukcja dla iPhone (Safari):
1. Kliknij ikonę "aA" po lewej stronie paska adresu.
2. Wybierz "Ustawienia witryny" (Website Settings).
3. Zmień opcję "Położenie" (Location) na "Zapytaj" lub "Pozwalaj".
4. Odśwież stronę.

Uwaga: Na urządzeniach Apple lokalizacja działa WYŁĄCZNIE, gdy adres strony zaczyna się od bezpiecznego https://. Jeżeli jesteś na http://, system zablokuje to automatycznie.`; } else { instructions = `Instrukcja dla Android / Chrome:
1. Kliknij ikonkę kłódki / ustawień 🔒 obok adresu strony na górze przeglądarki.
2. Znajdź Uprawnienia (Lokalizacja) i zmień z "Zablokuj" na "Zezwalaj".
3. Odśwież stronę.`; } geoMsg.innerHTML = `Przeglądarka zablokowała dostęp do lokalizacji.

Bez tego nie możemy zweryfikować, czy jesteś w restauracji.

${instructions}`; } else { geoMsg.innerHTML = "Nie udało się pobrać lokalizacji. Sprawdź zasięg lub włącz GPS i spróbuj ponownie."; } }, { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 } ); }; if (shouldBypassGeolocationHost()) { startApp(); } else if (isIOSDevice()) { showGeoConsentScreen(); } else { initGeolocation(); }