UI refinement: Back buttons
Quality Gate / gate (push) Failing after 49s

This commit is contained in:
Jim Lancaster
2026-08-24 12:44:02 -05:00
parent f15c9834e4
commit 9a7970c533
12 changed files with 129 additions and 13 deletions
@@ -109,7 +109,7 @@ def render_documents_table(rows: Sequence[DocumentTableRow]) -> None:
],
default_sort_by="name",
search_placeholder="Search documents by title, type, or author...",
on_row_click_id=lambda doc_id: ui.navigate.to(f"/documents/{doc_id}"),
on_row_click_id=lambda doc_id: ui.navigate.to(f"/documents/{doc_id}?from=documents"),
)
# Render document type using a subtle Quasar badge
@@ -57,13 +57,18 @@ def _serialize_rows(rows: Sequence[JobTableRow]) -> list[dict[str, Any]]:
]
def render_jobs_table(rows: Sequence[JobTableRow]) -> None:
def render_jobs_table(rows: Sequence[JobTableRow], *, document_context_id: str | None = None) -> None:
"""Render jobs table with search filtering and custom status chips."""
if not rows:
with archival_card(extra_classes="p-8 text-center"):
render_empty_state("No job records found in repository.")
return
def detail_target(job_id: str) -> str:
if document_context_id is not None:
return f"/jobs/{job_id}?from=document&document_id={document_context_id}"
return f"/jobs/{job_id}?from=jobs"
table = build_table(
rows=_serialize_rows(rows),
columns=[
@@ -117,7 +122,7 @@ def render_jobs_table(rows: Sequence[JobTableRow]) -> None:
default_sort_by="updated_sort",
default_descending=True,
search_placeholder="Search jobs by ID, document, or status...",
on_row_click_id=lambda job_id: ui.navigate.to(f"/jobs/{job_id}"),
on_row_click_id=lambda job_id: ui.navigate.to(detail_target(job_id)),
)
# Render job execution status using themed Quasar chips
@@ -94,7 +94,7 @@ def render_people_table(rows: Sequence[PersonTableRow]) -> None:
],
default_sort_by="name",
search_placeholder="Search people by name, tags, FamilySearch ID, or dates...",
on_row_click_id=lambda person_id: ui.navigate.to(f"/people/{person_id}"),
on_row_click_id=lambda person_id: ui.navigate.to(f"/people/{person_id}?from=people"),
)
# Custom column template adding an archival entity icon next to person's name
+17 -1
View File
@@ -209,6 +209,19 @@ def register_page() -> None: # noqa: PLR0915
document_service = DocumentService(session_factory=session_factory)
render_navigation_header(current_path="/documents")
settings = resolve_runtime_settings(request)
back_label = "Back to Documents"
back_target = "/documents"
from_context = request.query_params.get("from")
if from_context == "person":
person_id = parse_uuid(request.query_params.get("person_id"))
if person_id is not None:
back_label = "Back to Person"
back_target = f"/people/{person_id}"
elif from_context == "job":
job_id = parse_uuid(request.query_params.get("job_id"))
if job_id is not None:
back_label = "Back to Job"
back_target = f"/jobs/{job_id}"
parsed_doc_id = parsed_record_id(document_id, noun="Document")
if parsed_doc_id is None:
@@ -229,6 +242,7 @@ def register_page() -> None: # noqa: PLR0915
page_header(document.name, subtitle=f"Type: {type_display} | ID: {document.id}")
with ui.row().classes("items-center gap-2"):
ui.button(back_label, on_click=lambda: ui.navigate.to(back_target), icon="arrow_back").props("flat")
ui.button(
"Print",
on_click=lambda: ui.navigate.to(f"/documents/{document.id}/print"),
@@ -584,7 +598,9 @@ def _render_related_people_card(document: Document) -> None:
for person in grouped[role_label]:
ui.button(
person.full_name,
on_click=lambda _=None, person_id=person.id: ui.navigate.to(f"/people/{person_id}"),
on_click=lambda _=None, person_id=person.id: ui.navigate.to(
f"/people/{person_id}?from=document&document_id={document.id}"
),
icon="person",
).props("flat dense no-caps").classes("self-start text-xs font-semibold ui-link-primary")
+21 -6
View File
@@ -68,6 +68,12 @@ def register_page() -> None: # noqa: PLR0915
"ui-btn-primary"
)
ui.button("Refresh", on_click=lambda: render_table.refresh(), icon="refresh").props("flat")
elif parsed_document_id is not None:
ui.button(
"Back to Document",
on_click=lambda: ui.navigate.to(f"/documents/{parsed_document_id}"),
icon="arrow_back",
).props("flat")
@ui.refreshable
async def render_table() -> None:
@@ -86,7 +92,7 @@ def register_page() -> None: # noqa: PLR0915
)
for job in jobs
]
render_jobs_table(rows)
render_jobs_table(rows, document_context_id=str(parsed_document_id) if parsed_document_id else None)
await render_table()
@@ -241,9 +247,17 @@ def register_page() -> None: # noqa: PLR0915
ui.button("Back to Jobs", on_click=lambda: ui.navigate.to("/jobs"), icon="arrow_back").props("flat")
@ui.page("/jobs/{job_id}")
async def job_detail_page(job_id: str, session_factory: SessionFactoryDep) -> None:
async def job_detail_page(job_id: str, request: Request, session_factory: SessionFactoryDep) -> None:
jobs_service = JobService(session_factory=session_factory)
render_navigation_header(current_path="/jobs")
back_label = "Back to Jobs"
back_target = "/jobs"
from_context = request.query_params.get("from")
if from_context == "document":
document_id = parse_uuid(request.query_params.get("document_id"))
if document_id is not None:
back_label = "Back to Document"
back_target = f"/documents/{document_id}"
parsed_job_id = parsed_record_id(job_id, noun="Job")
if parsed_job_id is None:
@@ -262,7 +276,7 @@ def register_page() -> None: # noqa: PLR0915
@ui.refreshable
def render_detail() -> None:
active_job = current_job[0]
_render_job_detail_header(active_job)
_render_job_detail_header(active_job, back_label=back_label, back_target=back_target)
with ui.grid().classes("w-full grid-cols-1 md:grid-cols-2 gap-4"):
_render_job_logistics(active_job)
_render_job_document_links(active_job)
@@ -540,10 +554,11 @@ def _render_upload_section(uploaded_files: list[tuple[str, bytes]]) -> None:
render_upload_list()
def _render_job_detail_header(job: Job) -> None:
def _render_job_detail_header(job: Job, *, back_label: str, back_target: str) -> None:
with section_header_row(classes="justify-between items-center"):
page_header(f"Job Record: {job.id}")
with ui.row().classes("items-center gap-2"):
ui.button(back_label, on_click=lambda: ui.navigate.to(back_target), icon="arrow_back").props("flat")
archival_badge(job.status.value.upper())
if job.status in {JobStatus.QUEUED, JobStatus.PROCESSING}:
@@ -584,7 +599,7 @@ def _render_job_document_links(job: Job) -> None:
ui.label("Document Name:").classes("text-xs font-semibold ui-text-primary")
ui.button(
document_name,
on_click=lambda: ui.navigate.to(f"/documents/{job.document_id}"),
on_click=lambda: ui.navigate.to(f"/documents/{job.document_id}?from=job&job_id={job.id}"),
icon="description",
).props("flat dense no-caps").classes("ui-link-primary text-xs")
@@ -592,7 +607,7 @@ def _render_job_document_links(job: Job) -> None:
with ui.row().classes("w-full gap-2 mt-2"):
ui.button(
"View Sources",
on_click=lambda: ui.navigate.to(f"/sources?document_id={job.document_id}"),
on_click=lambda: ui.navigate.to(f"/sources?job_id={job.id}"),
icon="description",
).props("flat dense text-xs").classes("ui-link-primary")
+11 -1
View File
@@ -31,6 +31,7 @@ from transcription.ui.components.formatters import compact_date
from transcription.ui.components.formatters import family_search_url
from transcription.ui.components.formatters import google_maps_search_url
from transcription.ui.components.formatters import parse_iso_date
from transcription.ui.components.formatters import parse_uuid
from transcription.ui.components.guards import parsed_record_id
from transcription.ui.components.guards import render_record_not_found
from transcription.ui.components.media_urls import resolve_media_url
@@ -178,6 +179,14 @@ def register_page() -> None: # noqa: PLR0915
people_service = PeopleService(session_factory=session_factory)
photos_service = PhotosService(session_factory=session_factory)
render_navigation_header(current_path="/people")
back_label = "Back to People"
back_target = "/people"
from_context = request.query_params.get("from")
if from_context == "document":
document_id = parse_uuid(request.query_params.get("document_id"))
if document_id is not None:
back_label = "Back to Document"
back_target = f"/documents/{document_id}"
parsed_person_id = parsed_record_id(person_id, noun="Person")
if parsed_person_id is None:
@@ -197,6 +206,7 @@ def register_page() -> None: # noqa: PLR0915
page_header(person.full_name, subtitle=f"Person ID: {person.id}")
with ui.row().classes("items-center gap-2"):
ui.button(back_label, on_click=lambda: ui.navigate.to(back_target), icon="arrow_back").props("flat")
ui.button(
"New Document",
on_click=lambda: ui.navigate.to(f"/documents/new?person_id={person.id}"),
@@ -812,7 +822,7 @@ def _render_linked_documents(person: Person) -> None:
},
],
default_sort_by="document_name",
on_row_click_id=lambda doc_id: ui.navigate.to(f"/documents/{doc_id}"),
on_row_click_id=lambda doc_id: ui.navigate.to(f"/documents/{doc_id}?from=person&person_id={person.id}"),
show_search=False,
)
table.add_slot(