(function (global) { "use strict"; let observer = null; function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function planDate() { return ( global.WespDailyPlanPanel?.getSelectedPlanDate?.() || global.WespDailyPlanSkipDuration?.todayIso?.() || new Date().toISOString().slice(0, 10) ); } function getRecipeId() { return ( document.getElementById("recipeEdit")?.dataset?.recipeId || document.querySelector(".list-item.active[data-recipe-id]")?.dataset?.recipeId || null ); } function notifySuccess(message) { global.WespZootechNotify?.createAdapter?.()?.success?.(message); } function notifyError(message, fallback) { global.WespZootechNotify?.createAdapter?.()?.error?.(message, { fallback: fallback || message }); } async function reloadEditor() { const recipeId = getRecipeId(); if (!recipeId) return; if (typeof global.reloadRecipeEditor === "function") { await global.reloadRecipeEditor(recipeId); } else if (typeof global.loadRecipeDetailsWithPlanOverlay === "function") { await global.loadRecipeDetailsWithPlanOverlay(); } else if (typeof global.loadRecipeDetails === "function") { await global.loadRecipeDetails(recipeId); } } function planBtnHtml(action, title, icon, attrs = "") { return ( `` ); } function decorateRow(row) { const actions = row.querySelector(".recipe-table-row-actions"); if (!actions || actions.querySelector(".recipe-plan-actions")) return; const ingredientId = row.dataset.id; if (!ingredientId) return; const skipped = row.classList.contains("ingredient-row--skipped"); const replaced = row.classList.contains("ingredient-row--replaced"); const select = row.querySelector(".ingredient-select"); const componentId = select?.value || ""; const label = select?.selectedOptions?.[0]?.textContent?.trim() || "Компонент"; const recipeId = getRecipeId(); if (!recipeId) return; const wrap = document.createElement("div"); wrap.className = "btn-group recipe-plan-actions me-1"; wrap.setAttribute("role", "group"); if (skipped) { wrap.innerHTML = planBtnHtml( "recipe-plan-unskip-ingredient", "Вернуть в план", "fa-undo", `data-recipe-id="${escapeHtml(recipeId)}" data-ingredient-id="${escapeHtml(ingredientId)}"` ); } else if (replaced) { wrap.innerHTML = planBtnHtml( "recipe-plan-undo-replace-ingredient", "Отменить замену", "fa-undo", `data-recipe-id="${escapeHtml(recipeId)}" data-ingredient-id="${escapeHtml(ingredientId)}"` ); } else { wrap.innerHTML = planBtnHtml( "recipe-plan-skip-ingredient", "Убрать из плана", "fa-ban", `data-recipe-id="${escapeHtml(recipeId)}" data-ingredient-id="${escapeHtml(ingredientId)}" data-label="${escapeHtml(label)}"` ) + (componentId ? planBtnHtml( "recipe-plan-replace-ingredient", "Заменить в плане", "fa-exchange-alt", `data-recipe-id="${escapeHtml(recipeId)}" data-ingredient-id="${escapeHtml(ingredientId)}" ` + `data-component-id="${escapeHtml(componentId)}" data-label="${escapeHtml(label)}"` ) : ""); } const deleteBtn = actions.querySelector('[data-action="remove-ingredient"]'); if (deleteBtn) actions.insertBefore(wrap, deleteBtn); else actions.appendChild(wrap); } function decorateRows() { document.querySelectorAll("#ingredientsTableBody .ingredient-row").forEach(decorateRow); } function ensureStyles() { if (document.getElementById("wesp-lab-plan-overlay-styles")) return; const style = document.createElement("style"); style.id = "wesp-lab-plan-overlay-styles"; style.textContent = ".recipe-plan-action-btn{padding:2px 6px;}" + ".recipe-plan-actions .btn+.btn{margin-left:2px;}"; document.head.appendChild(style); } async function skipIngredient(recipeId, ingredientId, label) { const skip = global.WespDailyPlanSkipDuration; if (!skip?.skipDurationBody) return; const body = await skip.skipDurationBody(planDate(), { recipeId, ingredientId }, "На какой срок убрать компонент?"); if (!body) return; const resp = await fetch("/api/daily-plan/skips/ingredients", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(body), }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось убрать компонент"); } await reloadEditor(); notifySuccess(`«${label}» убран из плана`); } async function unskipIngredient(recipeId, ingredientId) { const params = new URLSearchParams({ recipe_id: recipeId, ingredient_id: ingredientId, date: planDate() }); const resp = await fetch(`/api/daily-plan/skips/ingredients?${params}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось вернуть компонент"); } await reloadEditor(); notifySuccess("Компонент снова в плане"); } async function openReplace(recipeId, ingredientId, componentId, label) { const replaceModal = global.WespDailyPlanReplaceModal; if (!replaceModal?.open) { notifyError("Модалка замены недоступна"); return; } await replaceModal.open({ currentName: label, onPick: async (replacementComponentId, replacementName) => { const skip = global.WespDailyPlanSkipDuration; const body = await skip?.skipDurationBody?.( planDate(), { recipeId, ingredientId, replacementComponentId }, "На какой срок заменить компонент?" ); if (!body) return; const resp = await fetch("/api/daily-plan/replacements/ingredients", { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(body), }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось заменить компонент"); } await reloadEditor(); notifySuccess(`Компонент заменён на «${replacementName}»`); }, }); } async function undoReplace(recipeId, ingredientId) { const params = new URLSearchParams({ recipe_id: recipeId, ingredient_id: ingredientId, date: planDate() }); const resp = await fetch(`/api/daily-plan/replacements/ingredients?${params}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось отменить замену"); } await reloadEditor(); notifySuccess("Замена отменена"); } document.addEventListener("click", async (ev) => { const btn = ev.target.closest("[data-action]"); if (!btn) return; const action = btn.dataset.action; if (!action?.startsWith("recipe-plan-")) return; if (!document.getElementById("recipeEdit")?.contains(btn)) return; ev.preventDefault(); const recipeId = btn.dataset.recipeId; const ingredientId = btn.dataset.ingredientId; try { if (action === "recipe-plan-skip-ingredient") { await skipIngredient(recipeId, ingredientId, btn.dataset.label || "Компонент"); } else if (action === "recipe-plan-unskip-ingredient") { await unskipIngredient(recipeId, ingredientId); } else if (action === "recipe-plan-replace-ingredient") { await openReplace(recipeId, ingredientId, btn.dataset.componentId, btn.dataset.label || "Компонент"); } else if (action === "recipe-plan-undo-replace-ingredient") { await undoReplace(recipeId, ingredientId); } } catch (e) { notifyError(e.message, "Ошибка плана"); } }); function watchIngredients() { const tbody = document.getElementById("ingredientsTableBody"); if (!tbody || observer) return; observer = new MutationObserver(() => decorateRows()); observer.observe(tbody, { childList: true, subtree: true }); } global.WespLabRecipesPlanOverlay = { init() { ensureStyles(); watchIngredients(); decorateRows(); }, decorateRows, }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => global.WespLabRecipesPlanOverlay.init()); } else { global.WespLabRecipesPlanOverlay.init(); } })(window);