/** * Панель «План на день» внутри hub-модалки «К». */ (function (global) { const DAILY_PLAN_DISPENSER_KEY = "wesp-daily-plan-dispenser"; const ALL_DISPENSERS_ID = "__all_dispensers__"; const ALL_MILLS_ID = "__all_mills__"; function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function formatDistribution(group) { if (group?.distributionLabel) return group.distributionLabel; const type = group?.distributionType || "percent"; const value = group?.value ?? ""; if (type === "heads") return `${value} гол.`; return `${value}%`; } function todayIso() { const d = new Date(); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } function createDailyPlanPanel() { let rootEl = null; let dateEl = null; let dispenserEl = null; let contentEl = null; let dispensers = []; let undoToastTimer = null; function getSelectedDispenserId() { return dispenserEl?.value || ""; } function getSelectedPlanDate() { return planDateValue(); } function notifySuccess(message) { try { global.WespZootechNotify?.createAdapter?.()?.success?.(message, { skipRecord: true }); } catch { /* ignore */ } } function notifyError(message, fallback) { try { global.WespZootechNotify?.createAdapter?.()?.error?.(message, { fallback: fallback || message, skipRecord: true, }); } catch { /* ignore */ } } function renderToolbar() { return ( '
' + '
' + '' + '' + "
" + '
' + '' + '' + "
" + '' + "
" ); } function mount(container) { rootEl = container; rootEl.innerHTML = '
' + renderToolbar() + '
' + '
Загрузка плана…
' + "
"; dateEl = rootEl.querySelector("[data-daily-plan-date]"); dispenserEl = rootEl.querySelector("[data-daily-plan-dispenser]"); contentEl = rootEl.querySelector("[data-daily-plan-content]"); if (dateEl) dateEl.value = todayIso(); dateEl?.addEventListener("change", () => loadPlan()); dispenserEl?.addEventListener("change", () => { if (dispenserEl.value) { try { localStorage.setItem(DAILY_PLAN_DISPENSER_KEY, dispenserEl.value); } catch { /* ignore */ } } loadPlan(); }); rootEl.querySelector("[data-action='daily-plan-pdf']")?.addEventListener("click", downloadPdf); rootEl.addEventListener("click", onPanelClick); loadDispensers(); } function planDateValue() { return dateEl?.value || todayIso(); } async function onPanelClick(event) { const btn = event.target.closest("[data-action]"); if (!btn || !rootEl?.contains(btn)) return; const action = btn.dataset.action; if (action === "daily-plan-skip-trip") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const recipeName = btn.dataset.recipeName || "Рейс"; if (recipeId) await skipTrip(recipeId, recipeName); return; } if (action === "daily-plan-unskip-trip") { event.preventDefault(); const recipeId = btn.dataset.recipeId; if (recipeId) await unskipTrip(recipeId); return; } if (action === "daily-plan-skip-ingredient") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const ingredientId = btn.dataset.ingredientId; const label = btn.dataset.label || "Компонент"; if (recipeId && ingredientId) await skipIngredient(recipeId, ingredientId, label); return; } if (action === "daily-plan-unskip-ingredient") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const ingredientId = btn.dataset.ingredientId; if (recipeId && ingredientId) await unskipIngredient(recipeId, ingredientId); return; } if (action === "daily-plan-skip-unloading-group") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const groupId = btn.dataset.groupId; const label = btn.dataset.label || "Группа"; if (recipeId && groupId) await skipUnloadingGroup(recipeId, groupId, label); return; } if (action === "daily-plan-unskip-unloading-group") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const groupId = btn.dataset.groupId; if (recipeId && groupId) await unskipUnloadingGroup(recipeId, groupId); return; } if (action === "daily-plan-replace-ingredient") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const ingredientId = btn.dataset.ingredientId; const componentId = btn.dataset.componentId; const label = btn.dataset.label || "Компонент"; if (recipeId && ingredientId && componentId) { await openReplaceIngredientModal(recipeId, ingredientId, componentId, label); } return; } if (action === "daily-plan-undo-replace-ingredient") { event.preventDefault(); const recipeId = btn.dataset.recipeId; const ingredientId = btn.dataset.ingredientId; if (recipeId && ingredientId) await undoReplaceIngredient(recipeId, ingredientId); return; } if (action === "daily-plan-undo-adjust-norm") { event.preventDefault(); const componentId = btn.dataset.componentId; if (componentId) await undoComponentNormAdjustment(componentId); } } function formatKg(value) { const n = Number(value); if (!Number.isFinite(n)) return "—"; if (Number.isInteger(n)) return String(n); return n.toFixed(2).replace(/\.?0+$/, ""); } function replacementRowTitle(ing) { if (!ing?.replacedToday) return ""; if (ing.recalculationMode === "dry_matter") { return ( `Пересчёт по СВ: ${formatKg(ing.originalDryMatterPerHead)} кг/гол сохранено, ` + `вес ${formatKg(ing.originalWeightPerHead)} → ${formatKg(ing.weightPerHead)} кг/гол ` + `(СВ ${formatKg(ing.originalDryMatterPct)}% → ${formatKg(ing.dryMatterPct)}%)` ); } return ( `Пересчёт по весу: ${formatKg(ing.weightPerHead)} кг/гол сохранено, ` + `СВ/гол ${formatKg(ing.originalDryMatterPerHead)} → ${formatKg(ing.dryMatterPerHead)} кг/гол` ); } function adjustmentRowTitle(ing) { if (!ing?.adjustedToday) return ""; return ( `Норма в плане: ${formatKg(ing.originalWeightPerHead)} → ${formatKg(ing.weightPerHead)} кг/гол, ` + `СВ/гол ${formatKg(ing.originalDryMatterPerHead)} → ${formatKg(ing.dryMatterPerHead)}` ); } function skippedIngredientHint(ing) { const wph = Number(ing?.baselineWeightPerHead ?? ing?.weightPerHead); const total = Number(ing?.baselineTotalKg); if (!Number.isFinite(wph) || !Number.isFinite(total)) return ""; return `Было: ${formatKg(wph)} кг/гол (${formatKg(total)} кг)`; } function renderSkippedValueCell(hint) { if (!hint) { return '—'; } return ( `—` ); } function renderPlanDot(title) { return ( `` ); } function renderSkipDot() { return renderPlanDot("Исключён из плана на этот день"); } function renderReplaceDot(title) { return renderPlanDot(title || "Сегодня заменили компонент в плане"); } function renderAdjustDot(title = "Сегодня изменили норму в плане") { return ``; } function renderRowActionBtn(action, attrs, title, iconClass) { return ( `` ); } function renderSkipActionBtn(action, attrs, title) { return renderRowActionBtn(action, attrs, title, "fa-times"); } function hubModalEl() { return ( rootEl?.closest(".modal.wesp-shell-modal") || document.getElementById("zootechNotificationCenterModal") ); } function planOverlayHost() { return hubModalEl() || rootEl?.closest(".modal-content") || rootEl; } function dismissAllPlanOverlays() { hubModalEl() ?.querySelectorAll(".zt-k-hub-plan-overlay") .forEach((el) => el.remove()); } function appendPlanOverlay(overlay) { dismissAllPlanOverlays(); overlay.setAttribute("data-daily-plan-overlay", "1"); planOverlayHost()?.appendChild(overlay); return overlay; } function promptSkipDuration(planDate, title = "На какой срок исключить?") { const skip = global.WespDailyPlanSkipDuration; if (!skip) return Promise.resolve(null); return skip.promptSkipDuration({ planDate, title, overlayHost: planOverlayHost() || document.body, beforeAppend: dismissAllPlanOverlays, onError: notifyError, }); } function skipDurationBody(planDate, extra = {}, title) { const skip = global.WespDailyPlanSkipDuration; if (!skip) return Promise.resolve(null); return skip.skipDurationBody(planDate, extra, title); } async function openReplaceIngredientModal(recipeId, ingredientId, componentId, label) { const planDate = planDateValue(); const replaceModal = global.WespDailyPlanReplaceModal; if (!replaceModal) return; await replaceModal.open({ componentId, label, overlayHost: planOverlayHost() || document.body, onError: notifyError, onPick: async (replacementComponentId, replacementName) => { const body = await 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 loadPlan(); notifySuccess(`Компонент заменён на «${replacementName}»`); }, }); } async function undoReplaceIngredient(recipeId, ingredientId) { const planDate = planDateValue(); try { const params = new URLSearchParams({ recipe_id: recipeId, ingredient_id: ingredientId, date: planDate, }); const resp = await fetch(`/api/daily-plan/replacements/ingredients?${params.toString()}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось отменить замену"); } await loadPlan(); notifySuccess("Замена отменена"); } catch (err) { notifyError(err.message, "Не удалось отменить замену"); } } async function undoComponentNormAdjustment(componentId) { const planDate = planDateValue(); try { const params = new URLSearchParams({ component_id: componentId, date: planDate, }); const resp = await fetch(`/api/daily-plan/adjustments/components?${params.toString()}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось вернуть норму"); } await loadPlan(); notifySuccess("Норма возвращена к мастеру"); if (typeof global.reloadAfterSkipRestore === "function") { global.reloadAfterSkipRestore(); } } catch (err) { notifyError(err.message, "Не удалось вернуть норму"); } } async function skipTrip(recipeId, recipeName) { const planDate = planDateValue(); const body = await skipDurationBody(planDate, { recipeId }); if (!body) return; try { const resp = await fetch("/api/daily-plan/skips", { 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 loadPlan(); showUndoToast( "daily-plan-unskip-trip", `data-recipe-id="${escapeHtml(recipeId)}"`, `Рейс «${escapeHtml(recipeName)}» убран из плана.` ); } catch (err) { notifyError(err.message, "Не удалось исключить рейс"); } } async function unskipTrip(recipeId) { const planDate = planDateValue(); try { const params = new URLSearchParams({ recipe_id: recipeId, date: planDate }); const resp = await fetch(`/api/daily-plan/skips?${params.toString()}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось вернуть рейс"); } dismissUndoToast(); await loadPlan(); notifySuccess("Рейс снова в плане"); } catch (err) { notifyError(err.message, "Не удалось вернуть рейс"); } } async function skipIngredient(recipeId, ingredientId, label) { const planDate = planDateValue(); const body = await skipDurationBody(planDate, { recipeId, ingredientId }); if (!body) return; try { 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 loadPlan(); showUndoToast( "daily-plan-unskip-ingredient", `data-recipe-id="${escapeHtml(recipeId)}" data-ingredient-id="${escapeHtml(ingredientId)}"`, `Компонент «${escapeHtml(label)}» убран из плана.` ); } catch (err) { notifyError(err.message, "Не удалось убрать компонент"); } } async function unskipIngredient(recipeId, ingredientId) { const planDate = planDateValue(); try { const params = new URLSearchParams({ recipe_id: recipeId, ingredient_id: ingredientId, date: planDate, }); const resp = await fetch(`/api/daily-plan/skips/ingredients?${params.toString()}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось вернуть компонент"); } dismissUndoToast(); await loadPlan(); notifySuccess("Компонент снова в плане"); } catch (err) { notifyError(err.message, "Не удалось вернуть компонент"); } } async function skipUnloadingGroup(recipeId, groupId, label) { const planDate = planDateValue(); const body = await skipDurationBody(planDate, { recipeId, unloadingGroupId: groupId }); if (!body) return; try { const resp = await fetch("/api/daily-plan/skips/unloading-groups", { 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 loadPlan(); showUndoToast( "daily-plan-unskip-unloading-group", `data-recipe-id="${escapeHtml(recipeId)}" data-group-id="${escapeHtml(groupId)}"`, `Группа «${escapeHtml(label)}» убрана из плана.` ); } catch (err) { notifyError(err.message, "Не удалось убрать группу"); } } async function unskipUnloadingGroup(recipeId, groupId) { const planDate = planDateValue(); try { const params = new URLSearchParams({ recipe_id: recipeId, unloading_group_id: groupId, date: planDate, }); const resp = await fetch( `/api/daily-plan/skips/unloading-groups?${params.toString()}`, { method: "DELETE", headers: { Accept: "application/json" } } ); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Не удалось вернуть группу"); } dismissUndoToast(); await loadPlan(); notifySuccess("Группа снова в плане"); } catch (err) { notifyError(err.message, "Не удалось вернуть группу"); } } function dismissUndoToast() { if (undoToastTimer) { clearTimeout(undoToastTimer); undoToastTimer = null; } rootEl?.querySelector("[data-daily-plan-undo]")?.remove(); } function showUndoToast(unskipAction, dataAttrs, message) { dismissUndoToast(); const toast = document.createElement("div"); toast.className = "zt-k-hub-plan__undo-toast"; toast.dataset.dailyPlanUndo = "1"; toast.innerHTML = `${message}` + ``; rootEl?.querySelector(".zt-k-hub-plan")?.appendChild(toast); undoToastTimer = setTimeout(() => dismissUndoToast(), 8000); } async function loadDispensers() { try { const resp = await fetch("/api/feed_dispensers?limit=200"); if (!resp.ok) throw new Error("dispensers"); const data = await resp.json(); dispensers = Array.isArray(data) ? data : []; if (!dispenserEl) return; if (!dispensers.length) { dispenserEl.innerHTML = ''; renderEmpty("Добавьте кормораздатчик в разделе «Кормораздатчики»."); return; } const saved = (() => { try { return localStorage.getItem(DAILY_PLAN_DISPENSER_KEY) || ""; } catch { return ""; } })(); const dispenserItems = dispensers.filter((d) => d.type !== "mill"); const millItems = dispensers.filter((d) => d.type === "mill"); let options = ``; if (dispenserItems.length) { options += ''; options += dispenserItems .map((d) => { const id = escapeHtml(d.id); const name = escapeHtml(d.name || d.id); const selected = d.id === saved ? " selected" : ""; return ``; }) .join(""); options += ""; } options += ``; if (millItems.length) { options += ''; options += millItems .map((d) => { const id = escapeHtml(d.id); const name = escapeHtml(d.name || d.id); const selected = d.id === saved ? " selected" : ""; return ``; }) .join(""); options += ""; } dispenserEl.innerHTML = options; if (!dispenserEl.value) { dispenserEl.value = saved || ALL_DISPENSERS_ID; } loadPlan(); } catch { if (dispenserEl) { dispenserEl.innerHTML = ''; } renderEmpty("Не удалось загрузить список кормораздатчиков."); } } function buildPlanUrl(pdf) { const params = new URLSearchParams(); if (dateEl?.value) params.set("date", dateEl.value); if (dispenserEl?.value) params.set("dispenser_id", dispenserEl.value); const base = pdf ? "/api/daily-plan/pdf" : "/api/daily-plan"; return `${base}?${params.toString()}`; } async function loadPlan() { if (!contentEl) return; if (!dispenserEl?.value) { renderEmpty("Выберите кормораздатчик."); return; } contentEl.innerHTML = '
Загрузка плана…
'; try { const resp = await fetch(buildPlanUrl(false)); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.message || "Ошибка загрузки"); } const plan = await resp.json(); renderPlan(plan); } catch (err) { renderEmpty(global.WespUserMessages?.safe?.(err.message, "Не удалось загрузить план.") || "Не удалось загрузить план."); } } function renderEmpty(message) { if (!contentEl) return; contentEl.innerHTML = '
' + '' + `

${escapeHtml(message)}

`; } function renderReplaceBtn(recipeId, ing) { if (!ing.componentId) return ""; const label = ing.originalName || ing.name || "Компонент"; return renderRowActionBtn( "daily-plan-replace-ingredient", `data-recipe-id="${escapeHtml(recipeId)}" data-ingredient-id="${escapeHtml(ing.id)}" ` + `data-component-id="${escapeHtml(ing.componentId)}" data-label="${escapeHtml(label)}"`, "Заменить компонент", "fa-exchange-alt" ); } function renderTotalsColumn(totals, grandTotalKg) { if (!totals.length) { return ( '' ); } let rows = ""; totals.forEach((row) => { rows += `${escapeHtml(row.name)}${escapeHtml(formatKg(row.totalKg))}`; }); const grand = grandTotalKg ?? totals.reduce((sum, row) => sum + Number(row.totalKg || 0), 0); return ( '" ); } function renderSkippedSection(plan) { const skippedTrips = plan.skippedTrips || []; if (!skippedTrips.length) return ""; let rows = ""; skippedTrips.forEach((trip) => { rows += '
  • ' + `${renderSkipDot()}Рейс: ${escapeHtml(trip.recipeName)}` + '
  • '; }); return ( '
    ' + '

    Исключено из плана

    ' + `
    ` ); } function renderTripsColumn(periods) { let html = '
    '; periods.forEach((period) => { const trips = period.trips || []; if (!trips.length) return; html += `

    ${escapeHtml(period.name)}

    `; trips.forEach((trip) => { const recipeId = escapeHtml(trip.recipeId); const hasAdjustedNorm = (trip.ingredients || []).some((ing) => ing.adjustedToday); const hasReplacedIngredient = (trip.ingredients || []).some((ing) => ing.replacedToday); const adjustBadge = hasAdjustedNorm ? ' ' + 'сегодня изменили норму' : ""; const replaceBadge = hasReplacedIngredient ? ' ' + 'заменён компонент' : ""; const ings = (trip.ingredients || []) .map((ing) => { const ingId = escapeHtml(ing.id); const name = escapeHtml(ing.name); const skipped = Boolean(ing.skippedToday); const replaced = Boolean(ing.replacedToday); const adjusted = Boolean(ing.adjustedToday); const rowClass = skipped ? "zt-k-hub-plan-row--skipped" : replaced ? "zt-k-hub-plan-row--replaced" : adjusted ? "zt-k-hub-plan-row--adjusted" : ""; const replaceTitle = replacementRowTitle(ing); const adjustTitle = adjustmentRowTitle(ing); const skipHint = skipped ? skippedIngredientHint(ing) : ""; const nameCell = skipped ? `${renderSkipDot()}${name}` : replaced ? `${renderReplaceDot(replaceTitle)}${name}` : adjusted ? `${renderAdjustDot(adjustTitle)}${name}` : `${name}`; const weightCell = skipped ? renderSkippedValueCell(skipHint) : `${escapeHtml(formatKg(ing.weightPerHead))}`; const totalCell = skipped ? renderSkippedValueCell(skipHint) : `${escapeHtml(formatKg(ing.totalKg))}`; let actionCell = ""; if (ing.id) { if (skipped) { actionCell = renderRowActionBtn( "daily-plan-unskip-ingredient", `data-recipe-id="${recipeId}" data-ingredient-id="${ingId}"`, "Вернуть в план", "fa-undo" ); } else { if (replaced) { actionCell += renderRowActionBtn( "daily-plan-undo-replace-ingredient", `data-recipe-id="${recipeId}" data-ingredient-id="${ingId}"`, "Отменить замену", "fa-undo" ); } else if (adjusted && ing.componentId) { actionCell += renderRowActionBtn( "daily-plan-undo-adjust-norm", `data-component-id="${escapeHtml(ing.componentId)}"`, "Вернуть норму", "fa-undo" ); } else { actionCell += renderReplaceBtn(recipeId, ing); } actionCell += renderSkipActionBtn( "daily-plan-skip-ingredient", `data-recipe-id="${recipeId}" data-ingredient-id="${ingId}" data-label="${escapeHtml(ing.originalName || ing.name)}"`, "Убрать компонент из плана на этот день" ); } } return ( `` + `${nameCell}${weightCell}${totalCell}` + `${actionCell}` ); }) .join(""); const groups = (trip.unloadingGroups || []) .map((g) => { const groupId = escapeHtml(g.id); const name = escapeHtml(g.name); const skipped = Boolean(g.skippedToday); const nameCell = skipped ? `${renderSkipDot()}${name}` : `${name}`; const weightCell = skipped ? '—' : `${escapeHtml(g.weightKg)}`; const distCell = skipped ? '—' : `${escapeHtml(formatDistribution(g))}`; let actionCell = ""; if (g.id) { if (skipped) { actionCell = renderRowActionBtn( "daily-plan-unskip-unloading-group", `data-recipe-id="${recipeId}" data-group-id="${groupId}"`, "Вернуть в план", "fa-undo" ); } else { actionCell = renderSkipActionBtn( "daily-plan-skip-unloading-group", `data-recipe-id="${recipeId}" data-group-id="${groupId}" data-label="${name}"`, "Убрать группу из плана на этот день" ); } } return ( `` + `${nameCell}${weightCell}${distCell}` + `${actionCell}` ); }) .join(""); html += '
    ' + '
    ' + `

    ${escapeHtml(trip.recipeName)}${replaceBadge}${adjustBadge}` + ` (${escapeHtml(trip.headsPerTrip)} гол., ${escapeHtml(trip.mixingTimeSec)} с)

    ` + '
    ' + '' + `${ings || ''}` + `
    Компоненткг/голВсего, кг
    Итого по рейсу${escapeHtml(formatKg(trip.totalWeightKg))}
    `; if (groups) { html += '

    Выгрузка

    ' + '' + `${groups}
    ГруппакгРаспределение
    `; } html += "
    "; }); html += "
    "; }); html += "
    "; return html; } function renderPlan(plan) { if (!contentEl) return; const periods = plan.periods || []; const skippedTrips = plan.skippedTrips || []; const hasTrips = periods.some((p) => (p.trips || []).length); const hasSkipped = skippedTrips.length; if (!hasTrips && !hasSkipped) { renderEmpty("Нет периодов или рейсов для выбранного кормораздатчика."); return; } contentEl.innerHTML = '
    ' + renderTotalsColumn(plan.ingredientTotals || [], plan.ingredientGrandTotalKg) + renderTripsColumn(periods) + "
    " + renderSkippedSection(plan); } function downloadPdf() { if (!dispenserEl?.value) return; global.open(buildPlanUrl(true), "_blank", "noopener"); } function destroy() { dismissUndoToast(); dismissAllPlanOverlays(); if (rootEl) { rootEl.removeEventListener("click", onPanelClick); rootEl.innerHTML = ""; } rootEl = null; dateEl = null; dispenserEl = null; contentEl = null; } return { mount, destroy, dismissAllPlanOverlays, reload: loadPlan, getSelectedDispenserId, getSelectedPlanDate, }; } let activePanel = null; global.WespDailyPlanPanel = { createDailyPlanPanel, _setActivePanel(panel) { activePanel = panel || null; }, getSelectedDispenserId() { return activePanel?.getSelectedDispenserId?.() || ""; }, getSelectedPlanDate() { return activePanel?.getSelectedPlanDate?.() || todayIso(); }, reload() { return activePanel?.reload?.(); }, }; })(typeof window !== "undefined" ? window : globalThis);