+ {/* Header: the took/skipped summary chart + the add affordance. */}
+
+ {compliance && compliance.denominator > 0 ? (
+
+ ) : (
+
+ {t("medications.detail.verlauf.summaryEmpty")}
+
+ )}
+
+
+
+ {isLoading && (
+
+
+ {t("medications.detail.verlauf.loading")}
+
+ )}
+
+ {isError && (
+
+ {t("medications.detail.verlauf.loadError")}
+
+ )}
+
+ {data && !isLoading && groups.length === 0 && (
+
+ {t("medications.detail.verlauf.empty")}
+
+ )}
+
+ {data &&
+ groups.map((group) => (
+
+
+ {fmt.dateWithWeekday(new Date(`${group.dayKey}T00:00:00`))}
+
+
+ {group.rows.map((row, i) => (
+ setEditingEvent(ev)}
+ onDelete={(id) => setPendingDeleteId(id)}
+ />
+ ))}
+
+
+ ))}
+
+
setEditingEvent(null)}
+ />
+
+ {addOpen && (
+
+ )}
+
+ {pendingDeleteId && (
+ void confirmRowDelete()}
+ onCancel={() => setPendingDeleteId(null)}
+ />
+ )}
+
+ );
+}
+
+/**
+ * One ledger row: time + status pill + (when an intake is attributed) its real
+ * take time, plus the inline actions. A pending/missed slot offers Genommen /
+ * Übersprungen; a row with an intake offers Bearbeiten / Löschen.
+ */
+function LedgerRowItem({
+ row,
+ marking,
+ onMark,
+ onEdit,
+ onDelete,
+}: {
+ row: LedgerRow;
+ marking: string | null;
+ onMark: (row: LedgerRow, action: "taken" | "skipped") => void;
+ onEdit: (event: {
+ id: string;
+ takenAt: string | null;
+ skipped: boolean;
+ }) => void;
+ onDelete: (id: string) => void;
+}) {
+ const { t } = useTranslations();
+ const fmt = useFormatters();
+
+ const actionable = isSlotActionable(row);
+ const intake = row.intake;
+ const takeBusy = marking === `${row.at}:taken`;
+ const skipBusy = marking === `${row.at}:skipped`;
+
+ // The leading time label: the slot's HH:mm for a slot row, else the ad-hoc
+ // take's real clock time.
+ const timeLabel =
+ row.timeOfDay ?? fmt.time(new Date(intake?.takenAt ?? row.at));
+
+ // When a take lands away from its slot, show both anchors so the row reads
+ // "geplant 07:00 · genommen 11:29".
+ const showTakeDetail =
+ row.kind === "slot" &&
+ intake?.takenAt &&
+ (row.status === "taken_on_time" || row.status === "taken_late");
+
+ return (
+
+
+
+
+ {timeLabel}
+
+
+
+ {t(`medications.detail.verlauf.status.${row.status}`)}
+
+ {row.kind === "ad_hoc" && (
+
+ {t("medications.detail.verlauf.adHocTag")}
+
+ )}
+
+ {showTakeDetail && (
+
+ {t("medications.detail.verlauf.takenDetail", {
+ planned: row.timeOfDay ?? fmt.time(new Date(row.at)),
+ taken: fmt.time(new Date(intake!.takenAt!)),
+ })}
+
+ )}
+
+
+
+ {actionable && (
+ <>
+
+
+ >
+ )}
+ {intake?.id && (
+ <>
+
+
+ >
+ )}
+
+
+ );
+}
+
+/**
+ * Compact took / skipped history chart — a single proportional bar split into
+ * on-time / late / missed, with the headline rate beside it. No Recharts: a
+ * three-segment CSS bar is calmer + lighter for a summary this small, and it
+ * tracks the ledger the user is already reading.
+ */
+function LedgerSummaryChart({
+ compliance,
+}: {
+ compliance: NonNullable