function tauriInvoke(cmd, args) { const g = window.__TAURI__; if (g && g.core && typeof g.core.invoke === "function") return g.core.invoke(cmd, args); if (g && typeof g.invoke === "function") return g.invoke(cmd, args); return Promise.reject(new Error("Tauri API не е достапно.")); } const state = { token: null, user: null, isAdmin: false, isManager: false, view: "tables", settings: { restaurant_name: "HoReCa", currency: "MKD", vat_rate: "0" }, categories: [], products: [], activeCategoryId: null, tables: [], order: { tableId: null, tableNumber: null, orderId: null, items: [], }, kitchenTimer: null, }; const pinBuffer = { value: "" }; const KIOSK_MODE = new URLSearchParams(window.location.search).get("mode") === "kitchen"; if (KIOSK_MODE) document.documentElement.classList.add("kiosk-mode"); function $(sel, root = document) { return root.querySelector(sel); } function $all(sel, root = document) { return Array.from(root.querySelectorAll(sel)); } function escapeHtml(str) { return String(str ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }[c])); } function toNumber(v) { if (v === null || v === undefined) return 0; const n = typeof v === "string" ? parseFloat(v) : Number(v); return Number.isFinite(n) ? n : 0; } function methodLabel(raw) { const s = String(raw ?? ""); if (!s) return "—"; return s.charAt(0) + s.slice(1).toLowerCase(); } function money(v) { const n = toNumber(v); return `${n.toFixed(2)} ${state.settings.currency || ""}`.trim(); } function toast(message, type = "info") { const stack = $("#toast-stack"); const el = document.createElement("div"); el.className = `toast toast--${type}`; el.textContent = message; stack.appendChild(el); setTimeout(() => el.remove(), 4200); } function errMsg(e) { if (typeof e === "string") return e; if (e && e.message) return e.message; return "Настана непозната грешка."; } async function call(cmd, args, opts = {}) { try { return await tauriInvoke(cmd, args); } catch (e) { if (!opts.silent) toast(errMsg(e), "error"); throw e; } } function openModal(innerHtml) { const root = $("#modal-root"); root.innerHTML = `${innerHtml}`; $("#modal-overlay").hidden = false; } function closeModal() { $("#modal-overlay").hidden = true; $("#modal-root").innerHTML = ""; } document.addEventListener("click", (e) => { if (e.target === $("#modal-overlay") || e.target.closest("[data-modal-close]")) closeModal(); }); $("#win-min").addEventListener("click", () => call("minimize_window", {}, { silent: true })); $("#win-close").addEventListener("click", () => call("close_window", {}, { silent: true })); $("#win-max").addEventListener("click", async () => { try { const maxed = await tauriInvoke("is_maximized", {}); await tauriInvoke(maxed ? "unmaximize_window" : "maximize_window", {}); } catch (_) { } }); function tickClock() { const now = new Date(); $("#sidebar-clock").textContent = now.toLocaleTimeString("mk-MK", { hour: "2-digit", minute: "2-digit" }); } setInterval(tickClock, 1000 * 20); function renderPinDots() { const dots = $all(".pin-dot"); dots.forEach((d, i) => d.classList.toggle("is-filled", i < pinBuffer.value.length)); } $all(".keypad__key").forEach((btn) => { btn.addEventListener("click", () => { const key = btn.dataset.key; if (key === "clear") pinBuffer.value = ""; else if (key === "back") pinBuffer.value = pinBuffer.value.slice(0, -1); else if (pinBuffer.value.length < 6) pinBuffer.value += key; renderPinDots(); $("#login-error").textContent = ""; }); }); document.addEventListener("keydown", (e) => { if (!$("#view-login").classList.contains("is-active")) return; if (/^[0-9]$/.test(e.key) && pinBuffer.value.length < 6) { pinBuffer.value += e.key; renderPinDots(); } else if (e.key === "Backspace") { pinBuffer.value = pinBuffer.value.slice(0, -1); renderPinDots(); } else if (e.key === "Enter") doLogin(); }); $("#btn-login").addEventListener("click", doLogin); async function doLogin() { const pin = pinBuffer.value; if (!pin) { $("#login-error").textContent = "Внесете ПИН"; return; } const btn = $("#btn-login"); btn.disabled = true; $("#btn-login-label").textContent = "Најавување…"; $("#login-error").textContent = ""; try { const res = await tauriInvoke("login", { pin }); state.token = res.token; state.user = res; state.isAdmin = /admin|админ/i.test(res.role_name || ""); state.isManager = /admin|админ|manager|менаџер/i.test(res.role_name || ""); enterApp(); } catch (e) { $("#login-error").textContent = errMsg(e); pinBuffer.value = ""; renderPinDots(); } finally { btn.disabled = false; $("#btn-login-label").textContent = "Најави се"; } } function enterApp() { pinBuffer.value = ""; renderPinDots(); $("#view-login").classList.remove("is-active"); $("#shell").hidden = false; $("#tb-session").hidden = false; $("#tb-user-name").textContent = state.user.name || `${state.user.first_name} ${state.user.last_name}`.trim(); $("#tb-user-role").textContent = state.user.role_name; $("#nav-admin-group").style.display = state.isManager ? "" : "none"; if (KIOSK_MODE) { switchView("kitchen"); return; } switchView("tables"); loadTables(); loadCategories(); } $("#btn-logout").addEventListener("click", () => { state.token = null; state.user = null; state.isAdmin = false; state.isManager = false; clearInterval(state.kitchenTimer); $("#shell").hidden = true; $("#tb-session").hidden = true; $("#tb-user-name").textContent = "—"; $("#tb-user-role").textContent = "—"; $("#view-login").classList.add("is-active"); }); const ADMIN_VIEWS = new Set(["inventory", "reports", "products", "employees", "settings"]); function switchView(view) { if (ADMIN_VIEWS.has(view) && !state.isManager) return; if (KIOSK_MODE && view !== "kitchen") return; state.view = view; $all(".view-panel").forEach((p) => p.classList.remove("is-active")); $all(".nav-item").forEach((n) => n.classList.toggle("is-active", n.dataset.view === view)); const panel = $(`#panel-${view}`); if (panel) panel.classList.add("is-active"); if (view === "kitchen") { loadKitchen(); startKitchenPolling(); } else stopKitchenPolling(); if (view === "inventory") loadInventory(); if (view === "reports") loadReports(); if (view === "products") loadAdminMenu(); if (view === "employees") loadEmployees(); if (view === "settings") loadSettings(); if (view === "tables") loadTables(); } $all(".nav-item").forEach((btn) => btn.addEventListener("click", () => switchView(btn.dataset.view))); async function loadSettingsQuiet() { try { const s = await tauriInvoke("get_settings", {}); state.settings = s; $("#tb-restaurant-name").textContent = s.restaurant_name || "HoReCa"; } catch (_) { } } async function loadTables() { if (!state.token) return; try { state.tables = await call("get_tables", { token: state.token }); renderTables(); } catch (_) { } } function statusClass(status) { if (status === "ЗАФАТЕНА") return "occupied"; if (status === "РЕЗЕРВИРАНА") return "reserved"; return "free"; } function statusLabel(status) { if (status === "ЗАФАТЕНА") return "Зафатена"; if (status === "РЕЗЕРВИРАНА") return "Резервирана"; return "Слободна"; } function renderTables() { const grid = $("#tables-grid"); if (!state.tables.length) { grid.innerHTML = `

Нема дефинирани маси.

`; return; } grid.innerHTML = state.tables.map((t) => { const cls = statusClass(t.status); const reserveBtn = t.status === "СЛОБОДНА" ? `` : ""; return `
Маса ${escapeHtml(t.table_number)}
${t.capacity} места · ${statusLabel(t.status)}
${reserveBtn}
`; }).join(""); } $("#tables-grid").addEventListener("click", async (e) => { const card = e.target.closest(".table-card"); if (!card) return; const tableId = Number(card.dataset.tableId); const tableNumber = card.dataset.tableNumber; const status = card.dataset.status; if (e.target.closest('[data-action="reserve"]')) { e.stopPropagation(); try { await call("reserve_table", { token: state.token, tableId }); toast(`Масата ${tableNumber} е резервирана.`, "success"); loadTables(); } catch (_) {} return; } if (status === "РЕЗЕРВИРАНА") { openReservedTableModal(tableId, tableNumber); return; } await openTable(tableId, tableNumber, status); }); function openReservedTableModal(tableId, tableNumber) { openModal(`

Маса ${escapeHtml(tableNumber)}

`); $("#m-open-order").addEventListener("click", async () => { closeModal(); await openTable(tableId, tableNumber, "РЕЗЕРВИРАНА"); }); $("#m-cancel-reservation").addEventListener("click", async () => { try { await call("cancel_reservation", { token: state.token, tableId: tableId }); toast("Резервацијата е откажана.", "success"); closeModal(); loadTables(); } catch (_) {} }); } async function openTable(tableId, tableNumber, status) { try { let orderId; if (status === "ЗАФАТЕНА") { orderId = await call("get_active_order", { tableId: tableId }); if (orderId === null || orderId === undefined) { orderId = await call("create_order", { userId: state.user.user_id, tableId: tableId }); } } else { orderId = await call("create_order", { userId: state.user.user_id, tableId: tableId }); } state.order.tableId = tableId; state.order.tableNumber = tableNumber; state.order.orderId = orderId; $("#order-table-title").textContent = `Маса ${tableNumber}`; switchView("order"); await Promise.all([loadCategories(), loadCartItems()]); renderCategoryTabs(); renderProductGrid(); } catch (_) {} } $("#btn-back-to-tables").addEventListener("click", () => switchView("tables")); async function loadCategories() { try { state.categories = await call("get_categories", {}, { silent: true }); } catch (_) { state.categories = []; } } async function loadAllProducts() { try { state.products = await call("get_products", { token: state.token }); } catch (_) { state.products = []; } } function renderCategoryTabs() { const wrap = $("#category-tabs"); const tabs = [``] .concat(state.categories.map((c) => ` `)); wrap.innerHTML = tabs.join(""); } $("#category-tabs").addEventListener("click", async (e) => { const btn = e.target.closest(".category-tab"); if (!btn) return; state.activeCategoryId = btn.dataset.cat === "all" ? null : Number(btn.dataset.cat); renderCategoryTabs(); await renderProductGrid(); }); async function renderProductGrid() { const grid = $("#product-grid"); grid.innerHTML = `

Вчитување…

`; try { const products = state.activeCategoryId === null ? await call("get_products", { token: state.token }, { silent: true }) : await call("get_products_by_category", { token: state.token, categoryId: state.activeCategoryId }, { silent: true }); if (!products.length) { grid.innerHTML = `

Нема производи во оваа категорија.

`; return; } grid.innerHTML = products.map((p) => ` `).join(""); } catch (_) { grid.innerHTML = `

Грешка при вчитување.

`; } } $("#product-grid").addEventListener("click", async (e) => { const card = e.target.closest(".product-card"); if (!card || !state.order.orderId) return; const productId = Number(card.dataset.productId); try { await call("add_order_item", { token: state.token, orderId: state.order.orderId, productId, quantity: 1, }); await loadCartItems(); } catch (_) {} }); async function loadCartItems() { if (!state.order.orderId) return; try { state.order.items = await call("get_order_items", { token: state.token, orderId: state.order.orderId }, { silent: true }); renderCart(); } catch (_) {} } function renderCart() { const wrap = $("#cart-items"); const items = state.order.items; if (!items.length) { wrap.innerHTML = `

Нема ставки во нарачката

`; } else { wrap.innerHTML = items.map((it) => `
${escapeHtml(it.product_name)}
${money(it.unit_price)} / ед.
${it.quantity}
`).join(""); } const total = items.reduce((sum, it) => sum + toNumber(it.quantity) * toNumber(it.unit_price), 0); $("#cart-total").textContent = money(total); $("#order-status-chip").textContent = items.length ? "Активна нарачка" : "Нова нарачка"; } $("#cart-items").addEventListener("click", async (e) => { const row = e.target.closest(".cart-item"); if (!row) return; const action = e.target.closest("[data-action]")?.dataset.action; if (!action) return; const productId = Number(row.dataset.productId); const item = state.order.items.find((i) => i.product_id === productId); if (!item) return; try { if (action === "inc") { await call("update_order_quantity", { orderId: state.order.orderId, productId, quantity: item.quantity + 1 }); } else if (action === "dec") { if (item.quantity <= 1) { await call("delete_order_item", { orderId: state.order.orderId, productId }); } else { await call("update_order_quantity", { orderId: state.order.orderId, productId, quantity: item.quantity - 1 }); } } else if (action === "remove") { await call("delete_order_item", { orderId: state.order.orderId, productId }); } await loadCartItems(); } catch (_) {} }); $("#btn-pay").addEventListener("click", () => { if (!state.order.items.length) { toast("Нарачката е празна.", "error"); return; } let selected = "КЕШ"; openModal(`

Наплата — Маса ${escapeHtml(state.order.tableNumber)}

`); $all(".pay-method").forEach((b) => b.addEventListener("click", () => { $all(".pay-method").forEach((x) => x.classList.remove("is-selected")); b.classList.add("is-selected"); selected = b.dataset.method; })); $("#m-confirm-pay").addEventListener("click", async () => { try { const invoiceNumber = await call("pay_order", { orderId: state.order.orderId, method: selected }); toast("Нарачката е наплатена.", "success"); await showInvoice(invoiceNumber); state.order = { tableId: null, tableNumber: null, orderId: null, items: [] }; loadTables(); } catch (_) {} }); }); async function showInvoice(invoiceNumber) { try { const inv = await call("get_invoice", { token: state.token, invoiceNumber }); const itemsHtml = inv.items.map((it) => `
${escapeHtml(it.name)} × ${it.quantity} ${money(toNumber(it.unit_price) * toNumber(it.quantity))}
`).join(""); openModal(`

Фактура издадена

${escapeHtml(inv.restaurant_name)}
${escapeHtml(inv.invoice_number)} · ${new Date(inv.issued_at).toLocaleString("mk-MK")}
${itemsHtml}
Начин на плаќање${escapeHtml(methodLabel(inv.method))}
ДДВ${escapeHtml(inv.vat_rate)}%
Вкупно${inv.total_amount} ${escapeHtml(inv.currency)}
`); $("#m-print-invoice").addEventListener("click", () => window.print()); } catch (_) { closeModal(); } } function startKitchenPolling() { stopKitchenPolling(); state.kitchenTimer = setInterval(loadKitchen, KIOSK_MODE ? 5000 : 15000); } function stopKitchenPolling() { if (state.kitchenTimer) clearInterval(state.kitchenTimer); state.kitchenTimer = null; } async function loadKitchen() { try { const rows = await call("get_kitchen_orders", {}, { silent: true }); renderKitchen(rows); } catch (_) {} } function renderKitchen(rows) { const grid = $("#kitchen-grid"); if (!rows.length) { grid.innerHTML = `

Нема активни нарачки.

`; return; } const byOrder = new Map(); for (const r of rows) { if (!byOrder.has(r.order_id)) { byOrder.set(r.order_id, { table_number: r.table_number, created_at: r.created_at, items: [] }); } byOrder.get(r.order_id).items.push(r); } grid.innerHTML = Array.from(byOrder.entries()).map(([orderId, o]) => `
Маса ${escapeHtml(o.table_number)} ${new Date(o.created_at).toLocaleTimeString("mk-MK", { hour: "2-digit", minute: "2-digit" })}
${o.items.map((it) => `
${escapeHtml(it.product_name)} ×${it.quantity}
`).join("")}
`).join(""); } $("#kitchen-grid").addEventListener("click", async (e) => { if (!e.target.closest('[data-action="ready"]')) return; const ticket = e.target.closest(".ticket"); const orderId = Number(ticket.dataset.orderId); try { await call("mark_kitchen_order_ready", { orderId }); ticket.remove(); toast("Нарачката е означена како спремна.", "success"); } catch (_) {} }); $("#btn-refresh-kitchen").addEventListener("click", loadKitchen); let inventoryCache = []; async function loadInventory() { try { inventoryCache = await call("get_inventory", {}, { silent: true }); renderInventory(); } catch (_) {} } function renderInventory() { const onlyLow = $("#toggle-low-stock").checked; const query = $("#inventory-search").value.trim().toLowerCase(); let rows = inventoryCache; if (onlyLow) rows = rows.filter((r) => r.quantity <= r.min_stock); if (query) rows = rows.filter((r) => r.name.toLowerCase().includes(query)); const tbody = $("#inventory-table tbody"); if (!rows.length) { tbody.innerHTML = `Нема резултати.`; return; } tbody.innerHTML = rows.map((r) => { const low = r.quantity <= r.min_stock; return ` ${escapeHtml(r.name)} ${r.quantity} ${r.min_stock} ${low ? "Критично" : "Во ред"} ${state.isManager ? `` : ""} `; }).join(""); } $("#toggle-low-stock").addEventListener("change", renderInventory); $("#inventory-search").addEventListener("input", renderInventory); $("#inventory-table tbody").addEventListener("click", (e) => { if (!e.target.closest('[data-action="restock"]')) return; const tr = e.target.closest("tr"); const productId = Number(tr.dataset.productId); const name = tr.children[0].textContent; openModal(`

Дополни залиха

`); $("#m-restock-form").addEventListener("submit", async (ev) => { ev.preventDefault(); const qty = Number($("#m-restock-qty").value); try { await call("restock_product", { token: state.token, productId, quantity: qty }); toast("Залихата е дополнета.", "success"); closeModal(); loadInventory(); } catch (_) {} }); }); async function loadReports() { try { const [sales, orders, best, perf] = await Promise.all([ call("today_sales", {}, { silent: true }), call("today_orders", {}, { silent: true }), call("best_product", {}, { silent: true }), call("employee_performance", { token: state.token }, { silent: true }), ]); $("#stat-today-sales").textContent = money(sales); $("#stat-today-orders").textContent = orders; $("#stat-best-product").textContent = best || "—"; renderEmployeePerf(perf); } catch (_) {} } function renderEmployeePerf(rows) { const tbody = $("#employee-perf-table tbody"); if (!rows.length) { tbody.innerHTML = `Нема податоци.`; return; } tbody.innerHTML = rows.map((r) => ` ${escapeHtml(r.name)} ${r.order_count} ${money(r.total)} `).join(""); } $("#btn-z-report").addEventListener("click", () => { openModal(`

Затвори смена

`); $("#m-confirm-z").addEventListener("click", async () => { try { const r = await call("z_report", { token: state.token }); openModal(`

Z-извештај

Период од${new Date(r.since).toLocaleString("mk-MK")}
Број нарачки${r.order_count}
Вкупно${money(r.total)}
`); $("#m-print-z").addEventListener("click", () => window.print()); loadReports(); } catch (_) {} }); }); $("#search-orders-form").addEventListener("submit", async (e) => { e.preventDefault(); const query = $("#search-query").value.trim(); const start = $("#search-start").value || null; const end = $("#search-end").value || null; try { const rows = await call("search_orders", { token: state.token, query, startDate: start, endDate: end }); const tbody = $("#search-orders-table tbody"); if (!rows.length) { tbody.innerHTML = `Нема резултати.`; return; } tbody.innerHTML = rows.map((r) => ` ${escapeHtml(r.invoice)} ${new Date(r.date).toLocaleDateString("mk-MK")} ${escapeHtml(r.employee)} ${money(r.amount)} `).join(""); } catch (_) {} }); async function loadAdminMenu() { await loadCategories(); renderCategoriesTable(); await loadAllProducts(); renderProductsTable(); } function renderCategoriesTable() { const tbody = $("#categories-table tbody"); if (!state.categories.length) { tbody.innerHTML = `Нема категории.`; return; } tbody.innerHTML = state.categories.map((c) => ` ${escapeHtml(c.name)} ${escapeHtml(c.description || "")} `).join(""); } function categoryModal(existing) { openModal(`

${existing ? "Уреди категорија" : "Нова категорија"}

`); $("#m-category-form").addEventListener("submit", async (e) => { e.preventDefault(); const name = $("#m-cat-name").value.trim(); const description = $("#m-cat-desc").value.trim(); try { if (existing) { await call("update_category", { token: state.token, categoryId: existing.category_id, name, description }); } else { await call("create_category", { token: state.token, name, description }); } toast("Категоријата е зачувана.", "success"); closeModal(); await loadCategories(); renderCategoriesTable(); } catch (_) {} }); } $("#btn-new-category").addEventListener("click", () => categoryModal(null)); $("#categories-table tbody").addEventListener("click", async (e) => { const tr = e.target.closest("tr"); if (!tr) return; const id = Number(tr.dataset.id); const cat = state.categories.find((c) => c.category_id === id); if (e.target.closest('[data-action="edit-cat"]')) categoryModal(cat); if (e.target.closest('[data-action="delete-cat"]')) { openModal(`

Избриши категорија

`); $("#m-confirm-del-cat").addEventListener("click", async () => { try { await call("delete_category", { token: state.token, categoryId: id }); toast("Категоријата е избришана.", "success"); closeModal(); await loadCategories(); renderCategoriesTable(); } catch (_) {} }); } }); function renderProductsTable() { const tbody = $("#products-table tbody"); if (!state.products.length) { tbody.innerHTML = `Нема производи.`; return; } const catName = (id) => state.categories.find((c) => c.category_id === id)?.name || "—"; const query = $("#product-search").value.trim().toLowerCase(); let rows = state.products; if (query) { rows = rows.filter((p) => p.name.toLowerCase().includes(query) || catName(p.category_id).toLowerCase().includes(query) ); } if (!rows.length) { tbody.innerHTML = `Нема резултати.`; return; } tbody.innerHTML = rows.map((p) => ` ${escapeHtml(p.name)} ${escapeHtml(catName(p.category_id))} ${money(p.price)} `).join(""); } $("#product-search").addEventListener("input", renderProductsTable); function productModal(existing) { const options = state.categories.map((c) => ``).join(""); openModal(`

${existing ? "Уреди производ" : "Нов производ"}

`); $("#m-product-form").addEventListener("submit", async (e) => { e.preventDefault(); const name = $("#m-prod-name").value.trim(); const categoryId = Number($("#m-prod-cat").value); const description = $("#m-prod-desc").value.trim(); const price = Number($("#m-prod-price").value); try { if (existing) { await call("update_product", { token: state.token, productId: existing.product_id, categoryId, name, description, price }); } else { await call("add_product", { token: state.token, categoryId, name, description, price }); } toast("Производот е зачуван.", "success"); closeModal(); await loadAllProducts(); renderProductsTable(); } catch (_) {} }); } $("#btn-new-product").addEventListener("click", () => { if (!state.categories.length) { toast("Прво креирајте категорија.", "error"); return; } productModal(null); }); $("#products-table tbody").addEventListener("click", (e) => { const tr = e.target.closest("tr"); if (!tr) return; const id = Number(tr.dataset.id); const prod = state.products.find((p) => p.product_id === id); if (e.target.closest('[data-action="edit-prod"]')) productModal(prod); if (e.target.closest('[data-action="delete-prod"]')) { openModal(`

Избриши производ

`); $("#m-confirm-del-prod").addEventListener("click", async () => { try { await call("delete_product", { token: state.token, productId: id }); toast("Производот е избришан.", "success"); closeModal(); await loadAllProducts(); renderProductsTable(); } catch (_) {} }); } }); let rolesCache = []; async function loadEmployees() { try { const [emps, roles] = await Promise.all([ call("get_employees", { token: state.token }, { silent: true }), call("get_roles", {}, { silent: true }), ]); rolesCache = roles; renderEmployeesTable(emps); } catch (_) {} } function renderEmployeesTable(emps) { const tbody = $("#employees-table tbody"); if (!emps.length) { tbody.innerHTML = `Нема вработени.`; return; } tbody.innerHTML = emps.map((e) => ` ${escapeHtml(e.first_name)} ${escapeHtml(e.last_name)} ${e.active ? "Активен" : "Неактивен"} `).join(""); } $("#employees-table tbody").addEventListener("change", async (e) => { if (!e.target.closest('[data-action="role-select"]')) return; const tr = e.target.closest("tr"); const userId = Number(tr.dataset.id); const roleId = Number(e.target.value); try { await call("update_employee_role", { token: state.token, userId, roleId }); toast("Улогата е ажурирана.", "success"); } catch (_) {} }); $("#employees-table tbody").addEventListener("click", async (e) => { const tr = e.target.closest("tr"); if (!tr) return; const userId = Number(tr.dataset.id); if (e.target.closest('[data-action="toggle-active"]')) { try { await call("toggle_employee", { token: state.token, userId }); toast("Статусот е променет.", "success"); loadEmployees(); } catch (_) {} } if (e.target.closest('[data-action="reset-pin"]')) { openModal(`

Ресетирај ПИН

`); $("#m-reset-pin-form").addEventListener("submit", async (ev) => { ev.preventDefault(); const newPin = $("#m-new-pin").value; try { await call("reset_employee_pin", { token: state.token, userId, newPin }); toast("ПИН-от е ресетиран.", "success"); closeModal(); } catch (_) {} }); } if (e.target.closest('[data-action="delete-emp"]')) { const nameCell = tr.querySelector("td"); const empName = nameCell ? nameCell.textContent.trim() : "вработениот"; openModal(`

Избриши вработен

`); $("#m-confirm-del-emp").addEventListener("click", async () => { try { await call("delete_employee", { token: state.token, userId }); toast("Вработениот е избришан.", "success"); closeModal(); loadEmployees(); } catch (_) {} }); } }); $("#btn-new-employee").addEventListener("click", () => { const options = rolesCache.map((r) => ``).join(""); openModal(`

Нов вработен

`); $("#m-new-emp-form").addEventListener("submit", async (e) => { e.preventDefault(); const firstName = $("#m-emp-first").value.trim(); const lastName = $("#m-emp-last").value.trim(); const roleId = Number($("#m-emp-role").value); const pin = $("#m-emp-pin").value; try { await call("add_employee", { token: state.token, firstName, lastName, pin, roleId }); toast("Вработениот е додаден.", "success"); closeModal(); loadEmployees(); } catch (_) {} }); }); async function loadSettings() { try { const s = await call("get_settings", {}, { silent: true }); state.settings = s; $("#set-restaurant-name").value = s.restaurant_name || ""; $("#set-vat-rate").value = toNumber(s.vat_rate); $("#set-currency").value = s.currency || ""; $("#set-printer-config").value = s.printer_config || ""; } catch (_) {} } $("#settings-form").addEventListener("submit", async (e) => { e.preventDefault(); try { await call("update_settings", { token: state.token, restaurantName: $("#set-restaurant-name").value.trim(), vatRate: Number($("#set-vat-rate").value), currency: $("#set-currency").value.trim(), printerConfig: $("#set-printer-config").value.trim(), }); toast("Поставките се зачувани.", "success"); loadSettingsQuiet(); } catch (_) {} }); tickClock(); loadSettingsQuiet(); $("#view-login").classList.add("is-active");