From 455a01d7c4562f0af16effb74438b2351b85f193 Mon Sep 17 00:00:00 2001 From: John Lancaster <32917998+jsl12@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:58:29 -0500 Subject: [PATCH] started app shell --- src/transcription/ui/components/__init__.py | 1 + src/transcription/ui/components/app_shell.py | 26 ++++++++++++++++++++ src/transcription/ui/pages/jobs_page.py | 3 +++ src/transcription/ui/pages/upload_page.py | 5 ++-- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/transcription/ui/components/app_shell.py diff --git a/src/transcription/ui/components/__init__.py b/src/transcription/ui/components/__init__.py index e69de29..313c2a5 100644 --- a/src/transcription/ui/components/__init__.py +++ b/src/transcription/ui/components/__init__.py @@ -0,0 +1 @@ +"""Reusable UI component exports.""" diff --git a/src/transcription/ui/components/app_shell.py b/src/transcription/ui/components/app_shell.py new file mode 100644 index 0000000..fada0b7 --- /dev/null +++ b/src/transcription/ui/components/app_shell.py @@ -0,0 +1,26 @@ +"""Reusable app shell primitives for page-level layout.""" + +from __future__ import annotations + +from nicegui import ui + + +def _nav_link_classes(*, is_active: bool) -> str: + base = "q-px-sm q-py-xs rounded-borders text-body2" + if is_active: + return f"{base} bg-primary text-white text-weight-medium" + return f"{base} text-primary" + + +def render_navigation_header(*, title: str, current_path: str | None = None) -> None: + """Render a shared app header with links for top-level pages.""" + normalized_path = (current_path or "").rstrip("/") + + with ( + ui.header(elevated=True).classes("bg-white text-dark q-px-md q-py-sm"), + ui.row().classes("w-full items-center justify-between q-gutter-sm"), + ): + ui.label(title).classes("text-subtitle1 text-weight-bold") + with ui.row().classes("items-center q-gutter-xs"): + ui.link("Upload", "/upload").classes(_nav_link_classes(is_active=normalized_path == "/upload")) + ui.link("Jobs", "/jobs").classes(_nav_link_classes(is_active=normalized_path.startswith("/jobs"))) diff --git a/src/transcription/ui/pages/jobs_page.py b/src/transcription/ui/pages/jobs_page.py index 8077a3c..adb83ab 100644 --- a/src/transcription/ui/pages/jobs_page.py +++ b/src/transcription/ui/pages/jobs_page.py @@ -13,6 +13,7 @@ from transcription.db import get_session from transcription.models import Document from transcription.models import Job from transcription.models import Transcript +from transcription.ui.components.app_shell import render_navigation_header from transcription.ui.components.error_presenter import show_error from transcription.ui.components.error_presenter import summarize_error from transcription.ui.components.job_detail import render_job_detail @@ -59,6 +60,7 @@ def register_page() -> None: @ui.page("/jobs") async def jobs_page() -> None: + render_navigation_header(title="Transcription", current_path="/jobs") ui.label("Transcription Jobs") status = ui.label("Ready") @@ -82,6 +84,7 @@ def register_page() -> None: @ui.page("/jobs/{job_id}") async def job_detail_page(job_id: str) -> None: + render_navigation_header(title="Transcription", current_path="/jobs") ui.label("Job Detail") try: parsed_id = UUID(job_id) diff --git a/src/transcription/ui/pages/upload_page.py b/src/transcription/ui/pages/upload_page.py index 4f3109b..ddd3939 100644 --- a/src/transcription/ui/pages/upload_page.py +++ b/src/transcription/ui/pages/upload_page.py @@ -8,6 +8,7 @@ from nicegui import ui from transcription.app_state import resolve_session_factory from transcription.db import get_session from transcription.services.store import create_upload_job +from transcription.ui.components.app_shell import render_navigation_header from transcription.ui.components.upload import render_upload_widget from transcription.worker import resolve_worker_notifier @@ -17,6 +18,7 @@ def register_page() -> None: @ui.page("/upload", title="Upload Document") def upload_page(request: Request) -> None: + render_navigation_header(title="Transcription", current_path="/upload") session_factory = resolve_session_factory(request.app.state) async def submit_upload(filename: str, file_bytes: bytes): @@ -29,6 +31,3 @@ def register_page() -> None: notify_worker = resolve_worker_notifier(request.app.state) render_upload_widget(submitter=submit_upload, notifier=notify_worker) - - with ui.row(): - ui.link("View jobs", "/jobs")