mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
The dashboard snapshot fires the moment login succeeds instead of waiting for the page chunk (start ~670 to ~200 ms throttled). Guided follow-ups are personalised from the user's snapshot and an answer gets the coach's streamed reaction before the next question. Archived schedule eras can be corrected through a superseding revision that keeps the original as an audit trail, era writes serialise per medication, and duplicate dose times collapse in validation. A rolling medication whose next dose is tomorrow can no longer read as heavily overdue — the card status derives strictly from the served next-due — and the adherence bars keep a fixed geometry in every state. The server-wide assistant key gains its admin card, the one duplicated coach preference collapses onto a single owner, twenty-seven admin and settings card headers move onto the shared pattern with sixty containers normalised, and the remaining hydration warnings are gone.
106 lines
4.2 KiB
TypeScript
106 lines
4.2 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import { Settings } from "lucide-react";
|
|
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { NativeSelect } from "@/components/ui/native-select";
|
|
import { SettingsToggle, useAdminSettings, useUpdateSettings } from "./_shared";
|
|
import { listSupportedTimezones } from "@/lib/tz/format";
|
|
|
|
// v1.4.27 MB7 / CF-52 — the in-file `NATIVE_SELECT_CLASS` constant
|
|
// retired; the shared `<NativeSelect>` primitive owns the visual
|
|
// contract now. The admin copy previously diverged on the
|
|
// `focus-visible:ring-[3px]` weight; the shared primitive pins
|
|
// `focus-visible:ring-2` so admin selects match the rest of the form
|
|
// surface.
|
|
|
|
export function GeneralSettingsSection() {
|
|
const { t } = useTranslations();
|
|
const { data: settings } = useAdminSettings();
|
|
const updateSettings = useUpdateSettings();
|
|
// Memoised — `listSupportedTimezones()` calls the engine's
|
|
// `Intl.supportedValuesOf('timeZone')` which returns ~400 strings.
|
|
const zones = useMemo(() => listSupportedTimezones(), []);
|
|
|
|
const currentDefaultTz = settings?.defaultUserTimezone ?? "";
|
|
|
|
return (
|
|
<div className="bg-card border-border rounded-xl border p-4 sm:p-6">
|
|
<SettingsCardHeader
|
|
icon={Settings}
|
|
title={t("admin.appSettings")}
|
|
description={t("admin.appSettingsDescription")}
|
|
/>
|
|
<div className="mt-4 space-y-4 pl-7">
|
|
<SettingsToggle
|
|
label={t("admin.registrationEnabled")}
|
|
description={t("admin.registrationEnabledDescription")}
|
|
checked={settings?.registrationEnabled ?? true}
|
|
onCheckedChange={(checked) =>
|
|
updateSettings.mutate({ registrationEnabled: checked })
|
|
}
|
|
disabled={updateSettings.isPending}
|
|
/>
|
|
|
|
{/* v1.4.27 MB7 / CF-56 — match the SettingsToggle stacking
|
|
contract: stack on `<sm`, side-by-side on `sm+`. */}
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="min-w-0">
|
|
<p className="text-sm font-medium">{t("admin.defaultLanguage")}</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
{t("admin.defaultLanguageDescription")}
|
|
</p>
|
|
</div>
|
|
<NativeSelect
|
|
value={settings?.defaultLocale ?? "de"}
|
|
onChange={(e) =>
|
|
updateSettings.mutate({ defaultLocale: e.target.value })
|
|
}
|
|
disabled={updateSettings.isPending}
|
|
className="self-end sm:w-auto sm:self-auto"
|
|
>
|
|
<option value="de">Deutsch</option>
|
|
<option value="en">English</option>
|
|
</NativeSelect>
|
|
</div>
|
|
|
|
{/* v1.4.25 W7 — server-wide default timezone for new
|
|
signups. Existing accounts are NOT touched; this drives
|
|
only the registration handler's fallback when the
|
|
client doesn't send a browser-detected zone. */}
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<p className="text-sm font-medium">
|
|
{t("admin.defaultUserTimezone")}
|
|
</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
{t("admin.defaultUserTimezoneDescription")}
|
|
</p>
|
|
</div>
|
|
<NativeSelect
|
|
value={currentDefaultTz}
|
|
onChange={(e) =>
|
|
updateSettings.mutate({ defaultUserTimezone: e.target.value })
|
|
}
|
|
disabled={updateSettings.isPending}
|
|
className="sm:w-auto"
|
|
>
|
|
<option value="">{t("admin.defaultUserTimezoneFallback")}</option>
|
|
{/* Preserve the stored value even if it's not in the
|
|
current engine's IANA list (rare — but defensive). */}
|
|
{currentDefaultTz && !zones.includes(currentDefaultTz) && (
|
|
<option value={currentDefaultTz}>{currentDefaultTz}</option>
|
|
)}
|
|
{zones.map((zone) => (
|
|
<option key={zone} value={zone}>
|
|
{zone}
|
|
</option>
|
|
))}
|
|
</NativeSelect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|