'use client'; import { useState, useEffect, useCallback } from 'react'; import { listRegimens, createRegimen, updateRegimen, deleteRegimen, getBurnRates, } from '@/services/regimens'; import { listMedicines } from '@/services/medicines'; import { DosageFrequency, TimeOfDay, DosageUnit, MedicineForm, allowedUnitsForForm, defaultUnitForForm, } from '@meshitrack/shared'; import type { CreateRegimenInput } from '@meshitrack/shared'; import type { z } from 'zod/v4'; import type { RegimenResponseSchema, RegimenMedicationInputSchema, BurnRateItemSchema, } from '@meshitrack/shared'; type Regimen = z.infer; type MedicationInput = z.infer; type BurnRateItem = z.infer; type MedicineOption = { _id: string; name: string; strength: number; strengthUnit: string; form: MedicineForm; }; const FREQUENCY_LABELS: Record = { daily: 'Once daily', twice_daily: 'Twice daily', three_times_daily: 'Three times daily', weekly: 'Weekly', every_other_day: 'Every other day', as_needed: 'As needed', custom: 'Custom', }; const TIME_LABELS: Record = { morning: 'Morning', afternoon: 'Afternoon', evening: 'Evening', bedtime: 'Bedtime', }; const FORM_LABELS: Record = { tablet: 'Tablet', capsule: 'Capsule', liquid: 'Liquid', injection: 'Injection', other: 'Other', }; function formatDate(dateStr: string): string { return new Date(dateStr).toLocaleDateString(); } // --- Medication sub-form row --- function MedicationRow({ medication, index, medicines, onChange, onRemove, }: { medication: MedicationInput; index: number; medicines: MedicineOption[]; onChange: (index: number, updated: MedicationInput) => void; onRemove: (index: number) => void; }) { const selectedMed = medicines.find((m) => m._id === medication.medicineId); const allowedUnits = selectedMed ? allowedUnitsForForm(selectedMed.form) : Object.values(DosageUnit); function handleMedicineChange(medicineId: string) { const med = medicines.find((m) => m._id === medicineId); const newUnit = med ? defaultUnitForForm(med.form) : DosageUnit.TABLET; onChange(index, { ...medication, medicineId, dosageUnit: newUnit }); } return (
Medication {index + 1}
onChange(index, { ...medication, dosage: Number(e.target.value) })} className="w-full rounded-lg border px-3 py-2 text-sm focus:border-primary-500 focus:ring-1 focus:ring-primary-500 focus:outline-none" />
{medication.frequency === DosageFrequency.CUSTOM && (
onChange(index, { ...medication, customFrequencyPerDay: Number(e.target.value) }) } className="w-full rounded-lg border px-3 py-2 text-sm focus:border-primary-500 focus:ring-1 focus:ring-primary-500 focus:outline-none" />
)}
onChange(index, { ...medication, instructions: e.target.value || undefined }) } placeholder="e.g. Take with food" className="w-full rounded-lg border px-3 py-2 text-sm focus:border-primary-500 focus:ring-1 focus:ring-primary-500 focus:outline-none" />
); } // --- Regimen create / edit form --- function emptyMedication(): MedicationInput { return { medicineId: '', dosage: 1, dosageUnit: DosageUnit.TABLET, frequency: DosageFrequency.DAILY, }; } function RegimenForm({ householdId, medicines, initial, onSaved, onCancel, }: { householdId: string; medicines: MedicineOption[]; initial?: Regimen; onSaved: () => void; onCancel: () => void; }) { const [name, setName] = useState(initial?.name ?? ''); const [isActive, setIsActive] = useState(initial?.isActive ?? true); const [medications, setMedications] = useState( initial?.medications.map((m) => ({ medicineId: m.medicineId, dosage: m.dosage, dosageUnit: m.dosageUnit as DosageUnit, frequency: m.frequency as DosageFrequency, customFrequencyPerDay: m.customFrequencyPerDay, timeOfDay: m.timeOfDay as TimeOfDay | undefined, instructions: m.instructions, })) ?? [emptyMedication()], ); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); function updateMedication(index: number, updated: MedicationInput) { setMedications((prev) => prev.map((m, i) => (i === index ? updated : m))); } function removeMedication(index: number) { setMedications((prev) => prev.filter((_, i) => i !== index)); } function addMedication() { setMedications((prev) => [...prev, emptyMedication()]); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (medications.length === 0) { setError('At least one medication is required.'); return; } setError(''); setSubmitting(true); try { const payload: CreateRegimenInput = { name: name.trim(), isActive, medications }; if (initial) { await updateRegimen(householdId, initial._id, payload); } else { await createRegimen(householdId, payload); } onSaved(); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save regimen'); } finally { setSubmitting(false); } } return (

