diff --git a/.gitignore b/.gitignore index 84a171bb3..20c829d72 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ results.xml moduleName .env + +# vim +.swp diff --git a/.tx/config b/.tx/config index 61326b06a..e00dfb552 100644 --- a/.tx/config +++ b/.tx/config @@ -1,7 +1,7 @@ [main] host = https://www.transifex.com -[o:openmrs:p:openmrs-esm-patient-management:r:esm-active-visits-app] +[o:openmrs:p:openmrs3:r:esm-active-visits-app] file_filter = packages/esm-active-visits-app/translations/.json source_file = packages/esm-active-visits-app/translations/en.json source_lang = en @@ -10,7 +10,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-active-visits-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-appointments-app] +[o:openmrs:p:openmrs3:r:esm-appointments-app] file_filter = packages/esm-appointments-app/translations/.json source_file = packages/esm-appointments-app/translations/en.json source_lang = en @@ -19,7 +19,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-appointments-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-bed-management-app] +[o:openmrs:p:openmrs3:r:esm-bed-management-app] file_filter = packages/esm-bed-management-app/translations/.json source_file = packages/esm-bed-management-app/translations/en.json source_lang = en @@ -28,7 +28,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-bed-management-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-patient-list-management-app] +[o:openmrs:p:openmrs3:r:esm-patient-list-management-app] file_filter = packages/esm-patient-list-management-app/translations/.json source_file = packages/esm-patient-list-management-app/translations/en.json source_lang = en @@ -37,7 +37,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-patient-list-management-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-patient-registration-app] +[o:openmrs:p:openmrs3:r:esm-patient-registration-app] file_filter = packages/esm-patient-registration-app/translations/.json source_file = packages/esm-patient-registration-app/translations/en.json source_lang = en @@ -46,7 +46,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-patient-registration-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-patient-search-app] +[o:openmrs:p:openmrs3:r:esm-patient-search-app] file_filter = packages/esm-patient-search-app/translations/.json source_file = packages/esm-patient-search-app/translations/en.json source_lang = en @@ -55,7 +55,7 @@ replace_edited_strings = false keep_translations = false resource_name = esm-patient-search-app -[o:openmrs:p:openmrs-esm-patient-management:r:esm-service-queues-app] +[o:openmrs:p:openmrs3:r:esm-service-queues-app] file_filter = packages/esm-service-queues-app/translations/.json source_file = packages/esm-service-queues-app/translations/en.json source_lang = en diff --git a/__mocks__/emr-configuration.mock.ts b/__mocks__/emr-configuration.mock.ts index 3eff29364..c1e8dddfb 100644 --- a/__mocks__/emr-configuration.mock.ts +++ b/__mocks__/emr-configuration.mock.ts @@ -3,4 +3,6 @@ export const emrConfigurationMock = { consultFreeTextCommentsConcept: { uuid: '162169AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }, visitNoteEncounterType: { uuid: 'd7151f82-c1f3-4152-a605-2f9ea7414a79' }, admissionEncounterType: { uuid: 'e8151f82-c1f3-4152-a605-2f9ea7414a79' }, + inpatientNoteEncounterType: { uuid: 'f8151f82-c1f3-4152-a605-2f9ea7414a79' }, + transferRequestEncounterType: { uuid: 'g8151f82-c1f3-4152-a605-2f9ea7414a79' }, }; diff --git a/e2e/commands/cohort-operations.ts b/e2e/commands/cohort-operations.ts index 0c672510e..496038970 100644 --- a/e2e/commands/cohort-operations.ts +++ b/e2e/commands/cohort-operations.ts @@ -1,5 +1,5 @@ -import { APIRequestContext, expect } from '@playwright/test'; -import { Patient } from './patient-operations'; +import { type APIRequestContext, expect } from '@playwright/test'; +import { type Patient } from './patient-operations'; export interface CohortType { uuid: string; diff --git a/packages/esm-active-visits-app/translations/es.json b/packages/esm-active-visits-app/translations/es.json index 5e7899bb1..08ec3ff89 100644 --- a/packages/esm-active-visits-app/translations/es.json +++ b/packages/esm-active-visits-app/translations/es.json @@ -16,21 +16,21 @@ "noActiveVisitsForLocation": "No hay visitas activas para esta ubicación.", "noDiagnosesFound": "No se encontraron diagnósticos", "noEncountersFound": "No se encontraron encuentros", - "noMedicationsFound": "No se encontraron medicaciones", - "noNotesToShowForPatient": "No hay notas para mostrar de este paciente", + "noMedicationsFound": "No se encontraron medicamentos", + "noNotesToShowForPatient": "No hay notas para mostrar para este paciente", "noObservationsFound": "No se encontraron observaciones", "notes": "Notas", "noVisitsToDisplay": "No hay visitas para mostrar", - "orderDurationAndUnit": "para {{duration}} {{durationUnit}}", + "orderDurationAndUnit": "por {{duration}} {{durationUnit}}", "orderIndefiniteDuration": "Duración indefinida", - "patients": "Patients", + "patients": "Pacientes", "provider": "Proveedor", "quantity": "Cantidad", - "refills": "Recargas", + "refills": "Reposiciones", "tests": "Pruebas", "thereIsNoInformationToDisplayHere": "No hay información para mostrar aquí", - "time": "Tiempo", - "totalVisits": "Total Visits Today", + "time": "Hora", + "totalVisits": "Total de Visitas Hoy", "visitStartTime": "Tiempo de Visita", "visitSummary": "Resumen de Visita", "visitType": "Tipo de Visita" diff --git a/packages/esm-appointments-app/translations/es.json b/packages/esm-appointments-app/translations/es.json index 784d82588..d65589c12 100644 --- a/packages/esm-appointments-app/translations/es.json +++ b/packages/esm-appointments-app/translations/es.json @@ -8,34 +8,34 @@ "appointmentCancelled": "Cita cancelada", "appointmentCancelledSuccessfully": "Cita cancelada con éxito", "appointmentColor": "Color de la cita", - "appointmentConflict": "Appointment conflict", - "appointmentEdited": "Appointment edited", - "appointmentEditError": "Error editing appointment", - "appointmentEnded": "Appointment ended", - "appointmentEndedAndVisitClosedSuccessfully": "Appointment successfully ended and visit successfully closed.", - "appointmentEndedButVisitNotClosedError": "Appointment ended, but error closing visit", - "appointmentEndedSuccessfully": "Appointment successfully ended.", - "appointmentEndError": "Error ending appointment", + "appointmentConflict": "Conflicto en las citas", + "appointmentEdited": "Cita editada", + "appointmentEditError": "Error al editar cita", + "appointmentEnded": "Cita finalizada", + "appointmentEndedAndVisitClosedSuccessfully": "Cita finalizada con éxito y visita cerrada con éxito ", + "appointmentEndedButVisitNotClosedError": "Cita finalizada, pero hubo un error al cerrar la visita", + "appointmentEndedSuccessfully": "Cita finalizada con éxito ", + "appointmentEndError": "Error al finalizar cita", "appointmentFormError": "Error al programar la cita", "appointmentHistory": "Historial de citas", "appointmentMetrics": "Métricas de citas", "appointmentMetricsLoadError": "", - "appointmentNoteLabel": "Write an additional note", - "appointmentNotePlaceholder": "Write any additional points here", + "appointmentNoteLabel": "Escriba una nota adicional", + "appointmentNotePlaceholder": "Escriba algún punto adicional aquí", "appointmentNotes": "Notas de cita", "appointmentNowVisible": "Ahora es visible en la página de Citas", "appointments": "Citas", - "Appointments": "Appointments", - "appointments_lower": "appointments", - "appointmentsCalendar": "Appointments calendar", + "Appointments": "Citas", + "appointments_lower": "citas", + "appointmentsCalendar": "Calendario de citas", "appointmentScheduled": "Cita programada", "appointmentService": "Servicio de citas", "appointmentServiceCreate": "Servicio de citas creado exitosamente", "appointmentServiceName": "Nombre del servicio de citas", - "appointmentsScheduledForToday": "appointments scheduled for today", - "appointmentsTable": "Appointments table", + "appointmentsScheduledForToday": "citas agendadas para hoy", + "appointmentsTable": "Tabla de citas", "appointmentStatus": "Appointment status", - "appointmentToFulfill": "Select appointment to fulfill", + "appointmentToFulfill": "Seleccione cita para completar", "appointmentType": "Tipo de cita", "appointmentType_title": "Tipo de cita", "back": "Atrás", @@ -47,113 +47,113 @@ "cancelled": "Cancelada", "checkedIn": "Checked in", "checkedOut": "Completada", - "checkFilters": "Check the filters above", + "checkFilters": "Compruebe los filtros anteriores", "checkIn": "Registrar", "checkOut": "Finalizar", - "chooseAppointmentType": "Choose appointment type", - "chooseLocation": "Choose a location", - "chooseProvider": "Choose a provider", + "chooseAppointmentType": "Escoja un tipo de cita", + "chooseLocation": "Escoja una ubicación", + "chooseProvider": "Escoja un proveedor", "chooseService": "Seleccionar servicio", "completed": "Completado", - "Contact": "Contact {{index}}", - "countMore_one": "{{count}} more", - "countMore_other": "{{count}} more", + "Contact": "Contacto {{index}}", + "countMore_one": "{{count}} más", + "countMore_other": "{{count}} más", "createAppointmentService": "Crear servicios de citas", "createNewAppointment": "Crear nueva cita", "date": "Fecha", "date&Time": "Date & time", - "dateAppointmentIssuedCannotBeAfterAppointmentDate": "Date appointment issued cannot be after the appointment date", - "dateOfBirth": "Date of birth", - "dateScheduled": "Date appointment issued", - "dateScheduledDetail": "Date appointment issued", - "dateTime": "Date & Time", - "day": "Day", - "daysOfWeek": "Days of the week", + "dateAppointmentIssuedCannotBeAfterAppointmentDate": "La fecha en que se emitió la cita no puede ser posterior a la fecha de la cita", + "dateOfBirth": "Fecha de nacimiento", + "dateScheduled": "Fecha de emisión de la cita", + "dateScheduledDetail": "Fecha de emisión de la cita", + "dateTime": "Fecha y hora", + "day": "Día", + "daysOfWeek": "Días de la semana", "discard": "Descartar", "download": "Descargar", - "durationErrorMessage": "Duration should be greater than zero", - "durationInMinutes": "Duration (minutes)", + "durationErrorMessage": "La duración debería ser mayor a cero", + "durationInMinutes": "Duración (minutos)", "durationMins": "Duración (min)", "edit": "Editar", "editAppointment": "Editar cita", "editAppointments": "Editar cita", - "emptyStateText": "There are no <1>{{displayText}} to display", + "emptyStateText": "No hay <1>{{displayText}} para mostrar", "encounters": "Encuentros", "encounterType": "Tipo de encuentro", - "endAppointmentAndVisitConfirmationMessage": "Checking the patient out will mark the appointment as complete, and close out the active visit for this patient.", - "endAppointmentConfirmation": "Are you sure you want to check the patient out for this appointment?", - "endAppointmentConfirmationMessage": "Checking the patient out will mark the appointment as complete.", - "endDate": "End date", + "endAppointmentAndVisitConfirmationMessage": "Marcar al paciente como dado de alta indicará que la cita está completa y cerrará la visita activa para este paciente.", + "endAppointmentConfirmation": "¿Está seguro de que desea marcar al paciente como dado de alta para esta cita?", + "endAppointmentConfirmationMessage": "Marcar al paciente como dado de alta indicará que la cita está completa.", + "endDate": "Fecha de finalización", "endTime": "Hora de finalización", "errorCreatingAppointmentService": "Error creating appointment service", "expected": "Expected", - "filterTable": "Filter table", + "filterTable": "Filtrar tabla", "gender": "Género", - "highestServiceVolume": "Highest volume service: {{time}}", + "highestServiceVolume": "Servicio de mayor volumen: {{time}}", "identifier": "Identificador", - "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", - "isRecurringAppointment": "Is this a recurring appointment?", - "itemsPerPage": "Items per page", + "invalidNumber": "El número no es válido", + "invalidTime": "Hora inválida", + "isRecurringAppointment": "¿Esta es una cita recurrente?", + "itemsPerPage": "Elementos por página", "loading": "Loading", "location": "Ubicación", "medications": "Medicamentos", "missed": "Perdida", - "next": "Next", - "nextMonth": "Next month", - "nextPage": "Next page", + "next": "Siguiente", + "nextMonth": "Siguiente mes", + "nextPage": "Siguiente página", "no": "No", - "noAppointmentsToDisplay": "No appointments to display", - "noContent": "No Content", + "noAppointmentsToDisplay": "No hay citas para mostrar", + "noContent": "No hay contenido", "noCurrentAppointments": "No hay citas programadas que mostrar para hoy para este paciente", "noEncountersFound": "No se encontraron encuentros", "noPastAppointments": "No hay citas anteriores para mostrar para este paciente", "noPreviousVisitFound": "No se encontraron visitas anteriores", "notArrived": "No ha llegado", - "note": "Note", + "note": "Nota", "notes": "Notas", "noUpcomingAppointments": "No hay próximas citas para mostrar para este paciente", "noUpcomingAppointmentsForPatient": "No hay próximas citas para mostrar para este paciente", - "pageNumber": "Page number", + "pageNumber": "Número de página", "past": "Anterior", "patientDetails": "Detalles del paciente", - "patientDoubleBooking": "Patient already booked for an appointment at this time", + "patientDoubleBooking": "El paciente ya agendó una cita para esta hora", "patientName": "Nombre del paciente", "patients": "Pacientes", - "period": "Period", - "prev": "Prev", - "previousMonth": "Previous month", - "previousPage": "Previous page", + "period": "Periodo", + "prev": "Ant", + "previousMonth": "Mes anterior", + "previousPage": "Página anterior", "provider": "Proveedor", "providers": "Proveedores", - "providersBooked": "Providers booked: {{time}}", - "recurringAppointment": "Recurring Appointment", - "recurringAppointmentShouldHaveEndDate": "A recurring appointment should have an end date", - "repeatEvery": "Repeat every", + "providersBooked": "Proveedores agendados: {{time}}", + "recurringAppointment": "Cita recurrente", + "recurringAppointmentShouldHaveEndDate": "Una cita recurrente debería tener una fecha de finalización", + "repeatEvery": "Repetir cada", "save": "Guardar", - "saveAndClose": "Save and close", + "saveAndClose": "Guardar y cerrar", "scheduled": "Programada", "scheduledAppointments": "Citas programadas", - "scheduledForToday": "Scheduled For Today", - "selectALocation": "Select a location", + "scheduledForToday": "Agendada Para Hoy", + "selectALocation": "Seleccionar una ubicación", "selectAppointmentStatus": "Seleccionar estado", "selectAppointmentType": "Seleccionar tipo de cita", "selectLocation": "Seleccionar ubicación", "selectOption": "Seleccionar una opción", - "selectProvider": "Select a provider", + "selectProvider": "Seleccionar un proveedor", "selectService": "Seleccionar servicio", "selectServiceType": "Seleccionar tipo de servicio", "service": "Servicio", "serviceName": "Nombre del servicio", "serviceType": "Tipo de servicio", - "serviceUnavailable": "Appointment time is outside of service hours", - "startDate": "Start date", + "serviceUnavailable": "La hora de la cita está fuera de las horas de servicio", + "startDate": "Fecha de inicio", "startTime": "Hora de inicio", - "status": "Status", + "status": "Estado", "time": "Tiempo", "today": "Hoy", - "todays": "Today's", - "type": "Type", + "todays": "Hoy día es", + "type": "Tip", "unscheduled": "No programada", "unscheduledAppointments": "Citas no programadas", "unscheduledAppointments_lower": "citas no programadas", @@ -161,6 +161,6 @@ "upcomingAppointments": "Próximas citas", "view": "Ver", "vitals": "Signos vitales", - "week": "Week", + "week": "Semana", "yes": "Sí" } diff --git a/packages/esm-appointments-app/translations/fr.json b/packages/esm-appointments-app/translations/fr.json index 466505034..d30f5b8a4 100644 --- a/packages/esm-appointments-app/translations/fr.json +++ b/packages/esm-appointments-app/translations/fr.json @@ -58,20 +58,20 @@ "Contact": "Contact {{index}}", "countMore_one": "{{count}} de plus", "countMore_other": "{{count}} de plus", - "createAppointmentService": "Create appointment services", + "createAppointmentService": "Créer des services de rendez-vous", "createNewAppointment": "Créer un nouveau rendez-vous", "date": "Date", "date&Time": "Date et heure", - "dateAppointmentIssuedCannotBeAfterAppointmentDate": "Date appointment issued cannot be after the appointment date", + "dateAppointmentIssuedCannotBeAfterAppointmentDate": "La date du rendez-vous ne peut pas être postérieure à la date du rendez-vous", "dateOfBirth": "Date de naissance", - "dateScheduled": "Date appointment issued", - "dateScheduledDetail": "Date appointment issued", + "dateScheduled": "Date de rendez vous émise", + "dateScheduledDetail": "Date de rendez vous émise", "dateTime": "Date et heure", "day": "Jour", "daysOfWeek": "Jours de la semaine", "discard": "Abandonner", "download": "Télécharger", - "durationErrorMessage": "Duration should be greater than zero", + "durationErrorMessage": "La durée doit être supérieure à zéro", "durationInMinutes": "Durée (minutes)", "durationMins": "Durée min", "edit": "Modifier", @@ -80,12 +80,12 @@ "emptyStateText": "Pas de <1>{{displayText}} à afficher", "encounters": "Rencontres", "encounterType": "Type de rencontre", - "endAppointmentAndVisitConfirmationMessage": "Checking the patient out will mark the appointment as complete, and close out the active visit for this patient.", - "endAppointmentConfirmation": "Are you sure you want to check the patient out for this appointment?", - "endAppointmentConfirmationMessage": "Checking the patient out will mark the appointment as complete.", + "endAppointmentAndVisitConfirmationMessage": " La sortie du patient marquera le rendez-vous comme terminé et clôturera la visite active pour ce patient.", + "endAppointmentConfirmation": " Etes-vous sûr de vouloir enregistrer le patient pour ce rendez-vous ?", + "endAppointmentConfirmationMessage": " La sortie du patient marquera le rendez-vous comme terminé.", "endDate": "Date de fin", "endTime": "Heure de fin", - "errorCreatingAppointmentService": "Error creating appointment service", + "errorCreatingAppointmentService": " Erreur lors de la création du service de rendez-vous", "expected": "Prévus:", "filterTable": "Filtrer le tableau", "gender": "Genre", @@ -117,10 +117,10 @@ "pageNumber": "Numéro de page", "past": "Passé", "patientDetails": "Détails du patient", - "patientDoubleBooking": "Patient already booked for an appointment at this time", + "patientDoubleBooking": "Le patient a déjà un rendez vous à cette date", "patientName": "Nom du patient", "patients": "Patients", - "period": "Period", + "period": "Période", "prev": "Préc", "previousMonth": "Mois précédent", "previousPage": "Page précédente", @@ -146,7 +146,7 @@ "service": "Service", "serviceName": "Nom du service", "serviceType": "Type de service", - "serviceUnavailable": "Appointment time is outside of service hours", + "serviceUnavailable": "L'heure du rendez-vous est en dehors des heures de service", "startDate": "Date de début", "startTime": "Heure de début", "status": "Statut", diff --git a/packages/esm-bed-management-app/translations/es.json b/packages/esm-bed-management-app/translations/es.json index dba1d2c0e..3a191820d 100644 --- a/packages/esm-bed-management-app/translations/es.json +++ b/packages/esm-bed-management-app/translations/es.json @@ -1,51 +1,51 @@ { - "actions": "Actions", - "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", - "allocationStatus": "Allocated", - "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", - "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", - "beds": "Beds", - "bedTag": "Bed Tag Name", + "actions": "Acciones", + "addBed": "Añadir cama", + "addBedTag": "Crear Etiqueta de Cama", + "addBedtype": "Añadir Tipo de Cama", + "addBedType": "Crear Tipo de Cama", + "allocationStatus": "Asignado", + "bedId": "ID de la Cama", + "bedIdPlaceholder": "p. ej. BMW-201", + "bedLocation": "Ubicación", + "bedManagement": "Administración de Camas", + "bedName": "Nombre de la Cama", + "beds": "Camas", + "bedTag": "Nombre de la Etiqueta de la Cama", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedType": "Añadir Tipo de Cama", "bedTypePlaceholder": "", - "checkFilters": "Check the filters above", - "chooseBedtype": "Choose a bed type", - "chooseOccupiedStatus": "Choose occupied status", - "createNewBed": "Create a new bed", - "description": "Description", - "displayName": "Display Name", + "checkFilters": "Compruebe los filtros anteriores", + "chooseBedtype": "Elija un tipo de cama", + "chooseOccupiedStatus": "Elija el estado ocupado", + "createNewBed": "Crear una nueva cama", + "description": "Descripción", + "displayName": "Mostrar Nombre", "displayNamePlaceholder": "", - "editBed": "Edit Tag", - "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBed": "Editar etiqueta", + "editBedTag": "Editar Etiqueta de la Cama", + "editBedType": "Editar Tipo de Cama", "error": "Error", - "errorCreatingForm": "Error creating bed", - "errorFetchingbedInformation": "Error fetching bed information", - "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", + "errorCreatingForm": "Error al crear cama", + "errorFetchingbedInformation": "Error al obtener información de la cama", + "filterByOccupancyStatus": "Filtrar por estado de ocupación", + "formCreated": "Añadir Tipo de Cama", + "formSaved": "Tipo de Cama", "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", - "name": "Name", + "location": "Ubicaciones", + "manageBeds": "Administrar Camas", + "name": "Nombre", "no": "No", - "No data": "No data to display", - "occupancyStatus": "Occupied", - "or": "or", + "No data": "No hay datos para mostrar", + "occupancyStatus": "Ocupada", + "or": "o", "pleaseFillField": "", - "required": "Required", - "save": "Save", - "saveSuccessMessage": "was saved successfully.", - "selectBedLocation": "Select a bed location", - "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", - "yes": "Yes" + "required": "Obligatorio", + "save": "Guardar", + "saveSuccessMessage": "fue guardado exitosamente.", + "selectBedLocation": "Seleccione una ubicación para la cama", + "viewBeds": "Ver camas", + "wardAllocation": "Asignación de sala", + "yes": "Sí" } diff --git a/packages/esm-bed-management-app/translations/fr.json b/packages/esm-bed-management-app/translations/fr.json index c17698032..2ed9929f3 100644 --- a/packages/esm-bed-management-app/translations/fr.json +++ b/packages/esm-bed-management-app/translations/fr.json @@ -1,11 +1,11 @@ { "actions": "Actions", "addBed": "Ajouter un lit", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", - "allocationStatus": "Allocated", - "bedId": "Bed ID", + "addBedTag": "Créer une étiquette de lit", + "addBedtype": "Ajouter type de lit", + "addBedType": "Créer Type de lit", + "allocationStatus": "Attribué", + "bedId": "Identifiant du Lit", "bedIdPlaceholder": "exemple : BMW-201", "bedLocation": "Emplacement", "bedManagement": "Gestion du lit", @@ -17,24 +17,24 @@ "bedTypePlaceholder": "", "checkFilters": "Vérifiez les filtres ci-dessus", "chooseBedtype": "Choisir un type de lit", - "chooseOccupiedStatus": "Choose occupied status", + "chooseOccupiedStatus": " Choisissez le statut occupé", "createNewBed": "Créer un nouveau lit", "description": "Description", "displayName": "Display Name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", - "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBed": "Afficher un tag", + "editBedTag": "Afficher un Tag de lit", + "editBedType": "Afficher un type de lit", "error": "Erreur", - "errorCreatingForm": "Error creating bed", - "errorFetchingbedInformation": "Error fetching bed information", - "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", + "errorCreatingForm": "Erreur lors de la création du lit", + "errorFetchingbedInformation": " Erreur lors de la récupération des informations sur le lit", + "filterByOccupancyStatus": "Filtrer par statut d'occupation", + "formCreated": "Ajouter un type de lit", + "formSaved": "Type de lit", "headerTitle": "", "ids": "Identifiant", "location": "Emplacements", - "manageBeds": "Manage Beds", + "manageBeds": "Gestion des lits", "name": "Nom", "no": "Non", "No data": "Pas de données à afficher", diff --git a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx index 5017deea3..f30042b81 100644 --- a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.component.tsx @@ -22,6 +22,7 @@ const AddPatientToPatientListMenuItem: React.FC dispose(), patientUuid, + size: 'sm', }); closeOverflowMenu(); }, [patientUuid]); diff --git a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx index 7334b4fe4..e4fe52b3d 100644 --- a/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient-to-patient-list-menu-item.test.tsx @@ -26,6 +26,10 @@ describe('AddPatientToPatientListMenuItem', () => { await user.click(button); - expect(mockShowModal).toHaveBeenCalledWith('add-patient-to-patient-list-modal', expect.any(Object)); + expect(mockShowModal).toHaveBeenCalledWith('add-patient-to-patient-list-modal', { + closeModal: expect.any(Function), + size: 'sm', + patientUuid: mockPatient.uuid, + }); }); }); diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx b/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx index fc040c167..6c6b5b345 100644 --- a/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx @@ -1,21 +1,11 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react'; import classNames from 'classnames'; +import { mutate } from 'swr'; import { useTranslation } from 'react-i18next'; -import type { TFunction } from 'i18next'; -import useSWR from 'swr'; -import { Button, Checkbox, Pagination, Search, CheckboxSkeleton } from '@carbon/react'; -import { - getDynamicOfflineDataEntries, - putDynamicOfflineData, - syncDynamicOfflineData, - showSnackbar, - toOmrsIsoString, - usePagination, - navigate, - useConfig, -} from '@openmrs/esm-framework'; -import { addPatientToList, getAllPatientLists, getPatientListIdsForPatient } from '../api/api-remote'; -import { type ConfigSchema } from '../config-schema'; +import { Button, Checkbox, CheckboxSkeleton, Pagination, Search, Tile } from '@carbon/react'; +import { navigate, restBaseUrl, showSnackbar, usePagination } from '@openmrs/esm-framework'; +import { type AddablePatientListViewModel } from '../api/types'; +import { useAddablePatientLists } from '../api/api-remote'; import styles from './add-patient.scss'; interface AddPatientProps { @@ -23,26 +13,19 @@ interface AddPatientProps { patientUuid: string; } -interface AddablePatientListViewModel { - addPatient(): Promise; - displayName: string; - checked?: boolean; - id: string; -} - const AddPatient: React.FC = ({ closeModal, patientUuid }) => { const { t } = useTranslation(); + const { data, isLoading } = useAddablePatientLists(patientUuid); const [searchValue, setSearchValue] = useState(''); const [selected, setSelected] = useState>([]); - const { data, isLoading } = useAddablePatientLists(patientUuid); - const handleCreateNewList = () => { + const handleCreateNewList = useCallback(() => { navigate({ to: window.getOpenmrsSpaBase() + 'home/patient-lists?new_cohort=true', }); closeModal(); - }; + }, [closeModal]); const handleSelectionChanged = useCallback((patientListId: string, listSelected: boolean) => { if (listSelected) { @@ -52,34 +35,39 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { } }, []); - const handleSubmit = useCallback(() => { - for (const selectedId of selected) { - const patientList = data.find((list) => list.id === selectedId); - if (!patientList) { - continue; - } + const mutateCohortMembers = useCallback(() => { + const key = `${restBaseUrl}/cohortm/cohortmember?patient=${patientUuid}&v=custom:(uuid,patient:ref,cohort:(uuid,name,startDate,endDate))`; - patientList - .addPatient() - .then(() => - showSnackbar({ - title: t('successfullyAdded', 'Successfully added'), - kind: 'success', - isLowContrast: true, - subtitle: `${t('successAddPatientToList', 'Patient added to list')}: ${patientList.displayName}`, - }), - ) - .catch(() => - showSnackbar({ - title: t('error', 'Error'), - kind: 'error', - subtitle: `${t('errorAddPatientToList', 'Patient not added to list')}: ${patientList.displayName}`, - }), - ); - } + return mutate((k) => typeof k === 'string' && k === key); + }, []); - closeModal(); - }, [data, selected, closeModal, t]); + const handleSubmit = useCallback(() => { + Promise.all( + selected.map((selectedId) => { + const patientList = data.find((list) => list.id === selectedId); + if (!patientList) return Promise.resolve(); + + return patientList + .addPatient() + .then(async () => { + await mutateCohortMembers(); + showSnackbar({ + title: t('successfullyAdded', 'Successfully added'), + kind: 'success', + isLowContrast: true, + subtitle: `${t('successAddPatientToList', 'Patient added to list')}: ${patientList.displayName}`, + }); + }) + .catch(() => { + showSnackbar({ + title: t('error', 'Error'), + kind: 'error', + subtitle: `${t('errorAddPatientToList', 'Patient not added to list')}: ${patientList.displayName}`, + }); + }); + }), + ).finally(closeModal); + }, [data, selected, closeModal, t, patientUuid]); const searchResults = useMemo(() => { if (!data) { @@ -94,7 +82,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { return data; }, [searchValue, data]); - const { results, goTo, currentPage, paginated } = usePagination(searchResults, 5); + const { results, goTo, currentPage, paginated } = usePagination(searchResults, 5); useEffect(() => { if (currentPage !== 1) { @@ -105,41 +93,54 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { return (
-

