"use client"; import { useState } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { Activity, Bell, CheckCircle2, HeartPulse, Loader2, XCircle, } from "lucide-react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { SettingsCardActions } from "@/components/settings/_card-actions"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { SettingsCard } from "@/components/settings/settings-card"; import { SettingsCardHeader } from "@/components/settings/_card-header"; import { useTranslations } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; import { useAdminSettings, useUpdateSettings } from "./_shared"; import { apiGet, apiPost } from "@/lib/api/api-fetch"; interface NotificationHealth { windowHours: number; since: string; channels: Array<{ channel: string; ok: number; error: number; skipped: number; total: number; }>; autoDisabledChannels: Array<{ type: string; count: number }>; } /** * v1.17.1 — operator-wide notification delivery-health panel. A single * `groupBy(channel, result)` over the trailing 24h of `push_attempts` plus the * count of auto-disabled channels across all users. Lets the operator answer * "are anyone's pushes failing?" without DB shell. */ function NotificationHealthPanel() { const { t } = useTranslations(); const windowHours = 24; const { data, isLoading } = useQuery({ queryKey: queryKeys.adminNotificationHealth(windowHours), queryFn: async () => apiGet( `/api/admin/notifications/health?hours=${windowHours}`, ), }); return (
{isLoading && ( )} {data && data.channels.length === 0 && (

{t("admin.notificationHealth.empty")}

)} {data && data.channels.length > 0 && (
{data.channels.map((c) => (
{c.channel} {t("admin.notificationHealth.ok")}: {c.ok} {t("admin.notificationHealth.error")}: {c.error} {t("admin.notificationHealth.skipped")}: {c.skipped}
))}
)} {data && data.autoDisabledChannels.length > 0 && (

{t("admin.notificationHealth.autoDisabled")}

{data.autoDisabledChannels.map((d) => ( {d.type} ({d.count}) ))}
)}
); } export function RemindersSection() { const { t } = useTranslations(); const { data: settings } = useAdminSettings(); const updateSettings = useUpdateSettings(); const [reminderLateDraft, setReminderLateDraft] = useState( null, ); const [reminderMissedDraft, setReminderMissedDraft] = useState( null, ); const testNotification = useMutation({ mutationFn: async () => { return apiPost< | { message?: string; results?: Array<{ channel: string; success: boolean; error?: string; }>; } | undefined >("/api/admin/notifications/test"); }, onSuccess: (data) => { const hasFailures = data?.results?.some((r) => !r.success); if (hasFailures) { toast.error(data?.message ?? t("admin.notificationTestFailed")); } else { toast.success(data?.message ?? t("admin.notificationTestSuccess")); } }, onError: (error) => { toast.error( error instanceof Error ? error.message : t("admin.notificationTestFailed"), ); }, }); const reminderCheck = useMutation({ mutationFn: async () => { return apiPost< | { message?: string; medications?: Array<{ name: string; dose: string; user: string; localTime: string; dayOfWeek: string; notificationsEnabled: boolean; schedules: Array<{ window: string; days: string; status: string; label: string; notificationSent?: boolean; }>; eventsToday: number; }>; notificationsSent?: number; } | undefined >("/api/admin/notifications/reminder-check"); }, onSuccess: (data) => { toast.success(data?.message ?? t("admin.reminderCheckSuccess")); }, onError: (error) => { toast.error( error instanceof Error ? error.message : t("admin.reminderCheckFailed"), ); }, }); return ( <> {/* v1.4.22 D / F-58 — icon swap so the section header matches the admin-shell nav tile (Bell). Clock now lives on the timing chips inline instead of competing as the page icon. */}

{t("admin.reminderLateMinutesDescription")}

setReminderLateDraft(Number(e.target.value))} autoComplete="off" data-lpignore="true" data-1p-ignore="true" disabled={updateSettings.isPending} className="w-32" />

{t("admin.reminderMissedMinutesDescription")}

setReminderMissedDraft(Number(e.target.value))} autoComplete="off" data-lpignore="true" data-1p-ignore="true" disabled={updateSettings.isPending} className="w-32" />
{testNotification.data?.results && testNotification.data.results.length > 0 && (
{testNotification.data.results.map((r, i) => (
{r.success ? ( ) : ( )} {r.channel} {r.error && ( — {r.error} )}
))}
)} {reminderCheck.data?.medications && reminderCheck.data.medications.length > 0 && (
{/* v1.4.33 IW9 — h4 -> h2 so the page outline stays sequential (parent admin SectionFrame ships

). */}

{t("admin.reminderCheckResults")}

{reminderCheck.data.medications.map((med, i) => (
{med.name} ({med.dose}) {med.notificationsEnabled ? t("admin.reminderCheckNotifOn") : t("admin.reminderCheckNotifOff")}

{med.user} — {med.dayOfWeek} {med.localTime} —{" "} {t("admin.reminderCheckEventsToday")}: {med.eventsToday}

{med.schedules.map((sched, j) => { const statusColor = sched.status === "open" ? "text-success" : sched.status === "threshold" ? "text-warning" : sched.status === "missed" ? "text-destructive" : sched.status === "skipped" ? "text-muted-foreground" : ""; return (
{sched.window} [{sched.days}] {sched.label} {sched.notificationSent && ( {t("admin.reminderCheckNotifSent")} )}
); })}
))}
)}
); }