Przebudowa - code review aplikaacji, podział na moduły.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import { endpoints } from "./config.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 "Przywołanie kelnera";
|
||||
}
|
||||
|
||||
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" },
|
||||
];
|
||||
|
||||
if (docType === "faktura") {
|
||||
lines.push({ label: "NIP:", value: billState.nip || "—" });
|
||||
lines.push({ label: "Firma:", 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(", ") });
|
||||
}
|
||||
}
|
||||
|
||||
return formatGuestQueueMessage("Prośba o rachunek", lines);
|
||||
}
|
||||
|
||||
export function guestActionBlockedMessage(messageType) {
|
||||
if (messageType === "waiter_call") {
|
||||
return "Kelner został już wezwany. Poczekaj, aż obsługa potwierdzi zgłoszenie na panelu.";
|
||||
}
|
||||
return "Prośba o rachunek została już wysłana. Poczekaj, aż obsługa ją obsłuży.";
|
||||
}
|
||||
|
||||
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" };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user