/** * Контроль отклонений — лента событий (не дубль отчётов). */ (function (global) { const EVENT_LABELS = { OVERLOAD: "Перегруз", UNDERLOAD: "Недогруз", LOADING_TIME: "Долгая загрузка", LOADING_FAST: "Быстрая загрузка", MIX_TIME: "Смешивание", LEFT_IN_MIXER: "Остаток в миксере", }; const EVENT_ICONS = { OVERLOAD: "fa-arrow-trend-up", UNDERLOAD: "fa-arrow-trend-down", LOADING_TIME: "fa-hourglass-half", LOADING_FAST: "fa-forward", MIX_TIME: "fa-clock", LEFT_IN_MIXER: "fa-triangle-exclamation", }; let currentView = "summary"; let journalSubView = "reports"; let alertsSeverityFilter = ""; function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function pluralDeviations(count) { const n = Math.abs(Number(count) || 0); const mod10 = n % 10; const mod100 = n % 100; if (mod10 === 1 && mod100 !== 11) return `${n} отклонение`; if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return `${n} отклонения`; return `${n} отклонений`; } function getDateParams() { return global.WespReportsFilters?.getDateParams?.() || { date_from: document.getElementById("date-from")?.value || "", date_to: document.getElementById("date-to")?.value || "", }; } function getDispenserId() { return global.WespReportsFilters?.getDispenserId?.() || ""; } function buildAlertsUrl() { const params = global.WespReportsFilters?.buildAlertsParams?.() || new URLSearchParams(getDateParams()); if (alertsSeverityFilter) params.set("severity", alertsSeverityFilter); return `/api/feed-quality/alerts?${params.toString()}`; } function buildSummaryUrl() { const params = global.WespReportsFilters?.buildAlertsParams?.() || new URLSearchParams(getDateParams()); return `/api/feed-quality/alerts/summary?${params.toString()}`; } function deviationClass(devKg, eventType) { if (eventType === "MIX_TIME" || eventType === "LOADING_TIME" || eventType === "LOADING_FAST") { return "deviation-significant"; } if (devKg == null || Number.isNaN(Number(devKg))) return ""; const v = Number(devKg); if (v > 0) return "deviation-positive"; if (v < 0) return "deviation-negative"; return ""; } function formatDeltaPct(item) { if ( item.eventType === "MIX_TIME" || item.eventType === "LOADING_TIME" || item.eventType === "LOADING_FAST" ) { if (item.deviationKg == null) return "—"; const sec = Number(item.deviationKg); return `${sec > 0 ? "+" : ""}${Math.round(sec)} с`; } if (item.deviationPct == null) return "—"; const pct = Number(item.deviationPct); return `${pct > 0 ? "+" : ""}${pct.toFixed(1)}%`; } function formatMixingClock(sec) { const total = Math.max(0, Math.round(Number(sec) || 0)); const m = Math.floor(total / 60); const s = total % 60; return `${m}:${String(s).padStart(2, "0")}`; } function objectLabel(item) { if (item.componentName) return item.componentName; if (item.groupName) return `группа «${item.groupName}»`; if (item.eventType === "MIX_TIME") return "смешивание"; if (item.eventType === "LEFT_IN_MIXER") return "миксер"; return "рейс"; } function buildContextLine(item) { const parts = []; const target = item.targetKg; const actual = item.actualKg; if (item.eventType === "MIX_TIME") { if (actual != null && target != null) { parts.push(`факт ${formatMixingClock(actual)} при плане ${formatMixingClock(target)}`); } } else if (item.eventType === "LEFT_IN_MIXER") { if (actual != null) parts.push(`осталось ${Number(actual).toFixed(1)} кг`); } else if (item.groupName) { if (actual != null && target != null) { parts.push(`выгружено ${Number(actual).toFixed(1)} из ${Number(target).toFixed(1)} кг`); } else if (actual != null) { parts.push(`выгружено ${Number(actual).toFixed(1)} кг`); } if (item.durationSec != null && Number(item.durationSec) > 0) { const sec = Number(item.durationSec); parts.push(sec >= 60 ? `выгрузка ${(sec / 60).toFixed(1)} мин` : `выгрузка ${sec.toFixed(1)} с`); } } else if (item.componentName) { if (actual != null && target != null) { parts.push(`загружено ${Number(actual).toFixed(1)} из ${Number(target).toFixed(1)} кг`); } else if (actual != null) { parts.push(`загружено ${Number(actual).toFixed(1)} кг`); } if (item.durationSec != null && Number(item.durationSec) > 0) { parts.push(`загрузка ${Number(item.durationSec).toFixed(1)} с`); } } if (item.costDeviationRub != null && item.costDeviationRub > 0) { parts.push(`+${Number(item.costDeviationRub).toFixed(0)} ₽`); } return parts.length ? parts.join(" · ") : (item.detail || "").split(":").pop()?.trim() || ""; } function sortItems(items) { return [...items].sort((a, b) => { const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0; const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0; return tb - ta; }); } async function refreshSummary() { const chip = document.getElementById("feedAlertsSummaryChip"); if (!chip) return; try { const resp = await fetch(buildSummaryUrl()); if (!resp.ok) return; const data = await resp.json(); const total = Number(data.total) || 0; if (total <= 0) { chip.hidden = true; chip.textContent = ""; return; } chip.hidden = false; const err = Number(data.error) || 0; chip.textContent = err > 0 ? `${pluralDeviations(total)} (${err} критич.)` : pluralDeviations(total) + " за период"; } catch { chip.hidden = true; } } function renderAlerts(items) { const listEl = document.getElementById("alertsList"); if (!listEl) return; if (!items || !items.length) { listEl.innerHTML = '
' + '
' + '

Отклонений нет

' + '

За выбранный период отклонений не зафиксировано

'; global.WespZootechModals?.revealContent(listEl.querySelector(".empty-state")); return; } const sorted = sortItems(items); const html = sorted .map((item) => { const sev = item.severity || "warning"; const typeLabel = EVENT_LABELS[item.eventType] || "Отклонение"; const icon = EVENT_ICONS[item.eventType] || "fa-circle-exclamation"; const devClass = deviationClass(item.deviationKg, item.eventType); const delta = formatDeltaPct(item); const context = buildContextLine(item); const when = item.createdAt ? new Date(item.createdAt).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", }) : ""; const trip = item.recipeName || "Рейс"; return ( `" ); }) .join(""); listEl.innerHTML = `
${html}
`; listEl.querySelectorAll("[data-loading-report-id]").forEach((btn) => { btn.addEventListener("click", () => { const id = btn.getAttribute("data-loading-report-id"); if (id) openReportFromAlert(id); }); }); global.WespZootechModals?.revealContent(listEl.querySelector(".deviation-feed")); } async function loadAlerts() { const listEl = document.getElementById("alertsList"); if (!listEl) return; listEl.innerHTML = '
' + '

Загрузка отклонений…

'; try { const resp = await fetch(buildAlertsUrl()); if (!resp.ok) throw new Error("Не удалось загрузить отклонения"); const data = await resp.json(); renderAlerts(data.items || []); await refreshSummary(); } catch (err) { listEl.innerHTML = `

${escapeHtml(err.message)}

`; } } function openReportFromAlert(loadingReportId) { switchView("journal"); setJournalSubView("reports"); global.__wespHighlightReportId = loadingReportId; if (typeof global.filterAndDisplayReports === "function") { global.filterAndDisplayReports(); } else if (typeof global.loadReports === "function") { global.loadReports(); } } function openJournalAlerts() { switchView("journal"); setJournalSubView("alerts"); } function setJournalSubView(sub) { journalSubView = sub === "alerts" ? "alerts" : "reports"; document.querySelectorAll("[data-journal-view-tab]").forEach((btn) => { btn.classList.toggle("active", btn.getAttribute("data-journal-view-tab") === journalSubView); }); const alertsContainer = document.querySelector(".reports-container--alerts"); const reportsContainer = document.querySelector(".reports-container--reports"); const severityBlock = document.getElementById("alertsSeverityBlock"); if (reportsContainer) reportsContainer.hidden = journalSubView !== "reports"; if (alertsContainer) alertsContainer.hidden = journalSubView !== "alerts"; if (severityBlock) severityBlock.hidden = journalSubView !== "alerts"; if (journalSubView === "alerts") loadAlerts(); else if (typeof global.loadReports === "function") global.loadReports(); } function updateToolbarVisibility() { const journalBlock = document.getElementById("journalSubTabsBlock"); if (journalBlock) journalBlock.hidden = currentView !== "journal"; const severityBlock = document.getElementById("alertsSeverityBlock"); if (severityBlock) { severityBlock.hidden = currentView !== "journal" || journalSubView !== "alerts"; } } function switchView(view) { const allowed = ["summary", "comparison", "journal"]; currentView = allowed.includes(view) ? view : "summary"; document.querySelectorAll("[data-reports-view-tab]").forEach((btn) => { btn.classList.toggle("active", btn.getAttribute("data-reports-view-tab") === currentView); }); const summaryEl = document.querySelector(".reports-container--summary"); const comparisonEl = document.querySelector(".reports-container--comparison"); const reportsContainer = document.querySelector(".reports-container--reports"); const alertsContainer = document.querySelector(".reports-container--alerts"); if (summaryEl) summaryEl.hidden = currentView !== "summary"; if (comparisonEl) comparisonEl.hidden = currentView !== "comparison"; if (reportsContainer) reportsContainer.hidden = currentView !== "journal" || journalSubView !== "reports"; if (alertsContainer) alertsContainer.hidden = currentView !== "journal" || journalSubView !== "alerts"; updateToolbarVisibility(); if (currentView === "summary") { global.WespAnalyticsSummary?.load?.(); } else if (currentView === "comparison") { global.WespAnalyticsPlanFact?.load?.(); } else if (currentView === "journal") { setJournalSubView(journalSubView); } else { refreshSummary(); } } function setSeverityFilter(value) { alertsSeverityFilter = value || ""; document.querySelectorAll("[data-alerts-severity]").forEach((btn) => { btn.classList.toggle("active", btn.getAttribute("data-alerts-severity") === alertsSeverityFilter); }); if (currentView === "journal" && journalSubView === "alerts") loadAlerts(); } function init() { document.querySelectorAll("[data-reports-view-tab]").forEach((btn) => { btn.addEventListener("click", () => { switchView(btn.getAttribute("data-reports-view-tab")); }); }); document.querySelectorAll("[data-journal-view-tab]").forEach((btn) => { btn.addEventListener("click", () => { if (currentView !== "journal") switchView("journal"); setJournalSubView(btn.getAttribute("data-journal-view-tab")); }); }); document.querySelectorAll("[data-alerts-severity]").forEach((btn) => { btn.addEventListener("click", () => { setSeverityFilter(btn.getAttribute("data-alerts-severity") || ""); }); }); global.WespAnalyticsSummary?.init?.(); switchView("summary"); refreshSummary(); } function onFiltersApplied() { refreshSummary(); global.WespAnalyticsSummary?.onFiltersApplied?.(); global.WespAnalyticsPlanFact?.onFiltersApplied?.(); if (currentView === "journal" && journalSubView === "alerts") loadAlerts(); } global.WespFeedAlerts = { init, loadAlerts, refreshSummary, switchView, openReportFromAlert, openJournalAlerts, onFiltersApplied, getCurrentView: () => currentView, }; })(typeof window !== "undefined" ? window : globalThis);