diff --git a/PTC/management_dashboard_preview.html b/PTC/management_dashboard_preview.html
index 0c4b1cf..040750c 100644
--- a/PTC/management_dashboard_preview.html
+++ b/PTC/management_dashboard_preview.html
@@ -102,11 +102,11 @@
box-shadow: 0 10px 24px rgba(17,63,103,0.18);
}
.panel {
- background: var(--soft);
- border: 1px solid rgba(216,226,236,0.95);
- border-radius: 26px;
- box-shadow: 0 18px 42px rgba(15, 28, 46, 0.07);
- backdrop-filter: blur(10px);
+ background: rgba(248, 251, 254, 0.82);
+ border: 1px solid rgba(232, 239, 246, 0.95);
+ border-radius: 18px;
+ box-shadow: 0 3px 10px rgba(15, 28, 46, 0.025);
+ backdrop-filter: blur(4px);
}
.layout {
display: grid;
@@ -219,9 +219,9 @@
gap: 12px;
}
.summary-card {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,252,0.98));
+ border: 1px solid #eef3f8;
+ border-radius: 16px;
+ background: rgba(255,255,255,0.9);
padding: 16px;
}
.summary-card-grid {
@@ -243,10 +243,11 @@
text-overflow: clip;
}
.metric, .mini-card {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,252,0.98));
- padding: 16px;
+ border: 1px solid rgba(233, 240, 247, 0.68);
+ border-radius: 12px;
+ background: rgba(255,255,255,0.7);
+ box-shadow: none;
+ padding: 14px;
}
.metric-value {
margin-top: 8px;
@@ -280,41 +281,41 @@
.project-list {
display: flex;
flex-direction: column;
- gap: 10px;
+ gap: 4px;
max-height: calc(100vh - 220px);
overflow: auto;
padding-right: 4px;
}
.project-item {
- border: 1px solid var(--line);
- border-radius: 18px;
- padding: 12px 13px;
- background: white;
+ border: none;
+ border-bottom: 1px solid rgba(233, 240, 247, 0.78);
+ border-radius: 0;
+ padding: 12px 8px 12px 6px;
+ background: transparent;
cursor: pointer;
transition: 0.18s ease;
}
- .project-item:hover { transform: translateY(-1px); border-color: #bfd3e5; }
+ .project-item:hover { transform: none; background: rgba(248,251,254,0.68); }
.project-item.active {
- border-color: var(--blue);
- box-shadow: inset 0 0 0 1px var(--blue);
- background: linear-gradient(180deg, #f7fbff, #eef5fb);
+ box-shadow: inset 3px 0 0 var(--blue);
+ background: #f8fbfe;
}
.project-item.selected {
box-shadow: inset 0 0 0 2px rgba(31,122,140,0.35);
}
.vendor-item {
- border: 1px solid var(--line);
- border-radius: 18px;
- padding: 14px;
- background: white;
+ border: none;
+ border-bottom: 1px solid rgba(233, 240, 247, 0.78);
+ border-radius: 0;
+ padding: 12px 8px 12px 6px;
+ background: transparent;
cursor: pointer;
transition: 0.18s ease;
}
- .vendor-item:hover { transform: translateY(-1px); border-color: #bfd3e5; }
+ .vendor-item:hover { transform: none; background: rgba(248,251,254,0.68); }
.vendor-item.active {
- border-color: var(--blue);
- box-shadow: inset 0 0 0 1px var(--blue);
- background: linear-gradient(180deg, #f7fbff, #eef5fb);
+ box-shadow: inset 3px 0 0 var(--blue);
+ background: #f8fbfe;
}
.list-mode-toggle {
display: grid;
@@ -535,10 +536,10 @@
width: 100%;
}
.dashboard-status-legend-item {
- border: 1px solid #e6eef6;
- border-radius: 14px;
- padding: 10px 12px;
- background: rgba(255,255,255,0.88);
+ border: 1px solid rgba(233, 240, 247, 0.5);
+ border-radius: 10px;
+ padding: 8px 10px;
+ background: rgba(255,255,255,0.46);
}
button.dashboard-status-legend-item {
width: 100%;
@@ -547,13 +548,14 @@
transition: 0.18s ease;
}
button.dashboard-status-legend-item:hover {
- transform: translateY(-1px);
- border-color: #bfd3e5;
+ transform: none;
+ border-color: #d2deea;
+ background: rgba(248,251,254,0.8);
}
button.dashboard-status-legend-item.active {
- border-color: var(--blue);
- box-shadow: inset 0 0 0 1px var(--blue);
- background: linear-gradient(180deg, #f7fbff, #eef5fb);
+ border-color: #d8e4ef;
+ box-shadow: inset 3px 0 0 var(--blue);
+ background: #f8fbfe;
}
.dashboard-status-legend-item.dashboard-status-inline-item {
display: flex;
@@ -630,10 +632,10 @@
margin-top: 16px;
}
.dashboard-family-chart-row {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,252,0.98));
- padding: 14px 16px;
+ border: 1px solid rgba(233, 240, 247, 0.62);
+ border-radius: 12px;
+ background: rgba(255,255,255,0.5);
+ padding: 12px 14px;
display: grid;
grid-template-columns: minmax(0, 1fr) 120px;
gap: 14px;
@@ -642,13 +644,14 @@
transition: 0.18s ease;
}
.dashboard-family-chart-row:hover {
- transform: translateY(-1px);
- border-color: #bfd3e5;
+ transform: none;
+ border-color: #d8e4ef;
+ background: rgba(248,251,254,0.78);
}
.dashboard-family-chart-row.active {
- border-color: var(--blue);
- box-shadow: inset 0 0 0 1px var(--blue);
- background: linear-gradient(180deg, #f7fbff, #eef5fb);
+ border-color: #d8e4ef;
+ box-shadow: inset 3px 0 0 var(--blue);
+ background: #f8fbfe;
}
.dashboard-family-row-main {
display: grid;
@@ -660,10 +663,10 @@
margin-top: 16px;
}
.dashboard-method-row {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,252,0.98));
- padding: 14px 16px;
+ border: 1px solid rgba(233, 240, 247, 0.62);
+ border-radius: 12px;
+ background: rgba(255,255,255,0.5);
+ padding: 12px 14px;
display: grid;
grid-template-columns: 92px minmax(0, 1fr) 180px;
gap: 16px;
@@ -672,13 +675,14 @@
transition: 0.18s ease;
}
.dashboard-method-row:hover {
- transform: translateY(-1px);
- border-color: #bfd3e5;
+ transform: none;
+ border-color: #d8e4ef;
+ background: rgba(248,251,254,0.78);
}
.dashboard-method-row.active {
- border-color: var(--blue);
- box-shadow: inset 0 0 0 1px var(--blue);
- background: linear-gradient(180deg, #f7fbff, #eef5fb);
+ border-color: #d8e4ef;
+ box-shadow: inset 3px 0 0 var(--blue);
+ background: #f8fbfe;
}
.dashboard-method-row-main {
display: grid;
@@ -696,9 +700,9 @@
margin-top: 14px;
}
.dashboard-selection-card {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,252,0.98));
+ border: 1px solid rgba(233, 240, 247, 0.62);
+ border-radius: 14px;
+ background: rgba(255,255,255,0.54);
padding: 16px;
}
.dashboard-finance-bars {
@@ -726,17 +730,25 @@
.dashboard-selection-kpis {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
- gap: 10px;
+ gap: 0;
margin-top: 14px;
+ border: 1px solid rgba(233, 240, 247, 0.72);
+ border-radius: 16px;
+ background: rgba(255,255,255,0.58);
+ overflow: hidden;
}
.dashboard-selection-kpis-wide {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.dashboard-selection-kpi {
- border: 1px solid #e6eef6;
- border-radius: 14px;
+ border: none;
+ border-radius: 0;
padding: 10px 12px;
- background: rgba(255,255,255,0.88);
+ background: transparent;
+ box-shadow: none;
+ }
+ .dashboard-selection-kpis > *:not(:last-child) {
+ border-right: 1px solid rgba(233, 240, 247, 0.72);
}
.dashboard-family-grid {
display: grid;
@@ -744,21 +756,22 @@
gap: 12px;
}
.dashboard-family-card {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,252,0.98));
- padding: 16px;
+ border: 1px solid rgba(233, 240, 247, 0.62);
+ border-radius: 12px;
+ background: rgba(255,255,255,0.52);
+ padding: 14px;
cursor: pointer;
transition: 0.18s ease;
}
.dashboard-family-card:hover {
- transform: translateY(-1px);
- border-color: #bfd3e5;
+ transform: none;
+ border-color: #d8e4ef;
+ background: rgba(248,251,254,0.78);
}
.dashboard-family-card.active {
- border-color: var(--blue);
- box-shadow: inset 0 0 0 1px var(--blue);
- background: linear-gradient(180deg, #f7fbff, #eef5fb);
+ border-color: #d8e4ef;
+ box-shadow: inset 3px 0 0 var(--blue);
+ background: #f8fbfe;
}
.dashboard-family-mini-track {
display: flex;
@@ -1841,8 +1854,11 @@
const [selectedManagementYear, setSelectedManagementYear] = useState("");
const [selectedManagementCategory, setSelectedManagementCategory] = useState("");
const [selectedManagementExcludedYear, setSelectedManagementExcludedYear] = useState("");
+ const [managementYearWindowStart, setManagementYearWindowStart] = useState(0);
+ const MANAGEMENT_YEAR_WINDOW_SIZE = 4;
const [managementOverviewAccounts, setManagementOverviewAccounts] = useState([]);
const [managementOverviewAccountsLoading, setManagementOverviewAccountsLoading] = useState(false);
+ const [managementYearDetailModalOpen, setManagementYearDetailModalOpen] = useState(false);
const [managementAccountModal, setManagementAccountModal] = useState(null);
const [managementAccountModalLoading, setManagementAccountModalLoading] = useState(false);
const [managementAccountModalView, setManagementAccountModalView] = useState("all");
@@ -1877,6 +1893,7 @@
: null
);
const [dashboardOngoingProjectModalOpen, setDashboardOngoingProjectModalOpen] = useState(false);
+ const [dashboardYearDetailModalOpen, setDashboardYearDetailModalOpen] = useState(false);
const [dashboardMarginGradeFilter, setDashboardMarginGradeFilter] = useState(
["all", "deficit", "caution", "good", "excellent"].includes(initialGradeParam) ? initialGradeParam : "all"
);
@@ -1964,6 +1981,26 @@
return params.toString();
}, [selectedManagementYear, selectedManagementCategory, managementDateFrom, managementDateTo]);
+ const sortedManagementOverviewItems = useMemo(
+ () =>
+ [...(managementOverview.items || [])].sort(
+ (a, b) => Number(a.year || 0) - Number(b.year || 0)
+ ),
+ [managementOverview.items]
+ );
+
+ const visibleManagementOverviewItems = useMemo(
+ () => sortedManagementOverviewItems.slice(managementYearWindowStart, managementYearWindowStart + MANAGEMENT_YEAR_WINDOW_SIZE),
+ [sortedManagementOverviewItems, managementYearWindowStart, MANAGEMENT_YEAR_WINDOW_SIZE]
+ );
+
+ const visibleManagementProfitItems = useMemo(() => {
+ const visibleYears = new Set(visibleManagementOverviewItems.map((item) => String(item.year)));
+ return [...(managementOverview.yearly_profit_items || [])]
+ .sort((a, b) => Number(a.year || 0) - Number(b.year || 0))
+ .filter((item) => visibleYears.has(String(item.year)));
+ }, [managementOverview.yearly_profit_items, visibleManagementOverviewItems]);
+
const overallSummaryQuery = useMemo(() => {
const params = new URLSearchParams();
if (projectType && projectType !== "전체") params.set("project_type", projectType);
@@ -2335,6 +2372,21 @@
}
}, [managementOverview, selectedManagementYear, selectedManagementCategory]);
+ useEffect(() => {
+ const total = sortedManagementOverviewItems.length;
+ if (!total) {
+ setManagementYearWindowStart(0);
+ return;
+ }
+ const maxStart = Math.max(0, total - MANAGEMENT_YEAR_WINDOW_SIZE);
+ setManagementYearWindowStart((prev) => {
+ if (prev > maxStart) return maxStart;
+ if (prev < 0) return 0;
+ if (prev === 0 && total > MANAGEMENT_YEAR_WINDOW_SIZE) return maxStart;
+ return prev;
+ });
+ }, [sortedManagementOverviewItems.length, MANAGEMENT_YEAR_WINDOW_SIZE]);
+
useEffect(() => {
const hasExcludedSelection = (managementOverview.items || []).some(
(item) => item.year === selectedManagementExcludedYear && Number(item.excluded_total || 0) !== 0
@@ -2374,6 +2426,12 @@
};
}, [filteredManagementAccountTransactions]);
+ function getManagementCategoryAmount(yearItem, categoryName) {
+ return Number(
+ ((yearItem?.categories || []).find((category) => category.name === categoryName)?.amount) || 0
+ );
+ }
+
useEffect(() => {
if (selectedManagementYear && selectedManagementCategory && managementCategorySectionRef.current) {
managementCategorySectionRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
@@ -2798,17 +2856,37 @@
const companyGraphMaxValue = useMemo(() => {
return companyGraphRows.reduce((max, row) => Math.max(max, Number(row.income_supply || 0), Number(row.expense_supply || 0)), 1);
}, [companyGraphRows]);
+ const companyGraphLayout = useMemo(() => {
+ const chartLeft = 90;
+ const chartRight = 980;
+ const chartWidth = chartRight - chartLeft;
+ const pointCount = Math.max(companyGraphRows.length, 1);
+ const centerStartX = chartLeft + 60;
+ const centerEndX = chartRight - 60;
+ const availableCenterWidth = Math.max(centerEndX - centerStartX, 0);
+ const stepX = pointCount > 1 ? availableCenterWidth / (pointCount - 1) : 0;
+ const barWidth = pointCount > 1
+ ? Math.max(22, Math.min(72, stepX * 0.68))
+ : 94;
+ return {
+ chartLeft,
+ chartRight,
+ chartWidth,
+ centerStartX,
+ centerEndX,
+ stepX,
+ barWidth,
+ chartHeight: 250,
+ baseY: 320,
+ };
+ }, [companyGraphRows.length]);
const companyGraphLinePoints = useMemo(() => {
- const startX = 150;
- const stepX = companyGraphRows.length > 1 ? 260 : 0;
- const chartHeight = 250;
- const baseY = 320;
return companyGraphRows.map((row, index) => {
- const x = startX + index * stepX;
- const y = baseY - (Number(row.income_supply || 0) / companyGraphMaxValue) * chartHeight;
+ const x = companyGraphLayout.centerStartX + index * companyGraphLayout.stepX;
+ const y = companyGraphLayout.baseY - (Number(row.income_supply || 0) / companyGraphMaxValue) * companyGraphLayout.chartHeight;
return `${x},${y}`;
}).join(" ");
- }, [companyGraphRows, companyGraphMaxValue]);
+ }, [companyGraphRows, companyGraphMaxValue, companyGraphLayout]);
const visibleDashboardProjects = useMemo(() => {
const filtered = selectedDashboardFamily === "전체"
? dashboardProjectsBase
@@ -3901,9 +3979,9 @@
minHeight: 96,
appearance: "none",
WebkitAppearance: "none",
- border: "1px solid #e6eef6",
- borderRadius: 14,
- background: "rgba(255,255,255,0.88)",
+ border: "none",
+ borderRadius: 0,
+ background: "transparent",
width: "100%",
textAlign: "left",
boxSizing: "border-box",
@@ -3977,25 +4055,20 @@
{!!(managementOverview.yearly_construction_margin_items || []).length && (
-
- {(managementOverview.yearly_construction_margin_items || []).map((yearItem) => (
-
-
{yearItem.year}년 시공 수익률
-
- {(Number(yearItem.margin_rate || 0) || 0).toFixed(1)}%
-
-
- 수익 {fmtEokManagement(yearItem.profit_supply || 0)}
-
+
+
+
년도별 시공 상세
+
+ 연도별 시공 수익률과 수익 금액을 표로 비교해서 봅니다.
- ))}
+
+
)}
@@ -4214,6 +4287,69 @@
{dashboardOngoingProjectModalContent}
)}
+ {dashboardYearDetailModalOpen && (
+
setDashboardYearDetailModalOpen(false)}>
+
e.stopPropagation()}>
+
+
+
년도별 시공 상세
+
+ 연도별 시공 수익률과 수익을 표로 비교합니다.
+
+
+
+
+
+
+
+
+ {["연도", "시공 수익률", "수익", "수입", "지출"].map((label, index) => (
+ |
+ {label}
+ |
+ ))}
+
+
+
+ {(managementOverview.yearly_construction_margin_items || []).map((yearItem) => (
+
+ |
+ {yearItem.year}년
+ |
+
+ {(Number(yearItem.margin_rate || 0) || 0).toFixed(1)}%
+ |
+
+ {fmtEokManagement(yearItem.profit_supply || 0)}
+ |
+
+ {fmtEokManagement(yearItem.income_supply || 0)}
+ |
+
+ {fmtEokManagement(yearItem.expense_supply || 0)}
+ |
+
+ ))}
+
+
+
+
+
+ )}
{currentTab === "project" && (
+ {!!sortedManagementOverviewItems.length && (
+
+
+
+ {visibleManagementOverviewItems[0]?.year || "-"}년 ~ {visibleManagementOverviewItems[visibleManagementOverviewItems.length - 1]?.year || "-"}년
+
+
+
+ )}
- {!!(managementOverview.yearly_profit_items || []).length && (
+ {!!visibleManagementProfitItems.length && (
- {(managementOverview.yearly_profit_items || []).map((yearItem) => (
-
-
{yearItem.year}년 수익
+ {visibleManagementProfitItems.map((yearItem, index) => (
+
+
{yearItem.year}년 수익
)}
-
- {(managementOverview.items || []).map((yearItem) => (
-
+
+ {visibleManagementOverviewItems.map((yearItem) => (
+
{yearItem.year}년
{fmtEokManagement(yearItem.total_expense || 0)}
@@ -5183,14 +5380,14 @@
alignItems: "baseline",
width: "100%",
height: "auto",
- minHeight: 44,
+ minHeight: 40,
boxSizing: "border-box",
- padding: "10px 12px",
+ padding: "8px 10px",
borderColor:
selectedManagementYear === yearItem.year &&
selectedManagementCategory === category.name
- ? "var(--blue-700)"
- : "var(--line)",
+ ? "#d8e4ef"
+ : "transparent",
color:
selectedManagementYear === yearItem.year &&
selectedManagementCategory === category.name
@@ -5199,8 +5396,13 @@
background:
selectedManagementYear === yearItem.year &&
selectedManagementCategory === category.name
- ? "rgba(45, 106, 176, 0.08)"
- : "white",
+ ? "#f8fbfe"
+ : "transparent",
+ boxShadow:
+ selectedManagementYear === yearItem.year &&
+ selectedManagementCategory === category.name
+ ? "inset 3px 0 0 var(--blue)"
+ : "none",
}}
>
{category.name}
@@ -5208,11 +5410,11 @@
))}