fix(dashboard): keep shell during menu transitions
Replace document navigation with native Streamlit state transitions, preserve dashboard contracts, and add regression plus authenticated browser evidence for Issue #9.
This commit is contained in:
+14
-6
@@ -12,7 +12,15 @@ if __package__ in (None, ""):
|
||||
|
||||
st.set_page_config(page_title="Jarca Ledger", page_icon="💰", layout="wide")
|
||||
|
||||
from dashboard.common import clear_cache, get_db_path, get_transfer_review_items, inject_app_css, render_sidebar_nav # noqa: F401
|
||||
from dashboard.common import (
|
||||
clear_cache,
|
||||
get_db_path,
|
||||
get_transfer_review_items, # noqa: F401
|
||||
inject_app_css,
|
||||
render_dashboard_body,
|
||||
render_sidebar_nav,
|
||||
validate_dashboard_page,
|
||||
) # noqa: F401
|
||||
from dashboard.authentication import require_dashboard_authentication
|
||||
from dashboard.page_views.accounts import page_accounts
|
||||
from dashboard.page_views.assets import page_assets
|
||||
@@ -32,15 +40,15 @@ def main() -> None:
|
||||
db_path = get_db_path()
|
||||
inject_app_css()
|
||||
pages = ["월정산", "요약", "자산/마감", "거래내역", "확인필요 큐", "미분류", "분류 규칙", "카테고리", "계좌", "Import"]
|
||||
query_page = st.query_params.get("page", "요약")
|
||||
page = query_page if query_page in pages else "요약"
|
||||
st.session_state["page"] = page
|
||||
query_page = st.query_params.get("page")
|
||||
page = validate_dashboard_page(query_page, pages)
|
||||
st.session_state["dashboard_page"] = page
|
||||
|
||||
with st.sidebar:
|
||||
st.markdown("## 💰 Jarca Ledger")
|
||||
st.caption("가계부 · 분류 · Import 운영 콘솔")
|
||||
st.markdown('<div class="jarca-nav-title">메뉴</div>', unsafe_allow_html=True)
|
||||
render_sidebar_nav(pages, page)
|
||||
page = render_sidebar_nav(pages, page)
|
||||
if st.button("새로고침", key="refresh_button", use_container_width=True):
|
||||
clear_cache()
|
||||
st.rerun()
|
||||
@@ -56,7 +64,7 @@ def main() -> None:
|
||||
"계좌": page_accounts,
|
||||
"Import": page_imports,
|
||||
}
|
||||
dispatch[page](db_path)
|
||||
render_dashboard_body(page, lambda: dispatch[page](db_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+42
-8
@@ -4,7 +4,6 @@ import sqlite3
|
||||
from datetime import datetime
|
||||
from html import escape
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
import importlib
|
||||
import inspect
|
||||
from zoneinfo import ZoneInfo
|
||||
@@ -314,6 +313,9 @@ def inject_app_css() -> None:
|
||||
border-color: rgba(37, 99, 235, 0.48);
|
||||
box-shadow: 0 10px 22px rgba(15, 23, 42, 0.10);
|
||||
}
|
||||
.st-key-dashboard_body:has(.st-key-dashboard_loading [data-testid="stAlert"]) .st-key-dashboard_content {
|
||||
display: none;
|
||||
}
|
||||
.jarca-card {
|
||||
border: 1px solid rgba(226, 232, 240, 0.86);
|
||||
border-radius: 22px;
|
||||
@@ -355,13 +357,45 @@ def render_section_label(icon: str, label: str) -> None:
|
||||
st.markdown(f'<div class="jarca-section-label"><span>{escape(icon)}</span><span>{escape(label)}</span></div>', unsafe_allow_html=True)
|
||||
|
||||
|
||||
def render_sidebar_nav(pages: list[str], current_page: str) -> None:
|
||||
items = []
|
||||
for name in pages:
|
||||
active = " active" if name == current_page else ""
|
||||
href = f"?page={quote(name)}"
|
||||
items.append(f'<a class="jarca-nav-item{active}" href="{href}" target="_self">{escape(name)}</a>')
|
||||
st.markdown(f'<div class="jarca-nav-list">{"".join(items)}</div>', unsafe_allow_html=True)
|
||||
def validate_dashboard_page(page: str | None, pages: list[str]) -> str:
|
||||
return page if page in pages else "요약"
|
||||
|
||||
|
||||
def _sync_dashboard_page_query() -> None:
|
||||
st.query_params["page"] = st.session_state["dashboard_page"]
|
||||
|
||||
|
||||
def render_sidebar_nav(pages: list[str], current_page: str) -> str:
|
||||
if "dashboard_page" not in st.session_state:
|
||||
st.session_state["dashboard_page"] = current_page
|
||||
return st.radio(
|
||||
"메뉴",
|
||||
pages,
|
||||
key="dashboard_page",
|
||||
label_visibility="collapsed",
|
||||
on_change=_sync_dashboard_page_query,
|
||||
)
|
||||
|
||||
|
||||
def render_dashboard_body(page: str, renderer) -> None:
|
||||
body = st.empty()
|
||||
body.empty()
|
||||
try:
|
||||
with body.container():
|
||||
with st.container(key="dashboard_body"):
|
||||
loading = st.empty()
|
||||
with loading.container():
|
||||
with st.container(key="dashboard_loading"):
|
||||
st.info(f"{page} 불러오는 중…")
|
||||
with st.container(key="dashboard_content"):
|
||||
renderer()
|
||||
loading.empty()
|
||||
except Exception:
|
||||
body.empty()
|
||||
with body.container():
|
||||
st.error("페이지를 불러오지 못했습니다. 다시 시도해 주세요.")
|
||||
if st.button("다시 시도", key=f"retry_{page}"):
|
||||
st.rerun()
|
||||
|
||||
|
||||
def parse_optional_int(value: str, field_name: str) -> int | None:
|
||||
|
||||
Reference in New Issue
Block a user