import { endpoints } from "./config.js"; import { getLang, onLangChange, t } from "./i18n.js"; import { trackEvent } from "./analytics.js"; let itemModalKeys = []; let itemModalIndex = -1; let itemModalTouchStart = null; let itemModalDragging = false; let itemModalAnimating = false; let menuLangChangeBound = false; function resetItemModalPane() { const pane = document.getElementById("itemModalPane"); if (!pane) return; pane.classList.remove( "is-dragging", "is-exiting-left", "is-exiting-right", "is-entering-from-left", "is-entering-from-right" ); pane.style.transform = ""; pane.style.opacity = ""; } function waitForPaneTransition(pane, timeoutMs = 400) { return new Promise((resolve) => { let settled = false; const finish = () => { if (settled) return; settled = true; pane.removeEventListener("transitionend", onEnd); resolve(); }; const onEnd = (e) => { if (e.target === pane) finish(); }; pane.addEventListener("transitionend", onEnd); setTimeout(finish, timeoutMs); }); } function isValidMenuImageUrl(url) { return typeof url === "string" && url.trim().length > 0; } export function handleMenuImageError(imgEl) { if (!imgEl) return; imgEl.onerror = null; imgEl.classList.add("hidden"); const wrap = imgEl.closest(".rmc-image-wrap, .item-modal-image-wrap"); const placeholder = wrap?.querySelector(".menu-image-placeholder"); if (placeholder) placeholder.classList.remove("hidden"); } function applyMenuItemImage(imgEl, placeholderEl, url) { if (!imgEl || !placeholderEl) return; if (!isValidMenuImageUrl(url)) { imgEl.src = ""; imgEl.classList.add("hidden"); placeholderEl.classList.remove("hidden"); return; } imgEl.onload = () => { imgEl.classList.remove("hidden"); placeholderEl.classList.add("hidden"); }; imgEl.onerror = () => handleMenuImageError(imgEl); imgEl.classList.remove("hidden"); placeholderEl.classList.add("hidden"); imgEl.src = url; } function renderMenuListImage(url) { const hasUrl = isValidMenuImageUrl(url); const imgClass = hasUrl ? "rmc-image" : "rmc-image hidden"; const placeholderClass = hasUrl ? "menu-image-placeholder hidden" : "menu-image-placeholder"; const srcAttr = hasUrl ? ` src="${url}"` : ""; const onerror = hasUrl ? ' onerror="handleMenuImageError(this)"' : ""; return `
`; } function findMenuItem(categoryId, position) { if (!window.menuDataRaw) return null; for (const cat of window.menuDataRaw) { for (const item of cat.items || []) { if (item.categoryId == categoryId && item.position == position) { return item; } } } return null; } function buildVisibleMenuItemKeys() { const keys = []; document.querySelectorAll(".rmc-position").forEach((el) => { if (el.style.display === "none") return; const category = el.closest(".rm-category"); if (category && category.style.display === "none") return; keys.push({ categoryId: el.getAttribute("data-category-id"), position: el.getAttribute("data-position"), }); }); return keys; } function updateItemModalNavigation() { const total = itemModalKeys.length; const counter = document.getElementById("itemModalCounter"); if (counter) counter.textContent = total > 1 ? `${itemModalIndex + 1} / ${total}` : ""; } function populateItemModal(item) { const imgEl = document.getElementById("itemModalImage"); const placeholderEl = document.getElementById("itemModalImagePlaceholder"); const titleEl = document.getElementById("itemModalTitle"); const descEl = document.getElementById("itemModalDesc"); const priceEl = document.getElementById("itemModalPrice"); applyMenuItemImage(imgEl, placeholderEl, item.image); if (titleEl) titleEl.textContent = item.title; if (descEl) descEl.textContent = item.description || ""; if (priceEl) priceEl.textContent = item.price; updateItemModalNavigation(); } export async function navigateItemModal(delta) { if (!itemModalKeys.length || !delta || itemModalAnimating) return; const nextIndex = itemModalIndex + delta; if (nextIndex < 0 || nextIndex >= itemModalKeys.length) return; const key = itemModalKeys[nextIndex]; const item = findMenuItem(key.categoryId, key.position); const pane = document.getElementById("itemModalPane"); if (!item || !pane) return; itemModalAnimating = true; resetItemModalPane(); const exitClass = delta > 0 ? "is-exiting-left" : "is-exiting-right"; const enterClass = delta > 0 ? "is-entering-from-right" : "is-entering-from-left"; pane.classList.add(exitClass); await waitForPaneTransition(pane); itemModalIndex = nextIndex; populateItemModal(item); pane.classList.remove(exitClass); pane.classList.add(enterClass); requestAnimationFrame(() => { requestAnimationFrame(() => { pane.classList.remove(enterClass); }); }); await waitForPaneTransition(pane); itemModalAnimating = false; } export function bindItemModalSwipe() { const content = document.getElementById("itemModalContent"); const pane = document.getElementById("itemModalPane"); const modal = document.getElementById("itemModal"); if (!content || !pane || !modal || content.dataset.swipeBound) return; content.dataset.swipeBound = "1"; content.addEventListener( "touchstart", (e) => { if (itemModalAnimating || itemModalKeys.length <= 1 || e.touches.length !== 1) return; if (e.target.closest("button")) return; const touch = e.touches[0]; itemModalTouchStart = { x: touch.clientX, y: touch.clientY }; itemModalDragging = false; }, { passive: true } ); content.addEventListener( "touchmove", (e) => { if (!itemModalTouchStart || itemModalAnimating || itemModalKeys.length <= 1) return; const touch = e.touches[0]; const dx = touch.clientX - itemModalTouchStart.x; const dy = touch.clientY - itemModalTouchStart.y; if (!itemModalDragging) { if (Math.abs(dx) > 12 && Math.abs(dx) > Math.abs(dy)) { itemModalDragging = true; pane.classList.add("is-dragging"); } else if (Math.abs(dy) > 12) { itemModalTouchStart = null; return; } else { return; } } let translateX = dx; if (itemModalIndex <= 0 && dx > 0) translateX = dx * 0.3; if (itemModalIndex >= itemModalKeys.length - 1 && dx < 0) translateX = dx * 0.3; pane.style.transform = `translateX(${translateX}px)`; pane.style.opacity = String(1 - Math.min(Math.abs(translateX) / 320, 0.18)); }, { passive: true } ); content.addEventListener( "touchend", (e) => { if (!itemModalTouchStart) return; const touch = e.changedTouches[0]; const dx = touch.clientX - itemModalTouchStart.x; const dy = touch.clientY - itemModalTouchStart.y; const wasDragging = itemModalDragging; itemModalTouchStart = null; itemModalDragging = false; if (!wasDragging) return; pane.classList.remove("is-dragging"); pane.style.transform = ""; pane.style.opacity = ""; if (Math.abs(dx) >= 50 && Math.abs(dx) > Math.abs(dy)) { navigateItemModal(dx < 0 ? 1 : -1); } }, { passive: true } ); document.addEventListener("keydown", (e) => { if (!modal.classList.contains("active")) return; if (e.key === "ArrowRight") navigateItemModal(1); if (e.key === "ArrowLeft") navigateItemModal(-1); if (e.key === "Escape") closeItemModal(); }); } function renderCategoryNav(categories) { const ul = document.querySelector(".menu-categories-nav ul"); if (!ul) return; const list = Array.isArray(categories) ? categories : []; let html = `
  • ${t("menu.all")}
  • `; list.forEach((cat) => { const id = String(cat?.id ?? "").trim(); const name = String(cat?.name ?? "").trim(); if (!id || !name) return; html += `
  • ${name}
  • `; }); ul.innerHTML = html; } function showMenuLoadError(container) { if (!container) return; container.innerHTML = `

    ${t("menu.load_error")}

    `; } export async function loadMenu(lang = getLang()) { const container = document.getElementById("menuContainer"); try { 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 = ""; 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 = `
    ${section.categoryName || ""}
    `; items.forEach((item) => { html += `
    ${renderMenuListImage(item.image)}

    ${item.title}${item.description || ""}

    ${item.price}
    `; }); html += `
    `; 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); showMenuLoadError(container); } } export function setMenuLanguageReload() { if (menuLangChangeBound) return; menuLangChangeBound = true; onLangChange(() => loadMenu()); } setMenuLanguageReload(); export function openItemModal(categoryId, position) { itemModalKeys = buildVisibleMenuItemKeys(); itemModalIndex = itemModalKeys.findIndex( (key) => key.categoryId == categoryId && key.position == position ); if (itemModalIndex < 0) { itemModalKeys = [{ categoryId, position }]; itemModalIndex = 0; } const foundItem = findMenuItem(categoryId, position); if (!foundItem) return; populateItemModal(foundItem); resetItemModalPane(); const modal = document.getElementById("itemModal"); if (modal) { modal.classList.add("active"); document.body.style.overflow = "hidden"; } } export function closeItemModal() { const modal = document.getElementById("itemModal"); if (modal) { modal.classList.remove("active"); document.body.style.overflow = ""; } itemModalTouchStart = null; itemModalDragging = false; itemModalAnimating = false; resetItemModalPane(); } export function filterMenu() { const query = document.getElementById("menuSearchInput").value.toLowerCase(); const now = Date.now(); if (query.length >= 2 && (!window.lastMenuSearchEventAt || now - window.lastMenuSearchEventAt > 8000)) { window.lastMenuSearchEventAt = now; trackEvent("menu_search", { queryLength: query.length }); } 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"; } }); } export function showCategory(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 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"; }); }