{t('addPatientToList', 'Add patient to list')}

-

+

{t('addPatientToList', 'Add patient to list')}

+

{t('searchForAListToAddThisPatientTo', 'Search for a list to add this patient to.')}

-
- { - setSearchValue(target.value); - }} - value={searchValue} - /> -
+ { + setSearchValue(target.value); + }} + value={searchValue} + />
-

{t('patientLists', 'Patient lists')}

{!isLoading && results ? ( results.length > 0 ? ( - results.map((patientList) => ( -
- handleSelectionChanged(patientList.id, e.target.checked)} - checked={patientList.checked || selected.includes(patientList.id)} - disabled={patientList.checked} - labelText={patientList.displayName} - id={patientList.id} - /> -
- )) + <> +

{t('patientLists', 'Patient lists')}

+ {results.map((patientList) => ( +
+ handleSelectionChanged(patientList.id, e.target.checked)} + checked={patientList.checked || selected.includes(patientList.id)} + disabled={patientList.checked} + labelText={patientList.displayName} + id={patientList.id} + /> +
+ ))} + ) : ( -

{t('noPatientListFound', 'No patient list found')}

+
+ +
+

{t('noMatchingListsFound', 'No matching lists found')}

+

+ {t('trySearchingForADifferentList', 'Try searching for a different list')} + — or — + +

+
+
+
) ) : ( <> @@ -180,7 +181,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => {
)}
-
@@ -196,76 +197,4 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { ); }; -// This entire modal is a little bit special since it not only displays the "real" patient lists (i.e. data from -// the cohorts/backend), but also a fake patient list which doesn't really exist in the backend: -// The offline patient list. -// When a patient is added to the offline list, that patient should become available offline, i.e. -// a dynamic offline data entry must be created. -// This is why the following abstracts away the differences between the real and the fake patient lists. -// The component doesn't really care about which is which - the only thing that matters is that the -// data can be fetched and that there is an "add patient" function. - -export function useAddablePatientLists(patientUuid: string) { - const { t } = useTranslation(); - const config = useConfig() as ConfigSchema; - return useSWR(['addablePatientLists', patientUuid], async () => { - // Using Promise.allSettled instead of Promise.all here because some distros might not have the - // cohort module installed, leading to the real patient list call failing. - // In that case we still want to show fake lists and *not* error out here. - const [fakeLists, realLists] = await Promise.allSettled([ - findFakePatientListsWithoutPatient(patientUuid, t), - findRealPatientListsWithoutPatient(patientUuid, config.myListCohortTypeUUID, config.systemListCohortTypeUUID), - ]); - - return [ - ...(fakeLists.status === 'fulfilled' ? fakeLists.value : []), - ...(realLists.status === 'fulfilled' ? realLists.value : []), - ]; - }); -} - -async function findRealPatientListsWithoutPatient( - patientUuid: string, - myListCohortUUID, - systemListCohortType, -): Promise> { - const [allLists, listsIdsOfThisPatient] = await Promise.all([ - getAllPatientLists({}, myListCohortUUID, systemListCohortType), - getPatientListIdsForPatient(patientUuid), - ]); - - return allLists.map((list) => ({ - id: list.id, - displayName: list.display, - checked: listsIdsOfThisPatient.includes(list.id), - async addPatient() { - await addPatientToList({ - cohort: list.id, - patient: patientUuid, - startDate: toOmrsIsoString(new Date()), - }); - }, - })); -} - -async function findFakePatientListsWithoutPatient( - patientUuid: string, - t: TFunction, -): Promise> { - const offlinePatients = await getDynamicOfflineDataEntries('patient'); - const isPatientOnOfflineList = offlinePatients.some((x) => x.identifier === patientUuid); - return isPatientOnOfflineList - ? [] - : [ - { - id: 'fake-offline-patient-list', - displayName: t('offlinePatients', 'Offline patients'), - async addPatient() { - await putDynamicOfflineData('patient', patientUuid); - await syncDynamicOfflineData('patient', patientUuid); - }, - }, - ]; -} - export default AddPatient; diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss b/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss index 6d985f25e..0b4fdaab5 100644 --- a/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.scss @@ -1,9 +1,11 @@ +@use '@carbon/colors'; @use '@carbon/layout'; @use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; .modalContent { width: 100%; - background-color: #f4f4f4; + background-color: $ui-02; } .modalHeader { @@ -18,6 +20,16 @@ padding-bottom: 0.875rem; } +.header { + @include type.type-style('heading-03'); + margin: layout.$spacing-05 0; +} + +.subheader { + @include type.type-style('body-01'); + margin: layout.$spacing-05 0; +} + .pagination { width: 100%; overflow: hidden; @@ -30,6 +42,11 @@ position: relative; } +.search { + background-color: colors.$white; + margin-bottom: 0.875rem; +} + .itemsCountDisplay { position: absolute; top: 0; @@ -37,7 +54,7 @@ height: layout.$spacing-09; display: flex; align-items: center; - color: #525252; + color: colors.$gray-70; } .pagination > div:first-child { @@ -47,8 +64,22 @@ .buttonSet { display: flex; - justify-content: space-between; - align-items: flex-start; + align-items: center; + justify-content: center; + width: 100%; + + .createButton { + flex: 2; + } + + div { + flex: 1; + display: flex; + + > button { + flex: 1; + } + } } .productiveHeading03 { @@ -58,3 +89,41 @@ .bodyLong01 { @include type.type-style('body-01'); } + +.tileContainer { + background-color: $ui-02; + padding: layout.$spacing-09 0; + margin-bottom: layout.$spacing-05; +} + +.tile { + margin: auto; + width: fit-content; +} + +.tileContent { + display: flex; + flex-direction: column; + align-items: center; +} + +.content { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: layout.$spacing-03; +} + +.helper { + @include type.type-style('body-compact-01'); + color: $text-02; +} + +.actionText { + @include type.type-style('body-01'); + color: $text-02; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: layout.$spacing-05; +} diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.test.tsx b/packages/esm-patient-list-management-app/src/add-patient/add-patient.test.tsx new file mode 100644 index 000000000..c9bcf398f --- /dev/null +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.test.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; +import { navigate } from '@openmrs/esm-framework'; +import { useAddablePatientLists } from '../api/api-remote'; +import { mockPatient } from '__mocks__'; +import AddPatient from './add-patient.component'; + +const mockNavigate = jest.mocked(navigate); +const mockUseAddablePatientLists = jest.mocked(useAddablePatientLists); +const mockCloseModal = jest.fn(); + +jest.mock('../api/api-remote', () => ({ + useAddablePatientLists: jest.fn(), +})); + +describe('AddPatient', () => { + beforeEach(() => { + mockUseAddablePatientLists.mockReturnValue({ + data: [ + { id: 'list1', displayName: 'List 1', addPatient: jest.fn() }, + { id: 'list2', displayName: 'List 2', addPatient: jest.fn() }, + ], + isLoading: false, + error: null, + mutate: jest.fn(), + isValidating: false, + }); + }); + + it('renders the Add Patient to List modal', () => { + render(); + + expect(screen.getByRole('heading', { name: /add patient to list/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /search for a list to add this patient to/i })).toBeInTheDocument(); + expect(screen.getByRole('searchbox', { name: /search for a list/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /clear search input/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /create new patient list/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /add to list/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: /list 1/i })).toBeInTheDocument(); + expect(screen.getByRole('checkbox', { name: /list 2/i })).toBeInTheDocument(); + }); + + it('allows selecting and deselecting patient lists', async () => { + const user = userEvent.setup(); + render(); + + const checkbox1 = screen.getByLabelText('List 1'); + const checkbox2 = screen.getByLabelText('List 2'); + + await user.click(checkbox1); + expect(checkbox1).toBeChecked(); + + await user.click(checkbox2); + expect(checkbox2).toBeChecked(); + + await user.click(checkbox1); + expect(checkbox1).not.toBeChecked(); + }); + + it('filters patient lists based on search input', async () => { + const user = userEvent.setup(); + render(); + + const searchInput = screen.getByRole('searchbox', { name: /search for a list/i }); + await user.type(searchInput, 'Bananarama'); + expect(screen.getByText(/no matching lists found/i)).toBeInTheDocument(); + expect(screen.getByText(/try searching for a different list/i)).toBeInTheDocument(); + expect(screen.getAllByRole('button', { name: /create new patient list/i })).toHaveLength(2); + + await user.clear(searchInput); + await user.type(searchInput, 'List 1'); + + expect(screen.getByText('List 1')).toBeInTheDocument(); + expect(screen.queryByText('List 2')).not.toBeInTheDocument(); + }); + + it('clicking the "Create new list" button opens the create list form', async () => { + const user = userEvent.setup(); + render(); + + const createNewListButton = screen.getByRole('button', { name: /create new patient list/i }); + await user.click(createNewListButton); + + expect(mockNavigate).toHaveBeenCalledWith({ + to: window.getOpenmrsSpaBase() + 'home/patient-lists?new_cohort=true', + }); + }); + + it('clicking the "Cancel" button closes the modal', async () => { + const user = userEvent.setup(); + render(); + + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + await user.click(cancelButton); + + expect(mockCloseModal).toHaveBeenCalled(); + }); +}); diff --git a/packages/esm-patient-list-management-app/src/api/api-remote.ts b/packages/esm-patient-list-management-app/src/api/api-remote.ts index 5e36dcf8c..dd75a8b1b 100644 --- a/packages/esm-patient-list-management-app/src/api/api-remote.ts +++ b/packages/esm-patient-list-management-app/src/api/api-remote.ts @@ -1,6 +1,22 @@ -import { type LoggedInUser, openmrsFetch, refetchCurrentUser, restBaseUrl, fhirBaseUrl } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { type TFunction } from 'i18next'; +import useSWR from 'swr'; +import { + type LoggedInUser, + openmrsFetch, + refetchCurrentUser, + restBaseUrl, + fhirBaseUrl, + getDynamicOfflineDataEntries, + syncDynamicOfflineData, + putDynamicOfflineData, + toOmrsIsoString, + useConfig, +} from '@openmrs/esm-framework'; +import { type ConfigSchema } from '../config-schema'; import { type AddPatientData, + type AddablePatientListViewModel, type CohortResponse, type NewCohortData, type NewCohortDataPayload, @@ -9,8 +25,8 @@ import { type OpenmrsCohortRef, type PatientListFilter, type PatientListMember, - PatientListType, type PatientListUpdate, + PatientListType, } from './types'; export const cohortUrl = `${restBaseUrl}/cohortm`; @@ -209,3 +225,75 @@ export async function getPatientListName(patientListUuid: string) { console.error('Error resolving patient list name: ', error); } } + +export async function findRealPatientListsWithoutPatient( + patientUuid: string, + myListCohortUUID: string, + systemListCohortType: string, +): Promise> { + const [allLists, listsIdsOfThisPatient] = await Promise.all([ + getAllPatientLists({}, myListCohortUUID, systemListCohortType), + getPatientListIdsForPatient(patientUuid), + ]); + + return allLists.map((list) => ({ + id: list.id, + displayName: list.display, + checked: listsIdsOfThisPatient.includes(list.id), + async addPatient() { + await addPatientToList({ + cohort: list.id, + patient: patientUuid, + startDate: toOmrsIsoString(new Date()), + }); + }, + })); +} + +export async function findFakePatientListsWithoutPatient( + patientUuid: string, + t: TFunction, +): Promise> { + const offlinePatients = await getDynamicOfflineDataEntries('patient'); + const isPatientOnOfflineList = offlinePatients.some((x) => x.identifier === patientUuid); + return isPatientOnOfflineList + ? [] + : [ + { + id: 'fake-offline-patient-list', + displayName: t('offlinePatients', 'Offline patients'), + async addPatient() { + await putDynamicOfflineData('patient', patientUuid); + await syncDynamicOfflineData('patient', patientUuid); + }, + }, + ]; +} + +// This entire model is a little bit special since it not only displays the "real" patient lists (i.e. data from +// the cohorts/backend), but also a fake patient list which doesn't really exist in the backend: +// The offline patient list. +// When a patient is added to the offline list, that patient should become available offline, i.e. +// a dynamic offline data entry must be created. +// This is why the following abstracts away the differences between the real and the fake patient lists. +// The component doesn't really care about which is which - the only thing that matters is that the +// data can be fetched and that there is an "add patient" function. + +export function useAddablePatientLists(patientUuid: string) { + const { t } = useTranslation(); + const config = useConfig(); + return useSWR(['addablePatientLists', patientUuid], async () => { + // Using Promise.allSettled instead of Promise.all here because some distros might not have the + // cohort module installed, leading to the real patient list call failing. + // In that case we still want to show fake lists and *not* error out here. + const [fakeLists, realLists] = await Promise.allSettled([ + findFakePatientListsWithoutPatient(patientUuid, t), + findRealPatientListsWithoutPatient(patientUuid, config.myListCohortTypeUUID, config.systemListCohortTypeUUID), + ]); + + return [ + ...(fakeLists.status === 'fulfilled' ? fakeLists.value : []), + ...(realLists.status === 'fulfilled' ? realLists.value : []), + ]; + }); +} diff --git a/packages/esm-patient-list-management-app/src/api/types.ts b/packages/esm-patient-list-management-app/src/api/types.ts index 65cca2835..0ca7a4882 100644 --- a/packages/esm-patient-list-management-app/src/api/types.ts +++ b/packages/esm-patient-list-management-app/src/api/types.ts @@ -7,6 +7,13 @@ export enum PatientListType { ALL = 'All', } +export interface AddablePatientListViewModel { + addPatient(): Promise; + displayName: string; + checked?: boolean; + id: string; +} + export interface PatientList { id: string; display: string; diff --git a/packages/esm-patient-list-management-app/src/routes.json b/packages/esm-patient-list-management-app/src/routes.json index eea291a3d..99932d660 100644 --- a/packages/esm-patient-list-management-app/src/routes.json +++ b/packages/esm-patient-list-management-app/src/routes.json @@ -23,14 +23,16 @@ "name": "list-details-table", "component": "listDetailsTable" }, - { - "name": "add-patient-to-patient-list-modal", - "component": "addPatientToListModal" - }, { "name": "add-patient-to-patient-list-button", "slot": "patient-actions-slot", "component": "addPatientToPatientListMenuItem" } + ], + "modals": [ + { + "name": "add-patient-to-patient-list-modal", + "component": "addPatientToListModal" + } ] } diff --git a/packages/esm-patient-list-management-app/translations/en.json b/packages/esm-patient-list-management-app/translations/en.json index 6f65985df..03432c1a8 100644 --- a/packages/esm-patient-list-management-app/translations/en.json +++ b/packages/esm-patient-list-management-app/translations/en.json @@ -47,11 +47,10 @@ "newPatientListNameLabel": "List name", "nextPage": "Next page", "noMatchingLists": "No matching lists to display", + "noMatchingListsFound": "No matching lists found", "noMatchingPatients": "No matching patients to display", "noOfPatients": "No. of patients", - "noPatientListFound": "No patient list found", "noPatientsInList": "There are no patients in this list", - "offlinePatients": "Offline patients", "openPatientList": "Add to list", "patientListMemberCount_one": "This list has {{count}} patient", "patientListMemberCount_other": "This list has {{count}} patients", @@ -77,6 +76,7 @@ "successfullyAdded": "Successfully added", "systemDefined": "system-defined", "systemLists": "System lists", + "trySearchingForADifferentList": "Try searching for a different list", "unstarList": "Unstar list", "updated": "Updated", "userDefined": "user-defined" diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx index 90ce72354..3a3166ceb 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx @@ -100,10 +100,10 @@ const IdentifierInput: React.FC = ({ patientIdentifier, fi ) : (

