mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
Bug-fix and consistency follow-through after the v1.4.27 mobile sweep. The headline is a tighter dashboard and insights surface: six widgets retire from code entirely (GLP-1 tile, dashboard `<DrugLevelChart>` mount, GLP-1 detail-page intake history + inventory, the `<InsightAdvisorCard>` block and its regeneration affordance, the weekly-report route), four broken edges close (workout-edit duplicate- timestamp 409, BD-Zielbereich tile rebuilt on the shared `<TrendCard>` primitive, `/insights/puls` chart timeout fallback, sticky tab-strip scroll lock), and one consistency contract lands per maintainer directive: every medication-list row, every medication- detail section header, every trend tile, every Coach launch glyph, every chart-height contract reads on one shape. The HealthScore card grows to fill its column, gains an accessible `?` tooltip explaining the delta, and drops the placeholder "Wochenbericht erstellen" button. iOS contracts are intact; the only `/api/*` evolution is additive (the `aggregate=monthly` grain on `/api/measurements` and an internal-only `/api/internal/web-vitals` beacon route).
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { queryKeys } from "@/lib/query-keys";
|
|
|
|
/**
|
|
* Shape returned by every `/api/insights/<metric>-status` endpoint.
|
|
*
|
|
* - `hasProvider` — whether the user has configured an AI provider; the
|
|
* sub-page hides the status card entirely when false.
|
|
* - `text` — the rendered status string; null when the provider has
|
|
* not yet produced one.
|
|
* - `cached` — whether the payload came from the per-locale cache or a
|
|
* fresh provider round-trip.
|
|
* - `updatedAt` — ISO timestamp of the cached payload's creation, or
|
|
* null when the route has never produced a value.
|
|
*/
|
|
export interface InsightStatusData {
|
|
hasProvider: boolean;
|
|
text: string | null;
|
|
cached: boolean;
|
|
updatedAt: string | null;
|
|
}
|
|
|
|
/**
|
|
* Metric slugs the sub-pages render. Each slug is paired with the
|
|
* Insights-status endpoint and the matching `queryKeys.*` factory so
|
|
* the cache keys stay aligned across the whole app — a hard-coded
|
|
* `["insights", "weight-status", locale]` array typoed once is exactly
|
|
* the class of bug `queryKeys` was introduced to defend against.
|
|
*/
|
|
type InsightStatusMetric =
|
|
| "blood-pressure"
|
|
| "weight"
|
|
| "pulse"
|
|
| "bmi"
|
|
| "mood"
|
|
| "medication-compliance";
|
|
|
|
const QUERY_KEY_FACTORY: Record<
|
|
InsightStatusMetric,
|
|
(locale: string) => readonly unknown[]
|
|
> = {
|
|
"blood-pressure": queryKeys.insightsBpStatus,
|
|
weight: queryKeys.insightsWeightStatus,
|
|
pulse: queryKeys.insightsPulseStatus,
|
|
bmi: queryKeys.insightsBmiStatus,
|
|
mood: queryKeys.insightsMoodStatus,
|
|
"medication-compliance": queryKeys.insightsMedicationComplianceStatus,
|
|
};
|
|
|
|
/**
|
|
* Shared loader for the insight-status payload backing
|
|
* `/insights/<metric>` sub-pages. Five sub-pages previously copy-pasted
|
|
* the same 13-line useQuery block plus an identical `XxxStatusData`
|
|
* interface; the hook collapses them onto one helper plus the existing
|
|
* `queryKeys` factory so the next "add a metric" PR touches one map
|
|
* entry instead of an entire page module.
|
|
*
|
|
* Returns the `useQuery` result narrowed to the `InsightStatusData`
|
|
* payload — callers stay free to use `data` / `isLoading` / refetch
|
|
* helpers without an extra unwrap.
|
|
*/
|
|
export function useInsightStatus(metric: InsightStatusMetric) {
|
|
const { isAuthenticated } = useAuth();
|
|
const { locale } = useTranslations();
|
|
|
|
return useQuery({
|
|
queryKey: QUERY_KEY_FACTORY[metric](locale),
|
|
queryFn: async (): Promise<InsightStatusData> => {
|
|
const res = await fetch(`/api/insights/${metric}-status?locale=${locale}`);
|
|
if (!res.ok) throw new Error("Failed");
|
|
const json = (await res.json()) as { data: InsightStatusData };
|
|
return json.data;
|
|
},
|
|
enabled: isAuthenticated,
|
|
staleTime: 60 * 1000,
|
|
// v1.4.28 FB-D2 — the status route now returns a deterministic
|
|
// cached-style envelope on provider timeout. Retrying that response
|
|
// re-fires the same expensive upstream call and the user perceives
|
|
// a longer hang. Drop retries to surface the fallback immediately.
|
|
retry: 0,
|
|
});
|
|
}
|