(() => { const getCsrfToken = () => { const meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.getAttribute("content") : ""; }; const scrollKey = "nouri-week-scroll"; const rememberScroll = () => { sessionStorage.setItem(scrollKey, String(window.scrollY)); }; const restoreScroll = () => { const savedScroll = sessionStorage.getItem(scrollKey); if (!savedScroll) return; sessionStorage.removeItem(scrollKey); window.requestAnimationFrame(() => { window.scrollTo({ top: Number(savedScroll), left: 0, behavior: "auto" }); }); }; const postAndRefreshInPlace = async (form) => { const payload = new URLSearchParams(new FormData(form)); const response = await fetch(form.action, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "X-Requested-With": "XMLHttpRequest", }, body: payload.toString(), }); if (!response.ok) { throw new Error("request failed"); } rememberScroll(); window.location.reload(); }; const initWeekDragAndDrop = () => { const board = document.querySelector(".week-board"); if (!board) return; let draggedEntry = null; board.querySelectorAll(".draggable-plan-entry").forEach((entry) => { if (entry.getAttribute("draggable") !== "true") return; entry.addEventListener("dragstart", () => { draggedEntry = entry; entry.classList.add("is-dragging"); }); entry.addEventListener("dragend", () => { entry.classList.remove("is-dragging"); draggedEntry = null; board.querySelectorAll(".drop-slot").forEach((slot) => slot.classList.remove("is-drag-over")); }); }); board.querySelectorAll(".drop-slot").forEach((slot) => { slot.addEventListener("dragover", (event) => { event.preventDefault(); if (!draggedEntry) return; slot.classList.add("is-drag-over"); }); slot.addEventListener("dragleave", () => { slot.classList.remove("is-drag-over"); }); slot.addEventListener("drop", async (event) => { event.preventDefault(); slot.classList.remove("is-drag-over"); if (!draggedEntry) return; // Keep DnD lightweight: move on the server, then refresh into the canonical rendered state. const moveUrl = draggedEntry.dataset.moveUrl; const payload = new URLSearchParams({ csrf_token: getCsrfToken(), target_date: slot.dataset.targetDate, target_daypart_id: slot.dataset.targetDaypartId, }); try { const response = await fetch(moveUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "X-Requested-With": "XMLHttpRequest", }, body: payload.toString(), }); if (!response.ok) { throw new Error("move failed"); } const result = await response.json(); rememberScroll(); if (result.redirect_url) { window.location.href = result.redirect_url; } else { window.location.reload(); } } catch (_error) { rememberScroll(); window.location.reload(); } }); }); }; const initWeekCopyForward = () => { document.querySelectorAll(".js-copy-forward-form").forEach((form) => { form.addEventListener("submit", async (event) => { event.preventDefault(); try { await postAndRefreshInPlace(form); } catch (_error) { window.location.reload(); } }); }); }; const initWeekSlotPicker = () => { const board = document.querySelector(".week-board"); if (!board) return; const closeAllPickers = () => { board.querySelectorAll(".week-card").forEach((card) => { card.classList.remove("has-open-picker"); }); board.querySelectorAll(".week-slot").forEach((slot) => { slot.classList.remove("is-picker-open"); }); board.querySelectorAll(".week-slot-picker").forEach((picker) => { picker.hidden = true; }); }; board.querySelectorAll("[data-week-slot-picker-open]").forEach((button) => { button.addEventListener("click", (event) => { event.preventDefault(); const slot = button.closest(".week-slot"); if (!slot) return; const picker = slot.querySelector(".week-slot-picker"); if (!picker) return; const card = slot.closest(".week-card"); const shouldOpen = picker.hidden; closeAllPickers(); if (shouldOpen) { picker.hidden = false; slot.classList.add("is-picker-open"); if (card) { card.classList.add("has-open-picker"); } const filterInput = picker.querySelector("[data-filter-input]"); if (filterInput instanceof HTMLInputElement) { filterInput.value = ""; filterInput.dispatchEvent(new Event("input", { bubbles: true })); window.requestAnimationFrame(() => filterInput.focus()); } } }); }); board.querySelectorAll("[data-week-slot-picker-close]").forEach((button) => { button.addEventListener("click", () => { const slot = button.closest(".week-slot"); if (!slot) return; const picker = slot.querySelector(".week-slot-picker"); const card = slot.closest(".week-card"); if (!picker) return; picker.hidden = true; slot.classList.remove("is-picker-open"); if (card) { card.classList.remove("has-open-picker"); } }); }); board.querySelectorAll(".js-week-slot-submit").forEach((form) => { form.addEventListener("submit", async (event) => { event.preventDefault(); try { await postAndRefreshInPlace(form); } catch (_error) { window.location.reload(); } }); }); document.addEventListener("click", (event) => { const target = event.target; if (!(target instanceof Element)) return; if (target.closest(".week-slot")) return; closeAllPickers(); }); }; const initWeekEntryDialogs = () => { const board = document.querySelector(".week-board"); if (!board) return; const openDialog = (trigger) => { const dialogId = trigger.getAttribute("data-week-entry-dialog-id"); if (!dialogId) return; const dialog = document.getElementById(dialogId); if (!(dialog instanceof HTMLDialogElement)) return; if (!dialog.open) { dialog.showModal(); } }; board.querySelectorAll("[data-week-entry-open]").forEach((entry) => { entry.addEventListener("click", (event) => { if (event.target instanceof Element && event.target.closest("button, a, input, select, textarea, label, form")) { return; } openDialog(entry); }); entry.addEventListener("keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); openDialog(entry); }); }); document.querySelectorAll(".week-entry-dialog").forEach((dialog) => { if (!(dialog instanceof HTMLDialogElement)) return; dialog.addEventListener("click", (event) => { const rect = dialog.getBoundingClientRect(); const clickedInside = rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; if (!clickedInside) { dialog.close(); } }); }); document.querySelectorAll("[data-week-entry-close]").forEach((button) => { button.addEventListener("click", () => { const dialog = button.closest(".week-entry-dialog"); if (dialog instanceof HTMLDialogElement) { dialog.close(); } }); }); document.querySelectorAll(".js-week-entry-submit").forEach((form) => { form.addEventListener("submit", async (event) => { event.preventDefault(); try { await postAndRefreshInPlace(form); } catch (_error) { window.location.reload(); } }); }); }; const syncActionContainerVisibility = (container) => { if (!(container instanceof HTMLElement)) return; const hasVisibleButtons = Array.from(container.querySelectorAll("button")).some((button) => { return !button.hidden; }); container.hidden = !hasVisibleButtons; }; const revealActionButton = (container, selector) => { if (!(container instanceof HTMLElement) || !selector) return; const button = container.querySelector(`button[data-target="${selector}"]`); if (!(button instanceof HTMLButtonElement)) return; button.hidden = false; container.hidden = false; }; const initDaySnackReveal = () => { document.querySelectorAll("[data-day-snack-open]").forEach((button) => { button.addEventListener("click", () => { const selector = button.getAttribute("data-target"); if (!selector) return; const tile = document.querySelector(selector); if (!(tile instanceof HTMLDetailsElement)) return; tile.hidden = false; tile.open = true; button.hidden = true; tile.scrollIntoView({ block: "nearest", inline: "nearest" }); syncActionContainerVisibility(button.closest("[data-day-snack-actions]")); }); }); document.querySelectorAll("[data-day-snack-hide]").forEach((button) => { button.addEventListener("click", () => { const selector = button.getAttribute("data-target"); if (!selector) return; const tile = document.querySelector(selector); if (!(tile instanceof HTMLDetailsElement)) return; tile.open = false; tile.hidden = true; revealActionButton(document.querySelector("[data-day-snack-actions]"), selector); }); }); }; const initWeekSnackReveal = () => { document.querySelectorAll("[data-week-snack-slot-open]").forEach((button) => { button.addEventListener("click", () => { const selector = button.getAttribute("data-target"); if (!selector) return; const slot = document.querySelector(selector); if (!(slot instanceof HTMLElement)) return; slot.hidden = false; button.hidden = true; syncActionContainerVisibility(button.closest("[data-week-snack-actions]")); const openButton = slot.querySelector("[data-week-slot-picker-open]"); if (openButton instanceof HTMLButtonElement) { openButton.click(); } else { slot.scrollIntoView({ block: "nearest", inline: "nearest" }); } }); }); document.querySelectorAll("[data-week-snack-slot-hide]").forEach((button) => { button.addEventListener("click", () => { const selector = button.getAttribute("data-target"); if (!selector) return; const slot = document.querySelector(selector); if (!(slot instanceof HTMLElement)) return; const picker = slot.querySelector(".week-slot-picker"); if (picker instanceof HTMLElement) { picker.hidden = true; } slot.classList.remove("is-picker-open"); slot.hidden = true; const card = slot.closest(".week-card"); if (card) { card.classList.remove("has-open-picker"); } revealActionButton(slot.closest(".week-card")?.querySelector("[data-week-snack-actions]"), selector); }); }); }; document.addEventListener("DOMContentLoaded", () => { restoreScroll(); initWeekDragAndDrop(); initWeekCopyForward(); initWeekSlotPicker(); initWeekEntryDialogs(); initDaySnackReveal(); initWeekSnackReveal(); }); })();