fix(ui): defensive optional chains across summaries/medications/preferences/entries/measurements

Same anti-pattern as the v1.4.2 hotfix on /dashboard and /insights —
the optional chain protected only the outer object, so any field on
the inner object that didn't exist threw at access time. Marc's
reports flagged dashboard, insights, medications,
notification-preferences, admin login overview, mood list, and
measurement list. Applied 'data?.X?.Y' to every spot the regex
caught.
This commit is contained in:
Marc-André Bombeck committed 2026-05-08 18:36:13 +02:00
1 parent cfbd822862
commit 680b0019c1
6 files changed
+15 -15

No files matched your search

+5 -5
View File
@@ -633,10 +633,10 @@ export default function InsightsPage() {
staleTime: 60 * 1000,
});
const w = analytics?.summaries?.WEIGHT;
const sys = analytics?.summaries.BLOOD_PRESSURE_SYS;
const dia = analytics?.summaries.BLOOD_PRESSURE_DIA;
const p = analytics?.summaries.PULSE;
const bf = analytics?.summaries.BODY_FAT;
const sys = analytics?.summaries?.BLOOD_PRESSURE_SYS;
const dia = analytics?.summaries?.BLOOD_PRESSURE_DIA;
const p = analytics?.summaries?.PULSE;
const bf = analytics?.summaries?.BODY_FAT;
const showBodyFatCard = (bf?.count ?? 0) > 0;
const moodSummary = data?.moodSummary;
const showMoodCard = (moodSummary?.count ?? 0) > 0;
@@ -710,7 +710,7 @@ export default function InsightsPage() {
classifyRangeValue(bmiAvg30, bmiRange),
]);
const bpMedications =
data?.medications.filter(
data?.medications?.filter(
(medication) => medication.category === "BLOOD_PRESSURE",
) ?? [];
const bpMedicationCompliance30 =
+1 -1
View File
@@ -112,7 +112,7 @@ export default function NotificationsPage() {
});
function isEnabled(channelId: string, eventType: string): boolean {
const pref = data?.preferences.find(
const pref = data?.preferences?.find(
(p) => p.channelId === channelId && p.eventType === eventType,
);
// Opt-out model: default is ON when no preference row exists
+6 -6
View File
@@ -198,12 +198,12 @@ export default function DashboardPage() {
});
const w = data?.summaries?.WEIGHT;
const sys = data?.summaries.BLOOD_PRESSURE_SYS;
const dia = data?.summaries.BLOOD_PRESSURE_DIA;
const p = data?.summaries.PULSE;
const bf = data?.summaries.BODY_FAT;
const sleepSummary = data?.summaries.SLEEP_DURATION;
const stepsSummary = data?.summaries.ACTIVITY_STEPS;
const sys = data?.summaries?.BLOOD_PRESSURE_SYS;
const dia = data?.summaries?.BLOOD_PRESSURE_DIA;
const p = data?.summaries?.PULSE;
const bf = data?.summaries?.BODY_FAT;
const sleepSummary = data?.summaries?.SLEEP_DURATION;
const stepsSummary = data?.summaries?.ACTIVITY_STEPS;
const moodSummary = moodData?.summary;
// Resolve full dashboard layout — controls visibility + order of every widget
@@ -45,7 +45,7 @@ export function LoginOverviewSection({ id }: { id: string }) {
const entries =
filter === "failed"
? data?.entries.filter((e) => e.action === "auth.login.failed")
? data?.entries?.filter((e) => e.action === "auth.login.failed")
: data?.entries;
return (
@@ -300,7 +300,7 @@ export function MeasurementList({ onEdit }: MeasurementListProps) {
<div className="flex h-32 items-center justify-center">
<Loader2 className="text-primary h-6 w-6 animate-spin" />
</div>
) : !data?.measurements.length ? (
) : !data?.measurements?.length ? (
<div className="text-muted-foreground flex h-32 items-center justify-center rounded-lg border border-dashed">
{t("measurements.noMeasurements")}
</div>
+1 -1
View File
@@ -296,7 +296,7 @@ export function MoodList() {
<div className="flex h-32 items-center justify-center">
<Loader2 className="text-primary h-6 w-6 animate-spin" />
</div>
) : !data?.entries.length ? (
) : !data?.entries?.length ? (
<div className="text-muted-foreground flex h-32 items-center justify-center rounded-lg border border-dashed">
{t("mood.noEntries")}
</div>