feat: 경영성과 대시보드 DB·엑셀 업로드·HTML 차트 연동
- mgmt_perf_uploads / mgmt_perf_snapshots 스키마 - POST /api/mgmt-perf/upload, 기본 페이로드 data/mgmt-perf-default-payload.json - 대시보드 페이지: 업로드 영역 + iframe embed - public/mgmt-perf: 원본 HTML 기반 CSS·dashboard-app.js - xlsx 미설치 시 기본 페이로드+메타만 저장 Made-with: Cursor
This commit is contained in:
@@ -6,6 +6,112 @@
|
||||
<%- include('partials/favicon') %>
|
||||
<title>경영성과 대시보드 - XAVIS</title>
|
||||
<link rel="stylesheet" href="/public/styles.css" />
|
||||
<style>
|
||||
.mgmt-perf-page main.container {
|
||||
max-width: 1200px;
|
||||
}
|
||||
.mgmt-perf-split {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.mgmt-upload-panel {
|
||||
border: 1px solid var(--border, #e0e0e0);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
background: var(--panel-bg, #fafafa);
|
||||
}
|
||||
.mgmt-upload-panel h2 {
|
||||
font-size: 1.05rem;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.mgmt-upload-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.mgmt-upload-form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mgmt-upload-form input[type="file"] {
|
||||
max-width: 280px;
|
||||
}
|
||||
.mgmt-upload-form select,
|
||||
.mgmt-upload-form input[type="number"] {
|
||||
min-width: 100px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.mgmt-upload-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.btn-mgmt-upload {
|
||||
padding: 10px 18px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: #1565c0;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-mgmt-upload:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.mgmt-upload-msg {
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
.mgmt-upload-msg.ok {
|
||||
color: #2e7d32;
|
||||
}
|
||||
.mgmt-upload-msg.err {
|
||||
color: #c62828;
|
||||
}
|
||||
.mgmt-dash-panel h2 {
|
||||
font-size: 1.05rem;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.mgmt-dash-embed-wrap {
|
||||
border: 1px solid var(--border, #e0e0e0);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #1a1a1a;
|
||||
min-height: 820px;
|
||||
}
|
||||
.mgmt-dash-embed-wrap iframe {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 820px;
|
||||
border: 0;
|
||||
}
|
||||
.mgmt-upload-history {
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.mgmt-upload-history table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.mgmt-upload-history th,
|
||||
.mgmt-upload-history td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: left;
|
||||
}
|
||||
.mgmt-upload-history th {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell">
|
||||
@@ -13,7 +119,7 @@
|
||||
activeMenu: 'dashboard',
|
||||
adminMode: typeof adminMode !== 'undefined' ? adminMode : false,
|
||||
}) %>
|
||||
<div class="content-area">
|
||||
<div class="content-area mgmt-perf-page">
|
||||
<header class="topbar">
|
||||
<h1>경영성과 대시보드</h1>
|
||||
</header>
|
||||
@@ -21,14 +127,116 @@
|
||||
<p class="breadcrumb" style="margin-bottom: 16px">
|
||||
<a href="/dashboard">← 대시보드 목록으로</a>
|
||||
</p>
|
||||
<section class="panel">
|
||||
<p class="subtitle">
|
||||
이 화면은 향후 경영·성과 지표 연동 및 위젯 구성을 위한 진입점입니다. 필요한 데이터 소스와 차트
|
||||
요구사항이 정해지면 이어서 구현할 수 있습니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="mgmt-perf-split">
|
||||
<section class="mgmt-upload-panel" aria-labelledby="mgmt-upload-heading">
|
||||
<h2 id="mgmt-upload-heading">엑셀 업로드</h2>
|
||||
<p class="subtitle" style="margin: 0 0 12px; font-size: 14px">
|
||||
매출 집계 엑셀(<strong>매출일보</strong> 시트 포함)을 업로드하면 스냅샷이 저장되고, 아래 대시보드에 반영됩니다.
|
||||
</p>
|
||||
<form id="mgmtPerfUploadForm" class="mgmt-upload-form" enctype="multipart/form-data">
|
||||
<label>
|
||||
연도
|
||||
<input type="number" name="fiscalYear" min="2020" max="2100" value="<%= typeof defaultYear !== 'undefined' ? defaultYear : 2026 %>" required />
|
||||
</label>
|
||||
<label>
|
||||
분기
|
||||
<select name="quarter" required>
|
||||
<option value="1" <%= (typeof selectedQuarter !== 'undefined' ? selectedQuarter : 1) === 1 ? 'selected' : '' %>>1분기 (Q1)</option>
|
||||
<option value="2" <%= (typeof selectedQuarter !== 'undefined' ? selectedQuarter : 1) === 2 ? 'selected' : '' %>>2분기 (Q2)</option>
|
||||
<option value="3" <%= (typeof selectedQuarter !== 'undefined' ? selectedQuarter : 1) === 3 ? 'selected' : '' %>>3분기 (Q3)</option>
|
||||
<option value="4" <%= (typeof selectedQuarter !== 'undefined' ? selectedQuarter : 1) === 4 ? 'selected' : '' %>>4분기 (Q4)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
파일 (.xlsx)
|
||||
<input type="file" name="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" required />
|
||||
</label>
|
||||
<div class="mgmt-upload-actions">
|
||||
<button type="submit" class="btn-mgmt-upload" id="mgmtPerfUploadBtn">업로드 및 반영</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="mgmtPerfUploadMsg" class="mgmt-upload-msg" role="status"></div>
|
||||
<% if (typeof uploadHistory !== 'undefined' && uploadHistory && uploadHistory.length) { %>
|
||||
<div class="mgmt-upload-history">
|
||||
<strong>최근 업로드</strong>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>일시</th>
|
||||
<th>파일명</th>
|
||||
<th>연도·분기</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% uploadHistory.forEach(function (u) { %>
|
||||
<tr>
|
||||
<td><%= u.created_at ? new Date(u.created_at).toLocaleString('ko-KR') : '-' %></td>
|
||||
<td><%= u.original_filename || '-' %></td>
|
||||
<td><%= u.fiscal_year %>년 Q<%= u.quarter %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
|
||||
<section class="mgmt-dash-panel" aria-labelledby="mgmt-dash-heading">
|
||||
<h2 id="mgmt-dash-heading">대시보드 조회</h2>
|
||||
<div class="mgmt-dash-embed-wrap">
|
||||
<iframe
|
||||
id="mgmtPerfDashFrame"
|
||||
title="경영성과 차트"
|
||||
src="/dashboard/business-performance/embed"
|
||||
></iframe>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var form = document.getElementById("mgmtPerfUploadForm");
|
||||
var btn = document.getElementById("mgmtPerfUploadBtn");
|
||||
var msg = document.getElementById("mgmtPerfUploadMsg");
|
||||
var frame = document.getElementById("mgmtPerfDashFrame");
|
||||
if (!form || !btn || !msg) return;
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
msg.textContent = "";
|
||||
msg.className = "mgmt-upload-msg";
|
||||
var fd = new FormData(form);
|
||||
btn.disabled = true;
|
||||
fetch("/api/mgmt-perf/upload", { method: "POST", body: fd, credentials: "same-origin" })
|
||||
.then(function (r) {
|
||||
return r.json().then(function (j) {
|
||||
return { ok: r.ok, body: j };
|
||||
});
|
||||
})
|
||||
.then(function (_ref) {
|
||||
var ok = _ref.ok;
|
||||
var body = _ref.body;
|
||||
if (ok) {
|
||||
msg.textContent = body.message || "저장되었습니다.";
|
||||
msg.className = "mgmt-upload-msg ok";
|
||||
if (frame) frame.src = "/dashboard/business-performance/embed?t=" + Date.now();
|
||||
} else {
|
||||
msg.textContent = body.error || "업로드에 실패했습니다.";
|
||||
msg.className = "mgmt-upload-msg err";
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
msg.textContent = "네트워크 오류입니다.";
|
||||
msg.className = "mgmt-upload-msg err";
|
||||
})
|
||||
.finally(function () {
|
||||
btn.disabled = false;
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user