/** * MedDetailModal - Medication detail view with nested modals * Displays medication information, stock, schedules, and provides refill/edit functionality * * Can work in two modes: * 1. Context mode: Uses useAppContext() for all state (when no props provided) * 2. Props mode: Accepts all required data as props (for gradual adoption) */ import { useTranslation } from "react-i18next"; import { Lightbox, MedicationAvatar } from "../components"; import type { Coverage, Medication, RefillEntry, StockThresholds } from "../types"; import { getMedTotal, getPackageSize } from "../types"; import { formatNumber, generateICS, getExpiryClass, getSystemLocale } from "../utils"; import { getStockStatus } from "../utils/schedule"; // ============================================================================= // Local Helper Functions // ============================================================================= /** * Calculate blister stock - divides current pills into full blisters and partial */ function getBlisterStock( currentPills: number, pillsPerBlister: number, _originalLooseTablets: number, _originalTotalPills: number ): { fullBlisters: number; openBlisterPills: number; loosePills: number } { if (pillsPerBlister <= 0 || pillsPerBlister === 1) { return { fullBlisters: 0, openBlisterPills: 0, loosePills: currentPills }; } const fullBlisters = Math.floor(currentPills / pillsPerBlister); const openBlisterPills = currentPills % pillsPerBlister; return { fullBlisters, openBlisterPills, loosePills: 0 }; } /** * Format full blisters column */ function formatFullBlisters(fullBlisters: number, t: (key: string) => string): string { if (fullBlisters === 0) return "—"; return `${fullBlisters} ${fullBlisters === 1 ? t("common.blister") : t("common.blisters")}`; } /** * Format open blister column */ function formatOpenBlisterAndLoose( openBlisterPills: number, _loosePills: number, pillsPerBlister: number, t: (key: string) => string ): string { if (openBlisterPills > 0) { return `${openBlisterPills} ${t("common.of")} ${pillsPerBlister} ${t("common.pills")}`; } return "—"; } // ============================================================================= // Props Interface // ============================================================================= export interface MedDetailModalProps { // Required selectedMed: Medication | null; coverage: { all: Coverage[] }; settings: StockThresholds; // Modal state showImageLightbox: boolean; showRefillModal: boolean; showEditStockModal: boolean; // Modal actions onClose: () => void; onOpenImageLightbox: () => void; onCloseImageLightbox: () => void; onOpenRefillModal: () => void; onCloseRefillModal: () => void; onOpenEditStockModal: () => void; onCloseEditStockModal: () => void; // Refill state refillPacks: number; onRefillPacksChange: (value: number) => void; refillLoose: number; onRefillLooseChange: (value: number) => void; refillSaving: boolean; refillHistory: RefillEntry[]; refillHistoryExpanded: boolean; onRefillHistoryExpandedChange: (value: boolean) => void; onSubmitRefill: (medId: number) => Promise; // Edit stock state editStockFullBlisters: number; onEditStockFullBlistersChange: (value: number) => void; editStockPartialBlisterPills: number; onEditStockPartialBlisterPillsChange: (value: number) => void; editStockSaving: boolean; onSubmitStockCorrection: (medId: number) => Promise; } export function MedDetailModal({ selectedMed, coverage, settings, showImageLightbox, showRefillModal, showEditStockModal, onClose, onOpenImageLightbox, onCloseImageLightbox, onOpenRefillModal, onCloseRefillModal, onOpenEditStockModal, onCloseEditStockModal, refillPacks, onRefillPacksChange, refillLoose, onRefillLooseChange, refillSaving, refillHistory, refillHistoryExpanded, onRefillHistoryExpandedChange, onSubmitRefill, editStockFullBlisters, onEditStockFullBlistersChange, editStockPartialBlisterPills, onEditStockPartialBlisterPillsChange, editStockSaving, onSubmitStockCorrection, }: MedDetailModalProps) { const { t, i18n } = useTranslation(); if (!selectedMed) return null; const medCoverage = coverage.all.find((c) => c.name === selectedMed.name); const packageSize = getPackageSize(selectedMed); const currentStock = medCoverage ? Math.round(medCoverage.medsLeft) : getMedTotal(selectedMed); const status = medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null; const textClass = status?.className === "danger" ? "danger-text" : status?.className === "warning" ? "warning-text" : "success-text"; const stock = getBlisterStock(currentStock, selectedMed.pillsPerBlister, selectedMed.looseTablets, packageSize); return (
e.stopPropagation()}>
{/* Header */}
selectedMed.imageUrl && onOpenImageLightbox()} > {selectedMed.imageUrl && 🔍}

{selectedMed.name}

{selectedMed.genericName && {selectedMed.genericName}} {selectedMed.takenBy && (selectedMed.takenBy || []).length > 0 && ( {t("modal.for")} {selectedMed.takenBy.join(", ")} )}
{/* Stock Info Section */}

