+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
파일을 업로드하면 프로젝트 현황이 표시됩니다.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
분석 데이터를 기다리는 중..
+
+
+
+
+
+
+
+
+
+
+
+
Target Limit
+
+
0.00h
+
+
+
+
※ 인정시간: 평일(8시간+연장 3시간) 및 주말(5시간)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
일
+
월
+
화
+
수
+
목
+
금
+
토
+
+
+
+
+
+
+
+
+
diff --git a/incoming-files/README.md b/incoming-files/README.md
index 000dad1..eacefdb 100644
--- a/incoming-files/README.md
+++ b/incoming-files/README.md
@@ -8,10 +8,12 @@
- 현재 사용 파일:
- `served/payment.html`
- `served/mh.html`
+ - `served/ledger/index.html`
주의:
- backend `/integrations/payment`, `/integrations/mh`는 위 `served/*`만 읽는다.
+- backend `/integrations/ledger`와 `/integrations/ledger-assets/*`도 `served/ledger/*`만 읽는다.
- 새 기능을 붙일 때도 실제 서비스 파일은 `served/` 기준으로 수정한다.
## Reference
@@ -26,6 +28,8 @@
- 샘플 스타일 파일
- 원본/백업 HTML
- 디자인 비교용 파일
+- `reference/ledger/MH 통합 대시보드_260320.html`
+- `reference/ledger/MH 통합 대시보드_260320.css`
## Temporary Comparison Copies
diff --git a/incoming-files/reference/README.md b/incoming-files/reference/README.md
index cd52bc4..ddf5ad6 100644
--- a/incoming-files/reference/README.md
+++ b/incoming-files/reference/README.md
@@ -1,9 +1,21 @@
# Reference Assets
-이 디렉터리는 앞으로 `8081`에서 직접 서빙하지 않는 참고 원본/복구 비교 자산을 모으기 위한 공간이다.
+이 디렉터리는 `8081`에서 직접 서빙하지 않는 참고 원본/복구 비교 자산을 모으는 공간이다.
-1차 정리에서는 위험한 대량 이동을 피하기 위해 기존 참고 파일을 즉시 옮기지 않는다.
-대신 실제 서빙 파일은 `incoming-files/served/`로 고정하고, 다음 차수에서 참고 자산을 단계적으로 재배치한다.
+`#21` 2차부터 실제 reference 재배치를 시작했다.
+
+현재 포함:
+
+- `ledger/`
+ - 사업관리대장 원본 wrapper/html/css/xlsx
+ - 이전 override 복사본
+ - 중첩 백업 디렉터리
+
+규칙:
+
+- runtime은 이 디렉터리를 직접 서빙하지 않는다.
+- 실제 서비스 수정은 `incoming-files/served/` 기준으로 먼저 반영한다.
+- reference는 비교, 복구, 출처 확인이 필요할 때만 본다.
예상 대상:
diff --git a/incoming-files/사업관리대장/사업관리대장/MH 통합 대시보드_260320.css b/incoming-files/reference/ledger/MH 통합 대시보드_260320.css
similarity index 100%
rename from incoming-files/사업관리대장/사업관리대장/MH 통합 대시보드_260320.css
rename to incoming-files/reference/ledger/MH 통합 대시보드_260320.css
diff --git a/incoming-files/사업관리대장/MH 통합 대시보드_260320.html b/incoming-files/reference/ledger/MH 통합 대시보드_260320.html
similarity index 100%
rename from incoming-files/사업관리대장/MH 통합 대시보드_260320.html
rename to incoming-files/reference/ledger/MH 통합 대시보드_260320.html
diff --git a/incoming-files/reference/ledger/ledger-override.css b/incoming-files/reference/ledger/ledger-override.css
new file mode 100644
index 0000000..505b65e
--- /dev/null
+++ b/incoming-files/reference/ledger/ledger-override.css
@@ -0,0 +1,328 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body.mh-business-theme {
+ overflow-x: hidden;
+ background:
+ radial-gradient(circle at top left, rgba(214, 138, 58, 0.16), transparent 24%),
+ radial-gradient(circle at top right, rgba(47, 153, 115, 0.10), transparent 20%),
+ linear-gradient(180deg, #f6efe6 0%, #f1eadf 100%);
+}
+
+body.mh-business-theme .wrap {
+ width: min(100%, 2000px);
+ max-width: 2000px;
+ margin: 0 auto;
+ padding: 18px 18px 26px;
+ box-sizing: border-box;
+}
+
+body.mh-business-theme .top,
+body.mh-business-theme .status {
+ display: none !important;
+}
+
+body.mh-business-theme .cards {
+ display: grid;
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ gap: 14px;
+ margin: 0 0 16px;
+}
+
+body.mh-business-theme .business-shell {
+ width: 100%;
+ box-sizing: border-box;
+ margin-top: 2px;
+ padding: 18px;
+ border-radius: 32px;
+ background:
+ radial-gradient(circle at 16% 14%, rgba(255,255,255,0.05), transparent 18%),
+ radial-gradient(circle at 88% 8%, rgba(255,255,255,0.04), transparent 16%),
+ linear-gradient(145deg, #0b352b 0%, #174e41 52%, #245f50 100%);
+ box-shadow: 0 26px 54px rgba(15, 58, 47, 0.16);
+ border: 1px solid rgba(255,255,255,0.08);
+}
+
+body.mh-business-theme .cards-toolbar {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 10px 0 2px;
+}
+
+body.mh-business-theme .cards-toolbar-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+body.mh-business-theme .cards-toolbar-search {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ min-width: min(360px, 100%);
+ flex: 1 1 320px;
+ max-width: 520px;
+}
+
+body.mh-business-theme .cards-toolbar-search .search {
+ width: 100%;
+ min-width: 0;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.12);
+ background: rgba(255,255,255,0.10);
+ color: #f4efe6;
+ padding: 14px 18px;
+ font-size: 14px;
+ font-weight: 800;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
+}
+
+body.mh-business-theme .cards-toolbar-search .search::placeholder {
+ color: rgba(244, 239, 230, 0.74);
+}
+
+body.mh-business-theme #btnUpload {
+ display: none !important;
+}
+
+body.mh-business-theme .cards-toolbar-metrics {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 14px;
+}
+
+body.mh-business-theme .summary-year-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 60px;
+ padding: 10px 16px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.14);
+ background: rgba(255,255,255,0.08);
+ color: #f4efe6;
+ font-size: 12px;
+ font-weight: 900;
+ cursor: pointer;
+}
+
+body.mh-business-theme .summary-year-chip.active {
+ background: linear-gradient(180deg, #fff8ee 0%, #f2dec0 100%);
+ color: #0a2a22;
+ border-color: rgba(242, 196, 132, 0.58);
+ box-shadow: 0 12px 28px rgba(10, 42, 34, 0.18);
+}
+
+body.mh-business-theme .summary-filter-chip {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ width: 100%;
+ min-height: 98px;
+ padding: 18px 22px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.14);
+ background: linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.07) 100%);
+ color: #f4efe6;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 16px 30px rgba(7, 28, 22, 0.14);
+ cursor: pointer;
+ text-align: center;
+}
+
+body.mh-business-theme .summary-filter-chip.active {
+ background: linear-gradient(180deg, #fff8ee 0%, #f2dec0 100%);
+ color: #0a2a22;
+ border-color: rgba(242, 196, 132, 0.58);
+}
+
+body.mh-business-theme .summary-filter-chip .label {
+ color: rgba(244, 239, 230, 0.78);
+ font-size: 13px;
+ font-weight: 900;
+}
+
+body.mh-business-theme .summary-filter-chip.active .label {
+ color: rgba(10, 42, 34, 0.78);
+}
+
+body.mh-business-theme .summary-filter-chip .count {
+ color: #fff7e6;
+ font-size: 32px;
+ line-height: 1;
+ font-weight: 900;
+}
+
+body.mh-business-theme .summary-filter-chip.active .count {
+ color: #b86b1f;
+}
+
+body.mh-business-theme .summary-filter-chip .meta {
+ color: #f2c484;
+ font-size: 11px;
+ font-weight: 800;
+ text-align: center;
+}
+
+body.mh-business-theme .summary-filter-chip.active .meta {
+ color: #7c5a20;
+}
+
+body.mh-business-theme .card {
+ grid-column: span 2;
+ min-height: 110px;
+ border-radius: 24px;
+ border: 1px solid rgba(217, 197, 168, 0.55);
+ background: linear-gradient(180deg, rgba(255,250,243,0.96) 0%, rgba(248,242,232,0.96) 100%);
+ padding: 18px 20px;
+ box-shadow: 0 18px 32px rgba(15, 58, 47, 0.08);
+}
+
+body.mh-business-theme .card.management {
+ grid-column: span 2;
+}
+
+body.mh-business-theme .card .k {
+ color: #5b6d63;
+ font-size: 12px;
+ font-weight: 900;
+}
+
+body.mh-business-theme .card .v {
+ margin-top: 8px;
+ color: #17392f;
+ font-size: 30px;
+ font-weight: 900;
+}
+
+body.mh-business-theme .card .n {
+ margin-top: 8px;
+ color: #7b6953;
+ font-size: 11px;
+ font-weight: 700;
+}
+
+body.mh-business-theme .panel {
+ border-radius: 28px;
+ border: 1px solid rgba(217, 197, 168, 0.55);
+ box-shadow: 0 18px 32px rgba(15, 58, 47, 0.08);
+}
+
+body.mh-business-theme .table-wrap {
+ width: 100%;
+ max-width: 100%;
+ border-radius: 28px;
+ overflow-x: hidden !important;
+}
+
+body.mh-business-theme .table-vat-note {
+ display: none !important;
+}
+
+body.mh-business-theme table {
+ width: 100% !important;
+ min-width: 0 !important;
+ table-layout: fixed;
+ background: rgba(255, 250, 243, 0.96);
+}
+
+body.mh-business-theme thead th {
+ background: #0f352b;
+ color: #fff5e6;
+ border-right: 1px solid rgba(242, 196, 132, 0.2);
+}
+
+body.mh-business-theme tbody td {
+ background: rgba(255, 250, 243, 0.96);
+}
+
+body.mh-business-theme .group-row td {
+ padding: 12px 14px 10px;
+ background: linear-gradient(180deg, rgba(255, 248, 238, 0.98) 0%, rgba(242, 222, 192, 0.78) 100%);
+ border-top: 1px solid rgba(214, 138, 58, 0.26);
+ border-bottom: 1px solid rgba(217, 197, 168, 0.54);
+}
+
+body.mh-business-theme .group-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: rgba(255, 250, 243, 0.98);
+ border: 1px solid rgba(214, 138, 58, 0.3);
+ color: #17392f;
+ font-size: 12px;
+ font-weight: 900;
+ box-shadow: 0 8px 18px rgba(15, 58, 47, 0.08);
+ cursor: pointer;
+}
+
+body.mh-business-theme .group-chip .group-toggle {
+ margin-left: 4px;
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 999px;
+ background: rgba(242, 196, 132, 0.18);
+ color: #b66e22;
+ font-size: 14px;
+ line-height: 1;
+}
+
+body.mh-business-theme .project-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 0;
+ border: 0;
+ background: none;
+ color: #17392f;
+ font: inherit;
+ font-weight: 900;
+ text-align: left;
+ cursor: pointer;
+}
+
+body.mh-business-theme .project-link:hover {
+ color: #0f6a55;
+}
+
+@media (max-width: 1280px) {
+ body.mh-business-theme .cards-toolbar-metrics {
+ grid-template-columns: 1fr;
+ }
+
+ body.mh-business-theme .card {
+ grid-column: span 4;
+ }
+}
+
+@media (max-width: 880px) {
+ body.mh-business-theme .wrap {
+ padding: 12px 12px 20px;
+ }
+
+ body.mh-business-theme .cards {
+ grid-template-columns: 1fr;
+ }
+
+ body.mh-business-theme .card {
+ grid-column: auto;
+ }
+
+ body.mh-business-theme .cards-toolbar-search {
+ margin-left: 0;
+ max-width: none;
+ flex-basis: 100%;
+ }
+}
diff --git a/incoming-files/reference/ledger/ledger-override.js b/incoming-files/reference/ledger/ledger-override.js
new file mode 100644
index 0000000..853e51c
--- /dev/null
+++ b/incoming-files/reference/ledger/ledger-override.js
@@ -0,0 +1,498 @@
+(function () {
+ window.__mhLedgerEnhancementLoaded = false;
+ if (typeof S === "undefined" || typeof E === "undefined" || typeof render !== "function") return;
+ window.__mhLedgerEnhancementLoaded = true;
+ if (!S.dashboard) S.dashboard = { year: "", section: "active" };
+ if (!S.collapsedGroups) S.collapsedGroups = {};
+
+ function bgToday() {
+ var now = new Date();
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ }
+
+ function bgParseDate(value) {
+ var text = String(value || "").trim();
+ if (!text) return null;
+ var match = text.match(/(20\d{2})\D?(\d{1,2})\D?(\d{1,2})/);
+ if (match) {
+ var parsed = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
+ return isNaN(parsed.getTime()) ? null : parsed;
+ }
+ var fallback = new Date(text);
+ if (isNaN(fallback.getTime())) return null;
+ return new Date(fallback.getFullYear(), fallback.getMonth(), fallback.getDate());
+ }
+
+ function bgYearFromText(value) {
+ var match = String(value || "").trim().match(/(20\d{2})/);
+ return match ? match[1] : "";
+ }
+
+ function bgStartYear(row) {
+ return bgYearFromText(row && row.sDate);
+ }
+
+ function bgEndYear(row) {
+ return bgYearFromText(row && row.eDate);
+ }
+
+ function bgDisplayYear(row) {
+ var start = bgStartYear(row);
+ if (start) return start;
+ var contractMatch = String((row && row.cDate) || "").trim().match(/(20\d{2})/);
+ if (contractMatch) return contractMatch[1];
+ var nameMatch = String((row && row.name) || "").trim().match(/^(20\d{2})/);
+ if (nameMatch) return nameMatch[1];
+ return bgEndYear(row) || "미지정";
+ }
+
+ function bgCompletionYear(row) {
+ return bgEndYear(row) || bgDisplayYear(row);
+ }
+
+ function bgDateOrYearStart(row) {
+ var yearText = bgDisplayYear(row);
+ return bgParseDate(row && row.sDate) || bgParseDate(row && row.cDate) || (/^20\d{2}$/.test(yearText) ? new Date(Number(yearText), 0, 1) : null);
+ }
+
+ function bgDateOrYearEnd(row) {
+ var completionYear = bgCompletionYear(row);
+ return bgParseDate(row && row.eDate) || (/^20\d{2}$/.test(completionYear) ? new Date(Number(completionYear), 11, 31) : null);
+ }
+
+ function bgYearCutoff(year) {
+ var targetYear = Number(year || 0);
+ if (!targetYear) return null;
+ var today = bgToday();
+ if (targetYear < today.getFullYear()) return new Date(targetYear, 11, 31);
+ if (targetYear === today.getFullYear()) return today;
+ return null;
+ }
+
+ function bgYearStartDate(year) {
+ var targetYear = Number(year || 0);
+ return targetYear ? new Date(targetYear, 0, 1) : null;
+ }
+
+ function bgActiveInYear(row, year) {
+ var cutoff = bgYearCutoff(year);
+ var yearStart = bgYearStartDate(year);
+ var startDate = bgDateOrYearStart(row);
+ var endDate = bgDateOrYearEnd(row);
+ if (!(cutoff && yearStart && startDate)) return false;
+ if (startDate > cutoff) return false;
+ if (endDate && endDate < yearStart) return false;
+ return !(endDate && endDate <= cutoff);
+ }
+
+ function bgStartedInYear(row, year) {
+ var cutoff = bgYearCutoff(year);
+ var startDate = bgDateOrYearStart(row);
+ if (!(cutoff && startDate)) return false;
+ return startDate.getFullYear() === Number(year || 0) && startDate <= cutoff;
+ }
+
+ function bgCompletedInYear(row, year) {
+ var cutoff = bgYearCutoff(year);
+ var endDate = bgDateOrYearEnd(row);
+ if (!(cutoff && endDate)) return false;
+ return endDate.getFullYear() === Number(year || 0) && endDate <= cutoff;
+ }
+
+ function bgYearRange(row) {
+ var years = [];
+ var startYear = Number(bgDisplayYear(row) || 0);
+ var endYear = Number(bgCompletionYear(row) || 0);
+ if (startYear && endYear && endYear >= startYear) {
+ for (var year = startYear; year <= endYear; year += 1) years.push(String(year));
+ } else if (startYear) {
+ years.push(String(startYear));
+ }
+ return years;
+ }
+
+ function bgYears(rows) {
+ var currentYear = new Date().getFullYear();
+ var years = Array.from(new Set((Array.isArray(rows) ? rows : []).flatMap(bgYearRange).filter(function (year) {
+ return /^20\d{2}$/.test(year);
+ }))).sort(function (a, b) {
+ return Number(b) - Number(a);
+ });
+ years = years.filter(function (year) {
+ var numericYear = Number(year);
+ return numericYear >= 2018 && numericYear <= currentYear;
+ });
+ return years.length ? years : [String(currentYear)];
+ }
+
+ function bgEnsureYear(rows) {
+ var years = bgYears(rows);
+ if (!years.includes(S.dashboard.year)) S.dashboard.year = years[0];
+ return years;
+ }
+
+ function bgTotals(targetRows) {
+ return (Array.isArray(targetRows) ? targetRows : []).reduce(function (acc, row) {
+ acc.c += Number((row && row.cSup) || 0);
+ acc.col += Number((row && row.col) || 0);
+ acc.recv += Number((row && row.recv) || 0);
+ return acc;
+ }, { c: 0, col: 0, recv: 0 });
+ }
+
+ function isSupportServiceRow(row) {
+ var category = String((row && row.cat) || "").trim();
+ return category.indexOf("경영지원") >= 0 || category.indexOf("서비스") >= 0;
+ }
+
+ function isBaronProjectRow(row) {
+ var category = String((row && row.cat) || "").trim();
+ if (category.indexOf("바론") < 0) return false;
+ if (isSupportServiceRow(row)) return false;
+ return true;
+ }
+
+ function bgSummarize(rows, selectedYear) {
+ var items = Array.isArray(rows) ? rows : [];
+ var targetYear = selectedYear || bgEnsureYear(items)[0];
+ var activeRows = items.filter(function (row) { return bgActiveInYear(row, targetYear); });
+ var newProjectRows = items.filter(function (row) { return bgStartedInYear(row, targetYear); });
+ var completedRows = items.filter(function (row) { return bgCompletedInYear(row, targetYear); });
+ var managementRows = newProjectRows.filter(isSupportServiceRow);
+ return {
+ targetYear: targetYear,
+ activeRows: activeRows,
+ newProjectRows: newProjectRows,
+ completedRows: completedRows,
+ managementRows: managementRows,
+ managementTotals: bgTotals(managementRows)
+ };
+ }
+
+ function bgMatches(row) {
+ var section = S.dashboard.section || "active";
+ var selectedYear = S.dashboard.year || bgEnsureYear(S.all)[0];
+ if (section === "new") return bgStartedInYear(row, selectedYear);
+ if (section === "completed") return bgCompletedInYear(row, selectedYear);
+ return bgActiveInYear(row, selectedYear);
+ }
+
+ function normalizeStatusLabel(status) {
+ var value = String(status || "").trim();
+ if (!value) return "-";
+ if (value.indexOf("진행") >= 0) return "과업 진행중";
+ return value;
+ }
+
+ function formatSplitPercent(split) {
+ var numeric = parseFloat(String(split || "").replace(/[^0-9.\-]/g, ""));
+ if (!Number.isFinite(numeric) || numeric === 0) return "분담율 -%";
+ return "분담율 " + numeric.toFixed(2) + "%";
+ }
+
+ function projectYear(row) {
+ var start = String((row && row.sDate) || "").trim();
+ var startMatch = start.match(/(20\d{2})/);
+ if (startMatch) return startMatch[1];
+ var name = String((row && row.name) || "").trim();
+ var nameMatch = name.match(/^(20\d{2})/);
+ if (nameMatch) return nameMatch[1];
+ var end = String((row && row.eDate) || "").trim();
+ var endMatch = end.match(/(20\d{2})/);
+ if (endMatch) return endMatch[1];
+ return "미지정";
+ }
+
+ function groupSortRank(row) {
+ var selectedYear = Number((S.dashboard && S.dashboard.year) || projectYear(row) || 0);
+ var startYear = Number(projectYear(row) || 0);
+ if (typeof bgCompletedInYear === "function" && bgCompletedInYear(row, String(selectedYear))) return 9999;
+ if (!startYear) return 9998;
+ return startYear;
+ }
+
+ function tableGroupLabel(row) {
+ var startYear = projectYear(row);
+ if (/^20\d{2}$/.test(startYear)) return startYear + "년";
+ return "미지정";
+ }
+
+ function renderLedgerTable() {
+ var table = document.querySelector(".panel table");
+ if (!table || !E.tbody) return;
+ var thead = table.querySelector("thead");
+ if (thead) {
+ thead.innerHTML = '
'
+ + '구분 ▼
'
+ + '사업코드 ▼
'
+ + '사업명(계약명) ▼
'
+ + '발주처(계약처) ▼
'
+ + '발주방법 ▼
'
+ + '진행상태 ▼
'
+ + '계약금 ▼
'
+ + '외주비 ▼
'
+ + '미수금 ▼
'
+ + '수금액 ▼
'
+ + '수금률 ▼
'
+ + " ";
+ }
+ var rows = (Array.isArray(S.viewRows) ? S.viewRows : []).slice().sort(function (a, b) {
+ var ar = groupSortRank(a);
+ var br = groupSortRank(b);
+ if (ar !== br) return ar - br;
+ return Number(b.recv || 0) - Number(a.recv || 0);
+ });
+ S.viewRows = rows;
+ var lastGroupLabel = "";
+ E.tbody.innerHTML = rows.map(function (r) {
+ var groupLabel = tableGroupLabel(r);
+ var isCollapsed = !!S.collapsedGroups[groupLabel];
+ var groupRow = "";
+ if (groupLabel !== lastGroupLabel) {
+ groupRow = '
' + esc(groupLabel) + ' ' + (isCollapsed ? "+" : "-") + " ";
+ lastGroupLabel = groupLabel;
+ }
+ if (isCollapsed) return groupRow;
+ return groupRow + '
'
+ + '= 0 ? 'badge-baron' : 'badge-family') + '">' + esc(r.cat || "-") + '
'
+ + '' + esc(r.code || "-") + '
'
+ + '' + esc(r.name || "-") + ' ' + esc(r.periodText || "-") + '
'
+ + '' + esc((r.client || "").trim() || "-") + '
' + esc(formatSplitPercent(r.split)) + '
'
+ + '' + esc(r.order || "-") + '
'
+ + '= 0 ? 'ok' : '') + '">' + esc(normalizeStatusLabel(r.status)) + '
'
+ + '' + esc(won(r.cSup || 0)) + ' '
+ + '' + esc(r.outsourceCost ? won(r.outsourceCost) : "-") + ' '
+ + '' + esc(won(r.recv || 0)) + ' '
+ + '' + esc(won(r.col || 0)) + ' '
+ + '' + esc((Number(r.rate || 0)).toFixed(2) + "%") + ' '
+ + ' ';
+ }).join("");
+ }
+
+ function renderCollectionBoard(r) {
+ var payments = Array.isArray(r.payments) && r.payments.length ? r.payments : [{
+ pay: r.pay || "-",
+ issueDate: r.issueDate || "",
+ collectDate: r.collectDateSummary || r.colDate || "",
+ collected: r.col || 0,
+ receivable: r.recv || Math.max(0, Number(r.sTot || 0) - Number(r.col || 0)),
+ note: r.note || "",
+ status: r.status || ""
+ }];
+ return '
C
수금 및 기성 현황
기성 차수별 세금계산서 발행 및 수금 내역
총 수금 ' + esc(won(r.col || 0)) + '
기성 차수 세금계산서 발행일 수금일 수금금액 미수금액 비고 '
+ + payments.map(function (payment, index) {
+ var noteParts = [];
+ if (payment.status) noteParts.push(payment.status);
+ if (payment.note) noteParts.push(payment.note);
+ return '' + esc((index + 1) + "차") + ' ' + esc(payment.pay || "-") + ' ' + esc(payment.issueDate ? d(payment.issueDate) : "-") + ' ' + esc(payment.collectDate ? d(payment.collectDate) : "-") + ' ' + esc(won(payment.collected || 0)) + ' ' + esc(won(payment.receivable || 0)) + ' ' + esc(noteParts.join(" / ") || "-") + ' ';
+ }).join("")
+ + "
";
+ }
+
+ function renderContactCard(label, name, company, department, phone, email) {
+ var hasValue = [name, company, department, phone, email].some(function (value) {
+ return String(value || "").trim() !== "";
+ });
+ if (!hasValue) {
+ return '
' + esc(label) + '
등록된 담당자 정보가 없습니다.
';
+ }
+ return '
";
+ }
+
+ function renderProjectInline(r) {
+ var payments = Array.isArray(r.payments) ? r.payments : [];
+ var latestCollect = d(r.collectDateSummary || r.colDate);
+ var hasOutsource = (Array.isArray(r.outsourceItems) && r.outsourceItems.length > 0) || Number(r.outsourceCost || 0) > 0 || Number(r.outsourcePaid || 0) > 0 || Number(r.outsourceRemaining || 0) > 0;
+ var clientDisplay = typeof normalizeClientDisplay === "function" ? normalizeClientDisplay(r.client) : (String(r.client || "").trim() || "-");
+ var splitDisplay = typeof formatSplitDisplay === "function" ? formatSplitDisplay(r.split) : formatSplitPercent(r.split).replace("분담율 ", "");
+ var summaryCards = [
+ '
계약금
' + esc(won(r.cSup || 0)) + '
',
+ '
수금액
' + esc(won(r.col || 0)) + '
' + esc(latestCollect === "-" ? "수금일 없음" : "최종 수금일 " + latestCollect) + '
',
+ '
수금률
' + esc((Number(r.rate || 0)).toFixed(2) + "%") + '
' + esc(payments.length ? "기성 " + payments.length + "차까지 반영" : "차수 정보 없음") + '
',
+ '
미수금액
' + esc(won(r.recv || 0)) + '
잔여 수금 필요 금액
'
+ ].join("");
+ var boards = [
+ hasOutsource && typeof renderOutsourceBoard === "function" ? renderOutsourceBoard(r) : "",
+ renderCollectionBoard(r)
+ ].filter(Boolean).join("");
+ return '
' + renderContactCard("계약 / 청구 담당자", r.cmNm, r.cmCo, r.cmDp, r.cmPh, r.cmEm) + renderContactCard("부서 담당자", r.dmNm, r.dmCo, r.dmDp, r.dmPh, r.dmEm) + '
' + boards + '
';
+ }
+
+ function openProjectWindow(r) {
+ var popupKey = typeof rowKey === "function"
+ ? rowKey(r).replace(/[^0-9a-zA-Z]/g, "_")
+ : String((r.code || "project") + "_" + (r.name || "")).replace(/[^0-9a-zA-Z_]/g, "_");
+ var popup = window.open("", "business_project_" + popupKey, "width=1600,height=980,resizable=yes,scrollbars=yes");
+ if (!popup) return;
+ var styleText = Array.from(document.querySelectorAll("style")).map(function (el) {
+ return el.textContent || "";
+ }).join("\n");
+ var detailHtml = renderProjectInline(r);
+ var pageHtml = '
'
+ + esc(r.name || "사업 상세")
+ + ' ";
+ popup.document.open();
+ popup.document.write(pageHtml);
+ popup.document.close();
+ popup.focus();
+ }
+
+ async function tryLoadDbDefaultBusinessLedger() {
+ if (window.__mhBusinessDefaultLoaded) return;
+ window.__mhBusinessDefaultLoaded = true;
+ try {
+ var response = await fetch("/api/integration/business-ledger-default");
+ if (!response.ok) throw new Error("기본 사업관리대장 원본을 불러오지 못했습니다.");
+ var fileName = response.headers.get("x-source-filename") || "사업관리대장-1.xlsx";
+ var buffer = await response.arrayBuffer();
+ if (!buffer || !buffer.byteLength) throw new Error("기본 사업관리대장 원본 데이터가 비어 있습니다.");
+ await loadLedgerFile(buffer, fileName);
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ function applyDashboardChrome() {
+ if (!E.cards) return;
+ document.body.setAttribute("data-mh-ledger-enhanced", "true");
+ var wrap = document.querySelector(".wrap");
+ var panel = document.querySelector(".panel");
+ if (wrap && panel) {
+ var shell = wrap.querySelector(".business-shell");
+ if (!shell) {
+ shell = document.createElement("div");
+ shell.className = "business-shell";
+ wrap.insertBefore(shell, E.cards);
+ }
+ if (E.cards.parentNode !== shell) shell.appendChild(E.cards);
+ if (panel.parentNode !== shell) shell.appendChild(panel);
+ }
+ var years = bgEnsureYear(S.all);
+ var summary = bgSummarize(S.all, S.dashboard.year);
+ var rows = Array.isArray(S.rows) ? S.rows : [];
+ var visibleBaronProjectRows = rows.filter(isBaronProjectRow);
+ var totals = bgTotals(visibleBaronProjectRows);
+ var totalRate = typeof rate === "function" ? rate("", totals.col, totals.col + totals.recv) : 0;
+ var toolbarHtml = '
";
+ var cards = [
+ { label: summary.targetYear + "년 프로젝트", value: visibleBaronProjectRows.length.toLocaleString("ko-KR") + " 건", note: "" },
+ { label: "계약금", value: won(totals.c), note: "" },
+ { label: "수금액", value: won(totals.col), note: "" },
+ { label: "미수금", value: won(totals.recv), note: "" },
+ { label: "수금률(%)", value: totalRate.toFixed(2) + "%", note: "" },
+ { label: "경영지원서비스 금액", value: won(summary.managementTotals.c), note: "", className: "management" }
+ ];
+ E.cards.innerHTML = toolbarHtml + cards.map(function (card) {
+ return '
' + esc(card.label) + '
' + esc(card.value) + '
' + esc(card.note || "") + "
";
+ }).join("");
+ var searchWrap = E.cards.querySelector(".cards-toolbar-search");
+ if (searchWrap && E.search) {
+ searchWrap.appendChild(E.search);
+ E.search.placeholder = "전체 검색";
+ }
+ }
+
+ var originalRender = render;
+ render = function () {
+ originalRender();
+ applyDashboardChrome();
+ renderLedgerTable();
+ };
+
+ filter = function () {
+ bgEnsureYear(S.all);
+ var q = String(E.search.value || "").trim().toLowerCase();
+ var searched = !q ? S.all.slice() : S.all.filter(function (r) {
+ return [r.code, r.name, r.client, r.pm, r.status, r.cat, r.corp, r.pay, (r.payments || []).map(function (p) { return p.pay; }).join(" "), r.periodText].join(" ").toLowerCase().includes(q);
+ });
+ S.rows = searched.filter(function (r) {
+ return bgMatches(r) && matchesColumnFilters(r);
+ });
+ render();
+ };
+
+ if (E.cards && !E.cards.dataset.dashboardBound) {
+ E.cards.dataset.dashboardBound = "true";
+ E.cards.addEventListener("click", function (event) {
+ var yearButton = event.target && event.target.closest ? event.target.closest("[data-dashboard-year]") : null;
+ if (yearButton) {
+ S.dashboard.year = yearButton.getAttribute("data-dashboard-year") || S.dashboard.year;
+ filter();
+ return;
+ }
+ var sectionButton = event.target && event.target.closest ? event.target.closest("[data-dashboard-section]") : null;
+ if (sectionButton) {
+ S.dashboard.section = sectionButton.getAttribute("data-dashboard-section") || "active";
+ filter();
+ }
+ });
+ }
+
+ if (E.tbody && !E.tbody.dataset.projectBound) {
+ E.tbody.dataset.projectBound = "true";
+ E.tbody.addEventListener("click", function (event) {
+ var groupButton = event.target && event.target.closest ? event.target.closest("[data-group-label]") : null;
+ if (groupButton) {
+ var label = groupButton.getAttribute("data-group-label") || "";
+ if (label) {
+ S.collapsedGroups[label] = !S.collapsedGroups[label];
+ render();
+ }
+ return;
+ }
+ var trigger = event.target && event.target.closest ? event.target.closest(".project-link") : null;
+ if (!trigger) return;
+ var key = trigger.getAttribute("data-project-key") || "";
+ var rows = Array.isArray(S.viewRows) ? S.viewRows : [];
+ var row = rows.find(function (item) {
+ return (String(item.code || "") + "|" + String(item.name || "")) === key;
+ });
+ if (row) openProjectWindow(row);
+ });
+ }
+
+ setTimeout(function () {
+ try {
+ filter();
+ if (typeof loadLedgerFile === "function") {
+ tryLoadDbDefaultBusinessLedger();
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }, 0);
+
+ window.addEventListener("message", function (event) {
+ var data = event.data || {};
+ if (data.source !== "total-upload" || data.type !== "business") return;
+ setTimeout(function () {
+ try {
+ applyDashboardChrome();
+ renderLedgerTable();
+ } catch (error) {
+ console.error(error);
+ }
+ }, 50);
+ });
+})();
diff --git a/incoming-files/사업관리대장/사업관리대장-1.xlsx b/incoming-files/reference/ledger/사업관리대장-1.xlsx
similarity index 100%
rename from incoming-files/사업관리대장/사업관리대장-1.xlsx
rename to incoming-files/reference/ledger/사업관리대장-1.xlsx
diff --git a/incoming-files/reference/ledger/사업관리대장/MH 통합 대시보드_260320.css b/incoming-files/reference/ledger/사업관리대장/MH 통합 대시보드_260320.css
new file mode 100644
index 0000000..8b948d4
--- /dev/null
+++ b/incoming-files/reference/ledger/사업관리대장/MH 통합 대시보드_260320.css
@@ -0,0 +1,1377 @@
+:root {
+ --bg: #f1eadf;
+ --panel: #fffaf3;
+ --panel-soft: #f4e9d7;
+ --ink: #10251d;
+ --muted: #66756d;
+ --line: #d9c5a8;
+ --brand: #0f3a2f;
+ --brand-deep: #0a2a22;
+ --brand-soft: #1a5645;
+ --accent: #d68a3a;
+ --accent-soft: #f2c484;
+ --mint: #2f9973;
+ --blue: #4b87b3;
+ --shadow: 0 22px 54px rgba(15, 58, 47, 0.12);
+ }
+
+ * { box-sizing: border-box; }
+ body {
+ margin: 0;
+ font-family: "Pretendard", "Malgun Gothic", sans-serif;
+ color: var(--ink);
+ background:
+ radial-gradient(circle at top left, rgba(214, 138, 58, 0.18), transparent 24%),
+ radial-gradient(circle at top right, rgba(47, 153, 115, 0.12), transparent 22%),
+ linear-gradient(180deg, #f6efe6 0%, #f1eadf 100%);
+ }
+
+ .page {
+ max-width: 1720px;
+ margin: 0 auto;
+ padding: 26px 22px 40px;
+ }
+
+ .hero {
+ position: relative;
+ overflow: hidden;
+ background:
+ radial-gradient(circle at 12% 18%, rgba(242, 196, 132, 0.18), transparent 24%),
+ radial-gradient(circle at 88% 12%, rgba(255, 255, 255, 0.10), transparent 18%),
+ linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 52%, var(--brand-soft) 100%);
+ color: #f7f0e4;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 30px;
+ padding: 34px 32px;
+ box-shadow: 0 28px 70px rgba(15, 58, 47, 0.22);
+ }
+
+ .hero::before {
+ content: "";
+ position: absolute;
+ inset: auto -6% -46% auto;
+ width: 320px;
+ height: 320px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.22), transparent 68%);
+ pointer-events: none;
+ }
+
+ .hero::after {
+ content: "";
+ position: absolute;
+ top: -90px;
+ right: 80px;
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+ border: 1px solid rgba(242, 196, 132, 0.16);
+ pointer-events: none;
+ }
+
+ h1 {
+ margin: 0;
+ font-size: 52px;
+ line-height: 1.12;
+ letter-spacing: -0.03em;
+ position: relative;
+ z-index: 1;
+ }
+
+ .summary {
+ margin-top: 10px;
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 14px;
+ }
+
+ .summary-card {
+ padding: 14px 16px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.07) 100%);
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ backdrop-filter: blur(8px);
+ position: relative;
+ z-index: 1;
+ }
+
+ .summary-label {
+ font-size: 11px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: rgba(255, 244, 230, 0.68);
+ font-weight: 900;
+ }
+
+ .summary-value {
+ margin-top: 8px;
+ font-size: 24px;
+ font-weight: 900;
+ }
+
+ .summary-sub {
+ margin-top: 4px;
+ font-size: 12px;
+ color: rgba(255, 244, 230, 0.82);
+ font-weight: 700;
+ }
+
+ .tabs {
+ margin-top: 18px;
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ .hero-actions {
+ margin-top: 0;
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ position: absolute;
+ top: 0;
+ right: 0;
+ justify-content: flex-end;
+ }
+
+ .hero-action {
+ padding: 12px 16px;
+ border-radius: 999px;
+ border: 1px solid rgba(242, 196, 132, 0.34);
+ background: rgba(255, 255, 255, 0.08);
+ color: #f4efe6;
+ font-size: 14px;
+ font-weight: 900;
+ cursor: pointer;
+ }
+
+ .hero-action:hover {
+ background: rgba(242, 196, 132, 0.16);
+ border-color: rgba(242, 196, 132, 0.52);
+ }
+
+ .tab {
+ padding: 12px 16px;
+ border-radius: 999px;
+ border: 1px solid #d6c1a3;
+ background: linear-gradient(180deg, #fffdf8 0%, #f5ebdd 100%);
+ color: #244638;
+ font-size: 14px;
+ font-weight: 900;
+ cursor: pointer;
+ transition: all 0.16s ease;
+ }
+
+ .tab.active {
+ background: linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 100%);
+ color: #f4efe6;
+ border-color: var(--brand);
+ box-shadow: 0 12px 28px rgba(15, 58, 47, 0.2);
+ }
+
+ .layout {
+ margin-top: 18px;
+ display: grid;
+ grid-template-columns: 320px minmax(0, 1fr);
+ gap: 18px;
+ }
+
+ .panel {
+ background: var(--panel);
+ border: 1px solid var(--line);
+ border-radius: 26px;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ }
+
+ .panel-head {
+ padding: 18px 20px 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ }
+
+ .panel-title {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 900;
+ }
+
+ .panel-note {
+ color: var(--muted);
+ font-size: 12px;
+ font-weight: 800;
+ }
+
+ .panel-body {
+ padding: 18px 20px 20px;
+ }
+
+ .company-card {
+ display: grid;
+ gap: 10px;
+ }
+
+ .metric {
+ padding: 14px 16px;
+ border-radius: 18px;
+ background: linear-gradient(180deg, #fffdf8 0%, #f2e7d6 100%);
+ border: 1px solid var(--line);
+ }
+
+ .metric-label {
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.16em;
+ color: #8a6b3d;
+ text-transform: uppercase;
+ }
+
+ .metric-value {
+ margin-top: 8px;
+ font-size: 20px;
+ font-weight: 900;
+ line-height: 1.2;
+ }
+
+ .metric-sub {
+ margin-top: 5px;
+ font-size: 12px;
+ line-height: 1.45;
+ color: #425148;
+ font-weight: 700;
+ }
+
+ .metric-sub-list {
+ display: grid;
+ gap: 4px;
+ margin-top: 6px;
+ max-height: 132px;
+ overflow: auto;
+ padding-right: 4px;
+ }
+
+.metric-sub-item {
+ font-size: 12px;
+ line-height: 1.4;
+ color: #425148;
+ font-weight: 700;
+ }
+
+ /* MH embedded business dashboard theme */
+ body.mh-business-theme {
+ color: var(--ink);
+ background:
+ radial-gradient(circle at top left, rgba(214, 138, 58, 0.18), transparent 24%),
+ radial-gradient(circle at top right, rgba(47, 153, 115, 0.12), transparent 22%),
+ linear-gradient(180deg, #f6efe6 0%, #f1eadf 100%) !important;
+ font-family: "Pretendard", "Malgun Gothic", sans-serif;
+ }
+
+ body.mh-business-theme .wrap {
+ width: calc(100vw - 60px);
+ max-width: calc(100vw - 60px);
+ margin: 0 auto;
+ padding: 18px 18px 16px;
+ }
+
+ body.mh-business-theme .top {
+ position: relative;
+ overflow: hidden;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(320px, 520px);
+ gap: 18px 24px;
+ align-items: start;
+ background:
+ radial-gradient(circle at 12% 18%, rgba(242, 196, 132, 0.18), transparent 24%),
+ radial-gradient(circle at 88% 12%, rgba(255, 255, 255, 0.10), transparent 18%),
+ linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 52%, var(--brand-soft) 100%);
+ color: #f7f0e4;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 30px;
+ padding: 30px 30px 26px;
+ box-shadow: 0 28px 70px rgba(15, 58, 47, 0.22);
+ margin-bottom: 14px;
+ }
+
+ body.mh-business-theme .top::before {
+ content: "";
+ position: absolute;
+ inset: auto -6% -46% auto;
+ width: 320px;
+ height: 320px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.22), transparent 68%);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .top::after {
+ content: "";
+ position: absolute;
+ top: -90px;
+ right: 80px;
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+ border: 1px solid rgba(242, 196, 132, 0.16);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .top > div:first-child {
+ min-width: 0;
+ }
+
+ body.mh-business-theme .sub,
+ body.mh-business-theme .title,
+ body.mh-business-theme .today-date-label,
+ body.mh-business-theme #btnUpload {
+ display: none !important;
+ }
+
+ body.mh-business-theme .brand-head {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ min-width: 0;
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .brand-ci {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ }
+
+ body.mh-business-theme .brand-logo-wrap {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ }
+
+ body.mh-business-theme .brand-logo {
+ width: 50px;
+ height: 28px;
+ flex: 0 0 auto;
+ display: block;
+ }
+
+ body.mh-business-theme .brand-company {
+ color: #8db4ff;
+ font-size: 20px;
+ font-weight: 900;
+ letter-spacing: -0.03em;
+ white-space: nowrap;
+ line-height: 1;
+ }
+
+ body.mh-business-theme .brand-copy {
+ min-width: 0;
+ }
+
+ body.mh-business-theme .brand-title {
+ min-width: 0;
+ color: #f4efe6;
+ font-size: clamp(28px, 2.5vw, 46px);
+ font-weight: 900;
+ letter-spacing: -0.03em;
+ line-height: 1.1;
+ word-break: keep-all;
+ }
+
+ body.mh-business-theme .brand-subtitle {
+ display: inline;
+ color: rgba(244, 239, 230, 0.92);
+ font-size: 0.5em;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+ margin-left: 6px;
+ white-space: nowrap;
+ }
+
+ body.mh-business-theme .controls {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ justify-self: end;
+ gap: 12px;
+ min-width: min(100%, 520px);
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .controls-top-row,
+ body.mh-business-theme .controls-top-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ body.mh-business-theme .search {
+ width: min(520px, 100%);
+ align-self: flex-end;
+ min-height: 52px;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ background: rgba(255, 255, 255, 0.14);
+ color: #fff7eb;
+ padding: 14px 18px;
+ font-size: 15px;
+ font-weight: 800;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
+ }
+
+ body.mh-business-theme .search::placeholder {
+ color: rgba(247, 240, 228, 0.66);
+ }
+
+ body.mh-business-theme .status {
+ display: none !important;
+ }
+
+ body.mh-business-theme .cards {
+ grid-column: 1 / -1;
+ display: grid;
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ gap: 14px;
+ margin-top: 6px;
+ margin-bottom: 0;
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .business-shell {
+ position: relative;
+ margin-top: 12px;
+ padding: 18px 18px 20px;
+ border-radius: 30px;
+ background:
+ radial-gradient(circle at 12% 12%, rgba(242, 196, 132, 0.10), transparent 26%),
+ radial-gradient(circle at 88% 10%, rgba(255, 255, 255, 0.08), transparent 18%),
+ linear-gradient(145deg, rgba(15, 58, 47, 0.96) 0%, rgba(26, 86, 69, 0.94) 100%);
+ border: 1px solid rgba(255, 255, 255, 0.10);
+ box-shadow: 0 26px 70px rgba(15, 58, 47, 0.16);
+ overflow: hidden;
+ }
+
+ body.mh-business-theme .business-shell::before {
+ content: "";
+ position: absolute;
+ inset: auto -10% -32% auto;
+ width: 360px;
+ height: 360px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.16), transparent 70%);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .business-shell::after {
+ content: "";
+ position: absolute;
+ top: -110px;
+ right: 90px;
+ width: 260px;
+ height: 260px;
+ border-radius: 50%;
+ border: 1px solid rgba(242, 196, 132, 0.12);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .cards-toolbar {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 6px;
+ }
+
+ body.mh-business-theme .cards-toolbar-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ body.mh-business-theme .summary-filter-chip,
+ body.mh-business-theme .summary-year-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-width: 58px;
+ padding: 9px 14px;
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ background: rgba(255, 255, 255, 0.08);
+ color: #f4efe6;
+ font-size: 12px;
+ font-weight: 900;
+ cursor: pointer;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
+ }
+
+ body.mh-business-theme .summary-filter-chip {
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 96px;
+ gap: 8px;
+ text-align: center;
+ }
+
+ body.mh-business-theme .summary-filter-chip .label {
+ color: rgba(244, 239, 230, 0.92);
+ letter-spacing: 0.01em;
+ }
+
+ body.mh-business-theme .summary-filter-chip .count {
+ color: #ffd08a;
+ font-size: 30px;
+ line-height: 1;
+ letter-spacing: -0.03em;
+ }
+
+ body.mh-business-theme .summary-filter-chip .meta {
+ color: rgba(255, 230, 190, 0.92);
+ font-size: 11px;
+ font-weight: 800;
+ }
+
+ body.mh-business-theme .summary-filter-chip.active,
+ body.mh-business-theme .summary-year-chip.active {
+ background: linear-gradient(180deg, #fff8ee 0%, #f2dec0 100%);
+ color: #0a2a22;
+ border-color: rgba(242, 196, 132, 0.58);
+ box-shadow: 0 12px 28px rgba(10, 42, 34, 0.18);
+ }
+
+ body.mh-business-theme .summary-filter-chip.active .count {
+ color: #b86b1f;
+ }
+
+ body.mh-business-theme .summary-filter-chip.active .label {
+ color: rgba(10, 42, 34, 0.78);
+ }
+
+ body.mh-business-theme .summary-filter-chip.active .meta {
+ color: #7c5a20;
+ }
+
+ body.mh-business-theme .card {
+ padding: 16px 16px 14px;
+ min-height: 108px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255, 250, 243, 0.98) 0%, rgba(247, 238, 225, 0.94) 100%);
+ border: 1px solid rgba(214, 193, 163, 0.78);
+ color: #173328;
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.75),
+ 0 14px 30px rgba(15, 58, 47, 0.08);
+ backdrop-filter: blur(10px);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+
+ body.mh-business-theme .card.management {
+ background: linear-gradient(180deg, rgba(255, 246, 232, 0.98) 0%, rgba(250, 236, 208, 0.96) 100%);
+ border-color: rgba(214, 138, 58, 0.42);
+ }
+
+ body.mh-business-theme .card .k {
+ color: rgba(23, 51, 40, 0.68);
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.04em;
+ }
+
+ body.mh-business-theme .card .v {
+ margin-top: 7px;
+ color: #173328;
+ font-size: clamp(19px, 1.55vw, 27px);
+ font-weight: 900;
+ letter-spacing: -0.03em;
+ white-space: nowrap;
+ }
+
+ body.mh-business-theme .card .n {
+ margin-top: 5px;
+ color: rgba(63, 84, 74, 0.88);
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1.4;
+ }
+
+ body.mh-business-theme .panel {
+ background: var(--panel);
+ border: 1px solid var(--line);
+ border-radius: 26px;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .table-wrap {
+ overflow: auto;
+ }
+
+ body.mh-business-theme table {
+ width: 100%;
+ min-width: 1250px;
+ border-collapse: collapse;
+ }
+
+ body.mh-business-theme thead th {
+ background: #122b23;
+ color: rgba(247, 240, 228, 0.92);
+ font-size: 11px;
+ letter-spacing: 0.08em;
+ padding: 12px 10px;
+ text-align: left;
+ white-space: nowrap;
+ vertical-align: middle;
+ }
+
+ body.mh-business-theme .th-trigger,
+ body.mh-business-theme .th-trigger:hover,
+ body.mh-business-theme .th-trigger.active,
+ body.mh-business-theme .th-trigger.open {
+ color: rgba(247, 240, 228, 0.92);
+ }
+
+ body.mh-business-theme .th-mark,
+ body.mh-business-theme .th-caret,
+ body.mh-business-theme .th-meta {
+ color: #f2c484;
+ }
+
+ body.mh-business-theme tbody td {
+ padding: 12px 10px;
+ border-bottom: 1px solid #ece1cf;
+ font-size: 13px;
+ background: #fffaf3;
+ }
+
+ body.mh-business-theme th:nth-child(1),
+ body.mh-business-theme td:nth-child(1) { width: 5%; }
+ body.mh-business-theme th:nth-child(2),
+ body.mh-business-theme td:nth-child(2) { width: 6%; }
+ body.mh-business-theme th:nth-child(3),
+ body.mh-business-theme td:nth-child(3) { width: 31%; }
+ body.mh-business-theme th:nth-child(4),
+ body.mh-business-theme td:nth-child(4) { width: 12%; }
+ body.mh-business-theme th:nth-child(5),
+ body.mh-business-theme td:nth-child(5) { width: 7%; }
+ body.mh-business-theme th:nth-child(6),
+ body.mh-business-theme td:nth-child(6) { width: 6%; }
+ body.mh-business-theme th:nth-child(7),
+ body.mh-business-theme td:nth-child(7) { width: 9%; }
+ body.mh-business-theme th:nth-child(8),
+ body.mh-business-theme td:nth-child(8) { width: 8%; }
+ body.mh-business-theme th:nth-child(9),
+ body.mh-business-theme td:nth-child(9) { width: 8%; }
+ body.mh-business-theme th:nth-child(10),
+ body.mh-business-theme td:nth-child(10) { width: 8%; }
+ body.mh-business-theme th:nth-child(11),
+ body.mh-business-theme td:nth-child(11) { width: 5%; }
+
+ body.mh-business-theme tbody tr:hover td {
+ background: #fff5e8;
+ }
+
+ body.mh-business-theme .name,
+ body.mh-business-theme td strong {
+ color: #10251d;
+ }
+
+ body.mh-business-theme .subline {
+ color: #7c8b82;
+ }
+
+ body.mh-business-theme .client-main {
+ display: block;
+ font-weight: 800;
+ color: #10251d;
+ }
+
+ body.mh-business-theme .badge.badge-baron {
+ border-color: #f0bb75;
+ background: #fff2df;
+ color: #b96820;
+ }
+
+ body.mh-business-theme .badge.badge-family {
+ border-color: #a5cbb6;
+ background: #eef7f1;
+ color: #2d6a4f;
+ }
+
+ body.mh-business-theme .group-row td {
+ padding: 12px 14px 10px;
+ background: linear-gradient(180deg, #fff9ef 0%, #f5ebdd 100%);
+ border-top: 1px solid #ead8bc;
+ border-bottom: 1px solid #ead8bc;
+ }
+
+ body.mh-business-theme .group-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 13px;
+ border-radius: 999px;
+ background: #fffdfa;
+ border: 1px solid #d6c1a3;
+ color: #244638;
+ font-size: 12px;
+ font-weight: 900;
+ box-shadow: 0 10px 24px rgba(15, 58, 47, 0.10);
+ cursor: pointer;
+ }
+
+ body.mh-business-theme .group-chip .group-toggle {
+ margin-left: 4px;
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 999px;
+ background: rgba(242, 196, 132, 0.16);
+ color: #8a5a1f;
+ font-size: 14px;
+ font-weight: 900;
+ line-height: 1;
+ }
+
+ body.mh-business-theme .detail-row td {
+ background: linear-gradient(180deg, #fff7ec 0%, #fffdf8 100%);
+ border-top: 1px solid #ecd8ba;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
+ }
+
+ body.mh-business-theme .detail-row .inline-panel {
+ background: transparent;
+ border-left: 3px solid var(--accent);
+ }
+
+ body.mh-business-theme .detail-row .inline-card,
+ body.mh-business-theme .detail-row .ledger-block {
+ box-shadow: 0 10px 24px rgba(15, 58, 47, 0.08);
+ }
+
+ body.mh-business-theme .table-top-note {
+ display: flex;
+ justify-content: flex-end;
+ margin: 0 2px 10px;
+ }
+
+ body.mh-business-theme .table-vat-note {
+ display: inline-flex;
+ align-items: center;
+ padding: 6px 10px;
+ border-radius: 999px;
+ background: rgba(255, 248, 238, 0.96);
+ border: 1px solid rgba(242, 196, 132, 0.46);
+ color: #6f5528;
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: -0.01em;
+ }
+
+ @media (max-width: 1280px) {
+ body.mh-business-theme .top {
+ grid-template-columns: 1fr;
+ }
+
+ body.mh-business-theme .controls {
+ min-width: 0;
+ justify-self: stretch;
+ }
+
+ body.mh-business-theme .controls-top-row,
+ body.mh-business-theme .controls-top-actions {
+ justify-content: flex-start;
+ }
+
+ body.mh-business-theme .search {
+ width: 100%;
+ align-self: stretch;
+ }
+
+ body.mh-business-theme .cards {
+ grid-template-columns: repeat(2, minmax(140px, 1fr));
+ }
+ }
+
+ @media (max-width: 720px) {
+ body.mh-business-theme .wrap {
+ width: calc(100vw - 30px);
+ max-width: calc(100vw - 30px);
+ padding: 12px 10px;
+ }
+
+ body.mh-business-theme .top {
+ padding: 18px 14px 16px;
+ gap: 14px;
+ }
+
+ body.mh-business-theme .brand-head {
+ gap: 8px;
+ align-items: flex-start;
+ }
+
+ body.mh-business-theme .brand-company {
+ font-size: 16px;
+ }
+
+ body.mh-business-theme .brand-title {
+ font-size: 30px;
+ }
+
+ body.mh-business-theme .brand-subtitle {
+ display: inline;
+ margin-left: 4px;
+ margin-top: 0;
+ white-space: normal;
+ }
+ }
+
+ .filter-row {
+ display: grid;
+ grid-template-columns: 1fr 160px 180px 180px;
+ gap: 12px;
+ margin-bottom: 14px;
+ }
+
+ .year-summary {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 10px;
+ margin-bottom: 14px;
+ }
+
+ .year-card {
+ padding: 14px 16px;
+ border-radius: 18px;
+ background: linear-gradient(180deg, #fffdf8 0%, #f3e7d6 100%);
+ border: 1px solid var(--line);
+ cursor: pointer;
+ transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease, background 0.16s ease;
+ }
+
+ .year-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 22px rgba(18, 48, 30, 0.10);
+ }
+
+ .year-card.active {
+ background: linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 62%, var(--brand-soft) 100%);
+ border-color: rgba(214, 138, 58, 0.42);
+ box-shadow: 0 14px 30px rgba(15, 58, 47, 0.2);
+ }
+
+ .year-card.active .year-card-label,
+ .year-card.active .year-card-value,
+ .year-card.active .year-card-sub {
+ color: #f4efe6;
+ }
+
+ .year-card-label {
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.14em;
+ color: #7a684d;
+ }
+
+ .year-card-value {
+ margin-top: 8px;
+ font-size: clamp(15px, 1.45vw, 18px);
+ font-weight: 900;
+ color: var(--brand);
+ line-height: 1.2;
+ white-space: nowrap;
+ letter-spacing: -0.04em;
+ }
+
+ .year-card-sub {
+ margin-top: 4px;
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--muted);
+ }
+
+ .input, .select {
+ width: 100%;
+ border: 1px solid #d7c4a7;
+ background: #fffdf8;
+ border-radius: 14px;
+ padding: 12px 14px;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--ink);
+ outline: none;
+ }
+
+ .input:focus, .select:focus {
+ border-color: var(--brand-soft);
+ box-shadow: 0 0 0 3px rgba(47, 153, 115, 0.12);
+ }
+
+ .chip-row {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ margin-bottom: 12px;
+ }
+
+ .chip {
+ padding: 8px 12px;
+ border-radius: 999px;
+ background: #efe3cf;
+ color: #315243;
+ font-size: 12px;
+ font-weight: 900;
+ }
+
+ .table-wrap {
+ overflow: auto;
+ border-top: 1px solid var(--line);
+ }
+
+ .table-summary {
+ display: flex;
+ justify-content: flex-end;
+ gap: 18px;
+ padding: 14px 20px 18px;
+ border-top: 1px solid var(--line);
+ background: #fbf4ea;
+ font-size: 13px;
+ font-weight: 800;
+ color: #244233;
+ }
+
+ .table-summary strong {
+ color: #12301e;
+ font-size: 15px;
+ }
+
+ table {
+ width: 100%;
+ min-width: 1180px;
+ border-collapse: separate;
+ border-spacing: 0;
+ }
+
+ th, td {
+ border-right: 1px solid #ecdfcc;
+ border-bottom: 1px solid #ecdfcc;
+ padding: 11px 12px;
+ vertical-align: middle;
+ text-align: left;
+ font-size: 13px;
+ line-height: 1.55;
+ white-space: nowrap;
+ }
+
+ th:last-child, td:last-child { border-right: none; }
+
+ thead th {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ background: linear-gradient(180deg, var(--brand-deep) 0%, var(--brand) 100%);
+ color: #f4efe6;
+ font-size: 12px;
+ font-weight: 900;
+ text-align: center;
+ border-bottom: 1px solid rgba(242, 196, 132, 0.3);
+ }
+
+ tbody tr:nth-child(even) td {
+ background: #fbf4ea;
+ }
+
+ .money {
+ white-space: nowrap;
+ font-weight: 900;
+ color: var(--brand-soft);
+ }
+
+ .muted {
+ color: var(--muted);
+ font-size: 12px;
+ }
+
+ .cell-filter {
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: #12301e;
+ font: inherit;
+ font-weight: 800;
+ cursor: pointer;
+ white-space: nowrap;
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
+ }
+
+ .cell-filter:hover {
+ color: var(--mint);
+ }
+
+ .chip-action {
+ border: 1px solid #d9c8af;
+ background: #fffaf2;
+ cursor: pointer;
+ }
+
+ .chip-action:hover {
+ border-color: var(--mint);
+ color: var(--mint);
+ }
+
+ .empty {
+ padding: 24px;
+ text-align: center;
+ color: var(--muted);
+ font-weight: 800;
+ }
+
+ .page {
+ position: relative;
+ }
+
+ .page::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ background:
+ linear-gradient(rgba(15, 58, 47, 0.03) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(15, 58, 47, 0.03) 1px, transparent 1px);
+ background-size: 32px 32px;
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.34), transparent 82%);
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .hero {
+ padding: 28px 34px 24px;
+ border-radius: 32px;
+ box-shadow: 0 30px 70px rgba(15, 58, 47, 0.22);
+ }
+
+ .hero-grid {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 24px;
+ align-items: end;
+ }
+
+ .hero-grid > div:first-child {
+ position: relative;
+ min-height: 108px;
+ padding-right: 260px;
+ }
+
+ .brand-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 9px 14px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.08);
+ border: 1px solid rgba(242, 196, 132, 0.24);
+ color: rgba(255, 244, 230, 0.86);
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ }
+
+ .brand-kicker::before {
+ content: "";
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--accent-soft);
+ box-shadow: 0 0 0 6px rgba(242, 196, 132, 0.12);
+ }
+
+ .brand-line {
+ display: grid;
+ grid-template-columns: 76px minmax(0, 1fr);
+ gap: 20px;
+ align-items: center;
+ margin-top: 0;
+ }
+
+ .hero-logo {
+ position: relative;
+ width: 76px;
+ height: 76px;
+ display: grid;
+ place-items: center;
+ border-radius: 24px;
+ background:
+ radial-gradient(circle at 30% 26%, rgba(255, 255, 255, 0.24), transparent 20%),
+ radial-gradient(circle at 68% 72%, rgba(242, 196, 132, 0.34), transparent 18%),
+ linear-gradient(145deg, rgba(242, 196, 132, 0.18) 0%, rgba(255, 255, 255, 0.04) 100%);
+ border: 1px solid rgba(242, 196, 132, 0.24);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 18px 36px rgba(10, 42, 34, 0.24);
+ }
+
+ .hero-logo::before {
+ content: "";
+ position: absolute;
+ inset: 10px;
+ border-radius: 18px;
+ border: 1px solid rgba(242, 196, 132, 0.16);
+ }
+
+ .hero-logo::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 28px;
+ height: 28px;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.92), rgba(214, 138, 58, 0.18));
+ box-shadow:
+ 0 0 0 8px rgba(242, 196, 132, 0.06),
+ 0 0 24px rgba(242, 196, 132, 0.24);
+ }
+
+ .hero-logo-core {
+ display: none;
+ }
+
+ .hero-copy {
+ display: none;
+ }
+
+ .hero-side {
+ display: grid;
+ gap: 12px;
+ }
+
+ .hero-mini-card {
+ position: relative;
+ padding: 16px 18px 16px 58px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(255, 255, 255, 0.06) 100%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ min-height: 88px;
+ }
+
+ .hero-mini-card::before {
+ content: attr(data-mark);
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ width: 30px;
+ height: 30px;
+ border-radius: 12px;
+ display: grid;
+ place-items: center;
+ background: rgba(242, 196, 132, 0.18);
+ color: #ffe6bf;
+ font-size: 11px;
+ font-weight: 1000;
+ letter-spacing: 0.08em;
+ }
+
+ .hero-mini-label {
+ color: rgba(255, 244, 230, 0.62);
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ }
+
+ .hero-mini-value {
+ margin-top: 8px;
+ color: #fff7eb;
+ font-size: 24px;
+ font-weight: 1000;
+ letter-spacing: -0.03em;
+ }
+
+ .hero-mini-sub {
+ margin-top: 6px;
+ color: rgba(255, 244, 230, 0.76);
+ font-size: 13px;
+ line-height: 1.5;
+ font-weight: 700;
+ }
+
+ .tabs-shell {
+ margin-top: 18px;
+ padding: 12px;
+ border-radius: 24px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.68) 0%, rgba(255, 250, 243, 0.92) 100%);
+ border: 1px solid rgba(217, 197, 168, 0.8);
+ box-shadow: 0 18px 36px rgba(15, 58, 47, 0.08);
+ backdrop-filter: blur(10px);
+ }
+
+ .tabs-shell .tabs {
+ margin-top: 0;
+ }
+
+ .page > .hero:not(.hero-reimagined),
+ .page > .tabs:not(.tabs-reimagined) {
+ display: none;
+ }
+
+ .summary-card {
+ min-height: 122px;
+ overflow: hidden;
+ transition: transform 0.18s ease, box-shadow 0.18s ease;
+ }
+
+ .summary-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 18px 30px rgba(10, 42, 34, 0.16);
+ }
+
+ .summary-card::after {
+ content: "";
+ position: absolute;
+ inset: auto -14px -28px auto;
+ width: 96px;
+ height: 96px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.14), transparent 70%);
+ }
+
+ .summary-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ display: none;
+ }
+
+ .summary-icon {
+ width: 42px;
+ height: 42px;
+ border-radius: 14px;
+ display: grid;
+ place-items: center;
+ background: rgba(255, 255, 255, 0.14);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ color: #fff3df;
+ font-size: 11px;
+ font-weight: 1000;
+ letter-spacing: 0.08em;
+ }
+
+ .summary-tail {
+ margin-top: auto;
+ padding-top: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ color: rgba(255, 244, 230, 0.74);
+ font-size: 11px;
+ font-weight: 800;
+ }
+
+ .summary-card .summary-value {
+ margin-top: 0;
+ }
+
+ .summary-line {
+ flex: 1;
+ height: 4px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.12);
+ overflow: hidden;
+ }
+
+ .summary-line span {
+ display: block;
+ height: 100%;
+ width: 64%;
+ border-radius: inherit;
+ background: linear-gradient(90deg, rgba(242, 196, 132, 0.96), rgba(47, 153, 115, 0.88));
+ }
+
+ .panel {
+ position: relative;
+ }
+
+ .panel::before {
+ content: "";
+ position: absolute;
+ inset: 0 0 auto 0;
+ height: 4px;
+ background: linear-gradient(90deg, var(--accent), #edd6af 38%, var(--mint) 100%);
+ opacity: 0.9;
+ }
+
+ tbody tr:hover td {
+ background: #f5ecdf;
+ }
+
+ @media (max-width: 1180px) {
+ .hero-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .hero-grid > div:first-child {
+ min-height: auto;
+ padding-right: 0;
+ }
+
+ .brand-line {
+ grid-template-columns: 92px minmax(0, 1fr);
+ gap: 16px;
+ }
+
+ .hero-logo {
+ width: 68px;
+ height: 68px;
+ }
+
+ .hero-actions {
+ position: static;
+ margin-top: 18px;
+ justify-content: flex-start;
+ }
+
+ .layout {
+ grid-template-columns: 1fr;
+ }
+
+ .filter-row {
+ grid-template-columns: 1fr;
+ }
+
+ .summary {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
+ @media (max-width: 720px) {
+ h1 {
+ font-size: 34px;
+ }
+
+ .summary {
+ grid-template-columns: 1fr;
+ }
+ }
diff --git a/incoming-files/사업관리대장/사업관리대장/MH 통합 대시보드_260320.html b/incoming-files/reference/ledger/사업관리대장/MH 통합 대시보드_260320.html
similarity index 100%
rename from incoming-files/사업관리대장/사업관리대장/MH 통합 대시보드_260320.html
rename to incoming-files/reference/ledger/사업관리대장/MH 통합 대시보드_260320.html
diff --git a/incoming-files/사업관리대장/사업관리대장/사업관리대장-1.xlsx b/incoming-files/reference/ledger/사업관리대장/사업관리대장-1.xlsx
similarity index 100%
rename from incoming-files/사업관리대장/사업관리대장/사업관리대장-1.xlsx
rename to incoming-files/reference/ledger/사업관리대장/사업관리대장-1.xlsx
diff --git a/incoming-files/served/README.md b/incoming-files/served/README.md
index eae0a30..d079717 100644
--- a/incoming-files/served/README.md
+++ b/incoming-files/served/README.md
@@ -6,9 +6,18 @@
- `payment.html`
- `mh.html`
+- `ledger/index.html`
+- `ledger/ledger-override.css`
+- `ledger/ledger-override.js`
+- `ledger/MH 통합 대시보드_260320.css`
+- `ledger/사업관리대장-1.xlsx`
규칙:
- `/integrations/payment` 는 이 디렉터리의 `payment.html`을 읽는다.
- `/integrations/mh` 는 이 디렉터리의 `mh.html`을 읽는다.
+- `/integrations/ledger` 는 `ledger/index.html`을 읽는다.
+- `/integrations/ledger-assets/*` 는 `ledger/` 하위 파일만 읽는다.
+- `payment.html` 수정 원본은 `frontend/apps/payment/index.html`이고, `scripts/publish_payment_app.sh`로 반영한다.
+- `mh.html` 수정 원본은 `frontend/apps/team/index.html`이고, `scripts/publish_team_app.sh`로 반영한다.
- 원본 참고 파일이나 비교용 파일은 이 디렉터리에 두지 않는다.
diff --git a/incoming-files/served/ledger/MH 통합 대시보드_260320.css b/incoming-files/served/ledger/MH 통합 대시보드_260320.css
new file mode 100644
index 0000000..8b948d4
--- /dev/null
+++ b/incoming-files/served/ledger/MH 통합 대시보드_260320.css
@@ -0,0 +1,1377 @@
+:root {
+ --bg: #f1eadf;
+ --panel: #fffaf3;
+ --panel-soft: #f4e9d7;
+ --ink: #10251d;
+ --muted: #66756d;
+ --line: #d9c5a8;
+ --brand: #0f3a2f;
+ --brand-deep: #0a2a22;
+ --brand-soft: #1a5645;
+ --accent: #d68a3a;
+ --accent-soft: #f2c484;
+ --mint: #2f9973;
+ --blue: #4b87b3;
+ --shadow: 0 22px 54px rgba(15, 58, 47, 0.12);
+ }
+
+ * { box-sizing: border-box; }
+ body {
+ margin: 0;
+ font-family: "Pretendard", "Malgun Gothic", sans-serif;
+ color: var(--ink);
+ background:
+ radial-gradient(circle at top left, rgba(214, 138, 58, 0.18), transparent 24%),
+ radial-gradient(circle at top right, rgba(47, 153, 115, 0.12), transparent 22%),
+ linear-gradient(180deg, #f6efe6 0%, #f1eadf 100%);
+ }
+
+ .page {
+ max-width: 1720px;
+ margin: 0 auto;
+ padding: 26px 22px 40px;
+ }
+
+ .hero {
+ position: relative;
+ overflow: hidden;
+ background:
+ radial-gradient(circle at 12% 18%, rgba(242, 196, 132, 0.18), transparent 24%),
+ radial-gradient(circle at 88% 12%, rgba(255, 255, 255, 0.10), transparent 18%),
+ linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 52%, var(--brand-soft) 100%);
+ color: #f7f0e4;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 30px;
+ padding: 34px 32px;
+ box-shadow: 0 28px 70px rgba(15, 58, 47, 0.22);
+ }
+
+ .hero::before {
+ content: "";
+ position: absolute;
+ inset: auto -6% -46% auto;
+ width: 320px;
+ height: 320px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.22), transparent 68%);
+ pointer-events: none;
+ }
+
+ .hero::after {
+ content: "";
+ position: absolute;
+ top: -90px;
+ right: 80px;
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+ border: 1px solid rgba(242, 196, 132, 0.16);
+ pointer-events: none;
+ }
+
+ h1 {
+ margin: 0;
+ font-size: 52px;
+ line-height: 1.12;
+ letter-spacing: -0.03em;
+ position: relative;
+ z-index: 1;
+ }
+
+ .summary {
+ margin-top: 10px;
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 14px;
+ }
+
+ .summary-card {
+ padding: 14px 16px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.07) 100%);
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ backdrop-filter: blur(8px);
+ position: relative;
+ z-index: 1;
+ }
+
+ .summary-label {
+ font-size: 11px;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: rgba(255, 244, 230, 0.68);
+ font-weight: 900;
+ }
+
+ .summary-value {
+ margin-top: 8px;
+ font-size: 24px;
+ font-weight: 900;
+ }
+
+ .summary-sub {
+ margin-top: 4px;
+ font-size: 12px;
+ color: rgba(255, 244, 230, 0.82);
+ font-weight: 700;
+ }
+
+ .tabs {
+ margin-top: 18px;
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ .hero-actions {
+ margin-top: 0;
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ position: absolute;
+ top: 0;
+ right: 0;
+ justify-content: flex-end;
+ }
+
+ .hero-action {
+ padding: 12px 16px;
+ border-radius: 999px;
+ border: 1px solid rgba(242, 196, 132, 0.34);
+ background: rgba(255, 255, 255, 0.08);
+ color: #f4efe6;
+ font-size: 14px;
+ font-weight: 900;
+ cursor: pointer;
+ }
+
+ .hero-action:hover {
+ background: rgba(242, 196, 132, 0.16);
+ border-color: rgba(242, 196, 132, 0.52);
+ }
+
+ .tab {
+ padding: 12px 16px;
+ border-radius: 999px;
+ border: 1px solid #d6c1a3;
+ background: linear-gradient(180deg, #fffdf8 0%, #f5ebdd 100%);
+ color: #244638;
+ font-size: 14px;
+ font-weight: 900;
+ cursor: pointer;
+ transition: all 0.16s ease;
+ }
+
+ .tab.active {
+ background: linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 100%);
+ color: #f4efe6;
+ border-color: var(--brand);
+ box-shadow: 0 12px 28px rgba(15, 58, 47, 0.2);
+ }
+
+ .layout {
+ margin-top: 18px;
+ display: grid;
+ grid-template-columns: 320px minmax(0, 1fr);
+ gap: 18px;
+ }
+
+ .panel {
+ background: var(--panel);
+ border: 1px solid var(--line);
+ border-radius: 26px;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ }
+
+ .panel-head {
+ padding: 18px 20px 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ }
+
+ .panel-title {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 900;
+ }
+
+ .panel-note {
+ color: var(--muted);
+ font-size: 12px;
+ font-weight: 800;
+ }
+
+ .panel-body {
+ padding: 18px 20px 20px;
+ }
+
+ .company-card {
+ display: grid;
+ gap: 10px;
+ }
+
+ .metric {
+ padding: 14px 16px;
+ border-radius: 18px;
+ background: linear-gradient(180deg, #fffdf8 0%, #f2e7d6 100%);
+ border: 1px solid var(--line);
+ }
+
+ .metric-label {
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.16em;
+ color: #8a6b3d;
+ text-transform: uppercase;
+ }
+
+ .metric-value {
+ margin-top: 8px;
+ font-size: 20px;
+ font-weight: 900;
+ line-height: 1.2;
+ }
+
+ .metric-sub {
+ margin-top: 5px;
+ font-size: 12px;
+ line-height: 1.45;
+ color: #425148;
+ font-weight: 700;
+ }
+
+ .metric-sub-list {
+ display: grid;
+ gap: 4px;
+ margin-top: 6px;
+ max-height: 132px;
+ overflow: auto;
+ padding-right: 4px;
+ }
+
+.metric-sub-item {
+ font-size: 12px;
+ line-height: 1.4;
+ color: #425148;
+ font-weight: 700;
+ }
+
+ /* MH embedded business dashboard theme */
+ body.mh-business-theme {
+ color: var(--ink);
+ background:
+ radial-gradient(circle at top left, rgba(214, 138, 58, 0.18), transparent 24%),
+ radial-gradient(circle at top right, rgba(47, 153, 115, 0.12), transparent 22%),
+ linear-gradient(180deg, #f6efe6 0%, #f1eadf 100%) !important;
+ font-family: "Pretendard", "Malgun Gothic", sans-serif;
+ }
+
+ body.mh-business-theme .wrap {
+ width: calc(100vw - 60px);
+ max-width: calc(100vw - 60px);
+ margin: 0 auto;
+ padding: 18px 18px 16px;
+ }
+
+ body.mh-business-theme .top {
+ position: relative;
+ overflow: hidden;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(320px, 520px);
+ gap: 18px 24px;
+ align-items: start;
+ background:
+ radial-gradient(circle at 12% 18%, rgba(242, 196, 132, 0.18), transparent 24%),
+ radial-gradient(circle at 88% 12%, rgba(255, 255, 255, 0.10), transparent 18%),
+ linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 52%, var(--brand-soft) 100%);
+ color: #f7f0e4;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 30px;
+ padding: 30px 30px 26px;
+ box-shadow: 0 28px 70px rgba(15, 58, 47, 0.22);
+ margin-bottom: 14px;
+ }
+
+ body.mh-business-theme .top::before {
+ content: "";
+ position: absolute;
+ inset: auto -6% -46% auto;
+ width: 320px;
+ height: 320px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.22), transparent 68%);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .top::after {
+ content: "";
+ position: absolute;
+ top: -90px;
+ right: 80px;
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+ border: 1px solid rgba(242, 196, 132, 0.16);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .top > div:first-child {
+ min-width: 0;
+ }
+
+ body.mh-business-theme .sub,
+ body.mh-business-theme .title,
+ body.mh-business-theme .today-date-label,
+ body.mh-business-theme #btnUpload {
+ display: none !important;
+ }
+
+ body.mh-business-theme .brand-head {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 10px;
+ min-width: 0;
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .brand-ci {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-width: 0;
+ }
+
+ body.mh-business-theme .brand-logo-wrap {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ }
+
+ body.mh-business-theme .brand-logo {
+ width: 50px;
+ height: 28px;
+ flex: 0 0 auto;
+ display: block;
+ }
+
+ body.mh-business-theme .brand-company {
+ color: #8db4ff;
+ font-size: 20px;
+ font-weight: 900;
+ letter-spacing: -0.03em;
+ white-space: nowrap;
+ line-height: 1;
+ }
+
+ body.mh-business-theme .brand-copy {
+ min-width: 0;
+ }
+
+ body.mh-business-theme .brand-title {
+ min-width: 0;
+ color: #f4efe6;
+ font-size: clamp(28px, 2.5vw, 46px);
+ font-weight: 900;
+ letter-spacing: -0.03em;
+ line-height: 1.1;
+ word-break: keep-all;
+ }
+
+ body.mh-business-theme .brand-subtitle {
+ display: inline;
+ color: rgba(244, 239, 230, 0.92);
+ font-size: 0.5em;
+ font-weight: 700;
+ letter-spacing: 0.01em;
+ margin-left: 6px;
+ white-space: nowrap;
+ }
+
+ body.mh-business-theme .controls {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ justify-self: end;
+ gap: 12px;
+ min-width: min(100%, 520px);
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .controls-top-row,
+ body.mh-business-theme .controls-top-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ body.mh-business-theme .search {
+ width: min(520px, 100%);
+ align-self: flex-end;
+ min-height: 52px;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ background: rgba(255, 255, 255, 0.14);
+ color: #fff7eb;
+ padding: 14px 18px;
+ font-size: 15px;
+ font-weight: 800;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
+ }
+
+ body.mh-business-theme .search::placeholder {
+ color: rgba(247, 240, 228, 0.66);
+ }
+
+ body.mh-business-theme .status {
+ display: none !important;
+ }
+
+ body.mh-business-theme .cards {
+ grid-column: 1 / -1;
+ display: grid;
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ gap: 14px;
+ margin-top: 6px;
+ margin-bottom: 0;
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .business-shell {
+ position: relative;
+ margin-top: 12px;
+ padding: 18px 18px 20px;
+ border-radius: 30px;
+ background:
+ radial-gradient(circle at 12% 12%, rgba(242, 196, 132, 0.10), transparent 26%),
+ radial-gradient(circle at 88% 10%, rgba(255, 255, 255, 0.08), transparent 18%),
+ linear-gradient(145deg, rgba(15, 58, 47, 0.96) 0%, rgba(26, 86, 69, 0.94) 100%);
+ border: 1px solid rgba(255, 255, 255, 0.10);
+ box-shadow: 0 26px 70px rgba(15, 58, 47, 0.16);
+ overflow: hidden;
+ }
+
+ body.mh-business-theme .business-shell::before {
+ content: "";
+ position: absolute;
+ inset: auto -10% -32% auto;
+ width: 360px;
+ height: 360px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.16), transparent 70%);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .business-shell::after {
+ content: "";
+ position: absolute;
+ top: -110px;
+ right: 90px;
+ width: 260px;
+ height: 260px;
+ border-radius: 50%;
+ border: 1px solid rgba(242, 196, 132, 0.12);
+ pointer-events: none;
+ }
+
+ body.mh-business-theme .cards-toolbar {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 6px;
+ }
+
+ body.mh-business-theme .cards-toolbar-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ body.mh-business-theme .summary-filter-chip,
+ body.mh-business-theme .summary-year-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-width: 58px;
+ padding: 9px 14px;
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ background: rgba(255, 255, 255, 0.08);
+ color: #f4efe6;
+ font-size: 12px;
+ font-weight: 900;
+ cursor: pointer;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
+ }
+
+ body.mh-business-theme .summary-filter-chip {
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 96px;
+ gap: 8px;
+ text-align: center;
+ }
+
+ body.mh-business-theme .summary-filter-chip .label {
+ color: rgba(244, 239, 230, 0.92);
+ letter-spacing: 0.01em;
+ }
+
+ body.mh-business-theme .summary-filter-chip .count {
+ color: #ffd08a;
+ font-size: 30px;
+ line-height: 1;
+ letter-spacing: -0.03em;
+ }
+
+ body.mh-business-theme .summary-filter-chip .meta {
+ color: rgba(255, 230, 190, 0.92);
+ font-size: 11px;
+ font-weight: 800;
+ }
+
+ body.mh-business-theme .summary-filter-chip.active,
+ body.mh-business-theme .summary-year-chip.active {
+ background: linear-gradient(180deg, #fff8ee 0%, #f2dec0 100%);
+ color: #0a2a22;
+ border-color: rgba(242, 196, 132, 0.58);
+ box-shadow: 0 12px 28px rgba(10, 42, 34, 0.18);
+ }
+
+ body.mh-business-theme .summary-filter-chip.active .count {
+ color: #b86b1f;
+ }
+
+ body.mh-business-theme .summary-filter-chip.active .label {
+ color: rgba(10, 42, 34, 0.78);
+ }
+
+ body.mh-business-theme .summary-filter-chip.active .meta {
+ color: #7c5a20;
+ }
+
+ body.mh-business-theme .card {
+ padding: 16px 16px 14px;
+ min-height: 108px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255, 250, 243, 0.98) 0%, rgba(247, 238, 225, 0.94) 100%);
+ border: 1px solid rgba(214, 193, 163, 0.78);
+ color: #173328;
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.75),
+ 0 14px 30px rgba(15, 58, 47, 0.08);
+ backdrop-filter: blur(10px);
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+
+ body.mh-business-theme .card.management {
+ background: linear-gradient(180deg, rgba(255, 246, 232, 0.98) 0%, rgba(250, 236, 208, 0.96) 100%);
+ border-color: rgba(214, 138, 58, 0.42);
+ }
+
+ body.mh-business-theme .card .k {
+ color: rgba(23, 51, 40, 0.68);
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.04em;
+ }
+
+ body.mh-business-theme .card .v {
+ margin-top: 7px;
+ color: #173328;
+ font-size: clamp(19px, 1.55vw, 27px);
+ font-weight: 900;
+ letter-spacing: -0.03em;
+ white-space: nowrap;
+ }
+
+ body.mh-business-theme .card .n {
+ margin-top: 5px;
+ color: rgba(63, 84, 74, 0.88);
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1.4;
+ }
+
+ body.mh-business-theme .panel {
+ background: var(--panel);
+ border: 1px solid var(--line);
+ border-radius: 26px;
+ box-shadow: var(--shadow);
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+ }
+
+ body.mh-business-theme .table-wrap {
+ overflow: auto;
+ }
+
+ body.mh-business-theme table {
+ width: 100%;
+ min-width: 1250px;
+ border-collapse: collapse;
+ }
+
+ body.mh-business-theme thead th {
+ background: #122b23;
+ color: rgba(247, 240, 228, 0.92);
+ font-size: 11px;
+ letter-spacing: 0.08em;
+ padding: 12px 10px;
+ text-align: left;
+ white-space: nowrap;
+ vertical-align: middle;
+ }
+
+ body.mh-business-theme .th-trigger,
+ body.mh-business-theme .th-trigger:hover,
+ body.mh-business-theme .th-trigger.active,
+ body.mh-business-theme .th-trigger.open {
+ color: rgba(247, 240, 228, 0.92);
+ }
+
+ body.mh-business-theme .th-mark,
+ body.mh-business-theme .th-caret,
+ body.mh-business-theme .th-meta {
+ color: #f2c484;
+ }
+
+ body.mh-business-theme tbody td {
+ padding: 12px 10px;
+ border-bottom: 1px solid #ece1cf;
+ font-size: 13px;
+ background: #fffaf3;
+ }
+
+ body.mh-business-theme th:nth-child(1),
+ body.mh-business-theme td:nth-child(1) { width: 5%; }
+ body.mh-business-theme th:nth-child(2),
+ body.mh-business-theme td:nth-child(2) { width: 6%; }
+ body.mh-business-theme th:nth-child(3),
+ body.mh-business-theme td:nth-child(3) { width: 31%; }
+ body.mh-business-theme th:nth-child(4),
+ body.mh-business-theme td:nth-child(4) { width: 12%; }
+ body.mh-business-theme th:nth-child(5),
+ body.mh-business-theme td:nth-child(5) { width: 7%; }
+ body.mh-business-theme th:nth-child(6),
+ body.mh-business-theme td:nth-child(6) { width: 6%; }
+ body.mh-business-theme th:nth-child(7),
+ body.mh-business-theme td:nth-child(7) { width: 9%; }
+ body.mh-business-theme th:nth-child(8),
+ body.mh-business-theme td:nth-child(8) { width: 8%; }
+ body.mh-business-theme th:nth-child(9),
+ body.mh-business-theme td:nth-child(9) { width: 8%; }
+ body.mh-business-theme th:nth-child(10),
+ body.mh-business-theme td:nth-child(10) { width: 8%; }
+ body.mh-business-theme th:nth-child(11),
+ body.mh-business-theme td:nth-child(11) { width: 5%; }
+
+ body.mh-business-theme tbody tr:hover td {
+ background: #fff5e8;
+ }
+
+ body.mh-business-theme .name,
+ body.mh-business-theme td strong {
+ color: #10251d;
+ }
+
+ body.mh-business-theme .subline {
+ color: #7c8b82;
+ }
+
+ body.mh-business-theme .client-main {
+ display: block;
+ font-weight: 800;
+ color: #10251d;
+ }
+
+ body.mh-business-theme .badge.badge-baron {
+ border-color: #f0bb75;
+ background: #fff2df;
+ color: #b96820;
+ }
+
+ body.mh-business-theme .badge.badge-family {
+ border-color: #a5cbb6;
+ background: #eef7f1;
+ color: #2d6a4f;
+ }
+
+ body.mh-business-theme .group-row td {
+ padding: 12px 14px 10px;
+ background: linear-gradient(180deg, #fff9ef 0%, #f5ebdd 100%);
+ border-top: 1px solid #ead8bc;
+ border-bottom: 1px solid #ead8bc;
+ }
+
+ body.mh-business-theme .group-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 13px;
+ border-radius: 999px;
+ background: #fffdfa;
+ border: 1px solid #d6c1a3;
+ color: #244638;
+ font-size: 12px;
+ font-weight: 900;
+ box-shadow: 0 10px 24px rgba(15, 58, 47, 0.10);
+ cursor: pointer;
+ }
+
+ body.mh-business-theme .group-chip .group-toggle {
+ margin-left: 4px;
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 999px;
+ background: rgba(242, 196, 132, 0.16);
+ color: #8a5a1f;
+ font-size: 14px;
+ font-weight: 900;
+ line-height: 1;
+ }
+
+ body.mh-business-theme .detail-row td {
+ background: linear-gradient(180deg, #fff7ec 0%, #fffdf8 100%);
+ border-top: 1px solid #ecd8ba;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.65);
+ }
+
+ body.mh-business-theme .detail-row .inline-panel {
+ background: transparent;
+ border-left: 3px solid var(--accent);
+ }
+
+ body.mh-business-theme .detail-row .inline-card,
+ body.mh-business-theme .detail-row .ledger-block {
+ box-shadow: 0 10px 24px rgba(15, 58, 47, 0.08);
+ }
+
+ body.mh-business-theme .table-top-note {
+ display: flex;
+ justify-content: flex-end;
+ margin: 0 2px 10px;
+ }
+
+ body.mh-business-theme .table-vat-note {
+ display: inline-flex;
+ align-items: center;
+ padding: 6px 10px;
+ border-radius: 999px;
+ background: rgba(255, 248, 238, 0.96);
+ border: 1px solid rgba(242, 196, 132, 0.46);
+ color: #6f5528;
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: -0.01em;
+ }
+
+ @media (max-width: 1280px) {
+ body.mh-business-theme .top {
+ grid-template-columns: 1fr;
+ }
+
+ body.mh-business-theme .controls {
+ min-width: 0;
+ justify-self: stretch;
+ }
+
+ body.mh-business-theme .controls-top-row,
+ body.mh-business-theme .controls-top-actions {
+ justify-content: flex-start;
+ }
+
+ body.mh-business-theme .search {
+ width: 100%;
+ align-self: stretch;
+ }
+
+ body.mh-business-theme .cards {
+ grid-template-columns: repeat(2, minmax(140px, 1fr));
+ }
+ }
+
+ @media (max-width: 720px) {
+ body.mh-business-theme .wrap {
+ width: calc(100vw - 30px);
+ max-width: calc(100vw - 30px);
+ padding: 12px 10px;
+ }
+
+ body.mh-business-theme .top {
+ padding: 18px 14px 16px;
+ gap: 14px;
+ }
+
+ body.mh-business-theme .brand-head {
+ gap: 8px;
+ align-items: flex-start;
+ }
+
+ body.mh-business-theme .brand-company {
+ font-size: 16px;
+ }
+
+ body.mh-business-theme .brand-title {
+ font-size: 30px;
+ }
+
+ body.mh-business-theme .brand-subtitle {
+ display: inline;
+ margin-left: 4px;
+ margin-top: 0;
+ white-space: normal;
+ }
+ }
+
+ .filter-row {
+ display: grid;
+ grid-template-columns: 1fr 160px 180px 180px;
+ gap: 12px;
+ margin-bottom: 14px;
+ }
+
+ .year-summary {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 10px;
+ margin-bottom: 14px;
+ }
+
+ .year-card {
+ padding: 14px 16px;
+ border-radius: 18px;
+ background: linear-gradient(180deg, #fffdf8 0%, #f3e7d6 100%);
+ border: 1px solid var(--line);
+ cursor: pointer;
+ transition: transform 0.16s ease, box-shadow 0.16s ease, border-color 0.16s ease, background 0.16s ease;
+ }
+
+ .year-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 22px rgba(18, 48, 30, 0.10);
+ }
+
+ .year-card.active {
+ background: linear-gradient(145deg, var(--brand-deep) 0%, var(--brand) 62%, var(--brand-soft) 100%);
+ border-color: rgba(214, 138, 58, 0.42);
+ box-shadow: 0 14px 30px rgba(15, 58, 47, 0.2);
+ }
+
+ .year-card.active .year-card-label,
+ .year-card.active .year-card-value,
+ .year-card.active .year-card-sub {
+ color: #f4efe6;
+ }
+
+ .year-card-label {
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.14em;
+ color: #7a684d;
+ }
+
+ .year-card-value {
+ margin-top: 8px;
+ font-size: clamp(15px, 1.45vw, 18px);
+ font-weight: 900;
+ color: var(--brand);
+ line-height: 1.2;
+ white-space: nowrap;
+ letter-spacing: -0.04em;
+ }
+
+ .year-card-sub {
+ margin-top: 4px;
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--muted);
+ }
+
+ .input, .select {
+ width: 100%;
+ border: 1px solid #d7c4a7;
+ background: #fffdf8;
+ border-radius: 14px;
+ padding: 12px 14px;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--ink);
+ outline: none;
+ }
+
+ .input:focus, .select:focus {
+ border-color: var(--brand-soft);
+ box-shadow: 0 0 0 3px rgba(47, 153, 115, 0.12);
+ }
+
+ .chip-row {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ margin-bottom: 12px;
+ }
+
+ .chip {
+ padding: 8px 12px;
+ border-radius: 999px;
+ background: #efe3cf;
+ color: #315243;
+ font-size: 12px;
+ font-weight: 900;
+ }
+
+ .table-wrap {
+ overflow: auto;
+ border-top: 1px solid var(--line);
+ }
+
+ .table-summary {
+ display: flex;
+ justify-content: flex-end;
+ gap: 18px;
+ padding: 14px 20px 18px;
+ border-top: 1px solid var(--line);
+ background: #fbf4ea;
+ font-size: 13px;
+ font-weight: 800;
+ color: #244233;
+ }
+
+ .table-summary strong {
+ color: #12301e;
+ font-size: 15px;
+ }
+
+ table {
+ width: 100%;
+ min-width: 1180px;
+ border-collapse: separate;
+ border-spacing: 0;
+ }
+
+ th, td {
+ border-right: 1px solid #ecdfcc;
+ border-bottom: 1px solid #ecdfcc;
+ padding: 11px 12px;
+ vertical-align: middle;
+ text-align: left;
+ font-size: 13px;
+ line-height: 1.55;
+ white-space: nowrap;
+ }
+
+ th:last-child, td:last-child { border-right: none; }
+
+ thead th {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ background: linear-gradient(180deg, var(--brand-deep) 0%, var(--brand) 100%);
+ color: #f4efe6;
+ font-size: 12px;
+ font-weight: 900;
+ text-align: center;
+ border-bottom: 1px solid rgba(242, 196, 132, 0.3);
+ }
+
+ tbody tr:nth-child(even) td {
+ background: #fbf4ea;
+ }
+
+ .money {
+ white-space: nowrap;
+ font-weight: 900;
+ color: var(--brand-soft);
+ }
+
+ .muted {
+ color: var(--muted);
+ font-size: 12px;
+ }
+
+ .cell-filter {
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: #12301e;
+ font: inherit;
+ font-weight: 800;
+ cursor: pointer;
+ white-space: nowrap;
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
+ }
+
+ .cell-filter:hover {
+ color: var(--mint);
+ }
+
+ .chip-action {
+ border: 1px solid #d9c8af;
+ background: #fffaf2;
+ cursor: pointer;
+ }
+
+ .chip-action:hover {
+ border-color: var(--mint);
+ color: var(--mint);
+ }
+
+ .empty {
+ padding: 24px;
+ text-align: center;
+ color: var(--muted);
+ font-weight: 800;
+ }
+
+ .page {
+ position: relative;
+ }
+
+ .page::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ background:
+ linear-gradient(rgba(15, 58, 47, 0.03) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(15, 58, 47, 0.03) 1px, transparent 1px);
+ background-size: 32px 32px;
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.34), transparent 82%);
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .hero {
+ padding: 28px 34px 24px;
+ border-radius: 32px;
+ box-shadow: 0 30px 70px rgba(15, 58, 47, 0.22);
+ }
+
+ .hero-grid {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 24px;
+ align-items: end;
+ }
+
+ .hero-grid > div:first-child {
+ position: relative;
+ min-height: 108px;
+ padding-right: 260px;
+ }
+
+ .brand-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 9px 14px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.08);
+ border: 1px solid rgba(242, 196, 132, 0.24);
+ color: rgba(255, 244, 230, 0.86);
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.22em;
+ text-transform: uppercase;
+ }
+
+ .brand-kicker::before {
+ content: "";
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--accent-soft);
+ box-shadow: 0 0 0 6px rgba(242, 196, 132, 0.12);
+ }
+
+ .brand-line {
+ display: grid;
+ grid-template-columns: 76px minmax(0, 1fr);
+ gap: 20px;
+ align-items: center;
+ margin-top: 0;
+ }
+
+ .hero-logo {
+ position: relative;
+ width: 76px;
+ height: 76px;
+ display: grid;
+ place-items: center;
+ border-radius: 24px;
+ background:
+ radial-gradient(circle at 30% 26%, rgba(255, 255, 255, 0.24), transparent 20%),
+ radial-gradient(circle at 68% 72%, rgba(242, 196, 132, 0.34), transparent 18%),
+ linear-gradient(145deg, rgba(242, 196, 132, 0.18) 0%, rgba(255, 255, 255, 0.04) 100%);
+ border: 1px solid rgba(242, 196, 132, 0.24);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), 0 18px 36px rgba(10, 42, 34, 0.24);
+ }
+
+ .hero-logo::before {
+ content: "";
+ position: absolute;
+ inset: 10px;
+ border-radius: 18px;
+ border: 1px solid rgba(242, 196, 132, 0.16);
+ }
+
+ .hero-logo::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 28px;
+ height: 28px;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.92), rgba(214, 138, 58, 0.18));
+ box-shadow:
+ 0 0 0 8px rgba(242, 196, 132, 0.06),
+ 0 0 24px rgba(242, 196, 132, 0.24);
+ }
+
+ .hero-logo-core {
+ display: none;
+ }
+
+ .hero-copy {
+ display: none;
+ }
+
+ .hero-side {
+ display: grid;
+ gap: 12px;
+ }
+
+ .hero-mini-card {
+ position: relative;
+ padding: 16px 18px 16px 58px;
+ border-radius: 22px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.11) 0%, rgba(255, 255, 255, 0.06) 100%);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ min-height: 88px;
+ }
+
+ .hero-mini-card::before {
+ content: attr(data-mark);
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ width: 30px;
+ height: 30px;
+ border-radius: 12px;
+ display: grid;
+ place-items: center;
+ background: rgba(242, 196, 132, 0.18);
+ color: #ffe6bf;
+ font-size: 11px;
+ font-weight: 1000;
+ letter-spacing: 0.08em;
+ }
+
+ .hero-mini-label {
+ color: rgba(255, 244, 230, 0.62);
+ font-size: 11px;
+ font-weight: 900;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ }
+
+ .hero-mini-value {
+ margin-top: 8px;
+ color: #fff7eb;
+ font-size: 24px;
+ font-weight: 1000;
+ letter-spacing: -0.03em;
+ }
+
+ .hero-mini-sub {
+ margin-top: 6px;
+ color: rgba(255, 244, 230, 0.76);
+ font-size: 13px;
+ line-height: 1.5;
+ font-weight: 700;
+ }
+
+ .tabs-shell {
+ margin-top: 18px;
+ padding: 12px;
+ border-radius: 24px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.68) 0%, rgba(255, 250, 243, 0.92) 100%);
+ border: 1px solid rgba(217, 197, 168, 0.8);
+ box-shadow: 0 18px 36px rgba(15, 58, 47, 0.08);
+ backdrop-filter: blur(10px);
+ }
+
+ .tabs-shell .tabs {
+ margin-top: 0;
+ }
+
+ .page > .hero:not(.hero-reimagined),
+ .page > .tabs:not(.tabs-reimagined) {
+ display: none;
+ }
+
+ .summary-card {
+ min-height: 122px;
+ overflow: hidden;
+ transition: transform 0.18s ease, box-shadow 0.18s ease;
+ }
+
+ .summary-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 18px 30px rgba(10, 42, 34, 0.16);
+ }
+
+ .summary-card::after {
+ content: "";
+ position: absolute;
+ inset: auto -14px -28px auto;
+ width: 96px;
+ height: 96px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(242, 196, 132, 0.14), transparent 70%);
+ }
+
+ .summary-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ display: none;
+ }
+
+ .summary-icon {
+ width: 42px;
+ height: 42px;
+ border-radius: 14px;
+ display: grid;
+ place-items: center;
+ background: rgba(255, 255, 255, 0.14);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ color: #fff3df;
+ font-size: 11px;
+ font-weight: 1000;
+ letter-spacing: 0.08em;
+ }
+
+ .summary-tail {
+ margin-top: auto;
+ padding-top: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ color: rgba(255, 244, 230, 0.74);
+ font-size: 11px;
+ font-weight: 800;
+ }
+
+ .summary-card .summary-value {
+ margin-top: 0;
+ }
+
+ .summary-line {
+ flex: 1;
+ height: 4px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.12);
+ overflow: hidden;
+ }
+
+ .summary-line span {
+ display: block;
+ height: 100%;
+ width: 64%;
+ border-radius: inherit;
+ background: linear-gradient(90deg, rgba(242, 196, 132, 0.96), rgba(47, 153, 115, 0.88));
+ }
+
+ .panel {
+ position: relative;
+ }
+
+ .panel::before {
+ content: "";
+ position: absolute;
+ inset: 0 0 auto 0;
+ height: 4px;
+ background: linear-gradient(90deg, var(--accent), #edd6af 38%, var(--mint) 100%);
+ opacity: 0.9;
+ }
+
+ tbody tr:hover td {
+ background: #f5ecdf;
+ }
+
+ @media (max-width: 1180px) {
+ .hero-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .hero-grid > div:first-child {
+ min-height: auto;
+ padding-right: 0;
+ }
+
+ .brand-line {
+ grid-template-columns: 92px minmax(0, 1fr);
+ gap: 16px;
+ }
+
+ .hero-logo {
+ width: 68px;
+ height: 68px;
+ }
+
+ .hero-actions {
+ position: static;
+ margin-top: 18px;
+ justify-content: flex-start;
+ }
+
+ .layout {
+ grid-template-columns: 1fr;
+ }
+
+ .filter-row {
+ grid-template-columns: 1fr;
+ }
+
+ .summary {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ }
+
+ @media (max-width: 720px) {
+ h1 {
+ font-size: 34px;
+ }
+
+ .summary {
+ grid-template-columns: 1fr;
+ }
+ }
diff --git a/incoming-files/served/ledger/README.md b/incoming-files/served/ledger/README.md
new file mode 100644
index 0000000..f88ae40
--- /dev/null
+++ b/incoming-files/served/ledger/README.md
@@ -0,0 +1,21 @@
+# Ledger Served Assets
+
+`8081` 사업관리대장 화면이 실제로 읽는 런타임 파일 모음이다.
+
+source-of-truth:
+
+- [frontend/apps/ledger](/home/hyunho/projects/mh-dashboard-organization/.dev-worktree-8081/frontend/apps/ledger)
+- 반영 스크립트: [scripts/publish_ledger_app.sh](/home/hyunho/projects/mh-dashboard-organization/.dev-worktree-8081/scripts/publish_ledger_app.sh)
+
+- `index.html`: `/integrations/ledger` 응답 본문
+ - `frontend/apps/ledger/index.html` 템플릿에서 publish 시 생성
+- `MH 통합 대시보드_260320.css`: ledger base stylesheet
+- `ledger-override.css`: 8081 ledger 디자인/레이아웃 오버라이드
+- `ledger-override.js`: 8081 ledger 상호작용/테이블/팝업 오버라이드
+- `사업관리대장-1.xlsx`: startup 시 기본 원본 DB 동기화에 사용하는 기본 데이터 파일
+
+규칙:
+
+- backend는 `사업관리대장` 원본 wrapper를 더 이상 직접 읽지 않는다.
+- runtime asset 수정은 `frontend/apps/ledger` 기준으로 먼저 반영하고, 이 디렉터리로 publish 한다.
+- 원본 비교가 필요하면 `incoming-files/reference/ledger/`를 본다.
diff --git a/incoming-files/served/ledger/index.html b/incoming-files/served/ledger/index.html
new file mode 100644
index 0000000..4098019
--- /dev/null
+++ b/incoming-files/served/ledger/index.html
@@ -0,0 +1,954 @@
+
+
+
+
+
+
사업관리대장 Dashboard
+
+
+
+
+
+
+
Live Management
사업관리대장 | Dashboard
+
파일 업로드
+
+
CSV/XLSX 파일을 업로드하면 데이터가 표시됩니다.
+
+
+
+
+
+
+
+
+
+ 구분 / 코드 ▼
+
+
+
+
+
+
+
+ 사업명 ▼
+
+
+
+
+
+
+
+ 계약법인 ▼
+
+
+
+
+
+
+
+ 진행상태 ▼
+
+
+
+
+
+
+
+ 외주비 (VAT 별도) ▼
+
+
+
+
+
+
+
+ 계약금 (VAT 별도) ▼
+
+
+
+
+
+
+
+ 수금액 (VAT 별도) ▼
+
+
+
+
+
+
+
+ 수금률 ▼
+
+
+
+
+
+
+
+
+
+
표시할 데이터가 없습니다.
+
+
+
+
+
+
+
+
+
+
+
외주 총액
+
외주 건수
+
계약기간
+
+
+
+
+
+
+
+
+
+
+
diff --git a/incoming-files/served/ledger/ledger-override.css b/incoming-files/served/ledger/ledger-override.css
new file mode 100644
index 0000000..505b65e
--- /dev/null
+++ b/incoming-files/served/ledger/ledger-override.css
@@ -0,0 +1,328 @@
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+body.mh-business-theme {
+ overflow-x: hidden;
+ background:
+ radial-gradient(circle at top left, rgba(214, 138, 58, 0.16), transparent 24%),
+ radial-gradient(circle at top right, rgba(47, 153, 115, 0.10), transparent 20%),
+ linear-gradient(180deg, #f6efe6 0%, #f1eadf 100%);
+}
+
+body.mh-business-theme .wrap {
+ width: min(100%, 2000px);
+ max-width: 2000px;
+ margin: 0 auto;
+ padding: 18px 18px 26px;
+ box-sizing: border-box;
+}
+
+body.mh-business-theme .top,
+body.mh-business-theme .status {
+ display: none !important;
+}
+
+body.mh-business-theme .cards {
+ display: grid;
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ gap: 14px;
+ margin: 0 0 16px;
+}
+
+body.mh-business-theme .business-shell {
+ width: 100%;
+ box-sizing: border-box;
+ margin-top: 2px;
+ padding: 18px;
+ border-radius: 32px;
+ background:
+ radial-gradient(circle at 16% 14%, rgba(255,255,255,0.05), transparent 18%),
+ radial-gradient(circle at 88% 8%, rgba(255,255,255,0.04), transparent 16%),
+ linear-gradient(145deg, #0b352b 0%, #174e41 52%, #245f50 100%);
+ box-shadow: 0 26px 54px rgba(15, 58, 47, 0.16);
+ border: 1px solid rgba(255,255,255,0.08);
+}
+
+body.mh-business-theme .cards-toolbar {
+ grid-column: 1 / -1;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ padding: 10px 0 2px;
+}
+
+body.mh-business-theme .cards-toolbar-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+body.mh-business-theme .cards-toolbar-search {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ min-width: min(360px, 100%);
+ flex: 1 1 320px;
+ max-width: 520px;
+}
+
+body.mh-business-theme .cards-toolbar-search .search {
+ width: 100%;
+ min-width: 0;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.12);
+ background: rgba(255,255,255,0.10);
+ color: #f4efe6;
+ padding: 14px 18px;
+ font-size: 14px;
+ font-weight: 800;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
+}
+
+body.mh-business-theme .cards-toolbar-search .search::placeholder {
+ color: rgba(244, 239, 230, 0.74);
+}
+
+body.mh-business-theme #btnUpload {
+ display: none !important;
+}
+
+body.mh-business-theme .cards-toolbar-metrics {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 14px;
+}
+
+body.mh-business-theme .summary-year-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 60px;
+ padding: 10px 16px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.14);
+ background: rgba(255,255,255,0.08);
+ color: #f4efe6;
+ font-size: 12px;
+ font-weight: 900;
+ cursor: pointer;
+}
+
+body.mh-business-theme .summary-year-chip.active {
+ background: linear-gradient(180deg, #fff8ee 0%, #f2dec0 100%);
+ color: #0a2a22;
+ border-color: rgba(242, 196, 132, 0.58);
+ box-shadow: 0 12px 28px rgba(10, 42, 34, 0.18);
+}
+
+body.mh-business-theme .summary-filter-chip {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ width: 100%;
+ min-height: 98px;
+ padding: 18px 22px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.14);
+ background: linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.07) 100%);
+ color: #f4efe6;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 16px 30px rgba(7, 28, 22, 0.14);
+ cursor: pointer;
+ text-align: center;
+}
+
+body.mh-business-theme .summary-filter-chip.active {
+ background: linear-gradient(180deg, #fff8ee 0%, #f2dec0 100%);
+ color: #0a2a22;
+ border-color: rgba(242, 196, 132, 0.58);
+}
+
+body.mh-business-theme .summary-filter-chip .label {
+ color: rgba(244, 239, 230, 0.78);
+ font-size: 13px;
+ font-weight: 900;
+}
+
+body.mh-business-theme .summary-filter-chip.active .label {
+ color: rgba(10, 42, 34, 0.78);
+}
+
+body.mh-business-theme .summary-filter-chip .count {
+ color: #fff7e6;
+ font-size: 32px;
+ line-height: 1;
+ font-weight: 900;
+}
+
+body.mh-business-theme .summary-filter-chip.active .count {
+ color: #b86b1f;
+}
+
+body.mh-business-theme .summary-filter-chip .meta {
+ color: #f2c484;
+ font-size: 11px;
+ font-weight: 800;
+ text-align: center;
+}
+
+body.mh-business-theme .summary-filter-chip.active .meta {
+ color: #7c5a20;
+}
+
+body.mh-business-theme .card {
+ grid-column: span 2;
+ min-height: 110px;
+ border-radius: 24px;
+ border: 1px solid rgba(217, 197, 168, 0.55);
+ background: linear-gradient(180deg, rgba(255,250,243,0.96) 0%, rgba(248,242,232,0.96) 100%);
+ padding: 18px 20px;
+ box-shadow: 0 18px 32px rgba(15, 58, 47, 0.08);
+}
+
+body.mh-business-theme .card.management {
+ grid-column: span 2;
+}
+
+body.mh-business-theme .card .k {
+ color: #5b6d63;
+ font-size: 12px;
+ font-weight: 900;
+}
+
+body.mh-business-theme .card .v {
+ margin-top: 8px;
+ color: #17392f;
+ font-size: 30px;
+ font-weight: 900;
+}
+
+body.mh-business-theme .card .n {
+ margin-top: 8px;
+ color: #7b6953;
+ font-size: 11px;
+ font-weight: 700;
+}
+
+body.mh-business-theme .panel {
+ border-radius: 28px;
+ border: 1px solid rgba(217, 197, 168, 0.55);
+ box-shadow: 0 18px 32px rgba(15, 58, 47, 0.08);
+}
+
+body.mh-business-theme .table-wrap {
+ width: 100%;
+ max-width: 100%;
+ border-radius: 28px;
+ overflow-x: hidden !important;
+}
+
+body.mh-business-theme .table-vat-note {
+ display: none !important;
+}
+
+body.mh-business-theme table {
+ width: 100% !important;
+ min-width: 0 !important;
+ table-layout: fixed;
+ background: rgba(255, 250, 243, 0.96);
+}
+
+body.mh-business-theme thead th {
+ background: #0f352b;
+ color: #fff5e6;
+ border-right: 1px solid rgba(242, 196, 132, 0.2);
+}
+
+body.mh-business-theme tbody td {
+ background: rgba(255, 250, 243, 0.96);
+}
+
+body.mh-business-theme .group-row td {
+ padding: 12px 14px 10px;
+ background: linear-gradient(180deg, rgba(255, 248, 238, 0.98) 0%, rgba(242, 222, 192, 0.78) 100%);
+ border-top: 1px solid rgba(214, 138, 58, 0.26);
+ border-bottom: 1px solid rgba(217, 197, 168, 0.54);
+}
+
+body.mh-business-theme .group-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: rgba(255, 250, 243, 0.98);
+ border: 1px solid rgba(214, 138, 58, 0.3);
+ color: #17392f;
+ font-size: 12px;
+ font-weight: 900;
+ box-shadow: 0 8px 18px rgba(15, 58, 47, 0.08);
+ cursor: pointer;
+}
+
+body.mh-business-theme .group-chip .group-toggle {
+ margin-left: 4px;
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 999px;
+ background: rgba(242, 196, 132, 0.18);
+ color: #b66e22;
+ font-size: 14px;
+ line-height: 1;
+}
+
+body.mh-business-theme .project-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 0;
+ border: 0;
+ background: none;
+ color: #17392f;
+ font: inherit;
+ font-weight: 900;
+ text-align: left;
+ cursor: pointer;
+}
+
+body.mh-business-theme .project-link:hover {
+ color: #0f6a55;
+}
+
+@media (max-width: 1280px) {
+ body.mh-business-theme .cards-toolbar-metrics {
+ grid-template-columns: 1fr;
+ }
+
+ body.mh-business-theme .card {
+ grid-column: span 4;
+ }
+}
+
+@media (max-width: 880px) {
+ body.mh-business-theme .wrap {
+ padding: 12px 12px 20px;
+ }
+
+ body.mh-business-theme .cards {
+ grid-template-columns: 1fr;
+ }
+
+ body.mh-business-theme .card {
+ grid-column: auto;
+ }
+
+ body.mh-business-theme .cards-toolbar-search {
+ margin-left: 0;
+ max-width: none;
+ flex-basis: 100%;
+ }
+}
diff --git a/incoming-files/served/ledger/ledger-override.js b/incoming-files/served/ledger/ledger-override.js
new file mode 100644
index 0000000..853e51c
--- /dev/null
+++ b/incoming-files/served/ledger/ledger-override.js
@@ -0,0 +1,498 @@
+(function () {
+ window.__mhLedgerEnhancementLoaded = false;
+ if (typeof S === "undefined" || typeof E === "undefined" || typeof render !== "function") return;
+ window.__mhLedgerEnhancementLoaded = true;
+ if (!S.dashboard) S.dashboard = { year: "", section: "active" };
+ if (!S.collapsedGroups) S.collapsedGroups = {};
+
+ function bgToday() {
+ var now = new Date();
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ }
+
+ function bgParseDate(value) {
+ var text = String(value || "").trim();
+ if (!text) return null;
+ var match = text.match(/(20\d{2})\D?(\d{1,2})\D?(\d{1,2})/);
+ if (match) {
+ var parsed = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
+ return isNaN(parsed.getTime()) ? null : parsed;
+ }
+ var fallback = new Date(text);
+ if (isNaN(fallback.getTime())) return null;
+ return new Date(fallback.getFullYear(), fallback.getMonth(), fallback.getDate());
+ }
+
+ function bgYearFromText(value) {
+ var match = String(value || "").trim().match(/(20\d{2})/);
+ return match ? match[1] : "";
+ }
+
+ function bgStartYear(row) {
+ return bgYearFromText(row && row.sDate);
+ }
+
+ function bgEndYear(row) {
+ return bgYearFromText(row && row.eDate);
+ }
+
+ function bgDisplayYear(row) {
+ var start = bgStartYear(row);
+ if (start) return start;
+ var contractMatch = String((row && row.cDate) || "").trim().match(/(20\d{2})/);
+ if (contractMatch) return contractMatch[1];
+ var nameMatch = String((row && row.name) || "").trim().match(/^(20\d{2})/);
+ if (nameMatch) return nameMatch[1];
+ return bgEndYear(row) || "미지정";
+ }
+
+ function bgCompletionYear(row) {
+ return bgEndYear(row) || bgDisplayYear(row);
+ }
+
+ function bgDateOrYearStart(row) {
+ var yearText = bgDisplayYear(row);
+ return bgParseDate(row && row.sDate) || bgParseDate(row && row.cDate) || (/^20\d{2}$/.test(yearText) ? new Date(Number(yearText), 0, 1) : null);
+ }
+
+ function bgDateOrYearEnd(row) {
+ var completionYear = bgCompletionYear(row);
+ return bgParseDate(row && row.eDate) || (/^20\d{2}$/.test(completionYear) ? new Date(Number(completionYear), 11, 31) : null);
+ }
+
+ function bgYearCutoff(year) {
+ var targetYear = Number(year || 0);
+ if (!targetYear) return null;
+ var today = bgToday();
+ if (targetYear < today.getFullYear()) return new Date(targetYear, 11, 31);
+ if (targetYear === today.getFullYear()) return today;
+ return null;
+ }
+
+ function bgYearStartDate(year) {
+ var targetYear = Number(year || 0);
+ return targetYear ? new Date(targetYear, 0, 1) : null;
+ }
+
+ function bgActiveInYear(row, year) {
+ var cutoff = bgYearCutoff(year);
+ var yearStart = bgYearStartDate(year);
+ var startDate = bgDateOrYearStart(row);
+ var endDate = bgDateOrYearEnd(row);
+ if (!(cutoff && yearStart && startDate)) return false;
+ if (startDate > cutoff) return false;
+ if (endDate && endDate < yearStart) return false;
+ return !(endDate && endDate <= cutoff);
+ }
+
+ function bgStartedInYear(row, year) {
+ var cutoff = bgYearCutoff(year);
+ var startDate = bgDateOrYearStart(row);
+ if (!(cutoff && startDate)) return false;
+ return startDate.getFullYear() === Number(year || 0) && startDate <= cutoff;
+ }
+
+ function bgCompletedInYear(row, year) {
+ var cutoff = bgYearCutoff(year);
+ var endDate = bgDateOrYearEnd(row);
+ if (!(cutoff && endDate)) return false;
+ return endDate.getFullYear() === Number(year || 0) && endDate <= cutoff;
+ }
+
+ function bgYearRange(row) {
+ var years = [];
+ var startYear = Number(bgDisplayYear(row) || 0);
+ var endYear = Number(bgCompletionYear(row) || 0);
+ if (startYear && endYear && endYear >= startYear) {
+ for (var year = startYear; year <= endYear; year += 1) years.push(String(year));
+ } else if (startYear) {
+ years.push(String(startYear));
+ }
+ return years;
+ }
+
+ function bgYears(rows) {
+ var currentYear = new Date().getFullYear();
+ var years = Array.from(new Set((Array.isArray(rows) ? rows : []).flatMap(bgYearRange).filter(function (year) {
+ return /^20\d{2}$/.test(year);
+ }))).sort(function (a, b) {
+ return Number(b) - Number(a);
+ });
+ years = years.filter(function (year) {
+ var numericYear = Number(year);
+ return numericYear >= 2018 && numericYear <= currentYear;
+ });
+ return years.length ? years : [String(currentYear)];
+ }
+
+ function bgEnsureYear(rows) {
+ var years = bgYears(rows);
+ if (!years.includes(S.dashboard.year)) S.dashboard.year = years[0];
+ return years;
+ }
+
+ function bgTotals(targetRows) {
+ return (Array.isArray(targetRows) ? targetRows : []).reduce(function (acc, row) {
+ acc.c += Number((row && row.cSup) || 0);
+ acc.col += Number((row && row.col) || 0);
+ acc.recv += Number((row && row.recv) || 0);
+ return acc;
+ }, { c: 0, col: 0, recv: 0 });
+ }
+
+ function isSupportServiceRow(row) {
+ var category = String((row && row.cat) || "").trim();
+ return category.indexOf("경영지원") >= 0 || category.indexOf("서비스") >= 0;
+ }
+
+ function isBaronProjectRow(row) {
+ var category = String((row && row.cat) || "").trim();
+ if (category.indexOf("바론") < 0) return false;
+ if (isSupportServiceRow(row)) return false;
+ return true;
+ }
+
+ function bgSummarize(rows, selectedYear) {
+ var items = Array.isArray(rows) ? rows : [];
+ var targetYear = selectedYear || bgEnsureYear(items)[0];
+ var activeRows = items.filter(function (row) { return bgActiveInYear(row, targetYear); });
+ var newProjectRows = items.filter(function (row) { return bgStartedInYear(row, targetYear); });
+ var completedRows = items.filter(function (row) { return bgCompletedInYear(row, targetYear); });
+ var managementRows = newProjectRows.filter(isSupportServiceRow);
+ return {
+ targetYear: targetYear,
+ activeRows: activeRows,
+ newProjectRows: newProjectRows,
+ completedRows: completedRows,
+ managementRows: managementRows,
+ managementTotals: bgTotals(managementRows)
+ };
+ }
+
+ function bgMatches(row) {
+ var section = S.dashboard.section || "active";
+ var selectedYear = S.dashboard.year || bgEnsureYear(S.all)[0];
+ if (section === "new") return bgStartedInYear(row, selectedYear);
+ if (section === "completed") return bgCompletedInYear(row, selectedYear);
+ return bgActiveInYear(row, selectedYear);
+ }
+
+ function normalizeStatusLabel(status) {
+ var value = String(status || "").trim();
+ if (!value) return "-";
+ if (value.indexOf("진행") >= 0) return "과업 진행중";
+ return value;
+ }
+
+ function formatSplitPercent(split) {
+ var numeric = parseFloat(String(split || "").replace(/[^0-9.\-]/g, ""));
+ if (!Number.isFinite(numeric) || numeric === 0) return "분담율 -%";
+ return "분담율 " + numeric.toFixed(2) + "%";
+ }
+
+ function projectYear(row) {
+ var start = String((row && row.sDate) || "").trim();
+ var startMatch = start.match(/(20\d{2})/);
+ if (startMatch) return startMatch[1];
+ var name = String((row && row.name) || "").trim();
+ var nameMatch = name.match(/^(20\d{2})/);
+ if (nameMatch) return nameMatch[1];
+ var end = String((row && row.eDate) || "").trim();
+ var endMatch = end.match(/(20\d{2})/);
+ if (endMatch) return endMatch[1];
+ return "미지정";
+ }
+
+ function groupSortRank(row) {
+ var selectedYear = Number((S.dashboard && S.dashboard.year) || projectYear(row) || 0);
+ var startYear = Number(projectYear(row) || 0);
+ if (typeof bgCompletedInYear === "function" && bgCompletedInYear(row, String(selectedYear))) return 9999;
+ if (!startYear) return 9998;
+ return startYear;
+ }
+
+ function tableGroupLabel(row) {
+ var startYear = projectYear(row);
+ if (/^20\d{2}$/.test(startYear)) return startYear + "년";
+ return "미지정";
+ }
+
+ function renderLedgerTable() {
+ var table = document.querySelector(".panel table");
+ if (!table || !E.tbody) return;
+ var thead = table.querySelector("thead");
+ if (thead) {
+ thead.innerHTML = '
'
+ + '구분 ▼
'
+ + '사업코드 ▼
'
+ + '사업명(계약명) ▼
'
+ + '발주처(계약처) ▼
'
+ + '발주방법 ▼
'
+ + '진행상태 ▼
'
+ + '계약금 ▼
'
+ + '외주비 ▼
'
+ + '미수금 ▼
'
+ + '수금액 ▼
'
+ + '수금률 ▼
'
+ + " ";
+ }
+ var rows = (Array.isArray(S.viewRows) ? S.viewRows : []).slice().sort(function (a, b) {
+ var ar = groupSortRank(a);
+ var br = groupSortRank(b);
+ if (ar !== br) return ar - br;
+ return Number(b.recv || 0) - Number(a.recv || 0);
+ });
+ S.viewRows = rows;
+ var lastGroupLabel = "";
+ E.tbody.innerHTML = rows.map(function (r) {
+ var groupLabel = tableGroupLabel(r);
+ var isCollapsed = !!S.collapsedGroups[groupLabel];
+ var groupRow = "";
+ if (groupLabel !== lastGroupLabel) {
+ groupRow = '
' + esc(groupLabel) + ' ' + (isCollapsed ? "+" : "-") + " ";
+ lastGroupLabel = groupLabel;
+ }
+ if (isCollapsed) return groupRow;
+ return groupRow + '
'
+ + '= 0 ? 'badge-baron' : 'badge-family') + '">' + esc(r.cat || "-") + '
'
+ + '' + esc(r.code || "-") + '
'
+ + '' + esc(r.name || "-") + ' ' + esc(r.periodText || "-") + '
'
+ + '' + esc((r.client || "").trim() || "-") + '
' + esc(formatSplitPercent(r.split)) + '
'
+ + '' + esc(r.order || "-") + '
'
+ + '= 0 ? 'ok' : '') + '">' + esc(normalizeStatusLabel(r.status)) + '
'
+ + '' + esc(won(r.cSup || 0)) + ' '
+ + '' + esc(r.outsourceCost ? won(r.outsourceCost) : "-") + ' '
+ + '' + esc(won(r.recv || 0)) + ' '
+ + '' + esc(won(r.col || 0)) + ' '
+ + '' + esc((Number(r.rate || 0)).toFixed(2) + "%") + ' '
+ + ' ';
+ }).join("");
+ }
+
+ function renderCollectionBoard(r) {
+ var payments = Array.isArray(r.payments) && r.payments.length ? r.payments : [{
+ pay: r.pay || "-",
+ issueDate: r.issueDate || "",
+ collectDate: r.collectDateSummary || r.colDate || "",
+ collected: r.col || 0,
+ receivable: r.recv || Math.max(0, Number(r.sTot || 0) - Number(r.col || 0)),
+ note: r.note || "",
+ status: r.status || ""
+ }];
+ return '
C
수금 및 기성 현황
기성 차수별 세금계산서 발행 및 수금 내역
총 수금 ' + esc(won(r.col || 0)) + '
기성 차수 세금계산서 발행일 수금일 수금금액 미수금액 비고 '
+ + payments.map(function (payment, index) {
+ var noteParts = [];
+ if (payment.status) noteParts.push(payment.status);
+ if (payment.note) noteParts.push(payment.note);
+ return '' + esc((index + 1) + "차") + ' ' + esc(payment.pay || "-") + ' ' + esc(payment.issueDate ? d(payment.issueDate) : "-") + ' ' + esc(payment.collectDate ? d(payment.collectDate) : "-") + ' ' + esc(won(payment.collected || 0)) + ' ' + esc(won(payment.receivable || 0)) + ' ' + esc(noteParts.join(" / ") || "-") + ' ';
+ }).join("")
+ + "
";
+ }
+
+ function renderContactCard(label, name, company, department, phone, email) {
+ var hasValue = [name, company, department, phone, email].some(function (value) {
+ return String(value || "").trim() !== "";
+ });
+ if (!hasValue) {
+ return '
' + esc(label) + '
등록된 담당자 정보가 없습니다.
';
+ }
+ return '
";
+ }
+
+ function renderProjectInline(r) {
+ var payments = Array.isArray(r.payments) ? r.payments : [];
+ var latestCollect = d(r.collectDateSummary || r.colDate);
+ var hasOutsource = (Array.isArray(r.outsourceItems) && r.outsourceItems.length > 0) || Number(r.outsourceCost || 0) > 0 || Number(r.outsourcePaid || 0) > 0 || Number(r.outsourceRemaining || 0) > 0;
+ var clientDisplay = typeof normalizeClientDisplay === "function" ? normalizeClientDisplay(r.client) : (String(r.client || "").trim() || "-");
+ var splitDisplay = typeof formatSplitDisplay === "function" ? formatSplitDisplay(r.split) : formatSplitPercent(r.split).replace("분담율 ", "");
+ var summaryCards = [
+ '
계약금
' + esc(won(r.cSup || 0)) + '
',
+ '
수금액
' + esc(won(r.col || 0)) + '
' + esc(latestCollect === "-" ? "수금일 없음" : "최종 수금일 " + latestCollect) + '
',
+ '
수금률
' + esc((Number(r.rate || 0)).toFixed(2) + "%") + '
' + esc(payments.length ? "기성 " + payments.length + "차까지 반영" : "차수 정보 없음") + '
',
+ '
미수금액
' + esc(won(r.recv || 0)) + '
잔여 수금 필요 금액
'
+ ].join("");
+ var boards = [
+ hasOutsource && typeof renderOutsourceBoard === "function" ? renderOutsourceBoard(r) : "",
+ renderCollectionBoard(r)
+ ].filter(Boolean).join("");
+ return '
' + renderContactCard("계약 / 청구 담당자", r.cmNm, r.cmCo, r.cmDp, r.cmPh, r.cmEm) + renderContactCard("부서 담당자", r.dmNm, r.dmCo, r.dmDp, r.dmPh, r.dmEm) + '
' + boards + '
';
+ }
+
+ function openProjectWindow(r) {
+ var popupKey = typeof rowKey === "function"
+ ? rowKey(r).replace(/[^0-9a-zA-Z]/g, "_")
+ : String((r.code || "project") + "_" + (r.name || "")).replace(/[^0-9a-zA-Z_]/g, "_");
+ var popup = window.open("", "business_project_" + popupKey, "width=1600,height=980,resizable=yes,scrollbars=yes");
+ if (!popup) return;
+ var styleText = Array.from(document.querySelectorAll("style")).map(function (el) {
+ return el.textContent || "";
+ }).join("\n");
+ var detailHtml = renderProjectInline(r);
+ var pageHtml = '
'
+ + esc(r.name || "사업 상세")
+ + ' ";
+ popup.document.open();
+ popup.document.write(pageHtml);
+ popup.document.close();
+ popup.focus();
+ }
+
+ async function tryLoadDbDefaultBusinessLedger() {
+ if (window.__mhBusinessDefaultLoaded) return;
+ window.__mhBusinessDefaultLoaded = true;
+ try {
+ var response = await fetch("/api/integration/business-ledger-default");
+ if (!response.ok) throw new Error("기본 사업관리대장 원본을 불러오지 못했습니다.");
+ var fileName = response.headers.get("x-source-filename") || "사업관리대장-1.xlsx";
+ var buffer = await response.arrayBuffer();
+ if (!buffer || !buffer.byteLength) throw new Error("기본 사업관리대장 원본 데이터가 비어 있습니다.");
+ await loadLedgerFile(buffer, fileName);
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ function applyDashboardChrome() {
+ if (!E.cards) return;
+ document.body.setAttribute("data-mh-ledger-enhanced", "true");
+ var wrap = document.querySelector(".wrap");
+ var panel = document.querySelector(".panel");
+ if (wrap && panel) {
+ var shell = wrap.querySelector(".business-shell");
+ if (!shell) {
+ shell = document.createElement("div");
+ shell.className = "business-shell";
+ wrap.insertBefore(shell, E.cards);
+ }
+ if (E.cards.parentNode !== shell) shell.appendChild(E.cards);
+ if (panel.parentNode !== shell) shell.appendChild(panel);
+ }
+ var years = bgEnsureYear(S.all);
+ var summary = bgSummarize(S.all, S.dashboard.year);
+ var rows = Array.isArray(S.rows) ? S.rows : [];
+ var visibleBaronProjectRows = rows.filter(isBaronProjectRow);
+ var totals = bgTotals(visibleBaronProjectRows);
+ var totalRate = typeof rate === "function" ? rate("", totals.col, totals.col + totals.recv) : 0;
+ var toolbarHtml = '
";
+ var cards = [
+ { label: summary.targetYear + "년 프로젝트", value: visibleBaronProjectRows.length.toLocaleString("ko-KR") + " 건", note: "" },
+ { label: "계약금", value: won(totals.c), note: "" },
+ { label: "수금액", value: won(totals.col), note: "" },
+ { label: "미수금", value: won(totals.recv), note: "" },
+ { label: "수금률(%)", value: totalRate.toFixed(2) + "%", note: "" },
+ { label: "경영지원서비스 금액", value: won(summary.managementTotals.c), note: "", className: "management" }
+ ];
+ E.cards.innerHTML = toolbarHtml + cards.map(function (card) {
+ return '
' + esc(card.label) + '
' + esc(card.value) + '
' + esc(card.note || "") + "
";
+ }).join("");
+ var searchWrap = E.cards.querySelector(".cards-toolbar-search");
+ if (searchWrap && E.search) {
+ searchWrap.appendChild(E.search);
+ E.search.placeholder = "전체 검색";
+ }
+ }
+
+ var originalRender = render;
+ render = function () {
+ originalRender();
+ applyDashboardChrome();
+ renderLedgerTable();
+ };
+
+ filter = function () {
+ bgEnsureYear(S.all);
+ var q = String(E.search.value || "").trim().toLowerCase();
+ var searched = !q ? S.all.slice() : S.all.filter(function (r) {
+ return [r.code, r.name, r.client, r.pm, r.status, r.cat, r.corp, r.pay, (r.payments || []).map(function (p) { return p.pay; }).join(" "), r.periodText].join(" ").toLowerCase().includes(q);
+ });
+ S.rows = searched.filter(function (r) {
+ return bgMatches(r) && matchesColumnFilters(r);
+ });
+ render();
+ };
+
+ if (E.cards && !E.cards.dataset.dashboardBound) {
+ E.cards.dataset.dashboardBound = "true";
+ E.cards.addEventListener("click", function (event) {
+ var yearButton = event.target && event.target.closest ? event.target.closest("[data-dashboard-year]") : null;
+ if (yearButton) {
+ S.dashboard.year = yearButton.getAttribute("data-dashboard-year") || S.dashboard.year;
+ filter();
+ return;
+ }
+ var sectionButton = event.target && event.target.closest ? event.target.closest("[data-dashboard-section]") : null;
+ if (sectionButton) {
+ S.dashboard.section = sectionButton.getAttribute("data-dashboard-section") || "active";
+ filter();
+ }
+ });
+ }
+
+ if (E.tbody && !E.tbody.dataset.projectBound) {
+ E.tbody.dataset.projectBound = "true";
+ E.tbody.addEventListener("click", function (event) {
+ var groupButton = event.target && event.target.closest ? event.target.closest("[data-group-label]") : null;
+ if (groupButton) {
+ var label = groupButton.getAttribute("data-group-label") || "";
+ if (label) {
+ S.collapsedGroups[label] = !S.collapsedGroups[label];
+ render();
+ }
+ return;
+ }
+ var trigger = event.target && event.target.closest ? event.target.closest(".project-link") : null;
+ if (!trigger) return;
+ var key = trigger.getAttribute("data-project-key") || "";
+ var rows = Array.isArray(S.viewRows) ? S.viewRows : [];
+ var row = rows.find(function (item) {
+ return (String(item.code || "") + "|" + String(item.name || "")) === key;
+ });
+ if (row) openProjectWindow(row);
+ });
+ }
+
+ setTimeout(function () {
+ try {
+ filter();
+ if (typeof loadLedgerFile === "function") {
+ tryLoadDbDefaultBusinessLedger();
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }, 0);
+
+ window.addEventListener("message", function (event) {
+ var data = event.data || {};
+ if (data.source !== "total-upload" || data.type !== "business") return;
+ setTimeout(function () {
+ try {
+ applyDashboardChrome();
+ renderLedgerTable();
+ } catch (error) {
+ console.error(error);
+ }
+ }, 50);
+ });
+})();
diff --git a/incoming-files/served/ledger/사업관리대장-1.xlsx b/incoming-files/served/ledger/사업관리대장-1.xlsx
new file mode 100644
index 0000000..a6d20ac
Binary files /dev/null and b/incoming-files/served/ledger/사업관리대장-1.xlsx differ
diff --git a/scripts/prepare_dev_worktree.sh b/scripts/prepare_dev_worktree.sh
index 904c209..c3b961b 100755
--- a/scripts/prepare_dev_worktree.sh
+++ b/scripts/prepare_dev_worktree.sh
@@ -44,7 +44,7 @@ copy_optional_path "incoming-files/1.png"
copy_optional_path "incoming-files/260320.html"
copy_optional_path "incoming-files/sample style.css"
copy_optional_path "incoming-files/seat/center_chair_people_map(2).html"
-copy_optional_path "incoming-files/사업관리대장"
+copy_optional_path "incoming-files/reference/ledger"
echo "[6/6] Dev worktree ready"
echo "Path: ${DEV_DIR}"
diff --git a/scripts/publish_ledger_app.sh b/scripts/publish_ledger_app.sh
new file mode 100755
index 0000000..2665244
--- /dev/null
+++ b/scripts/publish_ledger_app.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+APP_DIR="${ROOT_DIR}/frontend/apps/ledger"
+TARGET_DIR="${ROOT_DIR}/incoming-files/served/ledger"
+LEDGER_ASSET_VERSION="${LEDGER_ASSET_VERSION:-20260401-03}"
+
+mkdir -p "${TARGET_DIR}"
+
+cp "${APP_DIR}/assets/MH 통합 대시보드_260320.css" "${TARGET_DIR}/MH 통합 대시보드_260320.css"
+cp "${APP_DIR}/assets/ledger-override.css" "${TARGET_DIR}/ledger-override.css"
+cp "${APP_DIR}/assets/ledger-override.js" "${TARGET_DIR}/ledger-override.js"
+
+HEAD_ASSETS='
'
+BODY_SCRIPTS=''
+
+perl -0pe 's|__LEDGER_HEAD_ASSETS__|'"${HEAD_ASSETS}"'|g; s|__LEDGER_BODY_SCRIPTS__|'"${BODY_SCRIPTS}"'|g' \
+ "${APP_DIR}/index.html" > "${TARGET_DIR}/index.html"
+
+echo "Published ledger app source to ${TARGET_DIR}"
diff --git a/scripts/publish_payment_app.sh b/scripts/publish_payment_app.sh
new file mode 100755
index 0000000..81e2543
--- /dev/null
+++ b/scripts/publish_payment_app.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+APP_DIR="${ROOT_DIR}/frontend/apps/payment"
+TARGET_FILE="${ROOT_DIR}/incoming-files/served/payment.html"
+COMPARE_FILE="${ROOT_DIR}/incoming-files/payment.html"
+
+cp "${APP_DIR}/index.html" "${TARGET_FILE}"
+cp "${APP_DIR}/index.html" "${COMPARE_FILE}"
+
+echo "Published payment app source to ${TARGET_FILE}"
diff --git a/scripts/publish_team_app.sh b/scripts/publish_team_app.sh
new file mode 100755
index 0000000..f6cf187
--- /dev/null
+++ b/scripts/publish_team_app.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+APP_DIR="${ROOT_DIR}/frontend/apps/team"
+TARGET_FILE="${ROOT_DIR}/incoming-files/served/mh.html"
+COMPARE_FILE="${ROOT_DIR}/incoming-files/mh.html"
+
+cp "${APP_DIR}/index.html" "${TARGET_FILE}"
+cp "${APP_DIR}/index.html" "${COMPARE_FILE}"
+
+echo "Published team app source to ${TARGET_FILE}"