152 lines
6.0 KiB
JavaScript
152 lines
6.0 KiB
JavaScript
/**
|
|
* Вкладка «Итоги» на /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/finance?${params.toString()}`;
|
|
}
|
|
|
|
function valueToneClass(kind) {
|
|
if (kind === "loss") return "analytics-kpi-card__value--loss";
|
|
if (kind === "risk") return "analytics-kpi-card__value--risk";
|
|
return "";
|
|
}
|
|
|
|
function netHint(data) {
|
|
const c = COPY().summary || {};
|
|
if (data.dominantIssue === "overload") return c.netHintOverload || "";
|
|
if (data.dominantIssue === "underload") return c.netHintUnderload || "";
|
|
return c.netHintBalanced || "";
|
|
}
|
|
|
|
function overloadHint(data) {
|
|
const c = COPY().summary || {};
|
|
if (Number(data.overloadRub) > 0) return c.overloadHint || "";
|
|
return c.overloadHintZero || c.overloadHint || "";
|
|
}
|
|
|
|
function underloadHint(data) {
|
|
const c = COPY().summary || {};
|
|
if (Number(data.underloadRub) > 0) return c.underloadHint || "";
|
|
return c.underloadHintZero || c.underloadHint || "";
|
|
}
|
|
|
|
function netTone(data) {
|
|
if (data.dominantIssue === "overload") return "loss";
|
|
if (data.dominantIssue === "underload") return "risk";
|
|
return "";
|
|
}
|
|
|
|
function renderTopBar(data) {
|
|
const top = data.topComponents || [];
|
|
if (!top.length) {
|
|
return `<p class="text-muted small mb-0">${escapeHtml(COPY().summary?.empty || "")}</p>`;
|
|
}
|
|
const maxVal = Math.max(
|
|
...top.map((row) => Math.max(row.overloadRub, row.underloadRub)),
|
|
1
|
|
);
|
|
const rows = top
|
|
.map((row) => {
|
|
const isOverload = row.overloadRub > row.underloadRub;
|
|
const main = isOverload
|
|
? `${COPY().formatRub(row.overloadRub)} перерасход`
|
|
: `${COPY().formatRub(row.underloadRub)} недогруз`;
|
|
const tone = isOverload ? "analytics-top-row__value--loss" : "analytics-top-row__value--risk";
|
|
const widthPct = Math.round(
|
|
Math.min(100, (Math.max(row.overloadRub, row.underloadRub) / maxVal) * 1000)
|
|
) / 10;
|
|
return (
|
|
`<div class="analytics-top-row">` +
|
|
`<span class="analytics-top-row__name">${escapeHtml(row.name)}</span>` +
|
|
`<span class="analytics-top-row__bar analytics-top-row__bar--${isOverload ? "loss" : "risk"}">` +
|
|
`<span style="width:${widthPct}%"></span></span>` +
|
|
`<span class="analytics-top-row__value ${tone}">${escapeHtml(main)}</span>` +
|
|
`</div>`
|
|
);
|
|
})
|
|
.join("");
|
|
return `<div class="analytics-top-list">${rows}</div>`;
|
|
}
|
|
|
|
function render(data) {
|
|
const el = document.getElementById("analyticsSummaryList");
|
|
if (!el) return;
|
|
const c = COPY().summary || {};
|
|
const overloadTone = Number(data.overloadRub) > 0 ? "loss" : "";
|
|
const underloadTone = Number(data.underloadRub) > 0 ? "risk" : "";
|
|
const netT = netTone(data);
|
|
|
|
el.innerHTML =
|
|
`<div class="analytics-kpi-grid wesp-content-reveal">` +
|
|
`<div class="analytics-kpi-card">` +
|
|
`<div class="analytics-kpi-card__title">${escapeHtml(c.overloadTitle)}</div>` +
|
|
`<div class="analytics-kpi-card__value ${valueToneClass(overloadTone)}">${escapeHtml(COPY().formatRub(data.overloadRub))}</div>` +
|
|
`<div class="analytics-kpi-card__hint">${escapeHtml(overloadHint(data))}</div>` +
|
|
`</div>` +
|
|
`<div class="analytics-kpi-card">` +
|
|
`<div class="analytics-kpi-card__title">${escapeHtml(c.underloadTitle)}</div>` +
|
|
`<div class="analytics-kpi-card__value ${valueToneClass(underloadTone)}">${escapeHtml(COPY().formatRub(data.underloadRub))}</div>` +
|
|
`<div class="analytics-kpi-card__hint">${escapeHtml(underloadHint(data))}</div>` +
|
|
`</div>` +
|
|
`<div class="analytics-kpi-card">` +
|
|
`<div class="analytics-kpi-card__title">${escapeHtml(c.netTitle)}</div>` +
|
|
`<div class="analytics-kpi-card__value ${valueToneClass(netT)}">${escapeHtml(COPY().formatRub(Math.abs(data.netRub)))}</div>` +
|
|
`<div class="analytics-kpi-card__hint">${escapeHtml(netHint(data))}</div>` +
|
|
`</div>` +
|
|
`</div>` +
|
|
`<div class="analytics-top-block zt-section wesp-content-reveal">` +
|
|
`<h3 class="analytics-section-title">${escapeHtml(c.topTitle)}</h3>` +
|
|
renderTopBar(data) +
|
|
`</div>` +
|
|
`<div class="analytics-links wesp-content-reveal">` +
|
|
`<button type="button" class="btn btn-link btn-sm p-0" data-analytics-goto-alerts>${escapeHtml(c.allAlertsLink)}</button>` +
|
|
`</div>`;
|
|
el.querySelector("[data-analytics-goto-alerts]")?.addEventListener("click", () => {
|
|
global.WespFeedAlerts?.openJournalAlerts?.();
|
|
});
|
|
global.WespZootechModals?.revealContent(el);
|
|
}
|
|
|
|
async function load() {
|
|
const el = document.getElementById("analyticsSummaryList");
|
|
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?.() === "summary") load();
|
|
}
|
|
|
|
function init() {
|
|
/* экспорт — WespReportsExport */
|
|
}
|
|
|
|
global.WespAnalyticsSummary = { init, load, onFiltersApplied };
|
|
})(typeof window !== "undefined" ? window : globalThis);
|