{t("modal.stockInfo")}

{t("table.fullBlisters")} {formatFullBlisters(stock.fullBlisters, t)}
{t("table.openBlister")} {formatOpenBlisterAndLoose( stock.openBlisterPills, stock.loosePills, selectedMed.pillsPerBlister ?? 1, t )}
{t("modal.currentStock")} {currentStock} / {packageSize}
{/* Package Details Section */}

{t("modal.packageDetails")}

{t("modal.packs")} {selectedMed.packCount}
{t("modal.blistersPerPack")} {selectedMed.blistersPerPack}
{t("modal.pillsPerBlister")} {selectedMed.pillsPerBlister}
{selectedMed.pillWeightMg && (
{t("modal.pillWeight")} {selectedMed.pillWeightMg} mg
)} {selectedMed.expiryDate && (
{t("modal.expiryDate")} {new Date(selectedMed.expiryDate).toLocaleDateString(getSystemLocale(i18n.language), { day: "2-digit", month: "short", year: "numeric", })}
)}
{/* Intake Schedule Section */} {selectedMed.blisters.length > 0 && (

{t("modal.intakeSchedule")}{" "} {selectedMed.intakeRemindersEnabled && ( 🔔 )}

{selectedMed.blisters.map((blister, idx) => { const personCount = Math.max(1, selectedMed.takenBy?.length || 1); const totalUsage = blister.usage * personCount; return (
{totalUsage} {totalUsage !== 1 ? t("common.pills") : t("common.pill")} {selectedMed.pillWeightMg && ` (${totalUsage * selectedMed.pillWeightMg} mg)`} {t("form.blisters.every")} {blister.every}{" "} {blister.every !== 1 ? t("common.days") : t("common.day")} {t("modal.at")}{" "} {new Date(blister.start).toLocaleTimeString(getSystemLocale(i18n.language), { hour: "2-digit", minute: "2-digit", })}
); })}
)} {/* Coverage Status Section */} {medCoverage && status && (

{t("modal.coverageStatus")} {t(status.label)}

{t("modal.daysLeft")} {medCoverage.daysLeft !== null ? formatNumber(medCoverage.daysLeft) : "—"}
{t("modal.runsOut")} {medCoverage.depletionDate ?? "—"}
)} {/* Notes Section */} {selectedMed.notes && (

📝 {t("modal.notes")}

{selectedMed.notes}
)} {/* Refill History Section */} {refillHistory.length > 0 && (

onRefillHistoryExpandedChange(!refillHistoryExpanded)} > {t("refill.history")} ({refillHistory.length}) {refillHistoryExpanded ? "▼" : "▶"}

{refillHistoryExpanded && (
{refillHistory.map((entry) => (
{new Date(entry.refillDate).toLocaleDateString(getSystemLocale(i18n.language), { day: "2-digit", month: "short", year: "numeric", })} ,{" "} {new Date(entry.refillDate).toLocaleTimeString(getSystemLocale(i18n.language), { hour: "2-digit", minute: "2-digit", })} + {entry.packsAdded * selectedMed.blistersPerPack * selectedMed.pillsPerBlister + entry.loosePillsAdded}{" "} {t("common.pills")}
))}
)}
)}
{/* Footer */}
{selectedMed.blisters.length > 0 && ( )}
{/* Image Lightbox */} {showImageLightbox && selectedMed.imageUrl && ( )} {/* Refill Modal */} {showRefillModal && (
{ e.stopPropagation(); onCloseRefillModal(); }} >
e.stopPropagation()}>

{t("refill.title")}

{selectedMed.name}

{(refillPacks > 0 || refillLoose > 0) && ( +{refillPacks * selectedMed.blistersPerPack * selectedMed.pillsPerBlister + refillLoose}{" "} {t("common.pills")} )}
)} {/* Edit Stock Modal */} {showEditStockModal && (
{ e.stopPropagation(); onCloseEditStockModal(); }} >
e.stopPropagation()}>

{t("editStock.title")}

{selectedMed.name}

{t("editStock.hint")}

{(() => { const dbTotal = getMedTotal(selectedMed); const currentTotal = medCoverage ? Math.round(medCoverage.medsLeft) : dbTotal; const newTotal = editStockFullBlisters * selectedMed.pillsPerBlister + editStockPartialBlisterPills; const difference = newTotal - currentTotal; return ( <>
{t("editStock.currentTotal")}: {currentTotal} {t("common.pills")}
{t("editStock.newTotal")}: {newTotal} {t("common.pills")}
0 ? "positive" : difference < 0 ? "negative" : ""}`} > {t("editStock.difference")}: {difference > 0 ? "+" : ""} {difference} {t("common.pills")}
); })()}
)}
); }