{initial ? 'Edit Regimen' : 'New Regimen'}

{error && (
{error}
)}
setName(e.target.value)} placeholder="e.g. Morning routine" className="w-full rounded-lg border px-3 py-2 text-sm focus:border-primary-500 focus:ring-1 focus:ring-primary-500 focus:outline-none" />
setIsActive(e.target.checked)} className="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500" />

Medications

{medications.length === 0 ? (

No medications added yet.

) : (
{medications.map((med, i) => ( ))}
)}
); } // --- Burn rate table --- function BurnRateTable({ burnRates }: { burnRates: BurnRateItem[] }) { if (burnRates.length === 0) { return (

No active regimens with cabinet stock to calculate burn rates.

); } return (
{burnRates.map((item) => { const daysLeft = item.daysUntilEmpty; const daysColor = daysLeft === null ? 'text-gray-400' : daysLeft <= 7 ? 'text-red-600 font-semibold' : daysLeft <= 30 ? 'text-yellow-600' : 'text-green-600'; return ( ); })}
Medicine Daily use In cabinet Days left Monthly cost
{item.medicineName} {item.dailyConsumption.toFixed(2)} {item.totalInCabinet} {daysLeft !== null ? daysLeft : '-'} {item.projectedMonthlyCost !== null ? `${item.currency ?? ''} ${item.projectedMonthlyCost.toFixed(2)}`.trim() : '-'}
); } // --- Main component --- export function RegimensTab({ householdId }: { householdId: string }) { const [regimens, setRegimens] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [medicines, setMedicines] = useState([]); const [showForm, setShowForm] = useState(false); const [editingRegimen, setEditingRegimen] = useState(null); const [filterActive, setFilterActive] = useState<'all' | 'active' | 'inactive'>('all'); const [burnRates, setBurnRates] = useState([]); const [showBurnRate, setShowBurnRate] = useState(false); const [burnRateLoading, setBurnRateLoading] = useState(false); const fetchRegimens = useCallback(async () => { setLoading(true); try { const query = filterActive === 'active' ? { isActive: true } : filterActive === 'inactive' ? { isActive: false } : {}; const result = await listRegimens(householdId, { ...query, limit: 50 }); setRegimens(result.data); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load regimens'); } finally { setLoading(false); } }, [householdId, filterActive]); useEffect(() => { fetchRegimens(); }, [fetchRegimens]); // Medicines are needed for the form useEffect(() => { listMedicines(householdId, { limit: 100 }) .then((r) => setMedicines(r.data as MedicineOption[])) .catch(() => {}); }, [householdId]); async function handleDelete(id: string, name: string) { if (!confirm(`Delete regimen "${name}"?`)) return; try { await deleteRegimen(householdId, id); setRegimens((prev) => prev.filter((r) => r._id !== id)); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete'); } } async function handleToggleActive(regimen: Regimen) { try { const updated = await updateRegimen(householdId, regimen._id, { isActive: !regimen.isActive, }); setRegimens((prev) => prev.map((r) => (r._id === regimen._id ? updated : r))); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update'); } } async function handleShowBurnRate() { setShowBurnRate((prev) => !prev); if (!showBurnRate && burnRates.length === 0) { setBurnRateLoading(true); try { const result = await getBurnRates(householdId); setBurnRates(result.data); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load burn rates'); } finally { setBurnRateLoading(false); } } } const isFormOpen = showForm || editingRegimen !== null; return (
{error && (
{error}
)} {showBurnRate && (

Burn Rate & Spending Projections

{burnRateLoading ? (
) : ( )}
)} {showForm && !editingRegimen && ( { setShowForm(false); fetchRegimens(); setBurnRates([]); }} onCancel={() => setShowForm(false)} /> )} {editingRegimen && ( { setEditingRegimen(null); fetchRegimens(); setBurnRates([]); }} onCancel={() => setEditingRegimen(null)} /> )} {loading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : regimens.length === 0 ? (
{filterActive !== 'all' ? `No ${filterActive} regimens found.` : isFormOpen ? null : 'No regimens yet. Create your first medication schedule above.'}
) : (
{regimens.map((regimen) => (

{regimen.name}

{regimen.isActive ? 'Active' : 'Inactive'}

{regimen.medications.length} medication {regimen.medications.length !== 1 ? 's' : ''}

{regimen.medications.map((med, i) => ( {med.medicineName} — {med.dosage} {med.dosageUnit} ( {FREQUENCY_LABELS[med.frequency] ?? med.frequency}) ))}

Created {formatDate(regimen.createdAt)}

))}
)}
); }