Files
karczma-aplikacja-stoliki/public/assets/js/modules/queue.js
T

216 lines
6.0 KiB
JavaScript

import { endpoints } from "./config.js";
import { t } from "./i18n.js";
import { showToast } from "./toast.js";
import {
getBillState,
getCachedOpenBills,
getGuestPendingActions,
getHashParam,
getTableParam,
setCachedOpenBills,
setGuestPendingAction,
} from "./state.js";
let guestPendingPollTimer = null;
const GUEST_PENDING_POLL_MS = 15000;
export function cacheOpenBills(bills) {
setCachedOpenBills(bills);
}
function getQueueOperatorFields() {
const bills = getCachedOpenBills();
if (!bills.length) {
return { otwierajacyImie: "", otwierajacyNazwisko: "" };
}
let bill = bills[0];
const billState = getBillState();
if (billState.selectedBillId) {
const selected = bills.find((b) => b.id === billState.selectedBillId);
if (selected) bill = selected;
}
const o = bill.otwierajacy || {};
return {
otwierajacyImie: String(o.imie || "").trim(),
otwierajacyNazwisko: String(o.nazwisko || "").trim(),
};
}
export async function prefetchOpenBills() {
const hashParam = getHashParam();
if (!hashParam) return;
try {
const res = await fetch(`${endpoints.bills}?h=${encodeURIComponent(hashParam)}`);
const result = await res.json();
if (result.status === "success") {
cacheOpenBills(result.data);
}
} catch {
// best effort
}
}
/**
* Komunikat do kolejki KDS — zwykły tekst, wiersze oddzielone \n (w JSON jako entery).
* @param {string} title
* @param {{ label?: string, value?: string }[]} lines
*/
export function formatGuestQueueMessage(title, lines = []) {
const rows = (Array.isArray(lines) ? lines : [])
.map((line) => {
const label = String(line?.label ?? "").trim();
const value = String(line?.value ?? "").trim();
if (!label && !value) return "";
if (label && value) return `${label} ${value}`;
return label || value;
})
.filter(Boolean);
if (!rows.length) {
return title;
}
return `${title}\n${rows.join("\n")}`;
}
export function buildWaiterCallQueueMessage() {
return t("waiter.queue_title");
}
export function buildBillRequestQueueMessage(docType) {
const billState = getBillState();
const lines = [
{
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: 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: t("bill.queue_address"), value: addressParts.join(", ") });
}
}
return formatGuestQueueMessage(t("bill.queue_title"), lines);
}
export function guestActionBlockedMessage(messageType) {
if (messageType === "waiter_call") {
return t("waiter.blocked");
}
return t("bill.blocked");
}
export function updateGuestActionNavState() {
const guestPendingActions = getGuestPendingActions();
const waiterNav = document.querySelector(".bottom-nav .action-call");
const billNav = document.querySelector(".bottom-nav .action-bill");
if (waiterNav) {
waiterNav.classList.toggle("nav-action-pending", guestPendingActions.waiter_call);
}
if (billNav) {
billNav.classList.toggle("nav-action-pending", guestPendingActions.bill_request);
}
}
export async function refreshGuestPendingActions() {
const hashParam = getHashParam();
const tableParam = getTableParam();
const guestPendingActions = getGuestPendingActions();
if (!hashParam && !tableParam) {
return guestPendingActions;
}
const params = new URLSearchParams();
if (hashParam) params.set("h", hashParam);
if (tableParam) params.set("tableId", tableParam);
try {
const res = await fetch(`${endpoints.guestActionQueue}?${params.toString()}`);
const result = await res.json();
if (result.status === "success" && result.pending) {
setGuestPendingAction("waiter_call", !!result.pending.waiter_call);
setGuestPendingAction("bill_request", !!result.pending.bill_request);
updateGuestActionNavState();
}
} catch {
// best effort
}
return getGuestPendingActions();
}
export function startGuestPendingPoll() {
if (guestPendingPollTimer) return;
guestPendingPollTimer = setInterval(() => {
if (!getHashParam() && !getTableParam()) return;
refreshGuestPendingActions();
}, GUEST_PENDING_POLL_MS);
}
export async function ensureGuestActionAllowed(messageType) {
await refreshGuestPendingActions();
if (!getGuestPendingActions()[messageType]) {
return true;
}
showToast(guestActionBlockedMessage(messageType));
return false;
}
export async function queueGuestAction(messageType, messageText, extra = {}) {
const operator = getQueueOperatorFields();
const body = {
tableId: getTableParam() || null,
qrHash: getHashParam() || null,
messageType,
messageText,
otwierajacyImie: operator.otwierajacyImie,
otwierajacyNazwisko: operator.otwierajacyNazwisko,
extra,
};
try {
const res = await fetch(endpoints.guestActionQueue, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
keepalive: true,
});
const result = await res.json().catch(() => ({}));
if (res.status === 409 || result.code === "pending_on_kds") {
setGuestPendingAction(messageType, true);
updateGuestActionNavState();
return { ok: false, reason: "pending" };
}
if (res.status === 429 || result.code === "rate_limited") {
return { ok: false, reason: "error" };
}
if (!res.ok || result.status !== "success") {
return { ok: false, reason: "error" };
}
setGuestPendingAction(messageType, true);
updateGuestActionNavState();
return { ok: true };
} catch {
return { ok: false, reason: "error" };
}
}