feat(insights): add generic per-metric assessment loader

Add a parameterised query key (insightsMetricStatus) and a
useInsightMetricStatus hook that reads the generic per-metric
assessment route GET /api/insights/metric-status?metric=<id>. The
seven bespoke status routes keep their own keys and hook union; this
one covers the HealthKit metric sub-pages that share a single route
keyed by the metric id. The hook mirrors the existing status loader:
8 s client ceiling, no inline retry, and the bounded preparing poll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit a3407a2f6c1e9f54c92a6f182db894ae0593e69f)
This commit is contained in:
Marc-André Bombeck committed 2026-06-02 13:26:10 +02:00
1 parent e33911e2d4
commit 16b0c349f2
2 files changed
+66

No files matched your search

+56
View File
@@ -163,3 +163,59 @@ export function useInsightStatus(metric: InsightStatusMetric) {
),
});
}
/**
* v1.8.7.1 — generic per-metric assessment loader for the HealthKit
* metric sub-pages. The seven bespoke metrics above (`weight`, `pulse`,
* …) each own a hand-written `/api/insights/<metric>-status` route; the
* ~29 HealthKit pages instead share one generic route keyed by the
* metric id:
*
* GET /api/insights/metric-status?metric=<METRIC_ID>&locale=<locale>
*
* where METRIC_ID is the existing HealthKit measurement identifier
* (`HEART_RATE_VARIABILITY`, `RESTING_HEART_RATE`, `SLEEP_DURATION`, …).
* The response envelope, the 8 s client ceiling, the no-retry policy and
* the bounded `preparing` poll are identical to `useInsightStatus` — the
* card consumes the same `InsightStatusData` shape for every scope.
*
* `enabled` lets the page suppress the fetch entirely when the metric has
* no data (the page renders the insufficient-data empty state instead of
* the card), so a brand-new account never fires an assessment round-trip.
*/
export function useInsightMetricStatus(metric: string, enabled = true) {
const { isAuthenticated } = useAuth();
const { locale } = useTranslations();
return useQuery({
queryKey: queryKeys.insightsMetricStatus(metric, locale),
queryFn: async (): Promise<InsightStatusData> => {
const controller = new AbortController();
const timeoutHandle = setTimeout(
() => controller.abort(),
STATUS_TIMEOUT_MS,
);
try {
const res = await fetch(
`/api/insights/metric-status?metric=${encodeURIComponent(
metric,
)}&locale=${locale}`,
{ signal: controller.signal },
);
if (!res.ok) throw new Error("Failed");
const json = (await res.json()) as { data: InsightStatusData };
return json.data;
} finally {
clearTimeout(timeoutHandle);
}
},
enabled: isAuthenticated && enabled,
staleTime: 60 * 1000,
retry: 0,
refetchInterval: (query) =>
nextStatusPollInterval(
query.state.data?.preparing,
query.state.dataUpdateCount,
),
});
}
+10
View File
@@ -103,6 +103,16 @@ export const queryKeys = {
["insights", "mood-status", locale] as const,
insightsMedicationComplianceStatus: (locale: string) =>
["insights", "medication-compliance-status", locale] as const,
/**
* v1.8.7.1 — per-metric insight assessment for the HealthKit metric
* sub-pages. One generic route (`/api/insights/metric-status?metric=…`)
* backs every HealthKit metric, so the key is parameterised by the
* metric id alongside the locale rather than minting a bespoke factory
* entry per metric. The seven bespoke `*-status` routes above keep
* their own keys; this one covers the ~29 generic HealthKit pages.
*/
insightsMetricStatus: (metric: string, locale: string) =>
["insights", "metric-status", metric, locale] as const,
medications: () => ["medications"] as const,
medicationDetail: (id: string) => ["medications", id] as const,