document.addEventListener("DOMContentLoaded", function () { const dataEl = document.getElementById("report-data"); if (!dataEl) return console.error("report-data not found"); const data = JSON.parse(dataEl.textContent || "{}"); const files = data.files || data.f || []; const rules = data.rules || data.r || {}; const tabsList = document.getElementById("tabs-list"); const reportsContainer = document.getElementById("reports-container"); if (!tabsList || !reportsContainer) { console.error("Missing tabs-list or reports-container"); return; } /* --------------------------------------------------------- UTILS --------------------------------------------------------- */ function escapeHtml(str) { if (str == null) return ""; return String(str) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function countViolations(v) { return { c: v?.c?.length || 0, w: v?.w?.length || 0, i: v?.i?.length || 0 }; } const rendered = new Set(); // ленивый рендер /* --------------------------------------------------------- RENDER TABS --------------------------------------------------------- */ function renderTabs() { const frag = document.createDocumentFragment(); files.forEach((file, index) => { const v = countViolations(file.v); const btn = document.createElement("button"); btn.className = "tab"; btn.dataset.target = `file_${index}`; btn.dataset.index = index; const inner = document.createElement("div"); inner.className = "tab-inner"; const text = document.createElement("span"); text.className = "tab-text"; text.title = file.n; text.textContent = file.n; const counters = document.createElement("div"); counters.className = "tab-counters"; const c1 = document.createElement("span"); c1.className = "tab-counter critical" + (v.c ? "" : " empty"); c1.textContent = v.c || ""; const c2 = document.createElement("span"); c2.className = "tab-counter warning" + (v.w ? "" : " empty"); c2.textContent = v.w || ""; const c3 = document.createElement("span"); c3.className = "tab-counter info" + (v.i ? "" : " empty"); c3.textContent = v.i || ""; counters.append(c1, c2, c3); inner.append(text, counters); btn.append(inner); frag.append(btn); }); // Summary tab const summaryBtn = document.createElement("button"); summaryBtn.className = "tab"; summaryBtn.dataset.target = "summary_report"; const inner = document.createElement("div"); inner.className = "tab-inner"; const text = document.createElement("span"); text.className = "tab-text"; text.textContent = "Summary"; inner.append(text); summaryBtn.append(inner); frag.append(summaryBtn); tabsList.innerHTML = ""; tabsList.append(frag); } /* --------------------------------------------------------- GRID ROW CREATOR (VIOLATIONS) --------------------------------------------------------- */ function createGridRow(v, rule) { const row = document.createElement("div"); row.className = "grid-row"; const cIndex = document.createElement("div"); cIndex.textContent = v.i; const cLine = document.createElement("div"); cLine.textContent = v.l; const cCol = document.createElement("div"); cCol.textContent = v.c; const cRule = document.createElement("div"); cRule.textContent = rule.n; let text = rule.t || ""; const args = v.a || v.args; if (Array.isArray(args)) { for (let i = 0; i < args.length; i++) { text = text.replace(`{${i}}`, args[i]); } } const cDesc = document.createElement("div"); cDesc.textContent = text; row.append(cIndex, cLine, cCol, cRule, cDesc); return row; } /* --------------------------------------------------------- FILE REPORT --------------------------------------------------------- */ function renderFileReport(index) { const file = files[index]; const v = file.v || {}; const root = document.createElement("div"); // Title const titleWrap = document.createElement("div"); titleWrap.className = "file-title-container"; const title = document.createElement("div"); title.className = "file-title"; const name = document.createElement("span"); name.className = "file-name"; name.textContent = file.n; title.append(name); titleWrap.append(title); root.append(titleWrap); // Sections function addSection(label, list, cls) { if (!Array.isArray(list) || list.length === 0) return; const section = document.createElement("div"); section.className = `severity-section ${cls}`; const header = document.createElement("div"); header.className = "severity-header"; const hTitle = document.createElement("div"); hTitle.className = "severity-title"; const h2 = document.createElement("h2"); h2.textContent = label; const count = document.createElement("span"); count.className = "severity-count"; count.textContent = list.length; hTitle.append(h2, count); header.append(hTitle); section.append(header); const grid = document.createElement("div"); grid.className = "grid-table"; // header row const headerRow = document.createElement("div"); headerRow.className = "grid-header"; ["#", "Строка", "Колонка", "Правило", "Описание"].forEach(t => { const cell = document.createElement("div"); cell.textContent = t; headerRow.append(cell); }); grid.append(headerRow); // rows for (let i = 0; i < list.length; i++) { const vItem = list[i]; const rule = rules[vItem.r] || { n: "Unknown", t: "Описание отсутствует" }; grid.append(createGridRow(vItem, rule)); } section.append(grid); root.append(section); } addSection("Critical", v.c, "critical"); addSection("Warning", v.w, "warning"); addSection("Info", v.i, "info"); return root; } /* --------------------------------------------------------- SUMMARY --------------------------------------------------------- */ function renderSummary() { let totalC = 0, totalW = 0, totalI = 0; for (let i = 0; i < files.length; i++) { const v = countViolations(files[i].v); totalC += v.c; totalW += v.w; totalI += v.i; } const total = totalC + totalW + totalI || 1; const root = document.createElement("div"); // Title const titleWrap = document.createElement("div"); titleWrap.className = "file-title-container"; const title = document.createElement("div"); title.className = "file-title"; const name = document.createElement("span"); name.className = "file-name"; name.textContent = "Сводный отчет"; title.append(name); titleWrap.append(title); root.append(titleWrap); // Stats const section = document.createElement("div"); section.className = "summary-section"; const header = document.createElement("div"); header.className = "summary-header"; const hTitle = document.createElement("div"); hTitle.className = "summary-title"; const h2 = document.createElement("h2"); h2.textContent = "Общая статистика"; hTitle.append(h2); header.append(hTitle); section.append(header); const grid = document.createElement("div"); grid.className = "summary-stats-grid"; function statCard(cls, value, label) { const card = document.createElement("div"); card.className = `stat-card ${cls}`; const v = document.createElement("div"); v.className = "stat-value"; v.textContent = value; const l = document.createElement("div"); l.className = "stat-label"; l.textContent = label; card.append(v, l); return card; } grid.append( statCard("success", files.length, "Всего файлов"), statCard("critical", totalC, "Critical нарушений"), statCard("warning", totalW, "Warning нарушений"), statCard("info", totalI, "Info нарушений") ); section.append(grid); // Progress const dist = document.createElement("div"); dist.className = "violation-distribution"; const bar = document.createElement("div"); bar.className = "progress-bar"; const pc = (totalC / total) * 100; const pw = (totalW / total) * 100; const pi = (totalI / total) * 100; const f1 = document.createElement("div"); f1.className = "progress-fill critical"; f1.style.width = pc + "%"; const f2 = document.createElement("div"); f2.className = "progress-fill warning"; f2.style.width = pw + "%"; const f3 = document.createElement("div"); f3.className = "progress-fill info"; f3.style.width = pi + "%"; bar.append(f1, f2, f3); dist.append(bar); section.append(dist); root.append(section); // Files overview const section2 = document.createElement("div"); section2.className = "summary-section files-overview"; const header2 = document.createElement("div"); header2.className = "summary-header"; const hTitle2 = document.createElement("div"); hTitle2.className = "summary-title"; const h22 = document.createElement("h2"); h22.textContent = "Статистика файлов"; const count = document.createElement("span"); count.className = "severity-count"; count.textContent = files.length; hTitle2.append(h22, count); header2.append(hTitle2); section2.append(header2); const filesGrid = document.createElement("div"); filesGrid.className = "files-grid"; for (let i = 0; i < files.length; i++) { const f = files[i]; const v = countViolations(f.v); const t = v.c + v.w + v.i || 1; const card = document.createElement("div"); card.className = "file-card"; const header = document.createElement("div"); header.className = "file-card-header"; const name = document.createElement("span"); name.className = "file-name-small"; name.textContent = f.n; header.append(name); card.append(header); const bar = document.createElement("div"); bar.className = "progress-bar small"; const fc = document.createElement("div"); fc.className = "progress-fill critical"; fc.style.width = (v.c / t * 100) + "%"; const fw = document.createElement("div"); fw.className = "progress-fill warning"; fw.style.width = (v.w / t * 100) + "%"; const fi = document.createElement("div"); fi.className = "progress-fill info"; fi.style.width = (v.i / t * 100) + "%"; bar.append(fc, fw, fi); card.append(bar); const bottom = document.createElement("div"); bottom.className = "file-card-bottom"; const total = document.createElement("span"); total.className = "file-total"; total.textContent = "Total: " + (v.c + v.w + v.i); const badges = document.createElement("div"); badges.className = "file-violations"; function badge(cls, val) { const b = document.createElement("span"); b.className = `violation-badge ${cls}`; b.textContent = val; return b; } badges.append( badge("critical", v.c), badge("warning", v.w), badge("info", v.i) ); bottom.append(total, badges); card.append(bottom); filesGrid.append(card); } section2.append(filesGrid); root.append(section2); return root; } /* --------------------------------------------------------- TAB HANDLER --------------------------------------------------------- */ tabsList.addEventListener("click", function (e) { const tab = e.target.closest(".tab"); if (!tab) return; const targetId = tab.dataset.target; // activate tab tabsList.querySelectorAll(".tab").forEach(t => t.classList.remove("active")); tab.classList.add("active"); // hide all reports reportsContainer.innerHTML = ""; // lazy render let content; if (!rendered.has(targetId)) { if (targetId === "summary_report") { content = renderSummary(); } else { const index = Number(tab.dataset.index); content = renderFileReport(index); } rendered.add(targetId); } else { // already rendered → but we removed DOM → re-render if (targetId === "summary_report") { content = renderSummary(); } else { const index = Number(tab.dataset.index); content = renderFileReport(index); } } reportsContainer.append(content); window.scrollTo({ top: 0, behavior: "smooth" }); }); /* --------------------------------------------------------- INIT --------------------------------------------------------- */ renderTabs(); const first = tabsList.querySelector(".tab"); if (first) first.click(); });