Files

158 lines
7.0 KiB
JavaScript

(() => {
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();
});
})();