feat(mgmt-perf): 대시보드 연도·분기 조회(스냅샷별 로드)

- getPayloadRowForPeriod, listDistinctPeriods
- /dashboard/business-performance?year=&quarter= 및 상단 GET 폼
- 해당 기간 업로드 없을 때 샘플+안내

Made-with: Cursor
This commit is contained in:
2026-04-13 20:05:35 +09:00
parent dc7fca414e
commit 3872c32a91
4 changed files with 227 additions and 8 deletions

View File

@@ -1317,21 +1317,60 @@ pageRouter.get("/dashboard", requireDashboardAccess, (req, res) =>
opsUserEmail: !!res.locals.opsUserEmail,
})
);
function mgmtPerfBuildYearOptions(periods, viewYear) {
const set = new Set();
(periods || []).forEach((p) => {
if (p.fiscal_year != null) set.add(Number(p.fiscal_year));
});
const y = Number(viewYear) || new Date().getFullYear();
set.add(y);
const cy = new Date().getFullYear();
for (let i = cy - 3; i <= cy + 2; i++) set.add(i);
return Array.from(set)
.filter((n) => n >= 2020 && n <= 2100)
.sort((a, b) => b - a);
}
pageRouter.get("/dashboard/business-performance", requireDashboardAccess, async (req, res, next) => {
try {
const latest = await mgmtPerf.getLatestPayloadRow(pgPool);
const qy = parseInt(req.query.year, 10);
const qq = parseInt(req.query.quarter, 10);
const hasPeriodQuery =
Number.isFinite(qy) && qy >= 2020 && qy <= 2100 && Number.isFinite(qq) && qq >= 1 && qq <= 4;
const periods = await mgmtPerf.listDistinctPeriods(pgPool);
let row;
let viewYear;
let viewQuarter;
if (hasPeriodQuery) {
viewYear = qy;
viewQuarter = qq;
row = await mgmtPerf.getPayloadRowForPeriod(pgPool, viewYear, viewQuarter);
} else {
const latest = await mgmtPerf.getLatestPayloadRow(pgPool);
viewYear = latest.fiscal_year || new Date().getFullYear();
viewQuarter = latest.quarter || 1;
row = { ...latest, _noSnapshotForPeriod: false };
}
const uploadHistory = await mgmtPerf.listUploads(pgPool, 12);
const y = latest.fiscal_year || new Date().getFullYear();
const q = latest.quarter || 1;
const payloadJson = JSON.stringify(latest.payload).replace(/</g, "\\u003c");
const y = row.fiscal_year != null ? row.fiscal_year : viewYear;
const q = row.quarter != null ? row.quarter : viewQuarter;
const payloadJson = JSON.stringify(row.payload).replace(/</g, "\\u003c");
const dashboardTitle = `${y} Q${q} 경영성과 대시보드`;
const quarterLabel = `Q${q}`;
const yearOptions = mgmtPerfBuildYearOptions(periods, viewYear);
res.render("dashboard-business-performance", {
activeMenu: "dashboard",
adminMode: res.locals.adminMode,
opsUserEmail: !!res.locals.opsUserEmail,
defaultYear: y,
selectedQuarter: q,
defaultYear: viewYear,
selectedQuarter: viewQuarter,
viewYear,
viewQuarter,
yearOptions,
noSnapshotForPeriod: !!row._noSnapshotForPeriod,
uploadHistory,
payloadJson,
dashboardTitle,
@@ -1343,7 +1382,17 @@ pageRouter.get("/dashboard/business-performance", requireDashboardAccess, async
});
pageRouter.get("/dashboard/business-performance/embed", requireDashboardAccess, async (req, res, next) => {
try {
const row = await mgmtPerf.getLatestPayloadRow(pgPool);
const qy = parseInt(req.query.year, 10);
const qq = parseInt(req.query.quarter, 10);
const hasPeriodQuery =
Number.isFinite(qy) && qy >= 2020 && qy <= 2100 && Number.isFinite(qq) && qq >= 1 && qq <= 4;
let row;
if (hasPeriodQuery) {
row = await mgmtPerf.getPayloadRowForPeriod(pgPool, qy, qq);
} else {
row = await mgmtPerf.getLatestPayloadRow(pgPool);
}
const fy = row.fiscal_year || new Date().getFullYear();
const q = row.quarter || 1;
const payloadJson = JSON.stringify(row.payload).replace(/</g, "\\u003c");