diff --git a/backend/src/main.py b/backend/src/main.py index b653c28..465a398 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -91,6 +91,10 @@ class Settings(BaseSettings): PrescriptionCreate, PrescriptionUpdate, PrescriptionResponse, + DoseStatus, + MedicationDose, + MedicationDoseCreate, + MedicationDoseResponse, ) from .risk_calculator import calculate_health_risk from .health_summary import build_health_summary @@ -183,6 +187,7 @@ async def lifespan(app: FastAPI): Questionnaire, HealthRiskAssessment, Prescription, + MedicationDose, ], ) print("Database connection established.") @@ -811,3 +816,90 @@ async def archive_prescription( return SuccessResponse( data=PrescriptionResponse.model_validate(prescription) ) + + + +@app.post( + "/api/v1/prescriptions/{prescription_id}/doses", + response_model=SuccessResponse[MedicationDoseResponse], +) +async def record_medication_dose( + prescription_id: uuid.UUID, + dose_data: MedicationDoseCreate, + current_user: Annotated[User, Depends(require_health_consent)], +): + prescription = await Prescription.find_one( + Prescription.id == prescription_id, + Prescription.user_id == current_user.id, + Prescription.archived == False, + ) + + if not prescription: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Prescription not found", + ) + + existing_dose = await MedicationDose.find_one( + MedicationDose.user_id == current_user.id, + MedicationDose.prescription_id == prescription.id, + MedicationDose.scheduled_date == dose_data.scheduled_date, + ) + + if existing_dose: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="A dose record already exists for this scheduled date", + ) + dose = MedicationDose( + user_id=current_user.id, + prescription_id=prescription.id, + scheduled_date=dose_data.scheduled_date, + status=dose_data.status, + ) + + try: + await dose.insert() + except DuplicateKeyError: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="A dose record already exists for this scheduled date", + ) + + return SuccessResponse( + data=MedicationDoseResponse.model_validate(dose) + ) + + +@app.get( + "/api/v1/prescriptions/{prescription_id}/doses", + response_model=SuccessResponse[list[MedicationDoseResponse]], +) +async def get_medication_doses( + prescription_id: uuid.UUID, + current_user: Annotated[User, Depends(require_health_consent)], +): + prescription = await Prescription.find_one( + Prescription.id == prescription_id, + Prescription.user_id == current_user.id, + Prescription.archived == False, + ) + + if not prescription: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Prescription not found", + ) + + doses = await MedicationDose.find( + MedicationDose.user_id == current_user.id, + MedicationDose.prescription_id == prescription.id, + ).sort(-MedicationDose.recorded_at).to_list() + + return SuccessResponse( + data=[ + MedicationDoseResponse.model_validate(dose) + for dose in doses + ] + ) + diff --git a/backend/src/models.py b/backend/src/models.py index a456421..c2f8196 100644 --- a/backend/src/models.py +++ b/backend/src/models.py @@ -4,6 +4,7 @@ from enum import Enum from datetime import date, datetime, timezone from typing import Optional, Annotated, Generic, TypeVar +from pymongo import IndexModel # --- Consent (must match frontend CURRENT_CONSENT_VERSION) --- CURRENT_CONSENT_VERSION = "v1.0" @@ -451,3 +452,49 @@ class PrescriptionResponse(BaseModel): class Config: from_attributes = True + + + +class DoseStatus(str, Enum): + taken = "taken" + skipped = "skipped" + + +class MedicationDose(Document): + id: uuid.UUID = Field(default_factory=uuid.uuid4) + user_id: uuid.UUID + prescription_id: uuid.UUID + scheduled_date: date + status: DoseStatus + recorded_at: datetime = Field( + default_factory=lambda: datetime.now(timezone.utc) + ) + + class Settings: + name = "medication_doses" + indexes = [ + IndexModel( + [ + ("user_id", 1), + ("prescription_id", 1), + ("scheduled_date", 1), + ], + unique=True, + ) + ] + + +class MedicationDoseCreate(BaseModel): + scheduled_date: date + status: DoseStatus + + +class MedicationDoseResponse(BaseModel): + id: uuid.UUID + prescription_id: uuid.UUID + scheduled_date: date + status: DoseStatus + recorded_at: datetime + + class Config: + from_attributes = True diff --git a/backend/tests/test_prescriptions.py b/backend/tests/test_prescriptions.py index 906e3e6..d281f85 100644 --- a/backend/tests/test_prescriptions.py +++ b/backend/tests/test_prescriptions.py @@ -1,6 +1,6 @@ import os import uuid -from datetime import datetime, timezone +from datetime import date, datetime, timezone import pytest from beanie import init_beanie @@ -15,6 +15,8 @@ GenderEnum, Prescription, User, + MedicationDose, + DoseStatus, ) @@ -25,7 +27,7 @@ async def test_db(): await init_beanie( database=database, - document_models=[User, Prescription], + document_models=[User, Prescription, MedicationDose], ) yield database @@ -429,3 +431,206 @@ async def test_archived_prescription_cannot_be_archived_again( ) assert response.status_code == 404 + + +@pytest.mark.anyio +async def test_record_taken_dose(client, test_user): + prescription = Prescription( + user_id=test_user.id, + medication_name="Test Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + response = await client.post( + f"/api/v1/prescriptions/{prescription.id}/doses", + json={"status": "taken", "scheduled_date": "2026-10-28"}, + ) + + assert response.status_code == 200 + data = response.json()["data"] + assert data["status"] == "taken" + assert data["prescription_id"] == str(prescription.id) + assert data["recorded_at"] is not None + assert data["scheduled_date"] == "2026-10-28" + + doses = await MedicationDose.find( + MedicationDose.user_id == test_user.id, + MedicationDose.prescription_id == prescription.id, + ).to_list() + assert len(doses) == 1 + assert doses[0].status == DoseStatus.taken + +@pytest.mark.anyio +async def test_cannot_record_duplicate_dose_for_same_date(client, test_user): + prescription = Prescription( + user_id=test_user.id, + medication_name="Test Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + payload = { + "status": "taken", + "scheduled_date": "2026-01-15", + } + url = f"/api/v1/prescriptions/{prescription.id}/doses" + + first_response = await client.post(url, json=payload) + second_response = await client.post(url, json=payload) + + assert first_response.status_code == 200 + assert second_response.status_code == 409 + +@pytest.mark.anyio +async def test_cannot_record_dose_without_health_consent(test_db, test_user): + test_user.consent_given = False + await test_user.insert() + + async def override_current_user(): + return test_user + + app.dependency_overrides[get_current_user] = override_current_user + app.dependency_overrides.pop(require_health_consent, None) + + try: + async with AsyncClient( + transport=ASGITransport(app=app), + base_url="http://test", + ) as test_client: + prescription = Prescription( + user_id=test_user.id, + medication_name="Test Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + response = await test_client.post( + f"/api/v1/prescriptions/{prescription.id}/doses", + json={ + "status": "taken", + "scheduled_date": "2026-01-15", + }, + ) + + assert response.status_code == 403 + finally: + app.dependency_overrides.clear() + +@pytest.mark.anyio +async def test_record_skipped_dose(client, test_user): + prescription = Prescription( + user_id=test_user.id, + medication_name="Test Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + response = await client.post( + f"/api/v1/prescriptions/{prescription.id}/doses", + json={"status": "skipped", "scheduled_date": "2026-10-28"}, + ) + + assert response.status_code == 200 + assert response.json()["data"]["status"] == "skipped" + + +@pytest.mark.anyio +async def test_invalid_dose_status_is_rejected(client, test_user): + prescription = Prescription( + user_id=test_user.id, + medication_name="Test Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + response = await client.post( + f"/api/v1/prescriptions/{prescription.id}/doses", + json={"status": "maybe", "scheduled_date": "2026-10-28"}, + ) + + assert response.status_code == 422 + + +@pytest.mark.anyio +async def test_cannot_record_dose_for_another_users_prescription( + client, other_user +): + prescription = Prescription( + user_id=other_user.id, + medication_name="Other User Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + response = await client.post( + f"/api/v1/prescriptions/{prescription.id}/doses", + json={"status": "taken", "scheduled_date": "2026-10-28"}, + ) + + assert response.status_code == 404 + + doses = await MedicationDose.find( + MedicationDose.prescription_id == prescription.id, + ).to_list() + assert doses == [] + + +@pytest.mark.anyio +async def test_get_dose_history(client, test_user): + prescription = Prescription( + user_id=test_user.id, + medication_name="Test Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + for scheduled_date, dose_status in ( + (date(2026, 1, 14), DoseStatus.taken), + (date(2026, 1, 15), DoseStatus.skipped), + ): + await MedicationDose( + user_id=test_user.id, + prescription_id=prescription.id, + scheduled_date=scheduled_date, + status=dose_status, + ).insert() + + response = await client.get( + f"/api/v1/prescriptions/{prescription.id}/doses" + ) + + assert response.status_code == 200 + records = response.json()["data"] + assert len(records) == 2 + assert {record["status"] for record in records} == {"taken", "skipped"} + assert {record["scheduled_date"] for record in records} == { + "2026-01-14", + "2026-01-15", + } + + +@pytest.mark.anyio +async def test_cannot_get_dose_history_for_another_users_prescription( + client, other_user +): + prescription = Prescription( + user_id=other_user.id, + medication_name="Other User Medication", + dosage="10mg", + frequency="Once daily", + ) + await prescription.insert() + + response = await client.get( + f"/api/v1/prescriptions/{prescription.id}/doses" + ) + + assert response.status_code == 404 + diff --git a/frontend/public/locales/ar/modules.json b/frontend/public/locales/ar/modules.json index 05533ef..6372d04 100644 --- a/frontend/public/locales/ar/modules.json +++ b/frontend/public/locales/ar/modules.json @@ -52,5 +52,26 @@ "retry": "إعادة المحاولة", "confirmArchive": "هل أنت متأكد من أنك تريد أرشفة هذه الوصفة الطبية؟", "prescriptionRequiredFields": "يرجى ملء جميع الحقول المطلوبة.", -"prescriptionValidationError": "يرجى مراجعة المدخلات وتصحيح أي حقول غير صالحة." +"prescriptionValidationError": "يرجى مراجعة المدخلات وتصحيح أي حقول غير صالحة.", +"doseTracking": "الالتزام بتناول الدواء", +"recordTaken": "تحديد الجرعة على أنها تم تناولها", +"recordSkipped": "تحديد الجرعة على أنها فائتة", +"doseHistory": "سجل الجرعات", +"loadingDoseHistory": "جارٍ تحميل سجل الجرعات...", +"noDoseHistory": "لم يتم تسجيل أي جرعات بعد.", +"doseRecordedTaken": "تم تسجيل الجرعة على أنها تم تناولها.", +"doseRecordedSkipped": "تم تسجيل الجرعة على أنها فائتة.", +"doseRecordError": "تعذر تسجيل هذه الجرعة. يرجى المحاولة مرة أخرى.", +"doseHistoryError": "تعذر تحميل سجل الجرعات. يرجى المحاولة مرة أخرى.", +"recordingDose": "جارٍ التسجيل...", +"taken": "تم تناولها", +"skipped": "تم تخطيها", +"recordedAt": "وقت التسجيل", +"scheduledDoseDate": "التاريخ المحدد للجرعة", +"scheduledDateRequired": "يرجى اختيار التاريخ المحدد لهذه الجرعة.", +"doseAlreadyRecorded": "تم تسجيل جرعة من هذا الدواء بالفعل في هذا التاريخ.", +"recordTakenForMedication": "تحديد {{medication}} على أنها تم تناولها", +"recordSkippedForMedication": "تحديد {{medication}} على أنها فائتة", +"viewDoseHistoryForMedication": "عرض سجل جرعات {{medication}}", +"doseActionsForMedication": "إجراءات الجرعة الخاصة بـ {{medication}}" } \ No newline at end of file diff --git a/frontend/public/locales/en/modules.json b/frontend/public/locales/en/modules.json index 22355e3..aff572f 100644 --- a/frontend/public/locales/en/modules.json +++ b/frontend/public/locales/en/modules.json @@ -43,5 +43,26 @@ "retry": "Retry", "confirmArchive": "Are you sure you want to archive this prescription?", "prescriptionRequiredFields": "Please fill in all required fields.", -"prescriptionValidationError": "Please check your entries and correct any invalid fields." +"prescriptionValidationError": "Please check your entries and correct any invalid fields.", +"doseTracking": "Medication adherence", +"recordTaken": "Mark as taken", +"recordSkipped": "Mark as skipped", +"doseHistory": "Dose history", +"loadingDoseHistory": "Loading dose history...", +"noDoseHistory": "No doses recorded yet.", +"doseRecordedTaken": "Dose recorded as taken.", +"doseRecordedSkipped": "Dose recorded as skipped.", +"doseRecordError": "Could not record this dose. Please try again.", +"doseHistoryError": "Could not load dose history. Please try again.", +"recordingDose": "Recording...", +"taken": "Taken", +"skipped": "Skipped", +"recordedAt": "Recorded", +"scheduledDoseDate": "Scheduled dose date", +"scheduledDateRequired": "Please select the scheduled date for this dose.", +"doseAlreadyRecorded": "A dose has already been recorded for this medication on that date.", +"recordTakenForMedication": "Mark {{medication}} as taken", +"recordSkippedForMedication": "Mark {{medication}} as skipped", +"viewDoseHistoryForMedication": "View dose history for {{medication}}", +"doseActionsForMedication": "Dose actions for {{medication}}" } \ No newline at end of file diff --git a/frontend/public/locales/es/modules.json b/frontend/public/locales/es/modules.json index 7a8ff0b..66fa868 100644 --- a/frontend/public/locales/es/modules.json +++ b/frontend/public/locales/es/modules.json @@ -52,5 +52,26 @@ "retry": "Reintentar", "confirmArchive": "¿Está seguro de que desea archivar esta receta?", "prescriptionRequiredFields": "Completa todos los campos obligatorios.", -"prescriptionValidationError": "Revisa tus datos y corrige los campos no válidos." +"prescriptionValidationError": "Revisa tus datos y corrige los campos no válidos.", +"doseTracking": "Adherencia a la medicación", +"recordTaken": "Marcar como tomada", +"recordSkipped": "Marcar como omitida", +"doseHistory": "Historial de dosis", +"loadingDoseHistory": "Cargando el historial de dosis...", +"noDoseHistory": "Todavía no hay dosis registradas.", +"doseRecordedTaken": "Dosis registrada como tomada.", +"doseRecordedSkipped": "Dosis registrada como omitida.", +"doseRecordError": "No se pudo registrar esta dosis. Inténtalo de nuevo.", +"doseHistoryError": "No se pudo cargar el historial de dosis. Inténtalo de nuevo.", +"recordingDose": "Registrando...", +"taken": "Tomada", +"skipped": "Omitida", +"recordedAt": "Registrada", +"scheduledDoseDate": "Fecha programada de la dosis", +"scheduledDateRequired": "Selecciona la fecha programada de esta dosis.", +"doseAlreadyRecorded": "Ya se ha registrado una dosis de este medicamento en esa fecha.", +"recordTakenForMedication": "Marcar {{medication}} como tomada", +"recordSkippedForMedication": "Marcar {{medication}} como omitida", +"viewDoseHistoryForMedication": "Ver el historial de dosis de {{medication}}", +"doseActionsForMedication": "Acciones de dosis para {{medication}}" } diff --git a/frontend/public/locales/hi/modules.json b/frontend/public/locales/hi/modules.json index 508f114..5a7eea1 100644 --- a/frontend/public/locales/hi/modules.json +++ b/frontend/public/locales/hi/modules.json @@ -52,5 +52,26 @@ "retry": "पुनः प्रयास करें", "confirmArchive": "क्या आप वाकई इस नुस्खे को संग्रहित करना चाहते हैं?", "prescriptionRequiredFields": "कृपया सभी आवश्यक फ़ील्ड भरें।", -"prescriptionValidationError": "कृपया अपनी प्रविष्टियाँ जाँचें और अमान्य फ़ील्ड ठीक करें।" +"prescriptionValidationError": "कृपया अपनी प्रविष्टियाँ जाँचें और अमान्य फ़ील्ड ठीक करें।", +"doseTracking": "दवा लेने का पालन", +"recordTaken": "ली गई के रूप में चिह्नित करें", +"recordSkipped": "छोड़ी गई के रूप में चिह्नित करें", +"doseHistory": "दवा की खुराक का इतिहास", +"loadingDoseHistory": "खुराक का इतिहास लोड हो रहा है...", +"noDoseHistory": "अभी तक कोई खुराक दर्ज नहीं की गई है।", +"doseRecordedTaken": "खुराक को ली गई के रूप में दर्ज किया गया।", +"doseRecordedSkipped": "खुराक को छोड़ी गई के रूप में दर्ज किया गया।", +"doseRecordError": "यह खुराक दर्ज नहीं हो सकी। कृपया पुनः प्रयास करें।", +"doseHistoryError": "खुराक का इतिहास लोड नहीं हो सका। कृपया पुनः प्रयास करें।", +"recordingDose": "दर्ज किया जा रहा है...", +"taken": "ली गई", +"skipped": "छोड़ी गई", +"recordedAt": "दर्ज किया गया", +"scheduledDoseDate": "खुराक की निर्धारित तारीख", +"scheduledDateRequired": "कृपया इस खुराक की निर्धारित तारीख चुनें।", +"doseAlreadyRecorded": "इस तारीख को इस दवा की खुराक पहले ही दर्ज की जा चुकी है।", +"recordTakenForMedication": "{{medication}} को ली गई के रूप में चिह्नित करें", +"recordSkippedForMedication": "{{medication}} को छोड़ी गई के रूप में चिह्नित करें", +"viewDoseHistoryForMedication": "{{medication}} की खुराक का इतिहास देखें", +"doseActionsForMedication": "{{medication}} के लिए खुराक की कार्रवाइयाँ" } \ No newline at end of file diff --git a/frontend/public/locales/zh/modules.json b/frontend/public/locales/zh/modules.json index a291501..0889d42 100644 --- a/frontend/public/locales/zh/modules.json +++ b/frontend/public/locales/zh/modules.json @@ -52,5 +52,26 @@ "retry": "重试", "confirmArchive": "您确定要归档此处方吗?", "prescriptionRequiredFields": "请填写所有必填字段。", -"prescriptionValidationError": "请检查您填写的内容并更正无效字段。" +"prescriptionValidationError": "请检查您填写的内容并更正无效字段。", +"doseTracking": "用药依从性", +"recordTaken": "标记为已服用", +"recordSkipped": "标记为跳过", +"doseHistory": "服药记录", +"loadingDoseHistory": "正在加载服药记录...", +"noDoseHistory": "尚无服药记录。", +"doseRecordedTaken": "已记录为服用。", +"doseRecordedSkipped": "已记录为跳过。", +"doseRecordError": "无法记录此次服药,请重试。", +"doseHistoryError": "无法加载服药记录,请重试。", +"recordingDose": "正在记录...", +"taken": "已服用", +"skipped": "已跳过", +"recordedAt": "记录时间", +"scheduledDoseDate": "计划服药日期", +"scheduledDateRequired": "请选择此次服药的计划日期。", +"doseAlreadyRecorded": "该日期已记录过此药物的服药情况。", +"recordTakenForMedication": "将 {{medication}} 标记为已服用", +"recordSkippedForMedication": "将 {{medication}} 标记为跳过", +"viewDoseHistoryForMedication": "查看 {{medication}} 的服药记录", +"doseActionsForMedication": "{{medication}} 的服药操作" } \ No newline at end of file diff --git a/frontend/src/pages/modules/Prescription.jsx b/frontend/src/pages/modules/Prescription.jsx index 453f4e1..26b5de6 100644 --- a/frontend/src/pages/modules/Prescription.jsx +++ b/frontend/src/pages/modules/Prescription.jsx @@ -4,7 +4,7 @@ import PageShell from '../../components/PageShell'; import { prescriptionService } from '../../services/prescriptionService'; const Prescription = () => { - const { t } = useTranslation(['modules']); + const { t, i18n } = useTranslation(['modules']); const [prescriptions, setPrescriptions] = useState([]); const [loading, setLoading] = useState(true); @@ -27,6 +27,13 @@ const [editingSaving, setEditingSaving] = useState(false); const [archivingId, setArchivingId] = useState(null); const [message, setMessage] = useState(''); const [errorMessage, setErrorMessage] = useState(''); +const [doseHistory, setDoseHistory] = useState({}); +const [doseHistoryError, setDoseHistoryError] = useState({}); +const [doseHistoryLoading, setDoseHistoryLoading] = useState({}); +const [doseSavingId, setDoseSavingId] = useState(null); +const [doseHistoryVisible, setDoseHistoryVisible] = useState({}); +const [doseScheduledDates, setDoseScheduledDates] = useState({}); + const loadPrescriptions = async () => { try { @@ -34,7 +41,26 @@ const [errorMessage, setErrorMessage] = useState(''); setLoadError(false); const data = await prescriptionService.getAll(); - setPrescriptions(data); +setPrescriptions(data); + +const today = new Date(); +const year = today.getFullYear(); +const month = String(today.getMonth() + 1).padStart(2, '0'); +const day = String(today.getDate()).padStart(2, '0'); +const localToday = `${year}-${month}-${day}`; + +setDoseScheduledDates((previous) => { + const next = { ...previous }; + + data.forEach((prescription) => { + if (!next[prescription.id]) { + next[prescription.id] = localToday; + } + }); + + return next; +}); + } catch (error) { console.error('Failed to load prescriptions:', error); setLoadError(true); @@ -166,6 +192,97 @@ const [errorMessage, setErrorMessage] = useState(''); } }; + const handleRecordDose = async (prescriptionId, status) => { + const scheduledDate = doseScheduledDates[prescriptionId]; + if (!scheduledDate) { + setErrorMessage(t('modules:scheduledDateRequired')); + setMessage(''); + return; + } + + setDoseSavingId(prescriptionId); + setMessage(''); + setErrorMessage(''); + + try { + await prescriptionService.recordDose( + prescriptionId, + status, + scheduledDate + ); + + setMessage( + status === 'taken' + ? t('modules:doseRecordedTaken') + : t('modules:doseRecordedSkipped') + ); + + await handleLoadDoseHistory(prescriptionId); + } catch (error) { + console.error('Failed to record medication dose:', error); + setErrorMessage( + error?.response?.status === 409 + ? t('modules:doseAlreadyRecorded') + : t('modules:doseRecordError') + ); + } finally { + setDoseSavingId(null); + } +}; + + const handleLoadDoseHistory = async (prescriptionId) => { + setDoseHistoryLoading((previous) => ({ + ...previous, + [prescriptionId]: true, + })); + + setDoseHistoryError((previous) => ({ + ...previous, + [prescriptionId]: false, + })); + + try { + const history = + await prescriptionService.getDoseHistory(prescriptionId); + + setDoseHistory((previous) => ({ + ...previous, + [prescriptionId]: history, + })); + } catch (error) { + console.error('Failed to load dose history:', error); + + setDoseHistoryError((previous) => ({ + ...previous, + [prescriptionId]: true, + })); + } finally { + setDoseHistoryLoading((previous) => ({ + ...previous, + [prescriptionId]: false, + })); + } +}; + + const handleToggleDoseHistory = async (prescriptionId) => { + const isVisible = doseHistoryVisible[prescriptionId]; + + if (isVisible) { + setDoseHistoryVisible((previous) => ({ + ...previous, + [prescriptionId]: false, + })); + return; + } + + setDoseHistoryVisible((previous) => ({ + ...previous, + [prescriptionId]: true, + })); + + await handleLoadDoseHistory(prescriptionId); + }; + return (
@@ -439,6 +556,135 @@ const [errorMessage, setErrorMessage] = useState('');

)} + +
+

