Update intranet tools and voucher comparison
This commit is contained in:
+70
-17
@@ -520,7 +520,7 @@
|
||||
|
||||
.sync-status {
|
||||
margin-left: auto;
|
||||
max-width: 240px;
|
||||
max-width: 320px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: rgba(248, 250, 252, 0.92);
|
||||
@@ -645,6 +645,31 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sync-refresh-button {
|
||||
display: none;
|
||||
height: calc(var(--status-widget-height) - 10px);
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 999px;
|
||||
background: #ffffff;
|
||||
color: #111827;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sync-status.has-pending {
|
||||
border-color: #f59e0b;
|
||||
background: #fff7ed;
|
||||
}
|
||||
|
||||
.sync-status.has-pending .sync-refresh-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.stats {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -682,14 +707,15 @@
|
||||
<body>
|
||||
<div class="page">
|
||||
<nav class="nav">
|
||||
<a href="/" class="{% if request.url.path == '/' %}active{% endif %}">대시보드</a>
|
||||
<a href="/annual-summary" class="{% if request.url.path == '/annual-summary' %}active{% endif %}">연도별 수익/비용</a>
|
||||
<a href="/process-cost" class="{% if request.url.path == '/process-cost' %}active{% endif %}">프로젝트 원가</a>
|
||||
<a href="/projects" class="{% if request.url.path == '/projects' %}active{% endif %}">프로젝트 정보</a>
|
||||
<a href="/wehago-compare" class="{% if request.url.path == '/wehago-compare' %}active{% endif %}">전표비교</a>
|
||||
<a href="/hanmac-browser" class="{% if request.url.path == '/hanmac-browser' %}active{% endif %}">hanmac DB_external</a>
|
||||
<a href="/db-browser" class="{% if request.url.path.startswith('/db') %}active{% endif %}">DB 조회</a>
|
||||
{% for item in nav_items %}
|
||||
{% set is_active = request.url.path == item.href or (item.active == 'db' and request.url.path.startswith('/db')) or (item.active == 'admin' and request.url.path.startswith('/admin')) %}
|
||||
<a href="{{ item.href }}" class="{% if is_active %}active{% endif %}">{{ item.label }}</a>
|
||||
{% endfor %}
|
||||
<div class="nav-spacer"></div>
|
||||
{% if current_user %}
|
||||
<span class="sync-pill">{{ current_user.display_name }}</span>
|
||||
<a href="/logout">로그아웃</a>
|
||||
{% endif %}
|
||||
<button type="button" class="view-mode-switch" id="viewModeSwitch" data-mode="dual" aria-label="화면 구성 전환">
|
||||
<span class="label" id="viewModeSwitchLabel">듀얼</span>
|
||||
</button>
|
||||
@@ -698,19 +724,20 @@
|
||||
id="syncStatusWidget"
|
||||
data-data-version="{{ data_version or '' }}"
|
||||
data-refresh-url="{{ request.url.path }}{% if request.url.query %}?{{ request.url.query }}{% endif %}"
|
||||
data-refresh-mode="{{ 'disabled' if request.url.path in ['/projects', '/wehago-compare'] else 'auto' }}"
|
||||
data-refresh-mode="manual"
|
||||
>
|
||||
<div class="sync-status-head">
|
||||
<div class="sync-status-title">
|
||||
<span class="sync-dot" id="syncStatusDot"></span>
|
||||
<span id="syncStatusLabel">연결 확인 중</span>
|
||||
</div>
|
||||
<button type="button" class="sync-refresh-button" id="syncRefreshButton">새로고침</button>
|
||||
<span class="sync-pill" id="syncSessionPill">세션 준비 중</span>
|
||||
</div>
|
||||
<div class="sync-meta">
|
||||
<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 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>
|
||||
@@ -774,10 +801,11 @@
|
||||
const dot = document.getElementById("syncStatusDot");
|
||||
const label = document.getElementById("syncStatusLabel");
|
||||
const sessionPill = document.getElementById("syncSessionPill");
|
||||
const refreshButton = document.getElementById("syncRefreshButton");
|
||||
const lastChecked = document.getElementById("syncLastChecked");
|
||||
const serverTime = document.getElementById("syncServerTime");
|
||||
const dataVersion = document.getElementById("syncDataVersion");
|
||||
if (!dot || !label || !sessionPill || !lastChecked || !serverTime || !dataVersion) return;
|
||||
if (!dot || !label || !sessionPill || !refreshButton || !lastChecked || !serverTime || !dataVersion) return;
|
||||
let pageVersion = widget.dataset.dataVersion || "";
|
||||
const refreshUrl = widget.dataset.refreshUrl || window.location.href;
|
||||
const refreshMode = widget.dataset.refreshMode || "auto";
|
||||
@@ -856,6 +884,7 @@
|
||||
pageVersion = nextVersion || "";
|
||||
pendingVersion = "";
|
||||
widget.dataset.dataVersion = pageVersion;
|
||||
widget.classList.remove("has-pending");
|
||||
dataVersion.textContent = pageVersion || "-";
|
||||
updateWidgetTitle();
|
||||
}
|
||||
@@ -877,7 +906,7 @@
|
||||
|
||||
async function refreshPageWhenSafe(nextVersion) {
|
||||
if (refreshInFlight) return;
|
||||
if (refreshMode === "disabled") {
|
||||
if (refreshMode === "disabled" || refreshMode === "manual") {
|
||||
setPageDataVersion(nextVersion || pageVersion);
|
||||
setStatus("online", "서버 정상 연결");
|
||||
return;
|
||||
@@ -900,6 +929,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
function markPendingBusinessData(nextVersion) {
|
||||
pendingVersion = nextVersion || pendingVersion || pageVersion;
|
||||
widget.classList.add("has-pending");
|
||||
dataVersion.textContent = pendingVersion || "-";
|
||||
setStatus("online", "새 업무 데이터 있음");
|
||||
}
|
||||
|
||||
refreshButton.addEventListener("click", () => {
|
||||
if (refreshInFlight) return;
|
||||
refreshInFlight = true;
|
||||
setStatus("online", "새 데이터 반영 중");
|
||||
window.location.replace(refreshUrl);
|
||||
});
|
||||
|
||||
async function pollHealth() {
|
||||
if (document.hidden) {
|
||||
return;
|
||||
@@ -908,18 +951,28 @@
|
||||
const response = await fetch(`/health?ts=${Date.now()}`, { cache: "no-store" });
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const payload = await response.json();
|
||||
const businessDataVersion = payload.business_data_version || payload.data_version || "";
|
||||
serverTime.textContent = payload.server_time || "-";
|
||||
dataVersion.textContent = payload.data_version || "-";
|
||||
dataVersion.textContent = businessDataVersion || "-";
|
||||
updateWidgetTitle();
|
||||
if (payload.data_version && payload.data_version !== pageVersion) {
|
||||
pendingVersion = payload.data_version;
|
||||
await refreshPageWhenSafe(payload.data_version);
|
||||
if (businessDataVersion && businessDataVersion !== pageVersion) {
|
||||
if (refreshMode === "auto") {
|
||||
pendingVersion = businessDataVersion;
|
||||
await refreshPageWhenSafe(businessDataVersion);
|
||||
} else {
|
||||
markPendingBusinessData(businessDataVersion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pendingVersion && pendingVersion !== pageVersion) {
|
||||
await refreshPageWhenSafe(pendingVersion);
|
||||
if (refreshMode === "auto") {
|
||||
await refreshPageWhenSafe(pendingVersion);
|
||||
} else {
|
||||
markPendingBusinessData(pendingVersion);
|
||||
}
|
||||
return;
|
||||
}
|
||||
widget.classList.remove("has-pending");
|
||||
setStatus("online", "서버 정상 연결");
|
||||
} catch (error) {
|
||||
setStatus("error", "연결 오류");
|
||||
|
||||
Reference in New Issue
Block a user