Refactor case page template and oneliner rendering

Introduced a new macro `render` in `partials/oneliner.html` to handle
the display of the oneliner for cases. This macro encapsulates the logic
for rendering the oneliner, including states for ready, empty, error,
pending, and generating.

The `case_page.html` and `my_cases.html` templates are updated to use
this new macro. This refactoring centralizes the oneliner rendering
logic, making it more maintainable and consistent across different
views.

Additionally, the `OnelinerDisplay` enum is now used in
`CasePageTemplate` and `MyCasesTemplate` to represent the different
states of the oneliner, improving type safety and clarity.
This commit is contained in:
2026-04-20 14:44:05 +02:00
parent 6886c20e26
commit 75c89c132f
5 changed files with 578 additions and 225 deletions
+2 -1
View File
@@ -324,6 +324,7 @@ pub async fn handle_reset_case(
State(config): State<Arc<Config>>,
State(events_tx): State<EventSender>,
CaseIdPath(case_id): CaseIdPath,
headers: HeaderMap,
) -> Result<Redirect, AppError> {
if !user.is_admin() {
return Err(AppError::Forbidden("Nur für Admins".into()));
@@ -343,7 +344,7 @@ pub async fn handle_reset_case(
case_id.to_string(),
CaseEventKind::CaseReset,
);
Ok(Redirect::to("/web/cases"))
Ok(Redirect::to(&resolve_return_path(&headers)))
}
/// POST /web/cases/undo-delete
+50 -33
View File
@@ -163,7 +163,7 @@ struct MyCasesTemplate {
struct CasePageTemplate {
case_id: String,
case_id_short: String,
oneliner: Option<String>,
oneliner: OnelinerDisplay,
/// RFC3339 UTC timestamp of the most recent recording. `None` iff the
/// case has no recordings at all. Drives the datetime header under the
/// title; browser JS formats it to "Heute 11:34" / "13.12.2022 11:34".
@@ -337,7 +337,7 @@ pub async fn handle_case_page(
.map(|md| crate::analyze::render::md_to_html(&md));
let recordings = scan_recordings(&case_dir).await;
let oneliner = ready_text(&case_dir).await;
let oneliner = compute_oneliner_display(&case_dir, &recordings).await;
let a_busy = pipeline.analyze_busy.0.load(Ordering::Acquire);
let flags = compute_flags(&case_dir, &recordings, config.llm_configured(), a_busy).await;
@@ -531,8 +531,54 @@ async fn compute_case_view(
.filter(|r| !r.failed)
.all(|r| r.transcript.is_some());
let (state, _) = paths::read_oneliner_state(case_path).await;
let oneliner = if non_failed_count == 0 {
let oneliner = compute_oneliner_display(case_path, &recordings).await;
let has_document = any_document_exists(case_path).await;
let analyzing = !has_document && worker_busy && any_analysis_input_exists(case_path).await;
let can_analyze = !analyzing && all_transcribed && llm_configured;
let time_hms_utc = extract_hhmm_utc(&most_recent);
let recorded_at_iso = recorded_at_iso_of(&most_recent);
Some(UserCaseView {
case_id,
most_recent,
time_hms_utc,
recorded_at_iso,
oneliner,
recordings_count,
analyzing,
has_document,
can_analyze,
})
}
/// Extract only the text from a `Ready` state; other states (Empty,
/// Error, missing file) collapse to `None`. Used by the recordings
/// sub-page, which only needs a plain title string as heading.
async fn ready_text(case_dir: &Path) -> Option<String> {
match paths::read_oneliner_state(case_dir).await.0 {
Some(OnelinerState::Ready { text, .. }) => Some(text),
_ => None,
}
}
/// Compute the 5-state UI display for the oneliner. Identical logic to
/// what `compute_case_view` uses for the case list — extracted so the
/// case page can render the same states instead of a plain "Fall"
/// fallback.
async fn compute_oneliner_display(
case_dir: &Path,
recordings: &[RecordingView],
) -> OnelinerDisplay {
let non_failed_count = recordings.iter().filter(|r| !r.failed).count();
let all_transcribed = non_failed_count > 0
&& recordings
.iter()
.filter(|r| !r.failed)
.all(|r| r.transcript.is_some());
let (state, _) = paths::read_oneliner_state(case_dir).await;
if non_failed_count == 0 {
// All recordings failed — stable end state, no LLM will ever run.
// Missing state collapses to Empty ("unbenannt"): no title is
// expected, regardless of why.
@@ -558,34 +604,5 @@ async fn compute_case_view(
None => OnelinerDisplay::Pending,
Some(_) => OnelinerDisplay::Generating,
}
};
let has_document = any_document_exists(case_path).await;
let analyzing = !has_document && worker_busy && any_analysis_input_exists(case_path).await;
let can_analyze = !analyzing && all_transcribed && llm_configured;
let time_hms_utc = extract_hhmm_utc(&most_recent);
let recorded_at_iso = recorded_at_iso_of(&most_recent);
Some(UserCaseView {
case_id,
most_recent,
time_hms_utc,
recorded_at_iso,
oneliner,
recordings_count,
analyzing,
has_document,
can_analyze,
})
}
/// Extract only the text from a `Ready` state; other states (Empty,
/// Error, missing file) collapse to `None`. Used by single-case pages
/// whose headings simply fall back to a generic placeholder — they do
/// not need the full five-state UI treatment.
async fn ready_text(case_dir: &Path) -> Option<String> {
match paths::read_oneliner_state(case_dir).await.0 {
Some(OnelinerState::Ready { text, .. }) => Some(text),
_ => None,
}
}