@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Вкладка «Сравнение» на /reports.
|
||||
*/
|
||||
(function (global) {
|
||||
const COPY = () => global.WespAnalyticsCopy || {};
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function getDateParams() {
|
||||
const from = document.getElementById("date-from")?.value || "";
|
||||
const to = document.getElementById("date-to")?.value || "";
|
||||
return { date_from: from, date_to: to };
|
||||
}
|
||||
|
||||
function buildUrl() {
|
||||
const params = global.WespReportsFilters?.buildAnalyticsParams?.() || new URLSearchParams(getDateParams());
|
||||
return `/api/analytics/plan-fact?${params.toString()}`;
|
||||
}
|
||||
|
||||
function barWidth(value, max) {
|
||||
if (!max || max <= 0) return 0;
|
||||
return Math.min(100, (Number(value) / max) * 100);
|
||||
}
|
||||
|
||||
function faultBadge(fault, executionLabel) {
|
||||
const c = COPY().comparison || {};
|
||||
if (fault === "execution") {
|
||||
return `<span class="analytics-pf-fault analytics-pf-fault--execution">${escapeHtml(
|
||||
executionLabel || c.noteExecution || ""
|
||||
)}</span>`;
|
||||
}
|
||||
if (fault === "excluded" || fault === "zootech_ok") {
|
||||
return `<span class="analytics-pf-fault analytics-pf-fault--excluded">${escapeHtml(c.noteExcluded || "")}</span>`;
|
||||
}
|
||||
if (fault === "adjusted" || fault === "zootech") {
|
||||
return `<span class="analytics-pf-fault analytics-pf-fault--adjusted">${escapeHtml(c.noteAdjusted || "")}</span>`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function legendHtml() {
|
||||
const c = COPY().comparison || {};
|
||||
return (
|
||||
`<div class="analytics-pf-legend">` +
|
||||
`<span><i class="analytics-pf-legend__dot analytics-pf-legend__dot--base"></i>${escapeHtml(c.legendBase)}</span>` +
|
||||
`<span><i class="analytics-pf-legend__dot analytics-pf-legend__dot--plan"></i>${escapeHtml(c.legendPlan)}</span>` +
|
||||
`<span><i class="analytics-pf-legend__dot analytics-pf-legend__dot--actual"></i>${escapeHtml(c.legendActual)}</span>` +
|
||||
`</div>`
|
||||
);
|
||||
}
|
||||
|
||||
function renderMetricRows(rows, maxKg, options) {
|
||||
const executionLabel = options?.executionLabel;
|
||||
return rows
|
||||
.map((c) => {
|
||||
const notes = (c.notes || [])
|
||||
.map((n) => {
|
||||
const note = typeof n === "string" ? { text: n, kind: "execution" } : n;
|
||||
const kind = note.kind === "zootech" ? "zootech" : "execution";
|
||||
const cls =
|
||||
kind === "zootech"
|
||||
? "analytics-pf-note analytics-pf-note--zootech"
|
||||
: "analytics-pf-note analytics-pf-note--execution";
|
||||
return `<li class="${cls}">${escapeHtml(note.text)}</li>`;
|
||||
})
|
||||
.join("");
|
||||
const faultBadgeHtml = faultBadge(c.fault, executionLabel);
|
||||
const compCls =
|
||||
c.skippedToday && (c.fault === "excluded" || c.fault === "zootech_ok")
|
||||
? "analytics-pf-comp analytics-pf-comp--excluded"
|
||||
: options?.rowClass || "analytics-pf-comp";
|
||||
return (
|
||||
`<div class="${compCls}">` +
|
||||
`<div class="analytics-pf-comp__head">` +
|
||||
`<div class="analytics-pf-comp__name">${escapeHtml(c.name)}</div>` +
|
||||
faultBadgeHtml +
|
||||
`</div>` +
|
||||
`<div class="analytics-pf-bars">` +
|
||||
`<div class="analytics-pf-bar analytics-pf-bar--base" title="${escapeHtml(COPY().comparison?.legendBase)}"><span style="width:${barWidth(c.baseKg, maxKg)}%"></span><em>${Number(c.baseKg).toFixed(1)}</em></div>` +
|
||||
`<div class="analytics-pf-bar analytics-pf-bar--plan" title="${escapeHtml(COPY().comparison?.legendPlan)}"><span style="width:${barWidth(c.planTodayKg, maxKg)}%"></span><em>${Number(c.planTodayKg).toFixed(1)}</em></div>` +
|
||||
`<div class="analytics-pf-bar analytics-pf-bar--actual" title="${escapeHtml(COPY().comparison?.legendActual)}"><span style="width:${barWidth(c.actualKg, maxKg)}%"></span><em>${Number(c.actualKg).toFixed(1)}</em></div>` +
|
||||
`</div>` +
|
||||
(notes ? `<ul class="analytics-pf-notes">${notes}</ul>` : "") +
|
||||
`</div>`
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function sectionHtml(title, body) {
|
||||
if (!body) return "";
|
||||
return (
|
||||
`<section class="analytics-pf-section">` +
|
||||
`<h3 class="analytics-pf-section__title">${escapeHtml(title)}</h3>` +
|
||||
body +
|
||||
`</section>`
|
||||
);
|
||||
}
|
||||
|
||||
function renderItem(item) {
|
||||
const comps = item.components || [];
|
||||
const groups = item.unloadingGroups || [];
|
||||
const mixer = item.mixerRemainder || null;
|
||||
const compMax = Math.max(...comps.flatMap((c) => [c.baseKg, c.planTodayKg, c.actualKg]), 1);
|
||||
const unloadMax = Math.max(...groups.flatMap((c) => [c.baseKg, c.planTodayKg, c.actualKg]), 1);
|
||||
|
||||
const compHtml = renderMetricRows(comps, compMax, {
|
||||
executionLabel: COPY().comparison?.noteExecution,
|
||||
});
|
||||
const unloadHtml = renderMetricRows(groups, unloadMax, {
|
||||
executionLabel: COPY().comparison?.noteUnloadingExecution,
|
||||
});
|
||||
const mixerHtml = mixer
|
||||
? (() => {
|
||||
const kg = Number(mixer.actualKg);
|
||||
const sign = kg > 0 ? "+" : "";
|
||||
const cls =
|
||||
kg < 0
|
||||
? "analytics-pf-mixer-line analytics-pf-mixer-line--negative"
|
||||
: "analytics-pf-mixer-line";
|
||||
return (
|
||||
`<p class="${cls}">${escapeHtml(
|
||||
COPY().comparison?.mixerRemainderLabel || "Остаток в миксере"
|
||||
)}: <strong>${sign}${kg.toFixed(1)}</strong> кг</p>`
|
||||
);
|
||||
})()
|
||||
: "";
|
||||
|
||||
const when = item.startTime ? escapeHtml(item.startTime.slice(0, 16).replace("T", " ")) : "";
|
||||
const unloadingSection =
|
||||
groups.length || mixer
|
||||
? sectionHtml(
|
||||
COPY().comparison?.sectionUnloading || "Выгрузка",
|
||||
legendHtml() + unloadHtml + mixerHtml
|
||||
)
|
||||
: "";
|
||||
|
||||
return (
|
||||
`<article class="analytics-pf-card" data-loading-report-id="${escapeHtml(item.loadingReportId)}">` +
|
||||
`<header class="analytics-pf-card__head">` +
|
||||
`<strong>${escapeHtml(item.recipeName)}</strong>` +
|
||||
`<span class="text-muted small">${when}</span>` +
|
||||
`</header>` +
|
||||
sectionHtml(
|
||||
COPY().comparison?.sectionLoading || "Загрузка",
|
||||
legendHtml() + compHtml
|
||||
) +
|
||||
unloadingSection +
|
||||
`</article>`
|
||||
);
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
const el = document.getElementById("analyticsPlanFactList");
|
||||
if (!el) return;
|
||||
const items = data.items || [];
|
||||
if (!items.length) {
|
||||
el.innerHTML = `<div class="empty-state zt-empty wesp-content-reveal"><p class="zt-empty__hint">${escapeHtml(COPY().comparison?.empty || "")}</p></div>`;
|
||||
return;
|
||||
}
|
||||
el.innerHTML = `<div class="analytics-pf-list">${items.map(renderItem).join("")}</div>`;
|
||||
el.querySelectorAll("[data-loading-report-id]").forEach((card) => {
|
||||
card.addEventListener("click", () => {
|
||||
const id = card.getAttribute("data-loading-report-id");
|
||||
if (id) global.WespFeedAlerts?.openReportFromAlert?.(id);
|
||||
});
|
||||
});
|
||||
global.WespZootechModals?.revealContent(el.querySelector(".analytics-pf-list"));
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const el = document.getElementById("analyticsPlanFactList");
|
||||
if (!el) return;
|
||||
el.innerHTML =
|
||||
'<div class="loading-state wesp-content-reveal"><div class="zt-spinner" role="status"></div>' +
|
||||
'<p class="zt-loading-caption">Загрузка сравнения…</p></div>';
|
||||
try {
|
||||
const resp = await fetch(buildUrl());
|
||||
if (!resp.ok) throw new Error("Не удалось загрузить сравнение");
|
||||
render(await resp.json());
|
||||
} catch (err) {
|
||||
el.innerHTML = `<div class="empty-state zt-empty"><p class="zt-empty__hint">${escapeHtml(err.message)}</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function onFiltersApplied() {
|
||||
if (global.WespFeedAlerts?.getCurrentView?.() === "comparison") load();
|
||||
}
|
||||
|
||||
global.WespAnalyticsPlanFact = { load, onFiltersApplied };
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
Reference in New Issue
Block a user