+ {t('modules:doseTracking')} +

+ +
+ + + + setDoseScheduledDates((previous) => ({ + ...previous, + [prescription.id]: event.target.value, + })) + } + className="border rounded-md px-3 py-2" + /> +
+ +
+ + + + + +
+ + {doseHistoryVisible[prescription.id] && ( +
+ {doseHistoryLoading[prescription.id] ? ( +

{t('modules:loadingDoseHistory')}

+) : doseHistoryError[prescription.id] ? ( +
+

+ {t('modules:doseHistoryError')} +

+ +
+) : Array.isArray(doseHistory[prescription.id]) && + doseHistory[prescription.id].length > 0 ? ( +
    + {doseHistory[prescription.id].map((dose) => ( +
  • + + {t(`modules:${dose.status}`)} + + {' — '} + {t('modules:recordedAt')}:{' '} + {new Intl.DateTimeFormat(i18n.resolvedLanguage || i18n.language, { + dateStyle: 'medium', + timeStyle: 'short', + }).format(new Date(dose.recorded_at))} +
  • + ))} +
+) : ( +

+ {t('modules:noDoseHistory')} +

+)} +
+ )} +
+
-
+ 🌐 +
+ @@ -311,7 +333,7 @@ exports[`Issue #160: Responsive, Accessibility, RTL, and Visual Regression Verif

- © 2026 appName. All rights reserved. + © 2026 TheHealthApp. All rights reserved.

diff --git a/frontend/src/test/test-utils.jsx b/frontend/src/test/test-utils.jsx index 5d39306..b391c64 100644 --- a/frontend/src/test/test-utils.jsx +++ b/frontend/src/test/test-utils.jsx @@ -7,6 +7,8 @@ import { initReactI18next } from 'react-i18next'; import { AuthContext } from '../context/AuthContext'; import framinghamEn from '../../public/locales/en/framingham.json'; import summaryEn from '../../public/locales/en/summary.json'; +import modulesEn from '../../public/locales/en/modules.json'; +import modulesAr from '../../public/locales/ar/modules.json'; // Create a test i18n instance with inline translations const testI18n = i18n.createInstance(); @@ -86,7 +88,17 @@ testI18n.use(initReactI18next).init({ selectYourGender: 'Select gender', male: 'Male', female: 'Female', + doseTracking: modulesEn.doseTracking, + noDoseHistory: modulesEn.noDoseHistory, + doseRecordedTaken: modulesEn.doseRecordedTaken, + doseRecordedSkipped: modulesEn.doseRecordedSkipped, + doseRecordError: modulesEn.doseRecordError, + doseHistoryError: modulesEn.doseHistoryError, + recordTakenForMedication: modulesEn.recordTakenForMedication, + recordSkippedForMedication: modulesEn.recordSkippedForMedication, + viewDoseHistoryForMedication: modulesEn.viewDoseHistoryForMedication, }, + errors: { generic: 'Something went wrong', boundaryTitle: 'Something went wrong', @@ -127,6 +139,7 @@ testI18n.use(initReactI18next).init({ retry: 'حاول مرة أخرى', goHome: 'الذهاب إلى الصفحة الرئيسية', }, + modules: modulesAr, }, }, });