From 695fbd6d21608c91b0d4f0cc6478b8b09b236ba8 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Fri, 3 Jul 2026 03:43:17 +0200 Subject: [PATCH] feat(ui): time-in-stage on the dashboard + localized funnel labels - Adds a 'Median time in stage' block to the conversion-funnel card showing median days and active count per stage from the enriched analytics-overview endpoint. - Funnel bar labels and stage names now render through the shared pipeline statusLabel (localized; the funnel also now includes Waiting). - EN/NB translations. Full frontend suite green (20 suites / 46 tests). Co-Authored-By: Claude Fable 5 --- .../src/components/DashboardView.tsx | 20 ++++++++++++++++++- job-tracker-ui/src/i18n/translations.ts | 4 ++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/job-tracker-ui/src/components/DashboardView.tsx b/job-tracker-ui/src/components/DashboardView.tsx index f799268..a99d21c 100644 --- a/job-tracker-ui/src/components/DashboardView.tsx +++ b/job-tracker-ui/src/components/DashboardView.tsx @@ -25,6 +25,7 @@ import { api } from "../api"; import ViewStateNotice from "./ViewStateNotice"; import { getUserKeyFromToken } from "../themePrefs"; import { useI18n } from "../i18n/I18nProvider"; +import { statusLabel } from "../pipeline"; import { buildWorkflowPath, getWorkflowAction } from "../jobWorkflowSignals"; import { JobApplication } from "../types"; import { useViewResource } from "../hooks/useViewResource"; @@ -49,6 +50,7 @@ type OverviewAnalytics = { medianDaysToFirstResponse?: number | null; totalResponses: number; totalActive: number; + timeInStage?: { stage: string; medianDays: number; count: number }[]; }; type TagTrendResponse = { months: string[]; series: { tag: string; counts: number[] }[] }; @@ -453,7 +455,7 @@ export default function DashboardView() { return ( - {item.label} + {statusLabel(t, item.label)} {item.count} + {overview?.timeInStage?.length ? ( + + {t("dashboardTimeInStageTitle")} + + {overview.timeInStage.map((item) => ( + + {statusLabel(t, item.stage)} + + {t("dashboardTimeInStageValue", { days: item.medianDays, count: item.count })} + + + ))} + + + ) : null} + {summaryView.topSource?.label ?? t("dashboardResponseSources")} {summaryView.topSource ? `${summaryView.topSource.rate}%` : "—"} diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 797b0c6..b2fbd04 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -335,6 +335,8 @@ export const translations = { dashboardApplicationActivity: "Application activity", dashboardMonthlyApplicationsResponses: "Monthly applications versus responses.", dashboardConversionFunnelTitle: "Conversion funnel", + dashboardTimeInStageTitle: "Median time in stage", + dashboardTimeInStageValue: "{days}d · {count} active", dashboardResponseSources: "Response sources", dashboardTopCompaniesByActivity: "Top companies by activity", dashboardTopSkills: "Top skills", @@ -1266,6 +1268,8 @@ export const translations = { dashboardApplicationActivity: "Søknadsaktivitet", dashboardMonthlyApplicationsResponses: "Månedlige søknader versus svar.", dashboardConversionFunnelTitle: "Konverteringstrakt", + dashboardTimeInStageTitle: "Median tid i fase", + dashboardTimeInStageValue: "{days}d · {count} aktive", dashboardResponseSources: "Svar etter kilde", dashboardTopCompaniesByActivity: "Topp selskaper etter aktivitet", dashboardTopSkills: "Topp ferdigheter",