Fix contract linkage and persist related project groups
This commit is contained in:
+101
-57
@@ -1,13 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}연도별 수익 비용 정리{% endblock %}
|
||||
{% block title %}연도별 수익/비용{% endblock %}
|
||||
|
||||
{% block head_extra %}
|
||||
<style>
|
||||
.summary-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 320px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.summary-stack {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 220px 220px 1fr;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
@@ -15,7 +27,8 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
margin-top: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
@@ -43,7 +56,7 @@
|
||||
radial-gradient(circle at top left, rgba(17, 17, 17, 0.045), transparent 36%);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
padding: 14px;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.92);
|
||||
}
|
||||
|
||||
@@ -51,12 +64,6 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.chart-svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
@@ -78,11 +85,47 @@
|
||||
|
||||
.metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.chart-stack {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.summary-stack .panel {
|
||||
padding: 14px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.summary-stack .section-title h2,
|
||||
.chart-stack .section-title h2 {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.summary-stack .field select {
|
||||
min-width: 0;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.summary-stack .stat-card {
|
||||
padding: 10px 12px;
|
||||
border-radius: 12px;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.summary-stack .stat-card .label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.summary-stack .stat-card .value {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.summary-layout,
|
||||
.filter-grid,
|
||||
.metric-grid {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -93,52 +136,51 @@
|
||||
|
||||
{% block content %}
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>연도별 수익 비용 정리</h2>
|
||||
</div>
|
||||
<div class="filter-grid">
|
||||
<div class="field">
|
||||
<select id="granularity" aria-label="보기 기준">
|
||||
<option value="yearly">연간</option>
|
||||
<option value="monthly">월간</option>
|
||||
</select>
|
||||
<div class="summary-layout">
|
||||
<div class="summary-stack">
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>수익/비용 현황</h2>
|
||||
</div>
|
||||
<div class="filter-grid">
|
||||
<div class="field">
|
||||
<select id="granularity" aria-label="보기 기준">
|
||||
<option value="yearly">연간</option>
|
||||
<option value="monthly">월간</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select id="yearFilter" aria-label="연도 선택">
|
||||
<option value="recent10">최근 10개년</option>
|
||||
{% for year in available_years %}
|
||||
<option value="{{ year }}">{{ year }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-grid" id="metricGrid"></div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select id="yearFilter" aria-label="연도 선택">
|
||||
<option value="recent10">최근 10개년</option>
|
||||
{% for year in available_years %}
|
||||
<option value="{{ year }}">{{ year }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="chart-stack">
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>비용 구조</h2>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="legend chart-legend" id="expenseLegend"></div>
|
||||
<svg id="expenseChart" class="chart-svg expense-chart-svg" viewBox="0 0 1600 420" preserveAspectRatio="xMidYMid meet"></svg>
|
||||
</div>
|
||||
</section>
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>수금/비용/영업수지 그래프</h2>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="legend chart-legend" id="balanceLegend"></div>
|
||||
<svg id="balanceChart" class="chart-svg" viewBox="0 0 1200 420" preserveAspectRatio="xMidYMid meet"></svg>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>선택 구간 요약</h2>
|
||||
</div>
|
||||
<div class="metric-grid" id="metricGrid"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>비용 구조</h2>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="legend chart-legend" id="expenseLegend"></div>
|
||||
<svg id="expenseChart" class="chart-svg expense-chart-svg" viewBox="0 0 1600 420" preserveAspectRatio="xMidYMid meet"></svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>수금/비용/영업수지 그래프</h2>
|
||||
</div>
|
||||
<div class="chart-box">
|
||||
<div class="legend chart-legend" id="balanceLegend"></div>
|
||||
<svg id="balanceChart" class="chart-svg" viewBox="0 0 1200 420" preserveAspectRatio="xMidYMid meet"></svg>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -293,7 +335,9 @@
|
||||
}
|
||||
|
||||
function buildSignedAxis(maxPositiveValue, minNegativeValue, width, height, margin) {
|
||||
const rangeMax = Math.max(Math.abs(maxPositiveValue || 0), Math.abs(minNegativeValue || 0), 1);
|
||||
const forcedNegativeFloor = -20000000000;
|
||||
const normalizedNegative = Math.min(Number(minNegativeValue || 0), forcedNegativeFloor);
|
||||
const rangeMax = Math.max(Math.abs(maxPositiveValue || 0), Math.abs(normalizedNegative || 0), 1);
|
||||
const { tickStep, tickMax } = buildPositiveAxisScale(rangeMax, 4);
|
||||
const plotHeight = height - margin.top - margin.bottom;
|
||||
const zeroY = margin.top + (plotHeight * tickMax) / (tickMax * 2);
|
||||
|
||||
+2
-1
@@ -477,7 +477,7 @@
|
||||
<nav class="nav">
|
||||
<a href="/" class="{% if request.url.path == '/' %}active{% endif %}">대시보드</a>
|
||||
<a href="/projects" class="{% if request.url.path == '/projects' %}active{% endif %}">프로젝트 정보</a>
|
||||
<a href="/annual-summary" class="{% if request.url.path == '/annual-summary' %}active{% endif %}">연도별 수익 비용 정리</a>
|
||||
<a href="/annual-summary" class="{% if request.url.path == '/annual-summary' %}active{% endif %}">연도별 수익/비용</a>
|
||||
<div class="nav-spacer"></div>
|
||||
<aside
|
||||
class="sync-status"
|
||||
@@ -496,6 +496,7 @@
|
||||
<div>마지막 확인: <strong id="syncLastChecked">-</strong></div>
|
||||
<div>서버 시간: <strong id="syncServerTime">{{ server_time or '-' }}</strong></div>
|
||||
<div>데이터 버전: <strong id="syncDataVersion">{{ data_version or '-' }}</strong></div>
|
||||
<div>계약/청구 동기화: <strong>{{ import_sync_summary.contract_project_count or 0 }}</strong> / <strong>{{ import_sync_summary.billing_project_count or 0 }}</strong></div>
|
||||
</div>
|
||||
</aside>
|
||||
</nav>
|
||||
|
||||
@@ -228,6 +228,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-shell" style="margin-bottom: 16px;">
|
||||
<div class="legend-box" style="justify-content:flex-start;">
|
||||
<span class="legend-item"><span class="legend-swatch" style="background:#111827;"></span>계약 프로젝트 {{ import_sync_summary.contract_project_count or 0 }}건</span>
|
||||
<span class="legend-item"><span class="legend-swatch" style="background:#0f766e;"></span>청구 프로젝트 {{ import_sync_summary.billing_project_count or 0 }}건</span>
|
||||
<span class="legend-item"><span class="legend-swatch" style="background:#f59e0b;"></span>변경계약 검토 {{ import_sync_summary.review_needed_count or 0 }}건</span>
|
||||
<span class="legend-item"><span class="legend-swatch" style="background:#1d4ed8;"></span>한맥 계약합계 {{ "{:,.0f}".format(import_sync_summary.total_hanmac_contract_amount or 0) }}</span>
|
||||
<span class="legend-item"><span class="legend-swatch" style="background:#7c3aed;"></span>청구 수금합계 {{ "{:,.0f}".format(import_sync_summary.total_collected_amount or 0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-layout">
|
||||
<section class="dashboard-status-panel">
|
||||
<div class="dashboard-status-grid">
|
||||
|
||||
@@ -431,6 +431,35 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="section-title">
|
||||
<h2>계약·청구 동기화 현황</h2>
|
||||
<p>계약현황 / 기성청구현황 파일 기준</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="label">계약 프로젝트</div>
|
||||
<div class="value">{{ import_sync_summary.contract_project_count or 0 }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">청구 프로젝트</div>
|
||||
<div class="value">{{ import_sync_summary.billing_project_count or 0 }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">청구 행 수</div>
|
||||
<div class="value">{{ import_sync_summary.billing_entry_count or 0 }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">변경계약 검토</div>
|
||||
<div class="value">{{ import_sync_summary.review_needed_count or 0 }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">청구 수금합계</div>
|
||||
<div class="value">{{ "{:,.0f}".format(import_sync_summary.total_collected_amount or 0) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="two-col">
|
||||
<div class="stack">
|
||||
<section class="panel">
|
||||
|
||||
+652
-90
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user