Zmiana aplikacji na wersje lang
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { endpoints } from "./config.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { trackEvent } from "./analytics.js";
|
||||
import { requireFullAccess } from "./access.js";
|
||||
import {
|
||||
@@ -41,14 +42,14 @@ export async function callWaiter(type) {
|
||||
if (queued.reason === "pending") {
|
||||
showToast(guestActionBlockedMessage("waiter_call"));
|
||||
} else {
|
||||
showToast("Nie udało się wysłać wezwania. Spróbuj ponownie za chwilę.");
|
||||
showToast(t("waiter.toast_fail"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
trackEvent("waiter_call_requested", { waiterType: "order" });
|
||||
sendApiSimulated("CallWaiter_Order", { table: getTableParam() });
|
||||
showToast("Kelner wkrótce do Ciebie podejdzie!");
|
||||
showToast(t("waiter.toast_ok"));
|
||||
}
|
||||
|
||||
export function openWaiterDialog() {
|
||||
@@ -96,6 +97,7 @@ export async function openBillDialogInternal() {
|
||||
document.body.style.overflow = "hidden"; // Zablokuj scroll tła
|
||||
|
||||
document.getElementById("billLoading").classList.remove("hidden");
|
||||
document.getElementById("billLoading").textContent = t("bill.loading");
|
||||
document.getElementById("billListContainer").classList.add("hidden");
|
||||
goToStep("stepBillList");
|
||||
|
||||
@@ -115,10 +117,10 @@ export async function openBillDialogInternal() {
|
||||
document.getElementById("btnBackToBills").style.display = "block";
|
||||
}
|
||||
} else {
|
||||
document.getElementById("billLoading").innerHTML = "Brak otwartych rachunków do opłacenia.";
|
||||
document.getElementById("billLoading").innerHTML = t("bill.loading_empty");
|
||||
}
|
||||
} catch (err) {
|
||||
document.getElementById("billLoading").innerHTML = "Błąd pobierania rachunków.";
|
||||
document.getElementById("billLoading").innerHTML = t("bill.loading_error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ function renderBillList(bills) {
|
||||
div.style.padding = "15px";
|
||||
div.onclick = () => showBillReview(b);
|
||||
|
||||
const numerFormat = b.numer ? `#${b.numer}` : "Rachunek";
|
||||
const numerFormat = b.numer ? `#${b.numer}` : t("bill.bill_fallback");
|
||||
div.innerHTML = `
|
||||
<div>
|
||||
<div style="font-weight:bold;">${numerFormat}</div>
|
||||
@@ -212,7 +214,7 @@ export async function selectDocument(docType) {
|
||||
if (queued.reason === "pending") {
|
||||
showToast(guestActionBlockedMessage("bill_request"));
|
||||
} else {
|
||||
showToast("Nie udało się wysłać prośby o rachunek. Spróbuj ponownie za chwilę.");
|
||||
showToast(t("bill.toast_fail"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -224,7 +226,7 @@ export async function selectDocument(docType) {
|
||||
payment: billState.payment,
|
||||
doc: "paragon",
|
||||
});
|
||||
showToast("Kelner przyniesie paragon do opłacenia!");
|
||||
showToast(t("bill.toast_receipt"));
|
||||
} else {
|
||||
goToStep("stepNIP");
|
||||
document.getElementById("nipInput").value = "";
|
||||
@@ -235,11 +237,11 @@ export async function selectDocument(docType) {
|
||||
export async function fetchGUS() {
|
||||
const nip = document.getElementById("nipInput").value.replace(/[\s-]/g, "");
|
||||
if (nip.length < 10) {
|
||||
alert("Wprowadź poprawny numer NIP.");
|
||||
alert(t("bill.nip_invalid"));
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById("btnGUS");
|
||||
btn.textContent = "Szukam...";
|
||||
btn.textContent = t("bill.gus_searching");
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
@@ -267,24 +269,24 @@ export async function fetchGUS() {
|
||||
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;
|
||||
document.getElementById("cmpNip").value = `${t("bill.queue_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";
|
||||
document.getElementById("btnEditCompany").textContent = t("bill.edit_company");
|
||||
|
||||
goToStep("stepVerify");
|
||||
} else {
|
||||
alert("Nie udało się pobrać danych z GUS dla podanego NIP-u.");
|
||||
alert(t("bill.gus_fail"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Błąd pobierania danych z GUS:", error);
|
||||
alert("Błąd połączenia z API GUS.");
|
||||
alert(t("bill.gus_error"));
|
||||
} finally {
|
||||
btn.textContent = "Pobierz z GUS";
|
||||
btn.textContent = t("bill.gus");
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
@@ -302,13 +304,13 @@ export function editCompanyData() {
|
||||
z.readOnly = false;
|
||||
c.readOnly = false;
|
||||
n.focus();
|
||||
btn.textContent = "Zakończ edycję";
|
||||
btn.textContent = t("bill.edit_done");
|
||||
} else {
|
||||
n.readOnly = true;
|
||||
s.readOnly = true;
|
||||
z.readOnly = true;
|
||||
c.readOnly = true;
|
||||
btn.textContent = "Popraw ręcznie";
|
||||
btn.textContent = t("bill.edit_company");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +336,7 @@ export async function confirmInvoice() {
|
||||
if (queued.reason === "pending") {
|
||||
showToast(guestActionBlockedMessage("bill_request"));
|
||||
} else {
|
||||
showToast("Nie udało się wysłać prośby o rachunek. Spróbuj ponownie za chwilę.");
|
||||
showToast(t("bill.toast_fail"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -349,5 +351,5 @@ export async function confirmInvoice() {
|
||||
nip: billState.nip,
|
||||
company: billState.company,
|
||||
});
|
||||
showToast("Dziękujemy! Prośba o fakturę została wysłana.");
|
||||
showToast(t("bill.toast_invoice"));
|
||||
}
|
||||
|
||||
@@ -11,9 +11,15 @@ export const endpoints = {
|
||||
gusLookup: cfg.endpoints?.gusLookup || "../api/gus_lookup.php",
|
||||
kds: cfg.endpoints?.kds || "../api/kds.php",
|
||||
bills: cfg.endpoints?.bills || "../api/bills.php",
|
||||
menu: cfg.endpoints?.menu || "../api/menu.php",
|
||||
};
|
||||
|
||||
export const loaderMinMs = Number(cfg.loaderMinMs) || 10_000;
|
||||
export const availableLanguages = Array.isArray(cfg.languages)
|
||||
? cfg.languages
|
||||
: [
|
||||
{ code: "pl", label: "Polski", flag: "🇵🇱" },
|
||||
{ code: "en", label: "English", flag: "🇬🇧" },
|
||||
{ code: "de", label: "Deutsch", flag: "🇩🇪" },
|
||||
];
|
||||
|
||||
export const MENU_ASSET_VERSION =
|
||||
window.MENU_ASSET_VERSION || window.APP_ASSET_VERSION || "1";
|
||||
export const loaderMinMs = Number(cfg.loaderMinMs) || 10_000;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { endpoints, geoBypassHosts } from "./config.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { trackEvent } from "./analytics.js";
|
||||
import {
|
||||
runPendingProtectedAction,
|
||||
@@ -109,21 +110,15 @@ function showGreeting(name, firstVisitTime) {
|
||||
}
|
||||
}
|
||||
|
||||
function isIOSDevice() {
|
||||
return (
|
||||
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
||||
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)
|
||||
);
|
||||
function GEO_GATE_LABELS() {
|
||||
return {
|
||||
status: t("geo.feature.status"),
|
||||
waiter: t("geo.feature.waiter"),
|
||||
bill: t("geo.feature.bill"),
|
||||
};
|
||||
}
|
||||
|
||||
const GEO_GATE_LABELS = {
|
||||
status: "status zamówienia",
|
||||
waiter: "wezwanie kelnera",
|
||||
bill: "prośbę o rachunek",
|
||||
};
|
||||
|
||||
const GEO_DEFAULT_LEAD =
|
||||
"Przeglądaj menu od razu — albo potwierdź, że jesteś u nas, aby wezwać kelnera, śledzić zamówienie i poprosić o rachunek.";
|
||||
const GEO_DEFAULT_LEAD = () => t("geo.lead");
|
||||
|
||||
function setGeoLead(html) {
|
||||
const el = document.getElementById("geoLead");
|
||||
@@ -155,7 +150,7 @@ function setGeoActionBusy(busy) {
|
||||
btn.disabled = false;
|
||||
btn.setAttribute("aria-busy", busy ? "true" : "false");
|
||||
if (busy) {
|
||||
setGeoActionLabel("Sprawdzanie…");
|
||||
setGeoActionLabel(t("geo.btn.checking"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,19 +167,7 @@ function setGeoActionLabel(text) {
|
||||
}
|
||||
|
||||
function getGeoPermissionInstructions() {
|
||||
if (isIOSDevice()) {
|
||||
return `<b>iPhone (Safari):</b><br>
|
||||
1. Kliknij <b>aA</b> po lewej stronie paska adresu.<br>
|
||||
2. Wybierz <b>Ustawienia witryny</b>.<br>
|
||||
3. Ustaw <b>Położenie</b> na „Zapytaj” lub „Pozwalaj”.<br>
|
||||
4. Odśwież stronę.<br><br>
|
||||
<i>Lokalizacja działa tylko przez bezpieczne <b>https://</b>.</i>`;
|
||||
}
|
||||
|
||||
return `<b>Android / Chrome:</b><br>
|
||||
1. Kliknij ikonę <b>kłódki</b> obok adresu strony.<br>
|
||||
2. W <b>Uprawnieniach</b> zmień Lokalizację na „Zezwalaj”.<br>
|
||||
3. Odśwież stronę.`;
|
||||
return t("geo.hint.permission");
|
||||
}
|
||||
|
||||
let geoMenuButtonMode = "menu_only";
|
||||
@@ -231,7 +214,7 @@ function configureGeoSecondaryButton(mode) {
|
||||
|
||||
if (mode === "back_to_menu") {
|
||||
menuOnlyBtn.style.display = "";
|
||||
if (mainEl) mainEl.textContent = "Wróć do menu";
|
||||
if (mainEl) mainEl.textContent = t("geo.btn.back_menu");
|
||||
if (subEl) {
|
||||
subEl.textContent = "";
|
||||
subEl.style.display = "none";
|
||||
@@ -240,9 +223,9 @@ function configureGeoSecondaryButton(mode) {
|
||||
}
|
||||
|
||||
menuOnlyBtn.style.display = "";
|
||||
if (mainEl) mainEl.textContent = "Przejdź do menu";
|
||||
if (mainEl) mainEl.textContent = t("geo.btn.menu_main");
|
||||
if (subEl) {
|
||||
subEl.textContent = "bez lokalizacji";
|
||||
subEl.textContent = t("geo.btn.menu_sub");
|
||||
subEl.style.display = "";
|
||||
}
|
||||
}
|
||||
@@ -255,13 +238,13 @@ export function showGeoGateForAction(action) {
|
||||
if (loadingScreen) loadingScreen.classList.add("hidden");
|
||||
if (geoScreen) geoScreen.classList.remove("hidden");
|
||||
|
||||
const feature = GEO_GATE_LABELS[action] || "tę funkcję";
|
||||
setGeoLead(`Menu masz już otwarte. Aby skorzystać z <b>${feature}</b>, potwierdź krótko, że jesteś w restauracji.`);
|
||||
const feature = GEO_GATE_LABELS()[action] || t("geo.feature.other");
|
||||
setGeoLead(t("geo.lead_action", { feature }));
|
||||
setGeoStatus("");
|
||||
hideGeoInstructions();
|
||||
if (geoActionBtn) {
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Sprawdź lokalizację");
|
||||
setGeoActionLabel(t("geo.btn.check"));
|
||||
}
|
||||
configureGeoSecondaryButton(getAppAccessLevel() === "menu" ? "back_to_menu" : "menu_only");
|
||||
}
|
||||
@@ -425,24 +408,22 @@ function showGeoConsentScreen() {
|
||||
loadingScreen.classList.add("hidden");
|
||||
geoScreen.classList.remove("hidden");
|
||||
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoLead(GEO_DEFAULT_LEAD());
|
||||
setGeoStatus("");
|
||||
hideGeoInstructions();
|
||||
|
||||
if (geoActionBtn) {
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Zgoda, sprawdź lokalizację");
|
||||
setGeoActionLabel(t("geo.btn.locate"));
|
||||
}
|
||||
configureGeoSecondaryButton("menu_only");
|
||||
}
|
||||
|
||||
function showGeoPermissionBlockedState() {
|
||||
setGeoStatus("Przeglądarka zablokowała dostęp do lokalizacji.", { error: true });
|
||||
showGeoInstructions(
|
||||
`${getGeoPermissionInstructions()}<br><br>Po zmianie ustawień <b>odśwież stronę</b>, a potem kliknij „Spróbuj ponownie”.`
|
||||
);
|
||||
setGeoStatus(t("geo.status.blocked"), { error: true });
|
||||
showGeoInstructions(getGeoPermissionInstructions());
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
setGeoActionLabel(t("geo.btn.retry"));
|
||||
configureGeoSecondaryButton(getAppAccessLevel() === "menu" ? "back_to_menu" : "menu_only");
|
||||
}
|
||||
|
||||
@@ -539,26 +520,24 @@ export async function initGeolocationAfterBypassChecks(options = {}) {
|
||||
configureGeoSecondaryButton(getAppAccessLevel() === "menu" ? "back_to_menu" : "menu_only");
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("Ta strona wymaga bezpiecznego połączenia HTTPS.", { error: true });
|
||||
showGeoInstructions(
|
||||
"Przeglądarki mobilne blokują geolokalizację bez <b>https://</b>. Otwórz aplikację przez HTTPS i spróbuj ponownie."
|
||||
);
|
||||
setGeoLead(GEO_DEFAULT_LEAD());
|
||||
setGeoStatus(t("geo.status.https"), { error: true });
|
||||
showGeoInstructions(t("geo.hint.https"));
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
setGeoActionLabel(t("geo.btn.retry"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!navigator.geolocation) {
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("Twoja przeglądarka nie wspiera geolokalizacji.", { error: true });
|
||||
setGeoLead(GEO_DEFAULT_LEAD());
|
||||
setGeoStatus(t("geo.status.unsupported"), { error: true });
|
||||
hideGeoInstructions();
|
||||
setGeoActionBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setGeoLead(GEO_DEFAULT_LEAD);
|
||||
setGeoStatus("Sprawdzamy Twoją lokalizację…", { info: true });
|
||||
setGeoLead(GEO_DEFAULT_LEAD());
|
||||
setGeoStatus(t("geo.status.checking"), { info: true });
|
||||
hideGeoInstructions();
|
||||
setGeoActionBusy(true);
|
||||
|
||||
@@ -595,16 +574,16 @@ export async function initGeolocationAfterBypassChecks(options = {}) {
|
||||
accuracyMeters: Math.round(accuracy),
|
||||
});
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
setGeoActionLabel(t("geo.btn.retry"));
|
||||
configureGeoSecondaryButton(getAppAccessLevel() === "menu" ? "back_to_menu" : "menu_only");
|
||||
setGeoStatus(
|
||||
`Wygląda na to, że jesteś poza restauracją (ok. ${Math.round(dist)} m, dokładność GPS: ±${Math.round(accuracy)} m).`,
|
||||
t("geo.status.outside", {
|
||||
dist: Math.round(dist),
|
||||
accuracy: Math.round(accuracy),
|
||||
}),
|
||||
{ error: true }
|
||||
);
|
||||
showGeoInstructions(
|
||||
"Przeglądarka często podaje inną lokalizację niż aplikacja Map Google. " +
|
||||
"Spróbuj ponownie na zewnątrz lub bliżej okna — albo przejdź do menu bez lokalizacji."
|
||||
);
|
||||
showGeoInstructions(t("geo.hint.outside"));
|
||||
} catch (error) {
|
||||
trackEvent("geo_check_failed", {
|
||||
reason: "browser_error",
|
||||
@@ -612,18 +591,18 @@ export async function initGeolocationAfterBypassChecks(options = {}) {
|
||||
message: String(error.message || ""),
|
||||
});
|
||||
setGeoActionBusy(false);
|
||||
setGeoActionLabel("Spróbuj ponownie");
|
||||
setGeoActionLabel(t("geo.btn.retry"));
|
||||
configureGeoSecondaryButton(getAppAccessLevel() === "menu" ? "back_to_menu" : "menu_only");
|
||||
const deniedBecauseInsecure = /secure origins|only secure|https/i.test(String(error.message || ""));
|
||||
|
||||
if (deniedBecauseInsecure) {
|
||||
setGeoStatus("Geolokalizacja wymaga HTTPS.", { error: true });
|
||||
showGeoInstructions("Otwórz aplikację przez bezpieczny adres <b>https://</b> i spróbuj ponownie.");
|
||||
setGeoStatus(t("geo.status.https_short"), { error: true });
|
||||
showGeoInstructions(t("geo.hint.https"));
|
||||
} else if (isGeoPermissionDenied(error)) {
|
||||
showGeoPermissionBlockedState();
|
||||
} else {
|
||||
setGeoStatus("Nie udało się pobrać lokalizacji.", { error: true });
|
||||
showGeoInstructions("Sprawdź zasięg, włącz GPS i spróbuj ponownie.");
|
||||
setGeoStatus(t("geo.status.failed"), { error: true });
|
||||
showGeoInstructions(t("geo.hint.retry"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import pl from "../locales/pl.js";
|
||||
import en from "../locales/en.js";
|
||||
import de from "../locales/de.js";
|
||||
|
||||
const STORAGE_KEY = "karczma_lang";
|
||||
const catalogs = { pl, en, de };
|
||||
|
||||
let currentLang = "pl";
|
||||
/** @type {Set<(lang: string) => void>} */
|
||||
const listeners = new Set();
|
||||
|
||||
function interpolate(template, vars = {}) {
|
||||
return String(template).replace(/\{(\w+)\}/g, (_, key) => {
|
||||
return vars[key] != null ? String(vars[key]) : `{${key}}`;
|
||||
});
|
||||
}
|
||||
|
||||
export function getAvailableLanguages() {
|
||||
const fromConfig = window.APP_CONFIG?.languages;
|
||||
if (Array.isArray(fromConfig) && fromConfig.length) {
|
||||
return fromConfig;
|
||||
}
|
||||
return [
|
||||
{ code: "pl", label: "Polski", flag: "🇵🇱" },
|
||||
{ code: "en", label: "English", flag: "🇬🇧" },
|
||||
{ code: "de", label: "Deutsch", flag: "🇩🇪" },
|
||||
];
|
||||
}
|
||||
|
||||
export function getLang() {
|
||||
return currentLang;
|
||||
}
|
||||
|
||||
export function t(key, vars = {}) {
|
||||
const catalog = catalogs[currentLang] || catalogs.pl;
|
||||
const fallback = catalogs.pl;
|
||||
const value = catalog[key] ?? fallback[key] ?? key;
|
||||
return interpolate(value, vars);
|
||||
}
|
||||
|
||||
export function applyTranslations(root = document) {
|
||||
root.querySelectorAll("[data-i18n]").forEach((el) => {
|
||||
const key = el.getAttribute("data-i18n");
|
||||
if (!key) return;
|
||||
const html = el.hasAttribute("data-i18n-html");
|
||||
const text = t(key);
|
||||
if (html) {
|
||||
el.innerHTML = text;
|
||||
} else {
|
||||
el.textContent = text;
|
||||
}
|
||||
});
|
||||
|
||||
root.querySelectorAll("[data-i18n-placeholder]").forEach((el) => {
|
||||
const key = el.getAttribute("data-i18n-placeholder");
|
||||
if (!key) return;
|
||||
el.setAttribute("placeholder", t(key));
|
||||
});
|
||||
|
||||
root.querySelectorAll("[data-i18n-aria]").forEach((el) => {
|
||||
const key = el.getAttribute("data-i18n-aria");
|
||||
if (!key) return;
|
||||
el.setAttribute("aria-label", t(key));
|
||||
});
|
||||
|
||||
document.documentElement.lang = t("html.lang");
|
||||
document.title = t("doc.title");
|
||||
}
|
||||
|
||||
export function onLangChange(callback) {
|
||||
listeners.add(callback);
|
||||
return () => listeners.delete(callback);
|
||||
}
|
||||
|
||||
export function setLang(lang, { persist = true, notify = true } = {}) {
|
||||
const next = catalogs[lang] ? lang : "pl";
|
||||
const changed = next !== currentLang;
|
||||
currentLang = next;
|
||||
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, currentLang);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
applyTranslations();
|
||||
|
||||
if (notify && changed) {
|
||||
listeners.forEach((cb) => {
|
||||
try {
|
||||
cb(currentLang);
|
||||
} catch (err) {
|
||||
console.warn("[i18n] listener failed", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return currentLang;
|
||||
}
|
||||
|
||||
export function initI18n() {
|
||||
let stored = "";
|
||||
try {
|
||||
stored = localStorage.getItem(STORAGE_KEY) || "";
|
||||
} catch {
|
||||
stored = "";
|
||||
}
|
||||
const initial = catalogs[stored] ? stored : "pl";
|
||||
return setLang(initial, { persist: true, notify: false });
|
||||
}
|
||||
|
||||
function flagImgHtml(code) {
|
||||
const safe = String(code || "pl").toLowerCase().replace(/[^a-z]/g, "") || "pl";
|
||||
return `<img class="lang-flag-img" src="assets/img/flags/${safe}.svg" alt="" width="28" height="20" decoding="async">`;
|
||||
}
|
||||
|
||||
export function createLanguagePicker({ idPrefix = "lang" } = {}) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.className = "lang-picker";
|
||||
wrap.dataset.langPicker = "1";
|
||||
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "lang-picker-btn";
|
||||
btn.id = `${idPrefix}Btn`;
|
||||
btn.setAttribute("aria-haspopup", "listbox");
|
||||
btn.setAttribute("aria-expanded", "false");
|
||||
|
||||
const menu = document.createElement("div");
|
||||
menu.className = "lang-picker-menu hidden";
|
||||
menu.id = `${idPrefix}Menu`;
|
||||
menu.setAttribute("role", "listbox");
|
||||
|
||||
function refresh() {
|
||||
const langs = getAvailableLanguages();
|
||||
const current = langs.find((l) => l.code === getLang()) || langs[0];
|
||||
const code = current?.code || "pl";
|
||||
btn.innerHTML = flagImgHtml(code);
|
||||
btn.setAttribute("aria-label", `${t("lang.aria")}: ${current?.label || code}`);
|
||||
|
||||
menu.innerHTML = "";
|
||||
langs.forEach((lang) => {
|
||||
const option = document.createElement("button");
|
||||
option.type = "button";
|
||||
option.className = "lang-picker-option" + (lang.code === getLang() ? " is-active" : "");
|
||||
option.setAttribute("role", "option");
|
||||
option.setAttribute("aria-selected", lang.code === getLang() ? "true" : "false");
|
||||
option.innerHTML = `${flagImgHtml(lang.code)}<span>${lang.label}</span>`;
|
||||
option.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
close();
|
||||
if (lang.code !== getLang()) {
|
||||
setLang(lang.code);
|
||||
}
|
||||
});
|
||||
menu.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function open() {
|
||||
menu.classList.remove("hidden");
|
||||
btn.setAttribute("aria-expanded", "true");
|
||||
wrap.classList.add("is-open");
|
||||
}
|
||||
|
||||
function close() {
|
||||
menu.classList.add("hidden");
|
||||
btn.setAttribute("aria-expanded", "false");
|
||||
wrap.classList.remove("is-open");
|
||||
}
|
||||
|
||||
btn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (menu.classList.contains("hidden")) open();
|
||||
else close();
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!wrap.contains(e.target)) close();
|
||||
});
|
||||
|
||||
wrap.appendChild(btn);
|
||||
wrap.appendChild(menu);
|
||||
refresh();
|
||||
|
||||
onLangChange(() => refresh());
|
||||
|
||||
return { el: wrap, refresh, close };
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MENU_ASSET_VERSION } from "./config.js";
|
||||
import { endpoints } from "./config.js";
|
||||
import { getLang, onLangChange, t } from "./i18n.js";
|
||||
import { trackEvent } from "./analytics.js";
|
||||
|
||||
let itemModalKeys = [];
|
||||
@@ -6,6 +7,7 @@ let itemModalIndex = -1;
|
||||
let itemModalTouchStart = null;
|
||||
let itemModalDragging = false;
|
||||
let itemModalAnimating = false;
|
||||
let menuLangChangeBound = false;
|
||||
|
||||
function resetItemModalPane() {
|
||||
const pane = document.getElementById("itemModalPane");
|
||||
@@ -78,13 +80,13 @@ function renderMenuListImage(url) {
|
||||
const srcAttr = hasUrl ? ` src="${url}"` : "";
|
||||
const onerror = hasUrl ? ' onerror="handleMenuImageError(this)"' : "";
|
||||
|
||||
return `<div class="rmc-image-wrap"><img class="${imgClass}"${srcAttr} alt="" loading="lazy"${onerror}><div class="${placeholderClass}" aria-hidden="true"><svg viewBox="0 0 24 24" width="28" height="28" 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>Brak zdjęcia</span></div></div>`;
|
||||
return `<div class="rmc-image-wrap"><img class="${imgClass}"${srcAttr} alt="" loading="lazy"${onerror}><div class="${placeholderClass}" aria-hidden="true"><svg viewBox="0 0 24 24" width="28" height="28" 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>${t("menu.no_image")}</span></div></div>`;
|
||||
}
|
||||
|
||||
function findMenuItem(categoryId, position) {
|
||||
if (!window.menuDataRaw) return null;
|
||||
for (const cat of window.menuDataRaw) {
|
||||
for (const item of cat.items) {
|
||||
for (const item of cat.items || []) {
|
||||
if (item.categoryId == categoryId && item.position == position) {
|
||||
return item;
|
||||
}
|
||||
@@ -243,33 +245,62 @@ export function bindItemModalSwipe() {
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadMenu() {
|
||||
function renderCategoryNav(categories) {
|
||||
const ul = document.querySelector(".menu-categories-nav ul");
|
||||
if (!ul) return;
|
||||
|
||||
const list = Array.isArray(categories) ? categories : [];
|
||||
let html = `<li><a href="#" class="active" data-category-badge="0" onclick="showCategory(0); return false;">${t("menu.all")}</a></li>`;
|
||||
|
||||
list.forEach((cat) => {
|
||||
const id = String(cat?.id ?? "").trim();
|
||||
const name = String(cat?.name ?? "").trim();
|
||||
if (!id || !name) return;
|
||||
html += `<li><a href="#" data-category-badge="${id}" onclick="showCategory('${id}'); return false;">${name}</a></li>`;
|
||||
});
|
||||
|
||||
ul.innerHTML = html;
|
||||
}
|
||||
|
||||
function showMenuLoadError(container) {
|
||||
if (!container) return;
|
||||
container.innerHTML = `<p style="text-align:center; padding: 20px; color: var(--text-muted);">${t("menu.load_error")}</p>`;
|
||||
}
|
||||
|
||||
export async function loadMenu(lang = getLang()) {
|
||||
const container = document.getElementById("menuContainer");
|
||||
try {
|
||||
const response = await fetch(`menu.json?v=${encodeURIComponent(MENU_ASSET_VERSION)}`);
|
||||
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");
|
||||
const response = await fetch(`${endpoints.menu}?lang=${encodeURIComponent(lang)}`);
|
||||
if (!response.ok) throw new Error(t("menu.load_error"));
|
||||
const payload = await response.json();
|
||||
|
||||
if (payload.status !== "success" || !Array.isArray(payload.sections)) {
|
||||
throw new Error(t("menu.load_error"));
|
||||
}
|
||||
|
||||
const sections = payload.sections;
|
||||
window.menuDataRaw = sections;
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
menuData.forEach((category) => {
|
||||
const catId = category.items.length > 0 ? category.items[0].categoryId : "";
|
||||
sections.forEach((section) => {
|
||||
const items = Array.isArray(section.items) ? section.items : [];
|
||||
const catId = items.length > 0 ? items[0].categoryId : "";
|
||||
|
||||
const catDiv = document.createElement("div");
|
||||
catDiv.className = "rm-category";
|
||||
catDiv.setAttribute("data-cat-id", catId);
|
||||
|
||||
let html = `<div class="restaurant-menu-category">${category.categoryName}</div>
|
||||
let html = `<div class="restaurant-menu-category">${section.categoryName || ""}</div>
|
||||
<div class="rmc-positions">`;
|
||||
|
||||
category.items.forEach((item) => {
|
||||
items.forEach((item) => {
|
||||
html += `
|
||||
<div class="rmc-position" data-position="${item.position}" data-category-id="${item.categoryId}" onclick="openItemModal('${item.categoryId}', '${item.position}')" style="cursor: pointer;">
|
||||
${renderMenuListImage(item.image)}
|
||||
<div class="rmc-title">
|
||||
<h4>${item.title}<span>${item.description}</span></h4>
|
||||
<h4>${item.title}<span>${item.description || ""}</span></h4>
|
||||
</div>
|
||||
<div class="rmc-other"><span>${item.price}</span></div>
|
||||
</div>
|
||||
@@ -280,16 +311,28 @@ export async function loadMenu() {
|
||||
catDiv.innerHTML = html;
|
||||
container.appendChild(catDiv);
|
||||
});
|
||||
|
||||
renderCategoryNav(payload.categories);
|
||||
showCategory(0);
|
||||
|
||||
const searchInput = document.getElementById("menuSearchInput");
|
||||
if (searchInput?.value) {
|
||||
filterMenu();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Błąd ładowania menu:", err);
|
||||
const container = document.getElementById("menuContainer");
|
||||
if (container) {
|
||||
container.innerHTML =
|
||||
'<p style="text-align:center; padding: 20px; color: var(--text-muted);">Nie udało się załadować menu.</p>';
|
||||
}
|
||||
showMenuLoadError(container);
|
||||
}
|
||||
}
|
||||
|
||||
export function setMenuLanguageReload() {
|
||||
if (menuLangChangeBound) return;
|
||||
menuLangChangeBound = true;
|
||||
onLangChange(() => loadMenu());
|
||||
}
|
||||
|
||||
setMenuLanguageReload();
|
||||
|
||||
export function openItemModal(categoryId, position) {
|
||||
itemModalKeys = buildVisibleMenuItemKeys();
|
||||
itemModalIndex = itemModalKeys.findIndex(
|
||||
@@ -366,17 +409,16 @@ export function showCategory(categoryId) {
|
||||
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";
|
||||
}
|
||||
}
|
||||
const showAll = categoryId === 0 || categoryId === "0";
|
||||
const targetId = String(categoryId);
|
||||
|
||||
document.querySelectorAll(".rm-category").forEach((section) => {
|
||||
let hasVisible = false;
|
||||
section.querySelectorAll(".rmc-position").forEach((item) => {
|
||||
const match = showAll || String(item.getAttribute("data-category-id")) === targetId;
|
||||
item.style.display = match ? "" : "none";
|
||||
if (match) hasVisible = true;
|
||||
});
|
||||
section.style.display = hasVisible ? "" : "none";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { endpoints, loaderMinMs } from "./config.js";
|
||||
import { getLang, t } from "./i18n.js";
|
||||
import { runPendingProtectedAction, updateNavAccessState } from "./access.js";
|
||||
import {
|
||||
refreshGuestPendingActions,
|
||||
@@ -30,17 +31,29 @@ const SIX_MONTHS_MS = 180 * 24 * 60 * 60 * 1000;
|
||||
const LOADER_MIN_MS = loaderMinMs;
|
||||
const loadStartTime = Date.now();
|
||||
|
||||
const msgs = ["Rozgrzewamy piece...", "Szef kuchni sprawdza składniki...", "Łączenie z sercem restauracji...", "Prawie gotowe..."];
|
||||
function getLoaderMsgs() {
|
||||
return [t("loader.msg1"), t("loader.msg2"), t("loader.msg3"), t("loader.msg4")];
|
||||
}
|
||||
|
||||
let msgIdx = 0;
|
||||
const msgInterval = setInterval(() => {
|
||||
const msgs = getLoaderMsgs();
|
||||
msgIdx = (msgIdx + 1) % msgs.length;
|
||||
if (loaderMsg) loaderMsg.textContent = msgs[msgIdx];
|
||||
}, 4000);
|
||||
|
||||
function formatTableLabel(name) {
|
||||
const raw = String(name || "").trim();
|
||||
if (!raw) return t("table.label", { name: "" }).trim();
|
||||
if (raw.toUpperCase().startsWith("STOLIK") || raw.toUpperCase().startsWith("TABLE") || raw.toUpperCase().startsWith("TISCH")) {
|
||||
return raw;
|
||||
}
|
||||
return t("table.label", { name: raw });
|
||||
}
|
||||
|
||||
// Initial State
|
||||
if (getTableParam()) {
|
||||
const tableParam = getTableParam();
|
||||
tableLabel.textContent = tableParam.toUpperCase().startsWith("STOLIK") ? tableParam : `Stolik ${tableParam}`;
|
||||
tableLabel.textContent = formatTableLabel(getTableParam());
|
||||
}
|
||||
|
||||
/** Storage key must follow current hash (fallback: table), not a frozen empty tableParam at init. */
|
||||
@@ -73,9 +86,7 @@ export async function resolveTableLabel() {
|
||||
const response = await fetch(`${endpoints.kds}?h=${encodeURIComponent(hashParam)}`);
|
||||
const result = await response.json();
|
||||
if (result.status === "success" && result.tableName && result.tableName !== "") {
|
||||
tableLabel.textContent = result.tableName.toUpperCase().startsWith("STOLIK")
|
||||
? result.tableName
|
||||
: `Stolik ${result.tableName}`;
|
||||
tableLabel.textContent = formatTableLabel(result.tableName);
|
||||
setTableParam(result.tableName);
|
||||
}
|
||||
} catch {
|
||||
@@ -106,16 +117,16 @@ function showEmptyState() {
|
||||
|
||||
emptyState.classList.remove("hidden");
|
||||
itemsList.innerHTML = "";
|
||||
prepStatus.textContent = "Brak aktywnych zamówień";
|
||||
prepStatus.textContent = t("status.none");
|
||||
statusIcon.textContent = "🍃";
|
||||
progressBar.style.width = "0%";
|
||||
statusMeta.textContent = "Zapraszamy do sprawdzenia naszego menu.";
|
||||
statusMeta.textContent = t("status.none_meta");
|
||||
// Historia może istnieć nawet gdy brak bieżących pozycji
|
||||
renderGlobalHistory();
|
||||
}
|
||||
|
||||
function normalizeArticleName(rawName) {
|
||||
const name = String(rawName || "Pozycja");
|
||||
const name = String(rawName || t("orders.item_fallback"));
|
||||
|
||||
// Usuwa gramatury typu: "300G", "250 G", "500/200/150G".
|
||||
const withoutWeight = name.replace(
|
||||
@@ -126,7 +137,7 @@ function normalizeArticleName(rawName) {
|
||||
return withoutWeight
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.replace(/\s+([,.;:!?])/g, "$1")
|
||||
.trim() || "Pozycja";
|
||||
.trim() || t("orders.item_fallback");
|
||||
}
|
||||
|
||||
function loadPersistedItems() {
|
||||
@@ -202,6 +213,7 @@ function addItemsToGlobalHistory(items, sourceTable) {
|
||||
|
||||
export function renderGlobalHistory() {
|
||||
const now = Date.now();
|
||||
const lang = getLang();
|
||||
const history = loadGlobalHistory()
|
||||
.filter((e) => now - (e.archivedAt || 0) <= SIX_MONTHS_MS)
|
||||
.sort((a, b) => (b.archivedAt || 0) - (a.archivedAt || 0));
|
||||
@@ -224,7 +236,11 @@ export function renderGlobalHistory() {
|
||||
div.innerHTML = `
|
||||
<div class="item-info">
|
||||
<span class="item-name">${entry.name}</span>
|
||||
<span class="item-meta">Stolik ${entry.sourceTable || "?"} • ${dt.toLocaleDateString("pl-PL")} ${dt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</span>
|
||||
<span class="item-meta">${t("history.entry_meta", {
|
||||
table: formatTableLabel(entry.sourceTable || "?"),
|
||||
date: dt.toLocaleDateString(lang),
|
||||
time: dt.toLocaleTimeString(lang, { hour: "2-digit", minute: "2-digit" }),
|
||||
})}</span>
|
||||
</div>
|
||||
<div class="item-qty">x${entry.qty}</div>
|
||||
`;
|
||||
@@ -234,7 +250,7 @@ export function renderGlobalHistory() {
|
||||
|
||||
export function clearGlobalHistory(e) {
|
||||
if (e) e.preventDefault();
|
||||
if (confirm("Czy na pewno chcesz usunąć historię swoich poprzednich zamówień?")) {
|
||||
if (confirm(t("history.clear_confirm"))) {
|
||||
localStorage.removeItem(historyKey);
|
||||
renderGlobalHistory();
|
||||
}
|
||||
@@ -282,7 +298,7 @@ function mergeWithPersistedItems(articles) {
|
||||
// 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");
|
||||
return a.name.localeCompare(b.name, getLang());
|
||||
});
|
||||
|
||||
savePersistedItems(merged);
|
||||
@@ -301,11 +317,11 @@ function renderItems(items) {
|
||||
const div = document.createElement("div");
|
||||
div.className = `item-card ${isReady ? "ready" : ""} ${item.present ? "" : "archived"}`;
|
||||
|
||||
let meta = "🔥 W przygotowaniu";
|
||||
let meta = t("orders.item_preparing");
|
||||
if (isReady && item.completedByDisappear) {
|
||||
meta = "✅ Gotowe (zrealizowane)";
|
||||
meta = t("orders.item_ready_done");
|
||||
} else if (isReady) {
|
||||
meta = "✅ Gotowe";
|
||||
meta = t("orders.item_ready");
|
||||
}
|
||||
|
||||
div.innerHTML = `
|
||||
@@ -333,29 +349,29 @@ function updateStatus(bills, items) {
|
||||
progressBar.style.width = `${pct}%`;
|
||||
|
||||
if (pct >= 100) {
|
||||
prepStatus.textContent = "Gotowe do podania!";
|
||||
prepStatus.textContent = t("status.ready");
|
||||
statusIcon.innerHTML = "😋";
|
||||
statusMeta.textContent = "Wszystkie Twoje dania opuściły już kuchnię.";
|
||||
statusMeta.textContent = t("status.ready_meta");
|
||||
} else if (pct > 0) {
|
||||
prepStatus.textContent = "Częściowo gotowe";
|
||||
prepStatus.textContent = t("status.partial");
|
||||
statusIcon.innerHTML = "🍳";
|
||||
statusMeta.textContent = "Pierwsze pyszności już na Ciebie czekają!";
|
||||
statusMeta.textContent = t("status.partial_meta");
|
||||
} else {
|
||||
prepStatus.textContent = "W przygotowaniu";
|
||||
prepStatus.textContent = t("status.preparing");
|
||||
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.";
|
||||
statusMeta.textContent = t("status.preparing_meta");
|
||||
}
|
||||
|
||||
// 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" })
|
||||
? new Date(newest.Date).toLocaleTimeString(getLang(), { hour: "2-digit", minute: "2-digit" })
|
||||
: "--:--";
|
||||
metaFooter.textContent = `Zamówienie złożone o godzinie ${time} • Stolik ${getTableParam()}`;
|
||||
metaFooter.textContent = t("status.footer", { time, table: getTableParam() });
|
||||
}
|
||||
|
||||
export async function fetchOrders() {
|
||||
@@ -371,9 +387,7 @@ export async function fetchOrders() {
|
||||
|
||||
if (result.status === "success") {
|
||||
if (result.tableName && result.tableName !== "") {
|
||||
tableLabel.textContent = result.tableName.toUpperCase().startsWith("STOLIK")
|
||||
? result.tableName
|
||||
: `Stolik ${result.tableName}`;
|
||||
tableLabel.textContent = formatTableLabel(result.tableName);
|
||||
setTableParam(result.tableName); // Aktualizacja do właściwej nazwy na poczet innych zapytań
|
||||
refreshGuestPendingActions();
|
||||
startGuestPendingPoll();
|
||||
@@ -419,9 +433,9 @@ export async function fetchOrders() {
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
loaderMsg.textContent = "Błąd API: " + result.message;
|
||||
loaderMsg.textContent = t("loader.api_error", { message: result.message });
|
||||
}
|
||||
} catch (err) {
|
||||
loaderMsg.textContent = "Problem z połączeniem. Próbujemy ponownie...";
|
||||
loaderMsg.textContent = t("loader.connection");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { endpoints } from "./config.js";
|
||||
import { t } from "./i18n.js";
|
||||
import { showToast } from "./toast.js";
|
||||
import {
|
||||
getBillState,
|
||||
@@ -75,36 +76,42 @@ export function formatGuestQueueMessage(title, lines = []) {
|
||||
}
|
||||
|
||||
export function buildWaiterCallQueueMessage() {
|
||||
return "Przywołanie kelnera";
|
||||
return t("waiter.queue_title");
|
||||
}
|
||||
|
||||
export function buildBillRequestQueueMessage(docType) {
|
||||
const billState = getBillState();
|
||||
const lines = [
|
||||
{ label: "Forma płatności:", value: billState.payment || "nieznana" },
|
||||
{ label: "Dokument:", value: docType === "faktura" ? "faktura" : "paragon" },
|
||||
{
|
||||
label: t("bill.queue_payment"),
|
||||
value: billState.payment || t("bill.payment_unknown"),
|
||||
},
|
||||
{
|
||||
label: t("bill.queue_doc"),
|
||||
value: docType === "faktura" ? t("bill.doc_invoice") : t("bill.doc_receipt"),
|
||||
},
|
||||
];
|
||||
|
||||
if (docType === "faktura") {
|
||||
lines.push({ label: "NIP:", value: billState.nip || "—" });
|
||||
lines.push({ label: "Firma:", value: billState.company?.name || "—" });
|
||||
lines.push({ label: t("bill.queue_nip"), value: billState.nip || "—" });
|
||||
lines.push({ label: t("bill.queue_company"), value: billState.company?.name || "—" });
|
||||
const addressParts = [
|
||||
billState.company?.street,
|
||||
[billState.company?.zip, billState.company?.city].filter(Boolean).join(" "),
|
||||
].filter(Boolean);
|
||||
if (addressParts.length) {
|
||||
lines.push({ label: "Adres:", value: addressParts.join(", ") });
|
||||
lines.push({ label: t("bill.queue_address"), value: addressParts.join(", ") });
|
||||
}
|
||||
}
|
||||
|
||||
return formatGuestQueueMessage("Prośba o rachunek", lines);
|
||||
return formatGuestQueueMessage(t("bill.queue_title"), lines);
|
||||
}
|
||||
|
||||
export function guestActionBlockedMessage(messageType) {
|
||||
if (messageType === "waiter_call") {
|
||||
return "Kelner został już wezwany. Poczekaj, aż obsługa potwierdzi zgłoszenie na panelu.";
|
||||
return t("waiter.blocked");
|
||||
}
|
||||
return "Prośba o rachunek została już wysłana. Poczekaj, aż obsługa ją obsłuży.";
|
||||
return t("bill.blocked");
|
||||
}
|
||||
|
||||
export function updateGuestActionNavState() {
|
||||
|
||||
Reference in New Issue
Block a user