Keep view mode across tabs and speed up recommendation pipeline
This commit is contained in:
@@ -142,6 +142,48 @@
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .summary-panel {
|
||||
grid-template-columns: minmax(250px, 300px) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .summary-overview {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .metric-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .summary-overview .stat-card {
|
||||
min-height: 64px;
|
||||
padding: 10px 11px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .summary-overview .stat-card .value {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-stack {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-box {
|
||||
padding: 12px 12px 14px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-svg,
|
||||
body[data-view-mode="dual"] .expense-chart-svg {
|
||||
aspect-ratio: 1560 / 620;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.summary-layout,
|
||||
.summary-panel,
|
||||
|
||||
+135
-1
@@ -20,6 +20,9 @@
|
||||
--page-gutter: clamp(14px, 1.8vw, 24px);
|
||||
--panel-pad: clamp(16px, 1.4vw, 20px);
|
||||
--page-frame-width: min(1520px, calc(100vw - (var(--page-gutter) * 2)));
|
||||
--page-frame-width-dual: min(2560px, calc(100vw - (var(--page-gutter) * 2)));
|
||||
--page-frame-width-single: min(1520px, calc(100vw - (var(--page-gutter) * 2)));
|
||||
--status-widget-height: 34px;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -48,11 +51,28 @@
|
||||
padding: var(--page-gutter);
|
||||
}
|
||||
|
||||
body[data-view-mode="single"] {
|
||||
--page-frame-width: var(--page-frame-width-single);
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] {
|
||||
--page-frame-width: var(--page-frame-width-dual);
|
||||
--page-gutter: clamp(4px, 0.45vw, 10px);
|
||||
--panel-pad: clamp(12px, 1vw, 16px);
|
||||
}
|
||||
|
||||
.page {
|
||||
width: min(100%, var(--page-frame-width));
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: var(--page-gutter);
|
||||
min-height: calc(100vh - (var(--page-gutter) * 2));
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .page {
|
||||
width: calc(100vw - (var(--page-gutter) * 2));
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.page > * {
|
||||
@@ -436,13 +456,81 @@
|
||||
border-radius: 999px;
|
||||
background: rgba(248, 250, 252, 0.92);
|
||||
box-shadow: none;
|
||||
padding: 6px 10px;
|
||||
min-height: var(--status-widget-height);
|
||||
padding: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.view-mode-switch {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: var(--status-widget-height);
|
||||
min-height: var(--status-widget-height);
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: #eef2f6;
|
||||
color: #1f2937;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.01em;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.16s ease, border-color 0.16s ease;
|
||||
}
|
||||
|
||||
.view-mode-switch:hover {
|
||||
transform: none;
|
||||
background: #e7ecf2;
|
||||
}
|
||||
|
||||
.view-mode-switch .label {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 3px;
|
||||
width: 36px;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
transition: left 0.2s ease;
|
||||
}
|
||||
|
||||
.view-mode-switch::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 36px;
|
||||
height: calc(var(--status-widget-height) - 8px);
|
||||
border-radius: 999px;
|
||||
background: #111827;
|
||||
box-shadow: 0 6px 12px rgba(15, 23, 42, 0.18);
|
||||
transition: transform 0.2s ease;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.view-mode-switch[data-mode="dual"]::before {
|
||||
transform: translateX(38px);
|
||||
}
|
||||
|
||||
.view-mode-switch[data-mode="dual"] .label {
|
||||
left: 41px;
|
||||
}
|
||||
|
||||
.view-mode-switch[data-mode="single"]::before {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.sync-status-head {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -530,6 +618,9 @@
|
||||
<a href="/annual-summary" class="{% if request.url.path == '/annual-summary' %}active{% endif %}">연도별 수익/비용</a>
|
||||
<a href="/wehago-compare" class="{% if request.url.path == '/wehago-compare' %}active{% endif %}">전표비교</a>
|
||||
<div class="nav-spacer"></div>
|
||||
<button type="button" class="view-mode-switch" id="viewModeSwitch" data-mode="dual" aria-label="화면 구성 전환">
|
||||
<span class="label" id="viewModeSwitchLabel">듀얼</span>
|
||||
</button>
|
||||
<aside
|
||||
class="sync-status"
|
||||
id="syncStatusWidget"
|
||||
@@ -561,6 +652,49 @@
|
||||
</div>
|
||||
{% block script %}{% endblock %}
|
||||
<script>
|
||||
(() => {
|
||||
const body = document.body;
|
||||
const switchButton = document.getElementById("viewModeSwitch");
|
||||
const switchLabel = document.getElementById("viewModeSwitchLabel");
|
||||
if (!body || !switchButton || !switchLabel) return;
|
||||
|
||||
const MODE_KEY = "intranet-view-mode";
|
||||
const SOURCE_KEY = "intranet-view-mode-source";
|
||||
|
||||
const computeAutoMode = () => {
|
||||
const width = window.innerWidth || 0;
|
||||
const height = window.innerHeight || 1;
|
||||
const ratio = width / Math.max(height, 1);
|
||||
return (width >= 1450 || ratio >= 1.7) ? "dual" : "single";
|
||||
};
|
||||
|
||||
const applyMode = (mode) => {
|
||||
const normalized = mode === "single" ? "single" : "dual";
|
||||
body.dataset.viewMode = normalized;
|
||||
switchButton.dataset.mode = normalized;
|
||||
switchButton.setAttribute("aria-pressed", normalized === "single" ? "true" : "false");
|
||||
switchLabel.textContent = normalized === "single" ? "싱글" : "듀얼";
|
||||
};
|
||||
|
||||
const savedMode = window.localStorage.getItem(MODE_KEY);
|
||||
const savedSource = window.localStorage.getItem(SOURCE_KEY);
|
||||
if ((savedMode === "single" || savedMode === "dual") && savedSource === "manual") {
|
||||
applyMode(savedMode);
|
||||
} else {
|
||||
const autoMode = computeAutoMode();
|
||||
applyMode(autoMode);
|
||||
window.localStorage.setItem(MODE_KEY, autoMode);
|
||||
window.localStorage.setItem(SOURCE_KEY, "auto");
|
||||
}
|
||||
|
||||
switchButton.addEventListener("click", () => {
|
||||
const nextMode = body.dataset.viewMode === "single" ? "dual" : "single";
|
||||
applyMode(nextMode);
|
||||
window.localStorage.setItem(MODE_KEY, nextMode);
|
||||
window.localStorage.setItem(SOURCE_KEY, "manual");
|
||||
});
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const widget = document.getElementById("syncStatusWidget");
|
||||
if (!widget) return;
|
||||
|
||||
@@ -182,6 +182,46 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .dashboard-layout {
|
||||
grid-template-columns: minmax(250px, 300px) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .dashboard-status-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .dashboard-status-grid .stat-card {
|
||||
min-height: 64px;
|
||||
padding: 10px 11px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .dashboard-status-grid .stat-card .value {
|
||||
font-size: clamp(16px, 1.15vw, 24px);
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .dashboard-chart-stack {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-rows: minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-panel {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-shell {
|
||||
padding: 10px 12px 12px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-svg {
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dashboard-layout {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
+32
-4
@@ -123,10 +123,10 @@
|
||||
color: #363b44;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 6px 10px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
@@ -2298,6 +2298,34 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .overview-row {
|
||||
grid-template-columns: minmax(260px, 300px) minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .metric-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .metric-note-grid {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-stage {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .chart-svg {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .analysis-shell,
|
||||
body[data-view-mode="dual"] .analysis-main,
|
||||
body[data-view-mode="dual"] .analysis-content {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.overview-row,
|
||||
.analysis-toolbar,
|
||||
|
||||
@@ -648,6 +648,40 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .voucher-page:has(#pairRecommendPanel.active) {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
align-items: start;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .voucher-page:has(#pairRecommendPanel.active) > .voucher-topbar {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .voucher-page:has(#pairRecommendPanel.active) > .panel-shell:not(#pairRecommendPanel) {
|
||||
grid-column: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .voucher-page:has(#pairRecommendPanel.active) > #pairRecommendPanel.active {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
max-height: calc(100vh - 90px);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] #pairRecommendPanel.active .table-wrap {
|
||||
max-height: calc(100vh - 230px);
|
||||
}
|
||||
|
||||
body[data-view-mode="dual"] .detail-panel .table-wrap {
|
||||
max-height: clamp(300px, 44vh, 520px);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.voucher-topbar,
|
||||
.panel-header,
|
||||
@@ -1736,6 +1770,8 @@
|
||||
const panels = Array.from(document.querySelectorAll('.detail-panel'));
|
||||
cards.forEach((card) => {
|
||||
card.addEventListener('click', () => {
|
||||
// 상태 탭 전환 시 추천/개별 추천 패널은 즉시 닫는다.
|
||||
closeRecommendationPanel();
|
||||
const targetId = card.dataset.target;
|
||||
const statusKey = card.dataset.status;
|
||||
const panel = targetId ? document.getElementById(targetId) : null;
|
||||
|
||||
Reference in New Issue
Block a user