{identifierName}

-

+

{autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}

- + {/* This is added for any error descriptions */} {!!(identifierFieldMeta.touched && identifierFieldMeta.error) && ( {identifierFieldMeta.error && t(identifierFieldMeta.error)} diff --git a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx index 1cd2b9436..2647c6ee3 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx @@ -1,29 +1,31 @@ /* eslint-disable testing-library/no-node-access */ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { Formik, Form } from 'formik'; -import { type PatientIdentifierType } from '../../../patient-registration.types'; -import { initialFormValues } from '../../../patient-registration.component'; +import { Form, Formik } from 'formik'; +import { ResourcesContext, type Resources } from '../../../../offline.resources'; +import { + PatientRegistrationContext, + type PatientRegistrationContextProps, +} from '../../../patient-registration-context'; +import type { AddressTemplate, FormValues, PatientIdentifierValue } from '../../../patient-registration.types'; import IdentifierInput from './identifier-input.component'; +import userEvent from '@testing-library/user-event'; -// TODO: Fix this test -xdescribe('identifier input', () => { - const openmrsID = { - name: 'OpenMRS ID', +const predefinedAddressTemplate = { + uuid: 'test-address-template-uuid', + property: 'layout.address.format', + description: 'Test Address Template', + display: + 'Layout - Address Format = \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n address1\n address2\n cityVillage stateProvince country postalCode\n \n ', + value: + '\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n address1 address2\r\n cityVillage stateProvince postalCode\r\n country\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ', +}; + +const mockIdentifierTypes = [ + { fieldName: 'openMrsId', - required: true, - uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', - format: null, - isPrimary: true, + format: '', identifierSources: [ - { - uuid: '691eed12-c0f1-11e2-94be-8c13b969e334', - name: 'Generator 1 for OpenMRS ID', - autoGenerationOption: { - manualEntryEnabled: false, - automaticGenerationEnabled: true, - }, - }, { uuid: '01af8526-cea4-4175-aa90-340acb411771', name: 'Generator 2 for OpenMRS ID', @@ -33,72 +35,121 @@ xdescribe('identifier input', () => { }, }, ], - autoGenerationSource: null, - }; + isPrimary: true, + name: 'OpenMRS ID', + required: true, + uniquenessBehavior: 'UNIQUE' as const, + uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + }, +]; - const setupIdentifierInput = async (identifierType: PatientIdentifierType) => { - initialFormValues['source-for-' + identifierType.fieldName] = identifierType.identifierSources[0].name; +const mockResourcesContextValue: Resources = { + addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate, + currentSession: { + authenticated: true, + sessionId: 'JSESSION', + currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' }, + }, + relationshipTypes: [], + identifierTypes: [...mockIdentifierTypes], +}; +const mockContextValues: PatientRegistrationContextProps = { + currentPhoto: '', + inEditMode: false, + identifierTypes: [], + initialFormValues: {} as FormValues, + isOffline: false, + setCapturePhotoProps: jest.fn(), + setFieldValue: jest.fn(), + setInitialFormValues: jest.fn(), + setFieldTouched: jest.fn(), + validationSchema: null, + values: {} as FormValues, +}; + +describe('identifier input', () => { + const fieldName = 'openMrsId'; + const openmrsID = { + identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + initialValue: '', + identifierValue: '', + identifierName: 'OpenMRS ID', + selectedSource: { + uuid: '01af8526-cea4-4175-aa90-340acb411771', + name: 'Generator 2 for OpenMRS ID', + autoGenerationOption: { + manualEntryEnabled: true, + automaticGenerationEnabled: true, + }, + }, + autoGeneration: false, + preferred: true, + required: true, + }; + + const setupIdentifierInput = async (patientIdentifier: PatientIdentifierValue) => { render( - -
- - -
, + + +
+ + + +
+
+
, ); - const identifierInput = screen.getByLabelText(identifierType.fieldName) as HTMLInputElement; - let identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName); + + let identifierLabel: HTMLParagraphElement; + let identifierInput: HTMLInputElement; + if (patientIdentifier.autoGeneration) { + identifierLabel = screen.getByTestId('identifier-label'); + identifierInput = screen.getByTestId('identifier-input'); + } else { + identifierLabel = screen.getByText(patientIdentifier.identifierName); + identifierInput = screen.getByLabelText(patientIdentifier.identifierName); + } return { + identifierLabel, identifierInput, - identifierSourceSelectInput, }; }; - it('exists', async () => { - const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID); - - expect(identifierInput.type).toBe('text'); - expect(identifierSourceSelectInput.type).toBe('select-one'); + it('shows the identifier input', async () => { + const { identifierInput } = await setupIdentifierInput(openmrsID); + expect(identifierInput).toBeInTheDocument(); }); - it('has correct props for identifier source select input', async () => { - const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID); + describe('Auto-generated identifier', () => { + openmrsID.autoGeneration = true; - expect(identifierSourceSelectInput.childElementCount).toBe(3); - expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID'); - }); + it('hides the input when the identifier is auto-generated', async () => { + const { identifierInput } = await setupIdentifierInput(openmrsID); + expect(identifierInput.type).toBe('hidden'); + }); - it('has correct props for identifier input', async () => { - const { identifierInput } = await setupIdentifierInput(openmrsID); - expect(identifierInput.placeholder).toBe('Auto-generated'); - expect(identifierInput.disabled).toBe(true); - }); + it("displays 'Auto-Generated' when the indentifier has auto generation", async () => { + const { identifierLabel, identifierInput } = await setupIdentifierInput(openmrsID); + expect(identifierLabel.innerHTML).toBe('Auto-generated'); + expect(identifierInput.disabled).toBe(true); + }); - it('text input should not be disabled if manual entry is enabled', async () => { - // setup - openmrsID.identifierSources[0].autoGenerationOption.manualEntryEnabled = true; - // replay - const { identifierInput } = await setupIdentifierInput(openmrsID); - expect(identifierInput.placeholder).toBe('Auto-generated'); - expect(identifierInput.disabled).toBe(false); - }); + it('displays an edit button when there is an initial value', async () => { + // setup + openmrsID.required = false; + openmrsID.initialValue = '1002UU9'; + // replay + await setupIdentifierInput(openmrsID); + expect(screen.getByText('Edit')).toBeInTheDocument(); + }); - it('should not render select widget if auto-entry is false', async () => { - // setup - openmrsID.identifierSources = [ - { - uuid: '691eed12-c0f1-11e2-94be-8c13b969e334', - name: 'Generator 1 for OpenMRS ID', - autoGenerationOption: { - manualEntryEnabled: true, - automaticGenerationEnabled: false, - }, - }, - ]; - // replay - const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID); - expect(identifierInput.placeholder).toBe('Enter identifier'); - expect(identifierInput.disabled).toBe(false); - expect(identifierSourceSelectInput).toBe(undefined); + it('displays a delete button when the identifier is not a default type', async () => { + // setup + openmrsID.required = false; + // replay + await setupIdentifierInput(openmrsID); + expect(screen.getByText('Delete')).toBeInTheDocument(); + }); }); }); diff --git a/packages/esm-patient-registration-app/translations/am.json b/packages/esm-patient-registration-app/translations/am.json index 756bdc9c5..ff2890d24 100644 --- a/packages/esm-patient-registration-app/translations/am.json +++ b/packages/esm-patient-registration-app/translations/am.json @@ -93,7 +93,7 @@ "relationshipToPatient": "Relationship to patient", "relativeFullNameLabelText": "Full name", "relativeNamePlaceholder": "Firstname Familyname", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "Reset", "restoreRelationshipActionButton": "Undo", "searchAddress": "Search address", diff --git a/packages/esm-patient-registration-app/translations/ar.json b/packages/esm-patient-registration-app/translations/ar.json index 49c9496ef..d41dfda59 100644 --- a/packages/esm-patient-registration-app/translations/ar.json +++ b/packages/esm-patient-registration-app/translations/ar.json @@ -93,7 +93,7 @@ "relationshipToPatient": "العلاقة بالمريض", "relativeFullNameLabelText": "Full name", "relativeNamePlaceholder": "الاسم الأول اسم العائلة", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "إعادة تعيين", "restoreRelationshipActionButton": "تراجع", "searchAddress": "ابحث عن العنوان", diff --git a/packages/esm-patient-registration-app/translations/es.json b/packages/esm-patient-registration-app/translations/es.json index 7687b950b..0afa1e8eb 100644 --- a/packages/esm-patient-registration-app/translations/es.json +++ b/packages/esm-patient-registration-app/translations/es.json @@ -3,38 +3,38 @@ "addressHeader": "Dirección", "allFieldsRequiredText": "Todos los campos son obligatorios a menos que se indique como opcionales", "autoGeneratedPlaceholderText": "Autogenerado", - "birthdayNotInTheFuture": "Birthday cannot be in future", - "birthdayNotOver140YearsAgo": "Birthday cannot be more than 140 years ago", + "birthdayNotInTheFuture": "La fecha de nacimiento no puede ser en el futuro", + "birthdayNotOver140YearsAgo": "La fecha de nacimiento no puede ser de hace más de 140 años", "birthdayRequired": "Cumpleaños es obligatorio", "birthFieldLabelText": "Nacimiento", "cancel": "Cancelar", - "causeOfDeathInputLabel": "Cause of death", + "causeOfDeathInputLabel": "Causa de muerte", "closeOverlay": "Cerrar superposición", "codedPersonAttributeAnswerSetEmpty": "El campo de atributo de persona codificado '{{codedPersonAttributeFieldId}}' se ha definido con un conjunto de conceptos de respuesta UUID '{{answerConceptSetUuid}}' que no tiene respuestas de concepto.", "codedPersonAttributeAnswerSetInvalid": "El campo de atributo de persona codificado '{{codedPersonAttributeFieldId}}' se ha definido con un UUID de conjunto de conceptos de respuesta no válido '{{answerConceptSetUuid}}'.", "codedPersonAttributeNoAnswerSet": "El campo de atributo de persona '{{codedPersonAttributeFieldId}}' es de tipo 'codificado' pero se ha definido sin UUID de conjunto de conceptos de respuesta. La clave 'answerConceptSetUuid' es requerida.", "configure": "Configurar", "configureIdentifiers": "Configurar identificadores", - "confirmDiscardChangesBody": "Your unsaved changes will be lost if you proceed to discard the form", - "confirmDiscardChangesTitle": "Are you sure you want to discard these changes?", - "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?", + "confirmDiscardChangesBody": "Tus cambios sin guardar serán perdidos si procedes a descartar el formulario", + "confirmDiscardChangesTitle": "¿Estás seguro de que deseas descartar estos cambios?", + "confirmIdentifierDeletionText": "¿Estás seguro de que deseas eliminar este identificador?", "contactSection": "Detalles de Contacto", - "createNewPatient": "Create new patient", - "dateOfBirthLabelText": "Date of birth", - "deathCauseRequired": "Cause of death is required", - "deathDateInFuture": "Death date cannot be in future", - "deathDateInputLabel": "Date of death", - "deathDateRequired": "Death date is required", - "deathdayInvalidDate": "Death date and time cannot be before the birthday", - "deathdayIsRequired": "Death date is required when the patient is marked as deceased.", + "createNewPatient": "Crear nuevo paciente", + "dateOfBirthLabelText": "Fecha de nacimiento", + "deathCauseRequired": "La causa de muerte es obligatoria", + "deathDateInFuture": "La fecha de fallecimiento no puede ser en el futuro", + "deathDateInputLabel": "Fecha de fallecimiento", + "deathDateRequired": "La fecha de nacimiento es obligatoria", + "deathdayInvalidDate": "La fecha y hora del fallecimiento no pueden ser anteriores a la fecha de nacimiento", + "deathdayIsRequired": "La fecha de fallecimiento es obligatoria cuando el paciente se marca como fallecido", "deathdayNotInTheFuture": "", "deathSection": "Información de Fallecimiento", - "deathTimeFormatInvalid": "Time format is invalid", - "deathTimeFormatRequired": "Time format is required", - "deathTimeInvalid": "Time doesn't match the format 'hh:mm'", - "deathTimeRequired": "Death time is required", - "deleteIdentifierModalHeading": "Remove identifier?", - "deleteIdentifierModalText": " has a value of ", + "deathTimeFormatInvalid": "El formato de hora es inválido", + "deathTimeFormatRequired": "El formato de hora es obligatorio", + "deathTimeInvalid": "La hora no coincide con el formato 'hh:mm'", + "deathTimeRequired": "La hora de fallecimiento es requerida", + "deleteIdentifierModalHeading": "¿Eliminar identificador?", + "deleteIdentifierModalText": "tiene un valor de", "deleteIdentifierTooltip": "Eliminar", "deleteRelationshipTooltipText": "Eliminar", "demographicsSection": "Información Básica", @@ -43,45 +43,45 @@ "editIdentifierTooltip": "Editar", "editPatientDetails": "Modificar los datos del paciente", "editPatientDetailsBreadcrumb": "Editar detalles del paciente", - "enterNonCodedCauseOfDeath": "Enter non-coded cause of death", + "enterNonCodedCauseOfDeath": "Ingresar causa del fallecimiento no-codificada", "error": "Error", - "errorFetchingCodedCausesOfDeath": "Error fetching coded causes of death", + "errorFetchingCodedCausesOfDeath": "Error al obtener causas del fallecimiento codificadas", "errorFetchingOrderedFields": "Ocurrió un error al obtener campos ordenados para la jerarquía de direcciones", "estimatedAgeInMonthsLabelText": "Edad estimada en meses", "estimatedAgeInYearsLabelText": "Edad estimada en años", "familyNameLabelText": "Apellidos", "familyNameRequired": "Apellidos es obligatorio", "female": "Femenino", - "fieldsWithErrors": "The following fields have errors: ", + "fieldsWithErrors": "Los siguientes campos tienen errores:", "fullNameLabelText": "Nombre y Apellidos", "genderLabelText": "Sexo", "genderRequired": "El sexo es obligatorio", - "genderUnspecified": "Gender unspecified", + "genderUnspecified": "Género no especificado", "givenNameLabelText": "Nombre", "givenNameRequired": "El nombre es obligatorio", "identifierValueRequired": "El valor del identificador es obligatorio", "idFieldLabelText": "Identificadores", "IDInstructions": "Selecciona los identificadores que te gustaría agregar para este paciente:", - "invalidEmail": "Invalid email", + "invalidEmail": "Email inválido", "invalidInput": "Entrada no válida", - "isDeadInputLabel": "Is dead", + "isDeadInputLabel": "Está fallecido", "jumpTo": "Ir a", "male": "Masculino", "middleNameLabelText": "Segundo Nombre", - "negativeMonths": "Estimated months cannot be negative", - "negativeYears": "Estimated years cannot be negative", + "negativeMonths": "Los meses estimados no pueden ser negativos", + "negativeYears": "Los años estimados no pueden ser negativos", "no": "No", - "nonCodedCauseOfDeath": "Non-coded cause of death", - "nonCodedCauseOfDeathRequired": "Cause of death is required", - "nonsensicalYears": "Estimated years cannot be more than 140", + "nonCodedCauseOfDeath": "Causa del fallecimiento no-codificada", + "nonCodedCauseOfDeathRequired": "La causa del fallecimiento es obligatoria", + "nonsensicalYears": "Los años estimados no pueden ser mayores a 140", "numberInNameDubious": "Número en nombre es dudoso", "obsFieldUnknownDatatype": "El concepto para el campo de observación '{{fieldDefinitionId}}' tiene un tipo de datos desconocido '{{datatypeName}}'", "optional": "Opcional", "other": "Otro", "patientNameKnown": "¿Se sabe el nombre del paciente?", "patientRegistrationBreadcrumb": "Registro de Pacientes", - "refreshOrContactAdmin": "Try refreshing the page or contact your system administrator", - "registerPatient": "Register patient", + "refreshOrContactAdmin": "Intente refrescar la página o contactar al administrador de tu sistema", + "registerPatient": "Registrar paciente", "registerPatientSuccessSnackbarSubtitle": "El paciente ahora se puede encontrar buscándolo por su nombre o número de identificación", "registerPatientSuccessSnackbarTitle": "Nuevo paciente creado", "registrationErrorSnackbarTitle": "Error en el registro del paciente", @@ -91,9 +91,9 @@ "relationshipRemovedText": "Relación eliminada", "relationshipsSection": "Relaciones", "relationshipToPatient": "Relación con el paciente", - "relativeFullNameLabelText": "Full name", + "relativeFullNameLabelText": "Nombre completo", "relativeNamePlaceholder": "Nombre Apellido", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Eliminar identificador", "resetIdentifierTooltip": "Resetear", "restoreRelationshipActionButton": "Deshacer", "searchAddress": "Buscar dirección", @@ -102,15 +102,15 @@ "sexFieldLabelText": "Sexo", "source": "Fuente", "submitting": "Enviando", - "timeFormat": "Time Format", - "timeOfDeathInputLabel": "Time of death (hh:mm)", - "unableToFetch": "Unable to fetch person attribute type - {{personattributetype}}", + "timeFormat": "Formato de hora", + "timeOfDeathInputLabel": "Hora del fallecimiento (hh:mm)", + "unableToFetch": "No se puede obtener el tipo de atributo de persona - {{personattributetype}}", "unknown": "Desconocido", "unknownPatientAttributeType": "El tipo de atributo del paciente tiene un formato desconocido {{personAttributeTypeFormat}}", - "updatePatient": "Update patient", + "updatePatient": "Actualizar paciente", "updatePatientErrorSnackbarTitle": "Error al actualizar los detalles del paciente", "updatePatientSuccessSnackbarSubtitle": "La información del paciente se ha actualizado correctamente", "updatePatientSuccessSnackbarTitle": "Detalles del paciente actualizados", - "yearsEstimateRequired": "Estimated years required", + "yearsEstimateRequired": "Los años estimados son obligatorios", "yes": "Sí" } diff --git a/packages/esm-patient-registration-app/translations/fr.json b/packages/esm-patient-registration-app/translations/fr.json index acf75badb..c7958fef2 100644 --- a/packages/esm-patient-registration-app/translations/fr.json +++ b/packages/esm-patient-registration-app/translations/fr.json @@ -4,7 +4,7 @@ "allFieldsRequiredText": "Tous les champs sont requis sauf si explicitement indiqués facultatifs", "autoGeneratedPlaceholderText": "Généré automatiquement", "birthdayNotInTheFuture": "La date de naissance ne peut pas être dans le futur", - "birthdayNotOver140YearsAgo": "Birthday cannot be more than 140 years ago", + "birthdayNotOver140YearsAgo": "La date de naissance ne peut être antérieure à 140 ans", "birthdayRequired": "La date de naissance est requise", "birthFieldLabelText": "Naissance", "cancel": "Annuler", @@ -15,26 +15,26 @@ "codedPersonAttributeNoAnswerSet": "Le champ d'attribut de personne '{{codedPersonAttributeFieldId}}' est de type 'codé' mais a été défini sans UUID d'ensemble de concepts de réponse. La clé 'answerConceptSetUuid' est requise.", "configure": "Configurer", "configureIdentifiers": "Configurer les identifiants", - "confirmDiscardChangesBody": "Your unsaved changes will be lost if you proceed to discard the form", - "confirmDiscardChangesTitle": "Are you sure you want to discard these changes?", + "confirmDiscardChangesBody": " Vos modifications non enregistrées seront perdues si vous supprimez le formulaire.", + "confirmDiscardChangesTitle": " Êtes-vous sûr de vouloir annuler ces modifications ?", "confirmIdentifierDeletionText": "Voulez-vous vraiment supprimer cet identifiant?", "contactSection": "Coordonnées du contact", "createNewPatient": "Créer un nouveau patient", "dateOfBirthLabelText": "Date de naissance", "deathCauseRequired": "La date du décès est requise", - "deathDateInFuture": "Death date cannot be in future", + "deathDateInFuture": "La date de décès ne peut être dans le future", "deathDateInputLabel": "Date du décès", "deathDateRequired": "La date du décès est requise", - "deathdayInvalidDate": "Death date and time cannot be before the birthday", - "deathdayIsRequired": "Death date is required when the patient is marked as deceased.", + "deathdayInvalidDate": "La date et l'heure de décès ne peuvent être antérieures à la date de naissance", + "deathdayIsRequired": "La date de décès est obligatoire quand le patient est déclaré décédé", "deathdayNotInTheFuture": "", "deathSection": "Informations sur le décès", - "deathTimeFormatInvalid": "Time format is invalid", - "deathTimeFormatRequired": "Time format is required", - "deathTimeInvalid": "Time doesn't match the format 'hh:mm'", - "deathTimeRequired": "Death time is required", + "deathTimeFormatInvalid": "Format de temps invalide", + "deathTimeFormatRequired": "Un format de temps est obligatoire", + "deathTimeInvalid": "Le temps ne correspond pas au format 'hh:mm'", + "deathTimeRequired": "L'heure de décès est requis", "deleteIdentifierModalHeading": "Supprimer l'identifiant?", - "deleteIdentifierModalText": " has a value of ", + "deleteIdentifierModalText": "a une valeur de", "deleteIdentifierTooltip": "Supprimer", "deleteRelationshipTooltipText": "Supprimer", "demographicsSection": "Informations basiques", @@ -43,20 +43,20 @@ "editIdentifierTooltip": "Éditer", "editPatientDetails": "Modifier les détails du patient", "editPatientDetailsBreadcrumb": "Modifier les détails du patient", - "enterNonCodedCauseOfDeath": "Enter non-coded cause of death", + "enterNonCodedCauseOfDeath": " Entrez la cause du décès non codée", "error": "Erreur", - "errorFetchingCodedCausesOfDeath": "Error fetching coded causes of death", + "errorFetchingCodedCausesOfDeath": " Erreur lors de la récupération des causes de décès codées", "errorFetchingOrderedFields": "Une erreur s'est produite lors de la récupération des champs ordonnés pour la hiérarchie d'adresse", "estimatedAgeInMonthsLabelText": "Âge estimé en mois", "estimatedAgeInYearsLabelText": "Âge estimé en années", "familyNameLabelText": "Nom de famille", "familyNameRequired": "Le nom de famille est requis", "female": "Féminin", - "fieldsWithErrors": "The following fields have errors: ", + "fieldsWithErrors": "Ces lignes suivantes contiennent des erreurs:", "fullNameLabelText": "Nom et prénom", "genderLabelText": "Sexe", "genderRequired": "Le genre est requis", - "genderUnspecified": "Gender unspecified", + "genderUnspecified": "Genre non précisé", "givenNameLabelText": "Prénom", "givenNameRequired": "Le prénom est requis", "identifierValueRequired": "La valeur de l'identifiant est requise", @@ -68,19 +68,19 @@ "jumpTo": "Aller à", "male": "Masculin", "middleNameLabelText": "Deuxième Prénom", - "negativeMonths": "Estimated months cannot be negative", - "negativeYears": "Estimated years cannot be negative", + "negativeMonths": "L'estimation du mois ne peut être une valeur négative", + "negativeYears": "L'estimation de l'année ne peut être une valeur négative", "no": "Non", - "nonCodedCauseOfDeath": "Non-coded cause of death", - "nonCodedCauseOfDeathRequired": "Cause of death is required", - "nonsensicalYears": "Estimated years cannot be more than 140", + "nonCodedCauseOfDeath": "Cause de décès non-codé", + "nonCodedCauseOfDeathRequired": "La cause du décès est requise", + "nonsensicalYears": "L'âge estimé ne peut excéder 140 ans", "numberInNameDubious": "Le chiffre dans le nom est suspect", "obsFieldUnknownDatatype": "Le concept pour le champ d'observation '{{fieldDefinitionId}}' a un type de données inconnu '{{datatypeName}}'", "optional": "Optionnel", "other": "Autre", "patientNameKnown": "Le nom du patient est-il connu?", "patientRegistrationBreadcrumb": "Enregistrement du patient", - "refreshOrContactAdmin": "Try refreshing the page or contact your system administrator", + "refreshOrContactAdmin": "Essayer de rafraichir la page ou contacter l'administrateur", "registerPatient": "Enregistrer un patient", "registerPatientSuccessSnackbarSubtitle": "Le patient peut maintenant être trouvé en le recherchant par son nom ou son numéro d'identification", "registerPatientSuccessSnackbarTitle": "Nouveau patient créé", @@ -102,15 +102,15 @@ "sexFieldLabelText": "Sexe", "source": "Source", "submitting": "En cours de soumission", - "timeFormat": "Time Format", - "timeOfDeathInputLabel": "Time of death (hh:mm)", - "unableToFetch": "Unable to fetch person attribute type - {{personattributetype}}", + "timeFormat": "Format de temps", + "timeOfDeathInputLabel": "Heure de décès (hh:mm)", + "unableToFetch": " Impossible de récupérer le type d'attribut de la personne - {{personattributetype}}", "unknown": "Inconnu", "unknownPatientAttributeType": "Le type d'attribut de patient a un format inconnu {{personAttributeTypeFormat}}", "updatePatient": "Mettre à jour le patient", "updatePatientErrorSnackbarTitle": "Échec de la mise à jour des détails du patient", "updatePatientSuccessSnackbarSubtitle": "Les informations du patient ont été mises à jour avec succès", "updatePatientSuccessSnackbarTitle": "Détails du patient mis à jour", - "yearsEstimateRequired": "Estimated years required", + "yearsEstimateRequired": "estimation de l'année obligatoire", "yes": "Oui" } diff --git a/packages/esm-patient-registration-app/translations/he.json b/packages/esm-patient-registration-app/translations/he.json index 49f5e38f0..8bfd511eb 100644 --- a/packages/esm-patient-registration-app/translations/he.json +++ b/packages/esm-patient-registration-app/translations/he.json @@ -93,7 +93,7 @@ "relationshipToPatient": "קשר למטופל", "relativeFullNameLabelText": "Full name", "relativeNamePlaceholder": "שם פרטי שם משפחה", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "איפוס", "restoreRelationshipActionButton": "ביטול", "searchAddress": "חיפוש כתובת", diff --git a/packages/esm-patient-registration-app/translations/km.json b/packages/esm-patient-registration-app/translations/km.json index c9aba5f11..865a427a4 100644 --- a/packages/esm-patient-registration-app/translations/km.json +++ b/packages/esm-patient-registration-app/translations/km.json @@ -93,7 +93,7 @@ "relationshipToPatient": "ការទាក់ទងទៅនឹងអ្នកជំងឺ", "relativeFullNameLabelText": "Full name", "relativeNamePlaceholder": "នាមត្រកូលនាមខ្លួន", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "រៀបចំឡើងវិញ", "restoreRelationshipActionButton": "វិលត្រឡប់មកដើមវិញ", "searchAddress": "ស្វែងរកអាសយដ្ឋាន", diff --git a/packages/esm-patient-registration-app/translations/zh.json b/packages/esm-patient-registration-app/translations/zh.json index 7008353b0..ac7fedd3c 100644 --- a/packages/esm-patient-registration-app/translations/zh.json +++ b/packages/esm-patient-registration-app/translations/zh.json @@ -93,7 +93,7 @@ "relationshipToPatient": "与患者的关系", "relativeFullNameLabelText": "Full name", "relativeNamePlaceholder": "名字 姓氏", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "重置", "restoreRelationshipActionButton": "撤销", "searchAddress": "搜索地址", diff --git a/packages/esm-patient-registration-app/translations/zh_CN.json b/packages/esm-patient-registration-app/translations/zh_CN.json index 7008353b0..ac7fedd3c 100644 --- a/packages/esm-patient-registration-app/translations/zh_CN.json +++ b/packages/esm-patient-registration-app/translations/zh_CN.json @@ -93,7 +93,7 @@ "relationshipToPatient": "与患者的关系", "relativeFullNameLabelText": "Full name", "relativeNamePlaceholder": "名字 姓氏", - "removeIdentifierButton": "Remove Identifier", + "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "重置", "restoreRelationshipActionButton": "撤销", "searchAddress": "搜索地址", diff --git a/packages/esm-service-queues-app/translations/es.json b/packages/esm-service-queues-app/translations/es.json index a172806bc..da09cae24 100644 --- a/packages/esm-service-queues-app/translations/es.json +++ b/packages/esm-service-queues-app/translations/es.json @@ -2,15 +2,15 @@ "actions": "Actions", "activeVisits": "Active Visits", "activeVisitsNotInQueue": "Active visits not in queue", - "addAProviderQueueRoom": "Add a provider queue room?", + "addAProviderQueueRoom": "¿Agregar una sala de espera para el proveedor?", "addEntry": "Add entry", "addisitToQueueTooltip": "Add to queue", - "addNewQueueService": "Add New Queue Service", + "addNewQueueService": "Añadir Nuevo Servicio de Cola", "addNewQueueServiceRoom": "Add new queue service room", "addNewService": "Add new service", "addNewServiceRoom": "Add new service room", "addPatientToQueue": "Add patient to queue", - "addProviderQueueRoom": "Add provider queue room", + "addProviderQueueRoom": "Añadir Sala de Espera del Proveedor", "addQueue": "Add queue", "addQueueName": "Please add a queue name", "addQueueRoom": "Add queue room", @@ -26,10 +26,10 @@ "and": "And", "anotherVisitType": "Start another visit type", "any": "Any", - "applyFilters": "Apply filters", + "applyFilters": "Aplicar filtros", "averageWaitTime": "Average wait time today", "backToScheduledVisits": "Back to scheduled visits", - "backToSearchResults": "Back to search results", + "backToSearchResults": "Volver a los resultados de búsqueda", "backToSimpleSearch": "Back to simple search", "between": "Between", "bmi": "Bmi", @@ -41,50 +41,50 @@ "chooseRoom": "Select a room", "chooseService": "Select a service", "clearAllQueueEntries": "Clear all queue entries?", - "clearAllQueueEntriesWarningMessage": "Clearing all queue entries will remove all the patients from the queues", + "clearAllQueueEntriesWarningMessage": "Limpiar todas las entradas en la cola eliminará todos los pacientes de las colas", "clearQueue": "Clear queue", "clinicMetrics": "Clinic metrics", "configurePriorities": "Please configure priorities to continue.", "configureServices": "Please configure services to continue.", "configureStatus": "Please configure status to continue.", - "confirmDeleteQueueEntry": "Are you sure you want to delete this queue entry?", - "confirmMoveBackQueueAndStatus": "Are you sure you want to move patient back to queue \"{{queue}}\" with status \"{{status}}\"?", - "confirmMoveBackStatus": "Are you sure you want to move patient back to status \"{{status}}\"?", - "confirmRemovePatientFromQueue": "Are you sure you want to remove this patient from this queue?", - "currentValueFormatted": "{{value}} (Current)", + "confirmDeleteQueueEntry": "¿Está seguro de que desea eliminar esta entrada de cola?", + "confirmMoveBackQueueAndStatus": "¿Está seguro de que desea mover el paciente atrás a la cola \"{{queue}}\" con el estado \"{{status}}\"?", + "confirmMoveBackStatus": "¿Está seguro de que desea mover el paciente de regreso al estado \"{{status}}\"?", + "confirmRemovePatientFromQueue": "¿Está seguro de que desea eliminar este paciente de esta cola?", + "currentValueFormatted": "{{value}} (Actual)", "currentVisit": "Current visit", "date": "Date", "date&Time": "Date & time", "dateAndTimeOfVisit": "Date and time of visit", "dateOfBirth": "Date of birth", - "delete": "Delete", - "deleteQueueEntry": "Delete queue entry", + "delete": "Eliminar", + "deleteQueueEntry": "Eliminar entrada de la cola", "discard": "Discard", "dose": "Dose", - "edit": "Edit", + "edit": "Editar", "editPatientDetails": "Edit patient details", - "editQueueEntry": "Edit queue entry", - "editQueueEntryInstruction": "Edit fields of existing queue entry", + "editQueueEntry": "Editar entrada de la colaq", + "editQueueEntryInstruction": "Editar campos de la entrada de la cola existente", "encounterType": "Encounter type", "endAgeRangeInvalid": "End age range is not valid", "endDate": "End date", "endVisit": "End Visit", "endVisitWarningMessage": "Ending this visit will remove this patient from the queue and will not allow you to fill another encounter form for this patient", - "enterCommentHere": "Enter Comment here", + "enterCommentHere": "Ingresar Comentario Aquí", "errorAddingQueue": "Error adding queue", "errorAddingQueueRoom": "Error adding queue room", "errorClearingQueues": "Error clearing queues", - "errorFetchingAppointments": "Error fetching appointments", - "errorLoadingQueueEntries": "Error loading queue entries", + "errorFetchingAppointments": "Error al obtener las citas", + "errorLoadingQueueEntries": "Error al cargar las entradas de la cola", "errorPostingToScreen": "Error posting to screen", "facility": "Facility", - "failedToLoadRecommendedVisitTypes": "Failed to load recommended visit types", + "failedToLoadRecommendedVisitTypes": "Fallo al cargar tipos de visita recomendados", "female": "Female", "femaleLabelText": "Female", "fields": "of the following fields", "filter": "Filter (1)", - "filterByService": "Filter by service :", - "filterByStatus": "Filter by status :", + "filterByService": "Filtrar por servicio:", + "filterByStatus": "Filtrar por estado:", "filterTable": "Filter table", "firstName": "First name", "firstNameSort": "First name (a-z)", @@ -93,24 +93,24 @@ "gender": "Gender", "heartRate": "Heart rate", "height": "Height", - "hourAndMinuteFormatted": "{{hours}} hour(s) and {{minutes}} minute(s)", + "hourAndMinuteFormatted": "{{hours}} hora(s) y {{minutes}} minutos)", "idNumber": "ID Number", "indication": "Indication", - "invalidQueue": "Invalid Queue", - "invalidtableConfig": "Invalid table configuration", + "invalidQueue": "Cola Inválida", + "invalidtableConfig": "Tabla de configuración inválida", "lastEncounter": "Last encounter", "lastName": "Last name", "lastNameSort": "Last name (a-z)", "lastVisit": "Last visit", "lastVisitDate": "Date", "loading": "Loading...", - "location": "Location", + "location": "Ubicación", "male": "Male", "maleLabelText": "Male", "match": "Match", "medications": "Medications", "middleName": "Middle name", - "minuteFormatted": "{{minutes}} minute(s)", + "minuteFormatted": "{{minutes}} minuto(s)", "minutes": "Minutes", "missingLocation": "Missing location", "missingPriority": "Please select a priority", @@ -118,16 +118,16 @@ "missingQueueRoom": "Please select a queue room", "missingQueueRoomName": "Missing queue room name", "missingQueueRoomService": "Missing queue room service", - "missingService": "Missing service", + "missingService": "Servicio faltante", "missingVisitType": "Missing visit type", - "modifyDefaultValue": "Modify default value", + "modifyDefaultValue": "Modificar valor por defecto", "movePatientToNextService": "Move patient to the next service?", "moveToNextService": "Move to next service", "name": "Name", - "nextPage": "Next page", + "nextPage": "Siguiente página", "noActiveVisitsForLocation": "There are no active visits to display for this location.", "noAppointmentsFound": "No appointments found", - "noColumnsDefined": "No table columns defined. Check Configuration", + "noColumnsDefined": "No hay columnas de la tabla definidas. Revisar Configuración", "noEncountersFound": "No encounters found", "noLastEncounter": "There is no last encounter to display for this patient", "noLocationsAvailable": "No locations available", @@ -137,18 +137,18 @@ "noPatientsToDisplay": "No patients to display", "noPreviousVisitFound": "No previous visit found", "noPrioritiesConfigured": "No priorities configured", - "noPrioritiesForService": "The selected service does not have any allowed priorities. This is an error in configuration. Please contact your system administrator.", - "noPrioritiesForServiceTitle": "No priorities available", + "noPrioritiesForService": "El servicio seleccionado no tiene prioridades permitidas. Este es un error de configuración. Por favor, contacte a su administrador del sistema.", + "noPrioritiesForServiceTitle": "No hay prioridades disponibles", "noPriorityFound": "No priority found", "noResultsFound": "No results found", "noReturnDate": "There is no return date to display for this patient", "noServicesAvailable": "No services available", "noServicesConfigured": "No services configured", "noStatusConfigured": "No status configured", - "notableConfig": "No table configuration", + "notableConfig": "No hay tabla de configuración", "notes": "Notes", "noVisitsNotInQueueFound": "No visits currently not in queue found", - "noVisitTypesMatchingSearch": "There are no visit types matching this search text", + "noVisitTypesMatchingSearch": "No existen tipos de visita que coincidan con el texto a buscar", "noVitalsFound": "No vitals found", "oldest": "Oldest first", "onTime": "On time", @@ -157,17 +157,17 @@ "orderIndefiniteDuration": "Indefinite duration", "orInProperFormat": "Or", "orPatientName": "OR the patient's name(s)", - "patientAge": "Age", - "patientAlreadyInQueue": "Patient is already in the queue", + "patientAge": "Edad", + "patientAlreadyInQueue": "El paciente ya está en la cola", "patientAttendingService": "Patient attending service", "patientHasActiveVisit": "The patient already has an active visit", "patientInfo": "{{name}}{{sexInfo}}{{ageInfo}}", "patientList": "Patient list", "patientName": "Patient name", "patientNotInQueue": "The patient is not in the queue", - "patientRemoved": "Patient removed", - "patientRemovedFailed": "Error removing patient from queue", - "patientRemovedSuccessfully": "Paient removed from queue successfully", + "patientRemoved": "Paciente removido", + "patientRemovedFailed": "Error al eliminar el paciente de la cola", + "patientRemovedSuccessfully": "Paciente eliminado de la cola con éxito", "patientRequeued": "Patient has been requeued", "patients": "Patients", "patientsCurrentlyInQueue": "Patients currently in queue", @@ -176,44 +176,44 @@ "pleaseSelectLocation": "Please select a location", "pleaseSelectService": "Please select a service", "postCode": "Post code", - "previousPage": "Previous page", + "previousPage": "Página anterior", "previousVisit": "Previous visit", "primaryHelperText": "Search for a patient", "priority": "Priority", - "priorityComment": "Priority comment", - "priorityIsRequired": "Priority is required", + "priorityComment": "Comentario de prioridad", + "priorityIsRequired": "La prioridad es obligatoria", "program": "Program", "provider": "Provider", "quantity": "Quantity", - "queueAddedSuccessfully": "Queue added successfully", - "queueEntryAddedSuccessfully": "Queue entry added successfully", + "queueAddedSuccessfully": "La cola fue añadida con éxito", + "queueEntryAddedSuccessfully": "La entrada en la cola fue añadida con éxito", "queueEntryAddFailed": "Error adding queue entry status", - "queueEntryDeleteFailed": "Error deleting queue entry", - "queueEntryDeleteSuccessful": "Queue entry deleted successfully", - "queueEntryEdited": "Queue entry edited", - "queueEntryEditedSuccessfully": "Queue entry edited successfully", - "queueEntryEditingFailed": "Error editing queue entry", + "queueEntryDeleteFailed": "Error al eliminar la entrada de la cola", + "queueEntryDeleteSuccessful": "La entrada de la cola fue eliminada con éxito", + "queueEntryEdited": "Entrada de la cola editada", + "queueEntryEditedSuccessfully": "La entrada de la cola fue editada con éxito", + "queueEntryEditingFailed": "Error al editar entrada de la cola", "queueEntryError": "Error adding patient to the queue", "queueEntryRemoved": "Queue entry removed", "queueEntryRemovedSuccessfully": "", "queueEntryStatusUpdateFailed": "Error updating queue entry status", - "queueEntryTransitioned": "Queue entry transitioned", - "queueEntryTransitionedSuccessfully": "Queue entry transitioned successfully", - "queueEntryTransitionFailed": "Error transitioning queue entry", - "queueEntryTransitionUndoFailed": "Error undoing transition", - "queueEntryTransitionUndoSuccessful": "Queue entry transition undo success", + "queueEntryTransitioned": "Entrada de la cola trasladada", + "queueEntryTransitionedSuccessfully": "Entrada de la cola trasladada exitosamente", + "queueEntryTransitionFailed": "Error al trasladar la entrada de la cola", + "queueEntryTransitionUndoFailed": "Error al deshacer el traslado", + "queueEntryTransitionUndoSuccessful": "Traslado de la entrada de la cola deshecho con éxito", "queueEntryUpdateFailed": "Error updating queue entry", "queueEntryUpdateSuccessfully": "Queue Entry Updated Successfully", - "queueLocation": "Queue location", - "queueLocationRequired": "Queue location is required", + "queueLocation": "Ubicación de la cola", + "queueLocationRequired": "La ubicación de la cola es obligatoria", "queueName": "Queue name", "queuePriority": "Queue priority", "queueRoom": "Queue room", - "queueRoomAddedSuccessfully": "Queue room added successfully", + "queueRoomAddedSuccessfully": "Sala de espera añadida con éxito", "queueRoomAddFailed": "Error adding queue room", "queueRoomName": "Queue room name", "queueRoomUpdatedSuccessfully": "Queue room updated successfully", - "queuesClearedSuccessfully": "Queues cleared successfully", + "queuesClearedSuccessfully": "Colas limpiadas con éxito", "queueScreen": "Queue screen", "queueService": "Queue service", "queueStatus": "Queue status", @@ -222,8 +222,8 @@ "recommended": "Recommended", "refills": "Refills", "removeFromQueueAndEndVisit": "Remove patient from queue and end active visit?", - "removePatient": "Remove patient", - "removePatientFromQueue": "Remove patient from queue", + "removePatient": "Eliminar paciente", + "removePatientFromQueue": "Eliminar paciente de la cola", "removeQueueEntryError": "Error removing queue entry", "requeue": "Requeue", "retainLocation": "Retain location", @@ -237,15 +237,15 @@ "searchboxPlaceholder": "Search for a patient name or ID number", "searchForAVisitType": "Search for a visit type", "searchForPatient": "Search for a patient", - "searchPatient": "Search Patient", + "searchPatient": "Buscar Paciente", "searchThisList": "Search this list", "secondaryHelperText": "Type the patient's name or unique ID number", - "selectALocation": "Select a location", + "selectALocation": "Seleccionar una ubicación", "selectAVisitType": "Select visit type", "selectFacility": "Select a facility", "selectOption": "Select an option", "selectProgramType": "Select program type", - "selectQueue": "Select a queue", + "selectQueue": "Seleccionar una cola", "selectQueueLocation": "Select a queue location", "selectQueueService": "Select a queue service", "selectRoom": "Select a room", @@ -255,7 +255,7 @@ "serve": "Serve", "servePatient": "Serve patient", "service": "Service", - "serviceIsRequired": "Service is required", + "serviceIsRequired": "El servicio es obligatorio", "serviceQueue": "Service queue", "serviceQueues": "Service queues", "sex": "Sex", @@ -268,35 +268,35 @@ "startVisitError": "Error starting visit", "startVisitQueueSuccessfully": "Patient has been added to active visits list and queue.", "status": "Status", - "statusIsRequired": "Status is required", - "submitting": "Submitting...", + "statusIsRequired": "El estado es obligatorio", + "submitting": "Enviando...", "success": "Success", "temperature": "Temperature", "ticketNumber": "Ticket number", "time": "Time", - "timeCannotBeInFuture": "Time cannot be in the future", - "timeCannotBePriorToPreviousQueueEntry": "Time cannot be before start of previous queue entry: {{time}}", - "timeOfTransition": "Time of transition", + "timeCannotBeInFuture": "La hora no puede estar en el futuro", + "timeCannotBePriorToPreviousQueueEntry": "La hora no puede estar antes del inicio de la anterior entrada de la cola: {{time}}", + "timeOfTransition": "Hora del traslado", "tirageNotYetCompleted": "Triage has not yet been completed", "today": "Today", - "totalPatients": "Total Patients", - "transition": "Transition", - "TransitionLatestQueueEntry": "Transition patient to latest queue", - "transitionPatient": "Transition patient", - "transitionPatientStatusOrQueue": "Select a new status or queue for patient to transition to.", + "totalPatients": "Total de Pacientes", + "transition": "Traslado", + "TransitionLatestQueueEntry": "Paciente trasladado a la última cola", + "transitionPatient": "Trasladar paciente", + "transitionPatientStatusOrQueue": "Seleccione un nuevo estado o cola para el paciente a trasladar", "triageForm": "Triage form", "triageNote": "Triage note", "trySearchWithPatientUniqueID": "Try searching with the patient's unique ID number", - "undoQueueEntryTransitionSuccess": "Undo transition success", - "undoTransition": "Undo transition", - "unexpectedServerResponse": "Unexpected Server Response", + "undoQueueEntryTransitionSuccess": "Traslado deshecho con éxito", + "undoTransition": "Deshacer traslado", + "unexpectedServerResponse": "Respuesta del servidor no esperada", "unknown": "Unknown", "updateEntry": "Update entry", "updateRoom": "Update room", "useTodaysDate": "Use today's date", - "visitQueueNumberAttributeUuid not configured": "visitQueueNumberAttributeUuid not configured", + "visitQueueNumberAttributeUuid not configured": "visitQueueNumberAttributeUuid no configurado", "visitStartTime": "Visit Time", - "visitTabs": "Visit tabs", + "visitTabs": "Pestañas de visita", "visitType": "Visit Type", "vitals": "Vitals", "vitalsForm": "Vitals form", diff --git a/packages/esm-service-queues-app/translations/fr.json b/packages/esm-service-queues-app/translations/fr.json index 071bd4c38..7996b92d0 100644 --- a/packages/esm-service-queues-app/translations/fr.json +++ b/packages/esm-service-queues-app/translations/fr.json @@ -2,7 +2,7 @@ "actions": "Actions", "activeVisits": "Visites Actives", "activeVisitsNotInQueue": "Visites actives non dans la file d'attente", - "addAProviderQueueRoom": "Ajouter une salle d'attente pour les prestataires des service?", + "addAProviderQueueRoom": "Ajouter une salle d'attente pour les prestataires de service?", "addEntry": "Ajouter une entrée", "addisitToQueueTooltip": "Ajouter", "addNewQueueService": "Ajouter un nouveau service de file d'attente", @@ -18,7 +18,7 @@ "addQueueRoomService": "Veuillez ajouter un service de salle de file d'attente", "addRoom": "Ajouter une salle", "addToQueue": "Ajouter à la file d'attente", - "addVisitToQueue": "Add Visit To Queue?", + "addVisitToQueue": " Ajouter une visite à la file d’attente?", "advancedSearch": "Recherche avancée", "age": "Âge", "alistOfClients": "Liste de patients attendus", @@ -44,13 +44,13 @@ "clearAllQueueEntriesWarningMessage": "L'effacement de toutes les entrées de la file d'attente supprimera tous les patients de la file d'attente.", "clearQueue": "Effacer la file d'attente", "clinicMetrics": "Parametres de I'Etablissement de soins", - "configurePriorities": "Please configure priorities to continue.", - "configureServices": "Please configure services to continue.", - "configureStatus": "Please configure status to continue.", - "confirmDeleteQueueEntry": "Are you sure you want to delete this queue entry?", - "confirmMoveBackQueueAndStatus": "Are you sure you want to move patient back to queue \"{{queue}}\" with status \"{{status}}\"?", - "confirmMoveBackStatus": "Are you sure you want to move patient back to status \"{{status}}\"?", - "confirmRemovePatientFromQueue": "Are you sure you want to remove this patient from this queue?", + "configurePriorities": " Veuillez configurer les priorités pour pouvoir continuer.", + "configureServices": " Veuillez configurer les services pour pouvoir continuer.", + "configureStatus": " Veuillez configurer les statuts pour pouvoir continuer.", + "confirmDeleteQueueEntry": " Êtes-vous sûr de vouloir supprimer cette entrée de file d’attente ?", + "confirmMoveBackQueueAndStatus": " Êtes-vous sûr de vouloir déplacer le patient vers la file d'attente « {{queue}} » avec le statut « {{status}} » ?", + "confirmMoveBackStatus": " Êtes-vous sûr de vouloir ramener le patient au statut « {{status}} » ?", + "confirmRemovePatientFromQueue": " Êtes-vous sûr de vouloir supprimer ce patient de cette file d’attente ?", "currentValueFormatted": "{{value}} (Actuel)", "currentVisit": "Visite en cours", "date": "Date", @@ -64,21 +64,21 @@ "edit": "Modifier", "editPatientDetails": "Modifier les détails du patient", "editQueueEntry": "Modifier l'entrée dans la file d'attente.", - "editQueueEntryInstruction": "Edit fields of existing queue entry", + "editQueueEntryInstruction": " Modifier les champs d'une file d'attente existante", "encounterType": "Type de Rencontre", "endAgeRangeInvalid": "La fin de la tranche d'âge n'est pas valide", "endDate": "Date de fin", "endVisit": "Fin de la visite", - "endVisitWarningMessage": "Ending this visit will not allow you to fill another encounter form for this patient", - "enterCommentHere": "Enter Comment here", + "endVisitWarningMessage": "En terminant cette visite il ne vous sera pas permis de remplir un autre formulaire de rencontre pour ce patient", + "enterCommentHere": "Ecrire un commentaire ici", "errorAddingQueue": "Erreur lors de l'ajout de la file d'attente", - "errorAddingQueueRoom": "Error adding queue room", - "errorClearingQueues": "Error clearing queues", - "errorFetchingAppointments": "Error fetching appointments", - "errorLoadingQueueEntries": "Error loading queue entries", - "errorPostingToScreen": "Error posting to screen", + "errorAddingQueueRoom": "Erreur lors de l'ajout d'une salle d'attente", + "errorClearingQueues": "Erreur lors de la suppression des files d'attente", + "errorFetchingAppointments": " Erreur lors de la récupération des rendez-vous", + "errorLoadingQueueEntries": " Erreur lors du chargement des files d'attente", + "errorPostingToScreen": " Erreur lors de l'affichage sur l'écran", "facility": "Facilité", - "failedToLoadRecommendedVisitTypes": "Failed to load recommended visit types", + "failedToLoadRecommendedVisitTypes": " Échec du chargement des types de visites recommandées", "female": "Féminin", "femaleLabelText": "Féminin", "fields": "des champs suivants", @@ -88,16 +88,16 @@ "filterTable": "Filter table", "firstName": "Prénom", "firstNameSort": "Prénom (a-z)", - "futureScheduledVisits_one": "{{count}} visit scheduled for dates in the future", - "futureScheduledVisits_other": "{{count}} visits scheduled for dates in the future", + "futureScheduledVisits_one": "{{count}} visites prévues à des dates ultérieures", + "futureScheduledVisits_other": "{{count}} visites prévues à des dates ultérieures", "gender": "Genre", "heartRate": "Fréquence cardiaque", "height": "Hauteur", - "hourAndMinuteFormatted": "{{hours}} hour(s) and {{minutes}} minute(s)", + "hourAndMinuteFormatted": " {{hours}} heure(s) et {{minutes}} minute(s)", "idNumber": "ID Number", "indication": "Indication", "invalidQueue": "File d'attente non valide", - "invalidtableConfig": "Invalid table configuration", + "invalidtableConfig": "Configuration de table non valide", "lastEncounter": "Dernière rencontre", "lastName": "Nom de famille", "lastNameSort": "Nom de famille (a-z)", @@ -114,20 +114,20 @@ "minutes": "Minutes", "missingLocation": "L'emplacement est manquant", "missingPriority": "Veuillez sélectionner une priorité", - "missingQueueName": "Missing queue name", - "missingQueueRoom": "Please select a queue room", - "missingQueueRoomName": "Missing queue room name", - "missingQueueRoomService": "Missing queue room service", + "missingQueueName": "Nom de file d'attente manquant", + "missingQueueRoom": "Veuillez sélectionner une salle de file d'attente", + "missingQueueRoomName": "Nom de salle de file d'attente manquant", + "missingQueueRoomService": "Service de salle de file d'attente manquant", "missingService": "Service manquant", - "missingVisitType": "Missing visit type", - "modifyDefaultValue": "Modify default value", + "missingVisitType": " Type de visite manquant", + "modifyDefaultValue": "Modifier la valeur par défaut", "movePatientToNextService": "Déplacer le patient vers le service suivant?", "moveToNextService": "Déplacer vers le service suivant", "name": "Nom", "nextPage": "Page suivante", "noActiveVisitsForLocation": "Il n'y a pas de visite en cours pour cet emplacement", "noAppointmentsFound": "Aucun rendez-vous trouvé", - "noColumnsDefined": "No table columns defined. Check Configuration", + "noColumnsDefined": "Aucune colonne de tableau définie. Vérifier la configuration", "noEncountersFound": "Aucune rencontre trouvée", "noLastEncounter": "Il n'y a pas de dernier rendez-vous à afficher pour ce patient", "noLocationsAvailable": "Aucun emplacement disponible", @@ -137,15 +137,15 @@ "noPatientsToDisplay": "Aucun patient affiché", "noPreviousVisitFound": "Aucune visite précédente trouvée", "noPrioritiesConfigured": "Aucune priorité n'est configurée", - "noPrioritiesForService": "The selected service does not have any allowed priorities. This is an error in configuration. Please contact your system administrator.", - "noPrioritiesForServiceTitle": "No priorities available", - "noPriorityFound": "No priority found", + "noPrioritiesForService": "Le service sélectionné n'a aucune priorité autorisée. Il s'agit d'une erreur de configuration. Veuillez contacter votre administrateur système.", + "noPrioritiesForServiceTitle": "Aucunes priorités disponible", + "noPriorityFound": " Aucune priorité trouvée", "noResultsFound": "Aucun résultat trouvé", "noReturnDate": "There is no return date to display for this patient", - "noServicesAvailable": "No services available", - "noServicesConfigured": "No services configured", + "noServicesAvailable": "Aucun services disponibles", + "noServicesConfigured": "Aucun service configuré", "noStatusConfigured": "No status configured", - "notableConfig": "No table configuration", + "notableConfig": "Aucune configuration de table", "notes": "Notes", "noVisitsNotInQueueFound": "No visits currently not in queue found", "noVisitTypesMatchingSearch": "Il n'y a pas de visite correspondante à cette recherche", @@ -159,16 +159,16 @@ "orPatientName": "OU le nom(s) du patient", "patientAge": "Âge", "patientAlreadyInQueue": "Patient déjà dans la file d'attente", - "patientAttendingService": "Patient attending service", + "patientAttendingService": "Patient en attente de service", "patientHasActiveVisit": "Le patient a déjà une visite active", "patientInfo": "{{name}}{{sexInfo}}{{ageInfo}}", "patientList": "Liste de patients", "patientName": "Nom du patient", "patientNotInQueue": "Le patient n'est pas dans la file d'attente", "patientRemoved": "Patient enlevé", - "patientRemovedFailed": "Error removing patient from queue", - "patientRemovedSuccessfully": "Paient removed from queue successfully", - "patientRequeued": "Patient has been requeued", + "patientRemovedFailed": " Erreur lors de la suppression du patient de la file d'attente", + "patientRemovedSuccessfully": "Le patient a été retiré de la file d'attente avec succès", + "patientRequeued": "Le patient a été remis dans la file d'attente", "patients": "Les patients", "patientsCurrentlyInQueue": "Patients actuellement en file d'attente", "personalDetails": "Détails personnels", @@ -187,21 +187,21 @@ "quantity": "Quantité", "queueAddedSuccessfully": "File d'attente ajoutée avec succès", "queueEntryAddedSuccessfully": "Entrée dans la file d'attente ajoutée avec succès", - "queueEntryAddFailed": "Error adding queue entry status", - "queueEntryDeleteFailed": "Error deleting queue entry", - "queueEntryDeleteSuccessful": "Queue entry deleted successfully", - "queueEntryEdited": "Queue entry edited", - "queueEntryEditedSuccessfully": "Queue entry edited successfully", - "queueEntryEditingFailed": "Error editing queue entry", - "queueEntryError": "Error adding patient to the queue", - "queueEntryRemoved": "Queue entry removed", + "queueEntryAddFailed": " Erreur lors de l'ajout du statut de la file d'attente ", + "queueEntryDeleteFailed": "Erreur lors de la suppression de l'entrée de la file d'attente", + "queueEntryDeleteSuccessful": "Entrée de la file d'attente supprimée avec succès ", + "queueEntryEdited": "Entrée de la file d'attente affichée", + "queueEntryEditedSuccessfully": " Entrée de file d'attente affichée avec succès", + "queueEntryEditingFailed": " Erreur lors de l'affichage de l'entrée de la file d'attente", + "queueEntryError": " Erreur lors de l'ajout du patient à la file d'attente", + "queueEntryRemoved": " Entrée de file d'attente supprimée", "queueEntryRemovedSuccessfully": "", - "queueEntryStatusUpdateFailed": "Error updating queue entry status", + "queueEntryStatusUpdateFailed": " Erreur lors de la mise à jour du statut de l'entrée de la file d'attente", "queueEntryTransitioned": "Entrée dans la file d'attente transitionnée", "queueEntryTransitionedSuccessfully": "Entrée dans la file d'attente transitionnée avec succès", "queueEntryTransitionFailed": "Erreur lors de la transition de l'entrée dans la file d'attente", "queueEntryTransitionUndoFailed": "Erreur lors de l'annulation de la transition", - "queueEntryTransitionUndoSuccessful": "Annulation de la transition de l'entrée dans la file d'attente réussie", + "queueEntryTransitionUndoSuccessful": "Annulation de la transition de l'entrée de la file d'attente réussie", "queueEntryUpdateFailed": "Erreur lors de la mise à jour du statut de l'entrée dans la file d'attente", "queueEntryUpdateSuccessfully": "Entrée de la file d'attente mise à jour avec succès", "queueLocation": "Emplacement de la file d'attente", @@ -274,14 +274,14 @@ "temperature": "Température", "ticketNumber": "Numéro de Ticket", "time": "Temps", - "timeCannotBeInFuture": "Time cannot be in the future", + "timeCannotBeInFuture": " Le temps ne peut pas être dans le futur", "timeCannotBePriorToPreviousQueueEntry": "L’heure ne peut être antérieure au début de la file d’attente précédente: {{time}}", "timeOfTransition": "Heure de la transition", "tirageNotYetCompleted": "Le triage n'est pas encore terminé", "today": "Aujourd'hui", "totalPatients": "Nombre total de patients", "transition": "Transition", - "TransitionLatestQueueEntry": "Transitionner le patient vers la file d'attente la plus récente", + "TransitionLatestQueueEntry": "Transfert du patient vers la file d'attente la plus récente", "transitionPatient": "Transition du patient", "transitionPatientStatusOrQueue": "Sélectionnez un nouveau statut ou une file d'attente vers laquelle le patient doit être transféré.", "triageForm": "Formulaire de triage", diff --git a/packages/esm-ward-app/.fetch.swp b/packages/esm-ward-app/.fetch.swp deleted file mode 100644 index 9a648bb5e..000000000 Binary files a/packages/esm-ward-app/.fetch.swp and /dev/null differ diff --git a/packages/esm-ward-app/mock.tsx b/packages/esm-ward-app/mock.tsx new file mode 100644 index 000000000..d9b40cb80 --- /dev/null +++ b/packages/esm-ward-app/mock.tsx @@ -0,0 +1,54 @@ +import { mockAdmissionLocation, mockInpatientAdmissions, mockInpatientRequest } from '__mocks__'; +import { useAdmissionLocation } from './src/hooks/useAdmissionLocation'; +import { useInpatientAdmission } from './src/hooks/useInpatientAdmission'; +import { createAndGetWardPatientGrouping } from './src/ward-view/ward-view.resource'; +import { useInpatientRequest } from './src/hooks/useInpatientRequest'; +import { useWardPatientGrouping } from './src/hooks/useWardPatientGrouping'; + +jest.mock('./src/hooks/useAdmissionLocation', () => ({ + useAdmissionLocation: jest.fn(), +})); +jest.mock('./src/hooks/useInpatientAdmission', () => ({ + useInpatientAdmission: jest.fn(), +})); +jest.mock('./src/hooks/useInpatientRequest', () => ({ + useInpatientRequest: jest.fn(), +})); +jest.mock('./src/hooks/useWardPatientGrouping', () => ({ + useWardPatientGrouping: jest.fn(), +})); +const mockAdmissionLocationResponse = jest.mocked(useAdmissionLocation).mockReturnValue({ + error: undefined, + mutate: jest.fn(), + isValidating: false, + isLoading: false, + admissionLocation: mockAdmissionLocation, +}); +const mockInpatientAdmissionResponse = jest.mocked(useInpatientAdmission).mockReturnValue({ + data: mockInpatientAdmissions, + hasMore: false, + loadMore: jest.fn(), + isValidating: false, + isLoading: false, + error: undefined, + mutate: jest.fn(), + totalCount: mockInpatientAdmissions.length, +}); + +const mockInpatientRequestResponse = jest.mocked(useInpatientRequest).mockReturnValue({ + inpatientRequests: mockInpatientRequest, + hasMore: false, + loadMore: jest.fn(), + isValidating: false, + isLoading: false, + error: undefined, + mutate: jest.fn(), + totalCount: mockInpatientRequest.length, +}); + +export const mockWardPatientGroupDetails = jest.mocked(useWardPatientGrouping).mockReturnValue({ + admissionLocationResponse: mockAdmissionLocationResponse(), + inpatientAdmissionResponse: mockInpatientAdmissionResponse(), + inpatientRequestResponse: mockInpatientRequestResponse(), + ...createAndGetWardPatientGrouping(mockInpatientAdmissions, mockAdmissionLocation, mockInpatientRequest), +}); diff --git a/packages/esm-ward-app/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx b/packages/esm-ward-app/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx new file mode 100644 index 000000000..d5faca5b7 --- /dev/null +++ b/packages/esm-ward-app/src/action-menu-buttons/clinical-forms-workspace-siderail.component.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Document } from '@carbon/react/icons'; +import { useTranslation } from 'react-i18next'; +import { ActionMenuButton, launchWorkspace, useWorkspaces } from '@openmrs/esm-framework'; +import type { WardPatientWorkspaceProps } from '../types'; + +const ClinicalFormsWorkspaceSideRailIcon: React.FC = () => { + const { t } = useTranslation(); + const { workspaces } = useWorkspaces(); + + const formEntryWorkspaces = workspaces.filter((w) => w.name === 'ward-patient-form-entry-workspace'); + const recentlyOpenedForm = formEntryWorkspaces[0]; + + const isClinicalFormOpen = formEntryWorkspaces?.length >= 1; + + const launchPatientWorkspaceCb = () => { + if (isClinicalFormOpen) { + launchWorkspace('ward-patient-form-entry-workspace', { + workspaceTitle: recentlyOpenedForm?.additionalProps?.['workspaceTitle'], + }); + } else { + launchWorkspace('ward-patient-clinical-forms-workspace'); + } + }; + + return ( + } + label={t('clinicalForms', 'Clinical forms')} + iconDescription={t('clinicalForms', 'Clinical forms')} + handler={launchPatientWorkspaceCb} + type="ward-patient-clinical-form" + /> + ); +}; + +export default ClinicalFormsWorkspaceSideRailIcon; diff --git a/packages/esm-ward-app/src/hooks/useBeds.ts b/packages/esm-ward-app/src/hooks/useBeds.ts index 9be7e1b31..ca85b9554 100644 --- a/packages/esm-ward-app/src/hooks/useBeds.ts +++ b/packages/esm-ward-app/src/hooks/useBeds.ts @@ -1,5 +1,4 @@ -import { openmrsFetch, restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework'; -import useSWR from 'swr'; +import { restBaseUrl, useOpenmrsFetchAll } from '@openmrs/esm-framework'; import { type Bed, type BedStatus } from '../types/index'; interface BedSearchCriteria { diff --git a/packages/esm-ward-app/src/hooks/useEmrConfiguration.ts b/packages/esm-ward-app/src/hooks/useEmrConfiguration.ts index daa1ad2f6..2e2ba95ba 100644 --- a/packages/esm-ward-app/src/hooks/useEmrConfiguration.ts +++ b/packages/esm-ward-app/src/hooks/useEmrConfiguration.ts @@ -12,6 +12,8 @@ interface EmrApiConfigurationResponse { clinicianEncounterRole: OpenmrsResource; consultFreeTextCommentsConcept: OpenmrsResource; visitNoteEncounterType: OpenmrsResource; + inpatientNoteEncounterType: OpenmrsResource; + transferRequestEncounterType: OpenmrsResource; transferWithinHospitalEncounterType: OpenmrsResource; exitFromInpatientEncounterType: OpenmrsResource; supportsTransferLocationTag: LocationTag; @@ -65,6 +67,8 @@ const customRepProps = [ ['diagnosisSets', 'ref'], ['personImageDirectory', 'ref'], ['visitNoteEncounterType', 'ref'], + ['inpatientNoteEncounterType', 'ref'], + ['transferRequestEncounterType', 'ref'], ['consultEncounterType', 'ref'], ['diagnosisMetadata', 'ref'], ['narrowerThanConceptMapType', 'ref'], diff --git a/packages/esm-ward-app/src/index.ts b/packages/esm-ward-app/src/index.ts index 7474a605e..2d56c54f8 100644 --- a/packages/esm-ward-app/src/index.ts +++ b/packages/esm-ward-app/src/index.ts @@ -107,6 +107,16 @@ export const patientDischargeWorkspaceSideRailIcon = getAsyncLifecycle( options, ); +export const patientClinicalFormsWorkspace = getAsyncLifecycle( + () => import('./ward-workspace/patient-clinical-forms-workspace/patient-clinical-forms.workspace'), + options, +); + +export const clinicalFormWorkspaceSideRailIcon = getAsyncLifecycle( + () => import('./action-menu-buttons/clinical-forms-workspace-siderail.component'), + options, +); + export function startupApp() { registerBreadcrumbs([]); defineConfigSchema(moduleName, configSchema); @@ -117,7 +127,13 @@ export function startupApp() { registerFeatureFlag( 'bedmanagement-module', - 'Bed Management Module', + 'Bed management module', 'Enables features related to bed management / assignment. Requires the backend bed management module to be installed.', ); + + registerFeatureFlag( + 'ward-view-vertical-tiling', + 'Ward view vertical tiling', + 'Enable tiling of bed cards vertically in the ward view.', + ); } diff --git a/packages/esm-ward-app/src/routes.json b/packages/esm-ward-app/src/routes.json index 77f9d532a..7323ee840 100644 --- a/packages/esm-ward-app/src/routes.json +++ b/packages/esm-ward-app/src/routes.json @@ -55,6 +55,11 @@ "slot": "action-menu-ward-patient-items-slot", "component": "patientDischargeWorkspaceSideRailIcon" }, + { + "name": "clinical-forms-workspace-siderail-button", + "component": "clinicalFormWorkspaceSideRailIcon", + "slot": "action-menu-ward-patient-items-slot" + }, { "component": "admissionRequestNoteRowExtension", "name": "admission-request-note-card-row", @@ -122,6 +127,15 @@ "type": "ward-patient-discharge", "hasOwnSidebar": true, "sidebarFamily": "ward-patient" + }, + { + "name": "ward-patient-clinical-forms-workspace", + "component": "patientClinicalFormsWorkspace", + "title": "clinicalForms", + "type": "ward-patient-clinical-forms", + "hasOwnSidebar": true, + "sidebarFamily": "ward-patient", + "width": "wider" } ] } diff --git a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.component.tsx b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.component.tsx index 1e5d60416..9efc44045 100644 --- a/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.component.tsx +++ b/packages/esm-ward-app/src/ward-patient-card/ward-patient-card.component.tsx @@ -7,6 +7,7 @@ import WardPatientBedNumber from './row-elements/ward-patient-bed-number'; import WardPatientName from './row-elements/ward-patient-name'; import { WardPatientCardElement } from './ward-patient-card-element.component'; import styles from './ward-patient-card.scss'; +import { launchPatientWorkspace, setWardPatient } from './ward-patient-resource'; const WardPatientCard: WardPatientCard = (wardPatient) => { const { patient, bed } = wardPatient; @@ -57,9 +58,8 @@ const WardPatientCard: WardPatientCard = (wardPatient) => {