fix(mgmt-perf): /mgmt-perf 정적 제공, Chart.js 동봉, 파일명 복원·차트 리플로

- Express에 /mgmt-perf → public/mgmt-perf 정적 마운트(기존 뷰 경로와 일치)
- jsdelivr 대신 chart.umd.min.js 동봉으로 CDN 차단·오프라인 대응
- decodeMultipartFilename: Latin-1→UTF-8 복원 시 한글 검사 제거(ASCII·깨진 문자열 모두)
- 페이로드/Chart 실패 시 사용자에게 빨간 안내, 차트 resize 이중 rAF

Made-with: Cursor
This commit is contained in:
2026-04-13 18:52:17 +09:00
parent 419f529d06
commit 3ab42d58ce
6 changed files with 57 additions and 4 deletions

View File

@@ -1,7 +1,22 @@
(function () {
function showMgmtPerfEmbedError(msg) {
var root = document.querySelector(".mgmt-perf-embed");
if (!root) return;
var prev = document.getElementById("mgmt-perf-embed-error");
if (prev) prev.remove();
var el = document.createElement("div");
el.id = "mgmt-perf-embed-error";
el.setAttribute("role", "alert");
el.style.cssText =
"background:#ffebee;color:#b71c1c;padding:12px;margin:8px;border-radius:8px;font-size:13px;";
el.textContent = msg;
root.insertBefore(el, root.firstChild);
}
const payloadEl = document.getElementById("mgmt-perf-payload-json");
if (!payloadEl || !payloadEl.textContent.trim()) {
console.error("mgmt-perf: missing payload");
showMgmtPerfEmbedError("대시보드 데이터(JSON)가 없습니다. 서버 배포·페이지 소스를 확인하세요.");
return;
}
let P;
@@ -9,15 +24,20 @@
P = JSON.parse(payloadEl.textContent);
} catch (err) {
console.error("mgmt-perf: invalid JSON", err);
showMgmtPerfEmbedError("대시보드 데이터 JSON 파싱에 실패했습니다.");
return;
}
if (typeof Chart === "undefined") {
console.error("mgmt-perf: Chart.js not loaded");
showMgmtPerfEmbedError(
"Chart.js를 불러오지 못했습니다. /mgmt-perf/chart.umd.min.js 경로·방화벽을 확인하세요."
);
return;
}
const UM = P.UM;
if (!UM || typeof UM !== "object") {
console.error("mgmt-perf: payload missing UM");
showMgmtPerfEmbedError("페이로드에 UM(매출 집계)이 없습니다. 스냅샷·기본 JSON을 확인하세요.");
return;
}
const ORDER_CAT = P.ORDER_CAT;
@@ -124,6 +144,18 @@ let state = {
}
}
/** 숨김 탭·레이아웃 직후 캔버스 크기 0인 경우 대비 */
function scheduleChartReflow() {
requestAnimationFrame(function () {
requestAnimationFrame(function () {
Object.keys(state.charts).forEach(function (id) {
var ch = state.charts[id];
if (ch && typeof ch.resize === "function") ch.resize();
});
});
});
}
// Division-to-order-key mapping
const DIV_TO_ORDER = {
fscan: ['fscan'],
@@ -156,6 +188,7 @@ let state = {
renderSalesModelCharts(monthIdx);
}
renderSalesCategoryCharts(monthIdx);
scheduleChartReflow();
}
const omr = document.getElementById("orderModelRow");
@@ -165,9 +198,11 @@ let state = {
if (isSectionActive(SECTION.ORDER)) {
renderOrderSection();
scheduleChartReflow();
}
if (isSectionActive(SECTION.FORECAST)) {
renderForecastSection();
scheduleChartReflow();
}
}