diff --git a/src/components/medications/dose-history-add-dialog.tsx b/src/components/medications/dose-history-add-dialog.tsx index 49587bea0..c2b332f24 100644 --- a/src/components/medications/dose-history-add-dialog.tsx +++ b/src/components/medications/dose-history-add-dialog.tsx @@ -17,20 +17,13 @@ * ad-hoc (no `scheduledFor`); an exact slot match pins directly with no nudge. */ -import { useMemo, useState } from "react"; +import { useId, useMemo, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { Loader2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Label } from "@/components/ui/label"; import { DateTimeField } from "@/components/ui/date-time-field"; import { Switch } from "@/components/ui/switch"; @@ -135,6 +128,7 @@ export function LedgerAddDialog({ }: LedgerAddDialogProps) { const { t } = useTranslations(); const queryClient = useQueryClient(); + const formId = useId(); const [takenAt, setTakenAt] = useState(() => toDateTimeLocal(new Date())); const [skipped, setSkipped] = useState(false); @@ -200,22 +194,40 @@ export function LedgerAddDialog({ } return ( - !busy && onOpenChange(o)}> - - - - {t("medications.detail.verlauf.addDialog.title")} - - - {t("medications.detail.verlauf.addDialog.description")} - - - + !busy && onOpenChange(o)} + title={t("medications.detail.verlauf.addDialog.title")} + description={t("medications.detail.verlauf.addDialog.description")} + footer={ + <> + + + + } + > {/* v1.16.4 — a real form so Enter in the datetime field submits; - the buttons carry explicit types so cancel never submits. The - nested nudge dialog renders through a portal, so its buttons - never land inside this form. */} + the buttons carry explicit types so cancel never submits. */}
{ e.preventDefault(); @@ -263,49 +275,17 @@ export function LedgerAddDialog({

)} - - - - -
{/* The "diesem Slot zuordnen?" nudge — pin onto the slot, or keep the take as a standalone ad-hoc entry. */} {nudgeOpen && nearest && ( - !busy && setNudgeOpen(o)} - > - - - - {t("medications.detail.verlauf.nudge.title")} - - - {t("medications.detail.verlauf.nudge.body", { - slot: nearest.slotHm, - })} - - - + title={t("medications.detail.verlauf.nudge.title")} + footer={ + <> - - - + + } + > +

+ {t("medications.detail.verlauf.nudge.body", { + slot: nearest.slotHm, + })} +

+
)} -
-
+ ); } diff --git a/src/components/medications/intake-edit-dialog.tsx b/src/components/medications/intake-edit-dialog.tsx index e01ab2feb..59489a485 100644 --- a/src/components/medications/intake-edit-dialog.tsx +++ b/src/components/medications/intake-edit-dialog.tsx @@ -21,19 +21,13 @@ * persisted it (audit LOW-10), so the dialog no longer pretends. */ -import { useState } from "react"; +import { useId, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { DateTimeField } from "@/components/ui/date-time-field"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; @@ -107,6 +101,7 @@ function IntakeEditDialogBody({ const { t } = useTranslations(); const fmt = useFormatters(); const queryClient = useQueryClient(); + const formId = useId(); const [takenAt, setTakenAt] = useState(() => toDateTimeLocal(event?.takenAt ?? null), ); @@ -145,16 +140,38 @@ function IntakeEditDialogBody({ } return ( - !open && onClose()}> - - - - {t("medications.detail.intake.edit.dialogTitle")} - - + !open && onClose()} + title={t("medications.detail.intake.edit.dialogTitle")} + footer={ + <> + + + + } + > {/* v1.16.4 — a real form so Enter in the datetime field submits; the buttons carry explicit types so cancel never submits. */}
{ e.preventDefault(); @@ -208,24 +225,7 @@ function IntakeEditDialogBody({ /> - - - -
-
-
+ ); } diff --git a/src/components/medications/intake-import-dialog.tsx b/src/components/medications/intake-import-dialog.tsx index 591bf85eb..62589547f 100644 --- a/src/components/medications/intake-import-dialog.tsx +++ b/src/components/medications/intake-import-dialog.tsx @@ -22,12 +22,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { Loader2, MoreHorizontal, RotateCcw, Upload } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { DropdownMenu, DropdownMenuContent, @@ -143,12 +138,58 @@ export function IntakeImportDialog({ } return ( - handleClose()}> - - - {t("medications.importIntakes")} - -
+ handleClose()} + title={t("medications.importIntakes")} + contentWidth="lg" + footer={ + <> + + + + + + + + {t("common.reset")} + + + + + + + } + > +

{t("medications.importDescription")}

@@ -194,54 +235,7 @@ export function IntakeImportDialog({ {result}

)} -
- - - - - - - - {t("common.reset")} - - - - -
- - -
-
- -
+ ); } diff --git a/src/components/medications/log-injection-site-dialog.tsx b/src/components/medications/log-injection-site-dialog.tsx index 5ea4eeed1..a1f1fb09d 100644 --- a/src/components/medications/log-injection-site-dialog.tsx +++ b/src/components/medications/log-injection-site-dialog.tsx @@ -4,14 +4,7 @@ import { useState } from "react"; import { Loader2 } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Button } from "@/components/ui/button"; import { InjectionSitePicker } from "@/components/medications/injection-site-picker"; import { @@ -85,7 +78,7 @@ export function LogInjectionSiteDialog({ } return ( - { // Hold the dialog while the PATCH is in flight so a backdrop tap / @@ -93,18 +86,31 @@ export function LogInjectionSiteDialog({ if (submitting) return; if (!next) onSkip(); }} + title={t("medications.logInjectionSiteTitle")} + description={t("medications.logInjectionSiteDescription", { + name: medicationName, + })} + footer={ + <> + + + + } > - - - {t("medications.logInjectionSiteTitle")} - - {t("medications.logInjectionSiteDescription", { - name: medicationName, - })} - - - - {allowed.length === 0 ? ( + {allowed.length === 0 ? (

{t("medications.logInjectionSiteNoneAvailable")}

@@ -161,25 +167,6 @@ export function LogInjectionSiteDialog({ )} - - - - - -
-
+ ); } diff --git a/src/components/medications/log-intake-dialog.tsx b/src/components/medications/log-intake-dialog.tsx index 73ef6af5e..88e83f3c2 100644 --- a/src/components/medications/log-intake-dialog.tsx +++ b/src/components/medications/log-intake-dialog.tsx @@ -19,20 +19,13 @@ * the form state + the medication/slot pickers. */ -import { useMemo, useState } from "react"; +import { useId, useMemo, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { Loader2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { DateTimeField } from "@/components/ui/date-time-field"; @@ -119,6 +112,7 @@ export function LogIntakeDialog({ }: LogIntakeDialogProps) { const { t } = useTranslations(); const queryClient = useQueryClient(); + const formId = useId(); const [medicationId, setMedicationId] = useState( () => medications[0]?.id ?? "", @@ -208,18 +202,39 @@ export function LogIntakeDialog({ } return ( - !busy && onOpenChange(o)}> - - - {t("medications.logIntake.dialogTitle")} - - {t("medications.logIntake.dialogDescription")} - - - + !busy && onOpenChange(o)} + title={t("medications.logIntake.dialogTitle")} + description={t("medications.logIntake.dialogDescription")} + footer={ + <> + + + + } + > {/* v1.16.4 — a real form so Enter in the dose / datetime fields submits; mirrors the intake-edit and dose-history-add dialogs. */}
{ e.preventDefault(); if (busy || medications.length === 0 || !medicationId) return; @@ -326,29 +341,7 @@ export function LogIntakeDialog({ )} - - - - -
-
-
+ ); } diff --git a/src/components/medications/take-all-due-dialog.tsx b/src/components/medications/take-all-due-dialog.tsx index 7a2913d31..3954d2760 100644 --- a/src/components/medications/take-all-due-dialog.tsx +++ b/src/components/medications/take-all-due-dialog.tsx @@ -19,14 +19,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { CheckCheck, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { ResponsiveSheet } from "@/components/ui/responsive-sheet"; import { useTranslations } from "@/lib/i18n/context"; import { formatDose } from "@/lib/medications/format-dose"; import { formatTimeWindowRange } from "@/lib/time-window-format"; @@ -65,49 +58,17 @@ export function TakeAllDueDialog({ } return ( - { // Refuse to close while the take loop is in flight. if (submitting && !next) return; onOpenChange(next); }} - > - - - {t("medications.takeAllDue.dialogTitle")} - - {t("medications.takeAllDue.dialogDescription")} - - -
    - {dueMedications.map((med) => ( -
  • - - {med.name} - {med.dose ? ( - - {" "} - — {formatDose(med.dose, t)} - - ) : null} - - {med.window ? ( - - {formatTimeWindowRange( - med.window.start, - med.window.end, - locale, - )} - - ) : null} -
  • - ))} -
- + title={t("medications.takeAllDue.dialogTitle")} + description={t("medications.takeAllDue.dialogDescription")} + footer={ + <>
+ + } + > + + ); }