fix: localize dashboard evidence, explain blocked vehicles, clarify pending odometers
Three content defects found by a live reviewer:
- Dashboard attention subtext was raw, untranslated evidence.summary text, and for
11 of 15 seeded issues that text was literally "Synthetic deterministic seed
issue". AttentionItem now exposes evidence_signals (stable code + params, same
shape as the issue detail page) instead of a detail string; the frontend renders
them through a shared describeEvidenceSignal() used by both the dashboard and the
issue detail page. Every previously-placeholder seed row now cites a real,
per-rule-type fact (a genuinely crossed service threshold, a genuinely blank
field, or a real pair of booking odometer readings) instead of invented prose.
- 5 of 7 blocked vehicles had no quality issue at all and one had only a resolved
one, so "needs attention" led nowhere. Each now has a real open
missing_required_field issue backed by a genuinely blank field (no schema change,
no migration -- reuses the existing data-quality pipeline).
- Booking odometer fields showing a bare "-" for 25 reserved + 1 active booking now
show a localized explanation ("trip hasn't started yet" / "not yet closed").
MO-024's rented-but-service-overdue contradiction was already caught by the
vehicle-status evaluator (DQ-SCAN, vehicle.manual_review_required) -- added a
regression test rather than new logic.
Also fixed a related bug the above exposed: the vehicle entity_snapshot omitted
registration_number entirely, so the "provide missing fields" form always showed
it blank regardless of the real value.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3808bbe132
commit
4faac24b5a
@@ -12,6 +12,8 @@ import { SeverityBadge, StatusBadge } from "../components/Badge";
|
||||
import { Icon } from "../components/Icons";
|
||||
import { ErrorState, IntegrationMark, LoadingState, PageHeader, SectionHeading } from "../components/PageChrome";
|
||||
import { N8N_STATE_META, MCP_STATE_META } from "../data/integrationLabels";
|
||||
import { describeEvidenceSignal } from "../data/evidenceSignals";
|
||||
import type { AttentionItem } from "../api/types";
|
||||
|
||||
const FLEET_METRIC_KEYS: Array<{ key: keyof DashboardData["metrics"]; labelKey: string; tone: string }> = [
|
||||
{ key: "available", labelKey: "readiness.available", tone: "ready" },
|
||||
@@ -29,9 +31,20 @@ function attentionItemTitle(
|
||||
return `${ruleLabel} — ${item.link_ref}`;
|
||||
}
|
||||
|
||||
// The backend never emits prose here either -- only stable signal codes + params, exactly
|
||||
// like the data-quality issue detail page. Multiple signals on one item (e.g. the
|
||||
// duplicate-customer match) are joined into one readable line for this compact row.
|
||||
function attentionItemDetail(
|
||||
t: (key: string, options?: Record<string, unknown>) => string,
|
||||
formatNumber: (value: number) => string,
|
||||
item: Pick<AttentionItem, "evidence_signals">,
|
||||
): string {
|
||||
return item.evidence_signals.map((signal) => describeEvidenceSignal(t, formatNumber, signal)).join(" · ");
|
||||
}
|
||||
|
||||
export function Dashboard() {
|
||||
const { t } = useTranslation(["dashboard", "common", "integrations"]);
|
||||
const { formatTime, formatShortDate } = useLocaleFormat();
|
||||
const { t } = useTranslation(["dashboard", "common", "integrations", "quality"]);
|
||||
const { formatTime, formatShortDate, formatNumber } = useLocaleFormat();
|
||||
const greetingPeriod = useGreetingPeriod();
|
||||
const { user } = useAuth();
|
||||
const { manifest } = useDemoManifest();
|
||||
@@ -71,9 +84,9 @@ export function Dashboard() {
|
||||
|
||||
const attention = useMemo(() => data?.attention_items.filter((item) => {
|
||||
const matchesSeverity = severity === "all" || item.severity === severity;
|
||||
const haystack = `${attentionItemTitle(t, item)} ${item.detail} ${item.link_ref}`.toLowerCase();
|
||||
const haystack = `${attentionItemTitle(t, item)} ${attentionItemDetail(t, formatNumber, item)} ${item.link_ref}`.toLowerCase();
|
||||
return matchesSeverity && haystack.includes(query.toLowerCase());
|
||||
}) ?? [], [data, query, severity, t]);
|
||||
}) ?? [], [data, query, severity, t, formatNumber]);
|
||||
|
||||
const isUnfiltered = severity === "all" && query === "";
|
||||
const attentionTiers = useMemo(() => {
|
||||
@@ -157,7 +170,7 @@ export function Dashboard() {
|
||||
<SeverityBadge severity={item.severity} />
|
||||
<div className="queue-copy">
|
||||
<p className="attention-title">{title}</p>
|
||||
<p className="attention-detail">{item.detail}</p>
|
||||
<p className="attention-detail">{attentionItemDetail(t, formatNumber, item)}</p>
|
||||
</div>
|
||||
<span className="queue-ref">{item.link_ref}</span>
|
||||
<Icon name="chevron" className="row-chevron" />
|
||||
|
||||
Reference in New Issue
Block a user