(() => { document.querySelectorAll("[data-product-filter]").forEach((filter) => { const search = filter.querySelector("[data-product-search]"); const category = filter.querySelector("[data-product-category]"); const list = filter.closest(".card")?.querySelector("[data-product-list]"); const rows = list ? [...list.querySelectorAll("[data-product-row]")] : []; const select = filter.querySelector("[data-product-select]"); const options = select ? [...select.querySelectorAll("[data-product-option]")] : []; const empty = list?.parentElement.querySelector("[data-product-empty]"); const update = () => { const query = (search?.value || "").trim().toLowerCase(); const selectedCategory = category?.value || ""; const matches = (element) => { const nameMatches = !query || element.dataset.productName.includes(query); const categoryMatches = !selectedCategory || element.dataset.productCategory === selectedCategory; return nameMatches && categoryMatches; }; rows.forEach((row) => { row.classList.toggle("d-none", !matches(row)); }); options.forEach((option) => { option.hidden = !matches(option); }); if (empty) empty.classList.toggle("d-none", rows.some((row) => !row.classList.contains("d-none"))); }; search?.addEventListener("input", update); category?.addEventListener("change", update); update(); }); const amountModal = document.getElementById("amount-modal"); const amountForm = document.getElementById("amount-form"); const amountInput = document.getElementById("modal-amount"); const amountProductName = document.getElementById("amount-product-name"); if (amountModal && amountForm && amountInput && amountProductName) { let backdrop; const closeAmountModal = () => { amountModal.classList.remove("show"); amountModal.style.display = "none"; amountModal.setAttribute("aria-hidden", "true"); document.body.classList.remove("modal-open"); backdrop?.remove(); }; const openAmountModal = (item) => { amountForm.action = `/lists/${window.location.pathname.split("/")[2]}/items/${item.dataset.itemId}/amount`; amountProductName.textContent = item.dataset.productName; amountInput.value = item.dataset.itemAmount; amountModal.classList.add("show"); amountModal.style.display = "block"; amountModal.setAttribute("aria-hidden", "false"); document.body.classList.add("modal-open"); backdrop = document.createElement("div"); backdrop.className = "modal-backdrop fade show"; backdrop.addEventListener("click", closeAmountModal); document.body.append(backdrop); amountInput.focus(); }; document.querySelectorAll(".shopping-list-item").forEach((item) => { const swipeContainer = item.closest(".shopping-list-swipe"); let touchStartX = 0; let touchStartY = 0; let swiped = false; let touchMoved = false; item.addEventListener("click", (event) => { if (swiped) { swiped = false; event.preventDefault(); return; } if (event.target.closest("form, button, a, input, select")) return; openAmountModal(item); }); item.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openAmountModal(item); } }); item.addEventListener("touchstart", (event) => { const touch = event.changedTouches[0]; touchStartX = touch.clientX; touchStartY = touch.clientY; swiped = false; touchMoved = false; swipeContainer?.classList.remove("is-swiping"); swipeContainer?.classList.remove("swipe-left", "swipe-right"); }, { passive: true }); item.addEventListener("touchmove", (event) => { const touch = event.changedTouches[0]; const deltaX = touch.clientX - touchStartX; const deltaY = touch.clientY - touchStartY; if (Math.abs(deltaX) <= Math.abs(deltaY) || Math.abs(deltaX) < 8) return; touchMoved = true; event.preventDefault(); swipeContainer?.classList.add("is-swiping"); swipeContainer?.classList.toggle("swipe-left", deltaX < 0); swipeContainer?.classList.toggle("swipe-right", deltaX > 0); item.style.transform = `translateX(${Math.max(-120, Math.min(120, deltaX))}px)`; }, { passive: false }); item.addEventListener("touchend", (event) => { const touch = event.changedTouches[0]; const deltaX = touch.clientX - touchStartX; const deltaY = touch.clientY - touchStartY; item.style.transform = ""; swipeContainer?.classList.remove("is-swiping"); swipeContainer?.classList.remove("swipe-left", "swipe-right"); if (!touchMoved || Math.abs(deltaX) < 70 || Math.abs(deltaX) < Math.abs(deltaY)) return; swiped = true; event.preventDefault(); if (deltaX < 0) { fetch(item.dataset.toggleUrl, { method: "POST" }).then((response) => { if (response.ok) window.location.reload(); }); } else if (confirm("Product verwijderen?")) { fetch(item.dataset.deleteUrl, { method: "DELETE" }).then((response) => { if (response.ok) window.location.reload(); }); } }, { passive: false }); }); amountModal.querySelectorAll("[data-bs-dismiss='modal']").forEach((button) => { button.addEventListener("click", closeAmountModal); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && amountModal.classList.contains("show")) closeAmountModal(); }); } const logoutLink = document.querySelector('a[href="/logout"]'); if (!logoutLink) return; const socket = window.io && window.io(); if (!socket) return; logoutLink.addEventListener("click", (event) => { event.preventDefault(); socket.disconnect(); window.location.assign(logoutLink.href); }); const listMatch = window.location.pathname.match(/^\/lists\/(\d+)$/); if (listMatch) socket.emit("list:join", listMatch[1]); socket.on("list:updated", ({ listId }) => { if (window.location.pathname === `/lists/${listId}`) window.location.reload(); }); })();