Files
medassist-ng/frontend/src/components/dashboard/DashboardReminderSection.tsx
T

266 lines
9.3 KiB
TypeScript

import { type Coverage, getMedDisplayName, type Medication, type StockThresholds } from "../../types";
import { getStockStatus } from "../../utils/schedule";
type ReminderData = {
status: { className: string; text: string };
lowStockMeds: Array<{ name: string; daysLeft: number; isCritical: boolean }>;
lastStockSent: { medNames: string | null; date: string } | null;
lastIntakeSent: { medName: string | null; takenBy: string | null; date: string } | null;
};
type PrescriptionLowMed = {
id: number;
name: string;
remainingRefills: number;
threshold: number;
};
type DashboardReminderSectionProps = {
t: (key: string, options?: Record<string, unknown>) => string;
remindersLoading: boolean;
anyRemindersEnabled: boolean;
stockRemindersEnabled: boolean;
intakeRemindersEnabled: boolean;
prescriptionRemindersEnabled: boolean;
reminderData: ReminderData;
prescriptionLowMeds: PrescriptionLowMed[];
prescriptionStatus: { text: string; className: string } | null;
meds: Medication[];
coverage: { all: Coverage[] };
stockThresholds: StockThresholds;
sendingReminder: boolean;
reminderResult: { success: boolean; message: string } | null;
onSendManualReminder: () => void;
onOpenMedicationDetail: (med: Medication) => void;
};
function NotificationBellIcon() {
return (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={{ display: "block" }}
>
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
);
}
export function DashboardReminderSection({
t,
remindersLoading,
anyRemindersEnabled,
stockRemindersEnabled,
intakeRemindersEnabled,
prescriptionRemindersEnabled,
reminderData,
prescriptionLowMeds,
prescriptionStatus,
meds,
coverage,
stockThresholds,
sendingReminder,
reminderResult,
onSendManualReminder,
onOpenMedicationDetail,
}: DashboardReminderSectionProps) {
const getStatusTextClass = (statusClassName: string | undefined): string => {
if (statusClassName === "danger") return "danger-text";
if (statusClassName === "warning") return "warning-text";
return "";
};
if (remindersLoading) {
return (
<section className="reminder-status-bar reminder-status-skeleton" aria-busy="true">
<div className="reminder-status-header">
<span className="reminder-status-icon">
<NotificationBellIcon />
</span>
<span className="reminder-status-title">{t("dashboard.reminders.active")}</span>
</div>
<div className="reminder-status-details reminder-status-skeleton-lines">
<span className="skeleton-line skeleton-line-long" />
<span className="skeleton-line skeleton-line-medium" />
<span className="skeleton-line skeleton-line-short" />
</div>
</section>
);
}
if (!anyRemindersEnabled) {
return null;
}
return (
<section className="reminder-status-bar">
<div className="reminder-status-header">
<span className="reminder-status-icon">
<NotificationBellIcon />
</span>
<span className="reminder-status-title">{t("dashboard.reminders.active")}</span>
{stockRemindersEnabled && (
<span className={`status-chip small ${reminderData.status.className}`}>{reminderData.status.text}</span>
)}
{prescriptionStatus && (
<span className={`status-chip small ${prescriptionStatus.className}`}>{prescriptionStatus.text}</span>
)}
</div>
{(reminderData.lowStockMeds.length > 0 ||
(prescriptionRemindersEnabled && prescriptionLowMeds.length > 0) ||
(stockRemindersEnabled && reminderData.lastStockSent) ||
(intakeRemindersEnabled && reminderData.lastIntakeSent)) && (
<div className="reminder-status-details">
{stockRemindersEnabled && reminderData.lowStockMeds.length > 0 && (
<div className="reminder-status-row">
<span className="reminder-status-label">{t("dashboard.reminders.needsRefill")}:</span>
<span className="reminder-status-value">
{reminderData.lowStockMeds.map((med, idx) => {
const medication = meds.find((m) => getMedDisplayName(m) === med.name);
const cov = coverage.all.find((c) => c.name === med.name);
const status = cov
? getStockStatus(cov.daysLeft, cov.medsLeft, stockThresholds, medication?.packageType)
: null;
const textClass = getStatusTextClass(status?.className);
return (
<span key={med.name}>
{idx > 0 && ", "}
<span
className={`med-link clickable ${textClass}`}
onClick={() => medication && onOpenMedicationDetail(medication)}
onKeyDown={(e) => {
if ((e.key === "Enter" || e.key === " ") && medication) {
onOpenMedicationDetail(medication);
}
}}
>
{med.name}
</span>
<span className={`reminder-days-left ${textClass}`}>
{" "}
{t("dashboard.reminders.daysLeft", { count: med.daysLeft, days: med.daysLeft })}
</span>
</span>
);
})}
</span>
</div>
)}
{prescriptionRemindersEnabled && prescriptionLowMeds.length > 0 && (
<div className="reminder-status-row">
<span className="reminder-status-label">{t("dashboard.reminders.needsPrescriptionRefill")}:</span>
<span className="reminder-status-value">
{prescriptionLowMeds.map((med, idx) => {
const medication = meds.find((m) => m.id === med.id);
const textClass = med.remainingRefills <= 0 ? "danger-text" : "warning-text";
return (
<span key={med.id}>
{idx > 0 && ", "}
<span className={`reminder-days-left ${textClass}`}>
{t("prescription.remainingRefills")}: {med.remainingRefills} · {t("dashboard.reminders.usedBy")}
:{" "}
<span
className={`med-link clickable ${textClass}`}
onClick={() => medication && onOpenMedicationDetail(medication)}
onKeyDown={(e) => {
if ((e.key === "Enter" || e.key === " ") && medication) {
onOpenMedicationDetail(medication);
}
}}
>
{med.name}
</span>
</span>
</span>
);
})}
</span>
</div>
)}
{stockRemindersEnabled && reminderData.lastStockSent && (
<div className="reminder-status-row">
<span className="reminder-status-label">{t("dashboard.reminders.lastStockSent")}:</span>
<span className="reminder-status-value">
{reminderData.lastStockSent.medNames &&
(() => {
const names = reminderData.lastStockSent?.medNames?.split(", ") ?? [];
return names.map((name, idx) => {
const medication = meds.find((m) => getMedDisplayName(m) === name);
return (
<span key={name}>
{idx > 0 && ", "}
{medication ? (
<span
className="med-link clickable"
onClick={() => onOpenMedicationDetail(medication)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") onOpenMedicationDetail(medication);
}}
>
{name}
</span>
) : (
<span className="reminder-med-name">{name}</span>
)}
</span>
);
});
})()}
<span className="reminder-date"> {reminderData.lastStockSent.date}</span>
</span>
</div>
)}
{intakeRemindersEnabled && reminderData.lastIntakeSent && (
<div className="reminder-status-row">
<span className="reminder-status-label">{t("dashboard.reminders.lastSent")}:</span>
<span className="reminder-status-value">
{reminderData.lastIntakeSent.medName &&
(() => {
const medication = meds.find((m) => getMedDisplayName(m) === reminderData.lastIntakeSent?.medName);
return medication ? (
<span
className="med-link clickable"
onClick={() => onOpenMedicationDetail(medication)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") onOpenMedicationDetail(medication);
}}
>
{reminderData.lastIntakeSent?.medName}
</span>
) : (
<span className="reminder-med-name">{reminderData.lastIntakeSent?.medName}</span>
);
})()}
{reminderData.lastIntakeSent.takenBy && (
<span className="reminder-taken-by"> ({reminderData.lastIntakeSent.takenBy})</span>
)}
<span className="reminder-date"> {reminderData.lastIntakeSent.date}</span>
</span>
</div>
)}
</div>
)}
{((stockRemindersEnabled && reminderData.lowStockMeds.length > 0) ||
(prescriptionRemindersEnabled && prescriptionLowMeds.length > 0)) && (
<div className="reminder-send-row">
<button type="button" className="ghost" onClick={onSendManualReminder} disabled={sendingReminder}>
{sendingReminder ? t("common.sending") : t("dashboard.reorder.sendReminder")}
</button>
{reminderResult && (
<span className={`reminder-send-result ${reminderResult.success ? "success" : "error"}`}>
{reminderResult.message}
</span>
)}
</div>
)}
</section>
);
}