Refine lifecycle detail modal totals and project detail popup layout

This commit is contained in:
2026-05-04 14:32:15 +09:00
parent df25f04119
commit d75a57cc97

View File

@@ -1920,6 +1920,7 @@
const [companyAccountDetailModal, setCompanyAccountDetailModal] = useState(null); const [companyAccountDetailModal, setCompanyAccountDetailModal] = useState(null);
const [companyAccountDetailModalLoading, setCompanyAccountDetailModalLoading] = useState(false); const [companyAccountDetailModalLoading, setCompanyAccountDetailModalLoading] = useState(false);
const [lifecycleBreakdownModal, setLifecycleBreakdownModal] = useState(null); const [lifecycleBreakdownModal, setLifecycleBreakdownModal] = useState(null);
const [lifecycleProjectTotalModal, setLifecycleProjectTotalModal] = useState(null);
const [lifecycleAccountDetailModal, setLifecycleAccountDetailModal] = useState(null); const [lifecycleAccountDetailModal, setLifecycleAccountDetailModal] = useState(null);
const [lifecycleAccountDetailModalLoading, setLifecycleAccountDetailModalLoading] = useState(false); const [lifecycleAccountDetailModalLoading, setLifecycleAccountDetailModalLoading] = useState(false);
const [lifecycleAllocationModal, setLifecycleAllocationModal] = useState(null); const [lifecycleAllocationModal, setLifecycleAllocationModal] = useState(null);
@@ -5389,7 +5390,28 @@
<div style={{ display: "grid", gap: 18, marginTop: 18 }}> <div style={{ display: "grid", gap: 18, marginTop: 18 }}>
<section className="panel" style={{ padding: 16 }}> <section className="panel" style={{ padding: 16 }}>
<div style={{ fontSize: 16, fontWeight: 700 }}>프로젝트별 금액</div> <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12 }}>
<div style={{ fontSize: 16, fontWeight: 700 }}>프로젝트별 금액</div>
<button
type="button"
className="button-muted"
style={{ height: 30, padding: "0 10px", borderRadius: 9, fontSize: 12 }}
onClick={() => {
const allocationAccount = (lifecycleBreakdownModal.accounts || []).find(
(acc) => Array.isArray(acc.allocation_details) && acc.allocation_details.length
) || (lifecycleBreakdownModal.accounts || [])[0] || {};
setLifecycleProjectTotalModal({
label: lifecycleBreakdownModal.label || "",
total_amount: Number(lifecycleBreakdownModal.expense_supply || 0),
projects: Array.isArray(lifecycleBreakdownModal.projects) ? lifecycleBreakdownModal.projects.map((project) => ({ ...project })) : [],
allocation_mode: allocationAccount.allocation_mode || "",
allocation_details: Array.isArray(allocationAccount.allocation_details) ? allocationAccount.allocation_details.map((row) => ({ ...row })) : [],
});
}}
>
상세보기
</button>
</div>
<div className="subtle" style={{ marginTop: 6 }}>연결된 프로젝트들 항목에 반영된 지출 금액입니다.</div> <div className="subtle" style={{ marginTop: 6 }}>연결된 프로젝트들 항목에 반영된 지출 금액입니다.</div>
<div style={{ display: "grid", gap: 10, marginTop: 12 }}> <div style={{ display: "grid", gap: 10, marginTop: 12 }}>
{(lifecycleBreakdownModal.projects || []).length ? ( {(lifecycleBreakdownModal.projects || []).length ? (
@@ -6824,7 +6846,13 @@
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 12, marginTop: 16 }}> <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 12, marginTop: 16 }}>
<div className="mini-card"> <div className="mini-card">
<div className="subtle">지출 합계</div> <div className="subtle">지출 합계</div>
<div className="summary-value" style={{ marginTop: 6 }}>{fmt(lifecycleAccountDetailModal.detail.summary.expense_supply_sum || 0)}</div> <div className="summary-value" style={{ marginTop: 6 }}>
{fmt(
Number(lifecycleAccountDetailModal.allocation_result_amount || 0) > 0
? Number(lifecycleAccountDetailModal.allocation_result_amount || 0)
: Number(lifecycleAccountDetailModal.detail.summary.expense_supply_sum || 0)
)}
</div>
</div> </div>
<div className="mini-card"> <div className="mini-card">
<div className="subtle">거래 건수</div> <div className="subtle">거래 건수</div>
@@ -7000,6 +7028,84 @@
</div> </div>
)} )}
{lifecycleProjectTotalModal && (
<div className="modal-backdrop" onClick={() => setLifecycleProjectTotalModal(null)}>
<div
className="modal-panel modal-panel-wide"
onClick={(e) => e.stopPropagation()}
style={{ width: "min(980px, calc(100vw - 32px))", maxHeight: "min(760px, calc(100vh - 32px))", overflow: "auto" }}
>
<div style={{ display: "flex", justifyContent: "space-between", gap: 16, alignItems: "flex-start" }}>
<div>
<div style={{ fontSize: 20, fontWeight: 700 }}>{lifecycleProjectTotalModal.label || "전체"} 프로젝트별 상세</div>
<div className="subtle" style={{ marginTop: 6 }}>연결 프로젝트 기준 전체 금액 내역입니다.</div>
</div>
<button className="button-muted" onClick={() => setLifecycleProjectTotalModal(null)}>닫기</button>
</div>
<div className="mini-card" style={{ marginTop: 14 }}>
<div className="subtle">합계 금액</div>
<div className="summary-value" style={{ marginTop: 6 }}>{fmt(lifecycleProjectTotalModal.total_amount || 0)}</div>
</div>
<section className="mini-card" style={{ marginTop: 12 }}>
<div style={{ fontSize: 16, fontWeight: 700 }}>배부 계산식</div>
<div className="subtle" style={{ marginTop: 6 }}>본사관리비 배부원천 x (프로젝트 기준값 / 전체 기준값)</div>
<div className="subtle" style={{ marginTop: 2 }}>
기준값: {(lifecycleProjectTotalModal.allocation_mode || "") === "income_ratio" ? "프로젝트 입금 / 전체입금" : "프로젝트 지출 / 전체지출"}
</div>
<div className="subtle" style={{ marginTop: 8, lineHeight: 1.55 }}>
{(Array.isArray(lifecycleProjectTotalModal.allocation_details) && lifecycleProjectTotalModal.allocation_details.length
? lifecycleProjectTotalModal.allocation_details
: []
).map((row, idx) => {
const sourceAmount = Number(row.source_amount || 0);
const allocatedAmount = Number(row.allocated_amount || 0);
const projectBasisAmount = Number(row.display_project_basis_amount ?? row.project_basis_amount ?? 0);
const totalBasisAmount = Number(row.display_total_basis_amount ?? row.total_basis_amount ?? 0);
return (
<div key={`lifecycle-total-alloc-${idx}`}>
{((row.year_month || "").slice(0, 4) || "-")} · {fmt(sourceAmount)} x ({fmt(projectBasisAmount)} / {fmt(totalBasisAmount)}) = {fmt(allocatedAmount)}
</div>
);
})}
{!((lifecycleProjectTotalModal.allocation_details || []).length) && (
<div>표시할 배부 계산식이 없습니다.</div>
)}
</div>
</section>
<div style={{ display: "grid", gap: 10, marginTop: 14 }}>
{(lifecycleProjectTotalModal.projects || []).length ? (
(lifecycleProjectTotalModal.projects || []).map((item) => (
<div
key={`lifecycle-total-project-${item.project_code}`}
style={{
textAlign: "left",
border: "1px solid var(--line)",
borderRadius: 14,
background: "white",
padding: "12px 14px",
display: "grid",
gridTemplateColumns: "minmax(220px, 1fr) minmax(160px, 0.45fr)",
gap: 12,
alignItems: "center",
}}
>
<div>
<div style={{ fontWeight: 700 }}>{item.project_name || "(이름없음)"}</div>
<div className="subtle" style={{ marginTop: 4 }}>
{item.project_code || "-"} · {item.project_type || "미지정"} · {item.construction_family || "종류미지정"} · {item.construction_method || "공법미지정"}
</div>
</div>
<div style={{ textAlign: "right", fontWeight: 700 }}>{fmt(item.expense_supply || 0)}</div>
</div>
))
) : (
<div className="empty-state">표시할 프로젝트가 없습니다.</div>
)}
</div>
</div>
</div>
)}
{lifecycleAllocationModal && ( {lifecycleAllocationModal && (
<div className="modal-backdrop" onClick={() => setLifecycleAllocationModal(null)}> <div className="modal-backdrop" onClick={() => setLifecycleAllocationModal(null)}>
<div className="modal-panel" onClick={(e) => e.stopPropagation()} style={{ width: "min(560px, calc(100vw - 24px))" }}> <div className="modal-panel" onClick={(e) => e.stopPropagation()} style={{ width: "min(560px, calc(100vw - 24px))" }}>