diff --git a/package.json b/package.json index 508be95c2a..75cda7e47b 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "packageManager": "yarn@3.6.3", "dependencies": { "@hookform/resolvers": "^3.3.1", + "classnames": "^2.3.2", "react-hook-form": "^7.46.2", "react-to-print": "^2.14.13", "zod": "^3.22.2" diff --git a/packages/esm-form-engine-app/translations/km.json b/packages/esm-form-engine-app/translations/km.json index bb79ccad1c..fe1d9a15af 100644 --- a/packages/esm-form-engine-app/translations/km.json +++ b/packages/esm-form-engine-app/translations/km.json @@ -1,8 +1,8 @@ { - "closeThisPanel": "Close this panel", - "errorTitle": "There was an error with this form", - "loading": "Loading", - "or": "or", - "thisList": "this list", - "tryAgainMessage": "Try opening another form from" + "closeThisPanel": "បិទប្រអប់នេះ", + "errorTitle": "ទម្រង់នេះមិនដំណើរការ", + "loading": "កំពុងចម្លង...", + "or": "ឬ", + "thisList": "បញ្ជីនេះ។", + "tryAgainMessage": "ព្យាយាមបើកទម្រង់ផ្សេងទៀត" } diff --git a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.html b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.html index 3e55d4e04d..c9f012020e 100644 --- a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.html +++ b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.html @@ -1,33 +1,55 @@
-

{{'errorWithForm' | translate}}

+

{{ 'errorWithForm' | translate }}

- {{'tryOpeningAnotherForm' | translate}} + {{ 'tryOpeningAnotherForm' | translate }}

or

-
+
-
+
- +
-
- - + +
+ + +
+
-
-
- +
+ +
diff --git a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss index a78542a755..f3215b3fe9 100644 --- a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss +++ b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.scss @@ -166,3 +166,20 @@ border-bottom: 1px solid var(--brand-03); padding: 0.625rem; } + +.saveAndCloseButtons { + display: flex; + flex-direction: column; + width: 100%; + row-gap: 0.625rem; + margin-top: 1rem; + padding-top: 1rem; + margin-left: 0.625rem; + @include type.type-style("body-short-01"); + border-top: 1px solid #a8a8a8; + + & button { + width: 100%; + padding-inline-end: 0; + } +} diff --git a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.ts b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.ts index 04273b6c47..a8dc037d6f 100644 --- a/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.ts +++ b/packages/esm-form-entry-app/src/app/fe-wrapper/fe-wrapper.component.ts @@ -129,6 +129,7 @@ export class FeWrapperComponent implements OnInit, OnDestroy { import( /* webpackInclude: /\.mjs$/ */ /* webpackChunkName: "./assets/l10n/locales/[request]"*/ + /* webpackMode: "lazy" */ `@/../../../node_modules/@angular/common/locales/${locale}.mjs` ).then((module) => registerLocaleData(module.default)); @@ -228,11 +229,7 @@ export class FeWrapperComponent implements OnInit, OnDestroy { }); } - this.programService.handleProgramEnrollmentAndDiscontinuation( - this.form, - this.singleSpaPropsService.getProp('patientUuid'), - encounterToSubmit, - ); + this.programService.handlePatientCareProgram(this.form, encounter.uuid); showToast({ critical: true, kind: 'success', diff --git a/packages/esm-form-entry-app/src/app/openmrs-api/location-resource.service.ts b/packages/esm-form-entry-app/src/app/openmrs-api/location-resource.service.ts index e558061f2c..4ffcb24fa2 100644 --- a/packages/esm-form-entry-app/src/app/openmrs-api/location-resource.service.ts +++ b/packages/esm-form-entry-app/src/app/openmrs-api/location-resource.service.ts @@ -17,7 +17,7 @@ export class LocationResourceService { ) {} public getLocationByUuid(uuid: string): Observable { - const url = this.getUrl(uuid); + const url = this.getLocationByUuidUrl(uuid); return this.http.get(url).pipe(catchError(() => this.getLocationByUuidFallback(uuid))); } @@ -26,21 +26,24 @@ export class LocationResourceService { } public searchLocation(searchText: string): Observable> { - return this.getAllLocations().pipe( - map((locations) => - locations.filter((location) => location.display.toLowerCase().includes(searchText.toLowerCase())), - ), - ); + return this.getAllLocations(searchText); } - private getAllLocations(): Observable> { - const url = this.getUrl(); + public getAllLocations(searchText?: string): Observable> { + let url = ''; + searchText ? (url = this.getUrl(searchText)) : (url = this.getUrl()); return this.http.get>(url).pipe(map((r) => r.results)); } - public getUrl(uuid?: string) { - return uuid - ? this.windowRef.openmrsRestBase + 'location/' + uuid + '?v=' + LocationResourceService.v - : this.windowRef.openmrsRestBase + 'location?q=&v=' + LocationResourceService.v; + public getLocationByUuidUrl(uuid?: string) { + if (uuid) { + return this.windowRef.openmrsRestBase + 'location/' + uuid + '?v=' + LocationResourceService.v; + } + } + + public getUrl(searchText?: string) { + return searchText + ? this.windowRef.openmrsRestBase + `location?q=${searchText}&v=${LocationResourceService.v}` + : this.windowRef.openmrsRestBase + 'location?v=' + LocationResourceService.v; } } diff --git a/packages/esm-form-entry-app/src/app/openmrs-api/program-resource.service.ts b/packages/esm-form-entry-app/src/app/openmrs-api/program-resource.service.ts index 5992b9ba0a..5c85454a0c 100644 --- a/packages/esm-form-entry-app/src/app/openmrs-api/program-resource.service.ts +++ b/packages/esm-form-entry-app/src/app/openmrs-api/program-resource.service.ts @@ -1,148 +1,117 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { catchError } from 'rxjs/operators'; +import { HttpClient } from '@angular/common/http'; import { WindowRef } from '../window-ref'; import { Form } from '@openmrs/ngx-formentry'; -import { EncounterCreate, MetaData, PatientProgram } from '../types'; -import { showNotification, showToast } from '@openmrs/esm-framework'; -import first from 'lodash-es/first'; - -interface ProgramEnrollmentPayload { - patient: string; - program: string; - dateEnrolled: string | Date; - dateCompleted: string | Date | null; - location: string; -} +import { MetaData } from '../types'; +import { parseDate, showNotification, showToast } from '@openmrs/esm-framework'; +import { SingleSpaPropsService } from '../single-spa-props/single-spa-props.service'; +import { EncounterResourceService } from './encounter-resource.service'; +import moment from 'moment'; @Injectable() export class ProgramResourceService { constructor( private httpClient: HttpClient, protected windowRef: WindowRef, + private singleSpaService: SingleSpaPropsService, + private encounterResourceService: EncounterResourceService, ) {} - private getBaseProgramsUrl(): string { + private programEnrollmentUrl(): string { return `${this.windowRef.nativeWindow.openmrsBase}/ws/rest/v1/programenrollment`; } - public fetchPatientPrograms(patientUuid: string): Observable { - const params = new HttpParams() - .set('patient', patientUuid) - .set('v', 'custom:(uuid,program:(uuid,name),dateEnrolled,dateCompleted,location:(uuid,name))'); - return this.httpClient.get(this.getBaseProgramsUrl(), { params }); - } + public handlePatientCareProgram(form: Form, encounterUuid: string) { + const careProgramMeta: MetaData = form.schema.meta?.programs; + if (!careProgramMeta) return; + const { uuid, isEnrollment, enrollmentDateQuestionId, discontinuationDateQuestionId } = careProgramMeta; - public createProgramEnrollment(payload: ProgramEnrollmentPayload): Observable { - return this.httpClient - .post(this.getBaseProgramsUrl(), payload) - .pipe(catchError((error) => this.handleError(error, 'Program Enrollment'))); - } + const getNodeValueById = (questionId) => form.searchNodeByQuestionId(questionId)?.[0]?.control.value; + const enrollmentDate = + isEnrollment && enrollmentDateQuestionId ? getNodeValueById(enrollmentDateQuestionId) : undefined; + const discontinuationDate = + isEnrollment && discontinuationDateQuestionId ? undefined : getNodeValueById(discontinuationDateQuestionId); - public updateProgramEnrollment(payload: ProgramEnrollmentPayload, enrollmentUuid: string): Observable { - return this.httpClient - .post(`${this.getBaseProgramsUrl()}/${enrollmentUuid}`, payload) - .pipe(catchError((error) => this.handleError(error, 'Program Update'))); - } + const locationUuid = form.dataSourcesContainer.dataSources['userLocation'].uuid; - public deleteProgramEnrollment(enrollmentUuid: string): Observable { - return this.httpClient - .delete(`${this.getBaseProgramsUrl()}/${enrollmentUuid}?purge=true`) - .pipe(catchError((error) => this.handleError(error, 'Program Deletion'))); + isEnrollment + ? this.enrollPatientToCareProgram(enrollmentDate, uuid, locationUuid, encounterUuid) + : this.discontinuePatientFromCareProgram(discontinuationDate, encounterUuid); } - private createProgramEnrollmentPayload( + public enrollPatientToCareProgram( + enrollmentDate: string, programUuid: string, - locationUuid: string, - patientUuid: string, - dateEnrolled: Date | string, - dateCompleted: Date | null, - ): ProgramEnrollmentPayload { - return { + locationUuuid: string, + encounterUuid: string, + ) { + const patientUuid = this.singleSpaService.getPropOrThrow('patientUuid'); + const enrolledDate = enrollmentDate ? enrollmentDate : new Date().toISOString(); + + const payload = { patient: patientUuid, program: programUuid, - dateEnrolled: dateEnrolled, - dateCompleted: dateCompleted ?? null, - location: locationUuid, + dateEnrolled: parseDate(enrolledDate), + dateCompleted: null, + location: locationUuuid, }; - } - - private showToastMessage(title: string, description: string, kind: 'success' | 'error'): void { - showToast({ title, description, kind }); - } - private handleError(error: any, operation: string): Observable { - this.showToastMessage(`${operation} has failed`, 'An error occurred', 'error'); - console.error(error); // Log to console for debugging - throw error; - } - - public handleProgramEnrollmentAndDiscontinuation( - form: Form, - patientUuid: string, - encounterToCreate: EncounterCreate, - ): void { - const meta = form.schema?.meta; - if (!meta) { - return; - } - - const programInfo: MetaData = meta.programs; - const date = programInfo.enrollmentDateQuestionId - ? first(form.searchNodeByQuestionId(programInfo.enrollmentDateQuestionId)).control.value - : new Date(); - - const payload = this.createProgramEnrollmentPayload( - programInfo.uuid, - encounterToCreate.location, - patientUuid, - date, - null, + this.httpClient.post(this.programEnrollmentUrl(), payload).subscribe( + (response) => { + showToast({ + title: 'Program enrollment', + description: 'Patient has been enrolled successfully', + kind: 'success', + }); + }, + (err) => { + // void created encounter to prevent enrollment missing an encounter + this.encounterResourceService.voidEncounter(encounterUuid); + showNotification({ + title: 'Enrollment error', + description: 'An error occurred during care program enrollment, this encounter has been voided', + kind: 'error', + }); + }, ); - - if (programInfo.isEnrollment) { - this.createProgramEnrollment(payload).subscribe(() => { - this.showToastMessage('Enrollment saved successfully', 'Patient has been enrolled in the program', 'success'); - }); - return; - } - - this.fetchPatientPrograms(patientUuid).subscribe((patientPrograms) => { - this.handleProgramDiscontinuation(form, patientPrograms, programInfo, payload); - }); } - private handleProgramDiscontinuation( - form: Form, - patientPrograms: { results: Array }, - programInfo: MetaData, - payload, - ): void { - const activePrograms = patientPrograms.results.filter(({ dateCompleted }) => dateCompleted === null); - if (activePrograms.length === 0) { - return; - } + public discontinuePatientFromCareProgram(discontinuationDate: string, encounterUuid: string) { + const { enrollmenrDetails: enrollmentDetails } = this.singleSpaService.getPropOrThrow('additionalProps'); + + const currentDateTime = new Date(); + const hour = currentDateTime.getHours(); + const minutes = currentDateTime.getMinutes(); + const seconds = currentDateTime.getSeconds(); + + const discontinuationDateTime = moment(discontinuationDate ?? new Date()) + .set('hour', hour) + .set('minute', minutes) + .set('second', seconds) + .toISOString(); + const payload = { + dateEnrolled: moment(enrollmentDetails.dateEnrolled).toISOString(), + dateCompleted: moment(discontinuationDateTime).toISOString(), + }; - activePrograms.sort( - (programA, programB) => new Date(programB.dateEnrolled).getTime() - new Date(programA.dateEnrolled).getTime(), + this.httpClient.post(`${this.programEnrollmentUrl()}/${enrollmentDetails?.uuid}`, payload).subscribe( + (resp) => { + showToast({ + title: 'Program discontinuation', + description: 'Patient has been discontinued from care successfully', + kind: 'success', + }); + }, + (error) => { + // void created encounter to prevent care program discontinuation missing an encounter + this.encounterResourceService.voidEncounter(encounterUuid); + showNotification({ + title: 'Discontinuation error', + description: 'An error occurred during care program discontinuation, this encounter has been voided', + kind: 'error', + }); + }, ); - - const patientProgramInfo = activePrograms[0]; - const discontinuationDate = first(form.searchNodeByQuestionId(programInfo.discontinuationDateQuestionId)).control - .value; - payload.dateCompleted = new Date(discontinuationDate); - delete payload.program; - delete payload.patient; - payload.dateEnrolled = new Date(patientProgramInfo.dateEnrolled); - - this.updateProgramEnrollment(payload, patientProgramInfo.uuid).subscribe(() => { - this.showToastMessage( - 'Discontinuation saved successfully', - 'Patient has been discontinued from the program', - 'success', - ); - }); } } diff --git a/packages/esm-form-entry-app/src/single-spa-props.ts b/packages/esm-form-entry-app/src/single-spa-props.ts index 808d53978e..6b24d93018 100644 --- a/packages/esm-form-entry-app/src/single-spa-props.ts +++ b/packages/esm-form-entry-app/src/single-spa-props.ts @@ -4,22 +4,48 @@ import { Encounter, EncounterCreate } from './app/types'; export const singleSpaPropsSubject = new ReplaySubject(1); -// Add any custom single-spa props you have to this type def -// https://single-spa.js.org/docs/building-applications.html#custom-props -export type SingleSpaProps = AppProps & { - formUuid: string; +type VisitProperties = { visitTypeUuid?: string; - encounterUuid?: string; visitUuid?: string; visitStartDatetime: string; visitStopDatetime: string; - view: string; - closeWorkspace: () => void; - patient: any; - isOffline: boolean; - patientUuid: string; +}; + +type EncounterProperties = { + encounterUuid?: string; handleEncounterCreate?: (encounter: EncounterCreate) => void; handlePostResponse?: (encounter: Encounter) => void; +}; + +type PatientProperties = { + patient: any; + patientUuid: string; +}; + +type UIBehavior = { + view: string; + closeWorkspace: () => void; handleOnValidate?: (valid: boolean) => void; showDiscardSubmitButtons?: boolean; }; + +type ApplicationStatus = { + isOffline: boolean; +}; + +type Form = { + formUuid: string; +}; + +// Add any custom single-spa props you have to this type def +// https://single-spa.js.org/docs/building-applications.html#custom-props +export type SingleSpaProps = AppProps & + Form & + UIBehavior & + VisitProperties & + EncounterProperties & + PatientProperties & + UIBehavior & + ApplicationStatus & { + additionalProps?: any; + }; diff --git a/packages/esm-patient-allergies-app/translations/km.json b/packages/esm-patient-allergies-app/translations/km.json index 5f70785ebe..4dd6683854 100644 --- a/packages/esm-patient-allergies-app/translations/km.json +++ b/packages/esm-patient-allergies-app/translations/km.json @@ -8,7 +8,7 @@ "allergyNowVisible": "ឥឡូវនេះវាអាចមើលឃើញនៅលើទំព័រអាឡែស៊ី", "allergySaved": "អាឡែរហ្សីត្រូវបានរក្សាទុក", "allergySaveError": "កំហុសក្នុងការរក្សាទុកអាឡែរហ្សី", - "dateOfOnsetAndComments": "Date of onset and comments", + "dateOfOnsetAndComments": "កាលបរិច្ឆេទចាប់ផ្តើម និងមតិយោបល់", "discard": "បោះបង់", "drug": "ថ្នាំ", "environmental": "បរិស្ថាន", @@ -19,7 +19,7 @@ "mild": "ស្រាល", "moderate": "មធ្យម", "name": "ឈ្មោះ", - "onsetDateAndComments": "Onset date and comments", + "onsetDateAndComments": "កាលបរិច្ឆេទចាប់ផ្តើម និងមតិយោបល់", "otherNonCodedAllergen": "សារធាតុអាឡែហ្សីនដែលមិនមានកូដផ្សេងទៀត។", "otherNonCodedAllergicReaction": "ប្រតិកម្មអាលែហ្សីដែលមិនមានលេខកូដផ្សេងទៀត។", "reaction": "Reaction", diff --git a/packages/esm-patient-attachments-app/src/attachments/attachments-overview.component.tsx b/packages/esm-patient-attachments-app/src/attachments/attachments-overview.component.tsx index 86759bf8f4..de42eaf7bb 100644 --- a/packages/esm-patient-attachments-app/src/attachments/attachments-overview.component.tsx +++ b/packages/esm-patient-attachments-app/src/attachments/attachments-overview.component.tsx @@ -100,7 +100,13 @@ const AttachmentsOverview: React.FC<{ patientUuid: string }> = ({ patientUuid }) ); if (!attachments.length) { - return ; + return ( + + ); } return ( diff --git a/packages/esm-patient-attachments-app/translations/am.json b/packages/esm-patient-attachments-app/translations/am.json index d144015e80..c14a9f07af 100644 --- a/packages/esm-patient-attachments-app/translations/am.json +++ b/packages/esm-patient-attachments-app/translations/am.json @@ -7,6 +7,8 @@ "attachmentCaptionInstruction": "Enter caption", "attachments": "Attachments", "Attachments": "Attachments", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", "cameraError": "Camera Error", "cancel": "Cancel", "changeImage": "Change image", diff --git a/packages/esm-patient-attachments-app/translations/ar.json b/packages/esm-patient-attachments-app/translations/ar.json index 1344de490e..828b374391 100644 --- a/packages/esm-patient-attachments-app/translations/ar.json +++ b/packages/esm-patient-attachments-app/translations/ar.json @@ -7,6 +7,8 @@ "attachmentCaptionInstruction": "أدخل الوصف", "attachments": "المرفقات", "Attachments": "المرفقات", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", "cameraError": "خطأ في الكاميرا", "cancel": "إلغاء", "changeImage": "غير الصورة", diff --git a/packages/esm-patient-attachments-app/translations/en.json b/packages/esm-patient-attachments-app/translations/en.json index d5bd8cbecd..f4ef19c625 100644 --- a/packages/esm-patient-attachments-app/translations/en.json +++ b/packages/esm-patient-attachments-app/translations/en.json @@ -7,6 +7,8 @@ "attachmentCaptionInstruction": "Enter caption", "attachments": "Attachments", "Attachments": "Attachments", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", "cameraError": "Camera Error", "cancel": "Cancel", "changeImage": "Change image", diff --git a/packages/esm-patient-attachments-app/translations/es.json b/packages/esm-patient-attachments-app/translations/es.json index 9750860d7c..fa997661b3 100644 --- a/packages/esm-patient-attachments-app/translations/es.json +++ b/packages/esm-patient-attachments-app/translations/es.json @@ -7,6 +7,8 @@ "attachmentCaptionInstruction": "Introduzca título", "attachments": "Archivos adjuntos", "Attachments": "Archivos adjuntos", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", "cameraError": "Error de cámara", "cancel": "Cancelar", "changeImage": "Cambiar imagen", diff --git a/packages/esm-patient-attachments-app/translations/fr.json b/packages/esm-patient-attachments-app/translations/fr.json index 10a231606d..a48f58839b 100644 --- a/packages/esm-patient-attachments-app/translations/fr.json +++ b/packages/esm-patient-attachments-app/translations/fr.json @@ -7,6 +7,8 @@ "attachmentCaptionInstruction": "Ajouter une légende pour l'image", "attachments": "Pièces jointes", "Attachments": "Pièces jointes", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", "cameraError": "Erreur avec l'appareil photo", "cancel": "Annuller", "changeImage": "Changer l'image", diff --git a/packages/esm-patient-attachments-app/translations/he.json b/packages/esm-patient-attachments-app/translations/he.json index 642b3975b8..05cd8997ed 100644 --- a/packages/esm-patient-attachments-app/translations/he.json +++ b/packages/esm-patient-attachments-app/translations/he.json @@ -7,6 +7,8 @@ "attachmentCaptionInstruction": "הכנס תיאור לתמונה", "attachments": "קבצים מצורפים", "Attachments": "קבצים מצורפים", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", "cameraError": "שגיאת מצלמה", "cancel": "ביטול", "changeImage": "שנה תמונה", diff --git a/packages/esm-patient-attachments-app/translations/km.json b/packages/esm-patient-attachments-app/translations/km.json index cb27bb72a8..f84a4393f8 100644 --- a/packages/esm-patient-attachments-app/translations/km.json +++ b/packages/esm-patient-attachments-app/translations/km.json @@ -1,12 +1,14 @@ { "add": "បន្ថែម", "addAttachment": "បន្ថែមឯកសារភ្ជាប់", - "addAttachment_title": "Add Attachment", + "addAttachment_title": "បន្ថែមឯកសារភ្ជាប់", "addImage": "បន្ថែមរូបភាព +", "addMoreAttachments": "បន្ថែមឯកសារភ្ជាប់បន្ថែម", "attachmentCaptionInstruction": "បញ្ចូលចំណងជើងសម្រាប់រូបភាព", "attachments": "ឯកសារភ្ជាប់", "Attachments": "ឯកសារភ្ជាប់", + "attachmentsInLowerCase": "ឯកសារភ្ជាប់", + "attachmentsInProperFormat": "ឯកសារភ្ជាប់", "cameraError": "កំហុសកាមេរ៉ា", "cancel": "បោះបង់", "changeImage": "ផ្លាស់ប្តូររូបភាព", @@ -14,7 +16,7 @@ "closePreview": "បិទការមើល", "dateUploaded": "កាលបរិច្ឆេទដែលបានបង្ហោះ", "delete": "លុបចោល", - "deleteAttachmentConfirmationText": "", + "deleteAttachmentConfirmationText": "Are you sure you want to delete this {{attachmentType}}? This action can't be undone.", "deleteImage": "លុបរូបភាពចោល", "error": "កំហុស", "errorUploading": "កំហុសក្នុងការបង្ហោះឯកសារ", diff --git a/packages/esm-patient-banner-app/translations/km.json b/packages/esm-patient-banner-app/translations/km.json index aca8444b23..72876c65c7 100644 --- a/packages/esm-patient-banner-app/translations/km.json +++ b/packages/esm-patient-banner-app/translations/km.json @@ -2,8 +2,8 @@ "actions": "ជ្រើសរើសមុខងារ", "activeVisit": "ការមកពិនិត្យជំងឺសកម្ម", "address": "អាស័យដ្ឋាន", - "address1": "Address line 1", - "address2": "Address line 2", + "address1": "អាសយដ្ឋាន​ជួរទី១", + "address2": "អ​ស​យ​ដ្ឋាន​ជួរ​ទី 2", "city": "ទីក្រុង", "cityVillage": "ទីក្រុង", "contactDetails": "ព័ត៌មានលម្អិតទំនាក់ទំនង", @@ -16,13 +16,13 @@ "loading": "កំពុងផ្ទុក...", "male": "ប្រុស", "other": "ផ្សេងៗ", - "patientLists": "Patient Lists", - "postalCode": "Postal code", + "patientLists": "បញ្ជីអ្នកជំងឺ", + "postalCode": "លេខ​កូដ​តំបន់", "relationships": "ទំនាក់ទំនង", - "seeMoreLists_other": "See {{count}} more lists", + "seeMoreLists_other": "មើលបញ្ជី {{count}} ទៀត", "showDetails": "បង្ហាញលម្អិត", "started": "បានចាប់ផ្តើម", - "state": "State", - "stateProvince": "State", + "state": "រដ្ឋ", + "stateProvince": "រដ្ឋ", "unknown": "មិនដឹង" } diff --git a/packages/esm-patient-biometrics-app/src/biometrics/biometrics-base.component.tsx b/packages/esm-patient-biometrics-app/src/biometrics/biometrics-base.component.tsx index 709b4211a9..328836621f 100644 --- a/packages/esm-patient-biometrics-app/src/biometrics/biometrics-base.component.tsx +++ b/packages/esm-patient-biometrics-app/src/biometrics/biometrics-base.component.tsx @@ -44,11 +44,11 @@ const BiometricsBase: React.FC = ({ patientUuid, pageSize, ); const tableHeaders = [ - { key: 'date', header: 'Date and time' }, - { key: 'weight', header: withUnit('Weight', conceptUnits.get(config.concepts.weightUuid) ?? '') }, - { key: 'height', header: withUnit('Height', conceptUnits.get(config.concepts.heightUuid) ?? '') }, - { key: 'bmi', header: `BMI (${bmiUnit})` }, - { key: 'muac', header: withUnit('MUAC', conceptUnits.get(config.concepts.muacUuid) ?? '') }, + { key: 'date', header: t('dateAndTime', 'Date and time') }, + { key: 'weight', header: withUnit(t('weight', 'Weight'), conceptUnits.get(config.concepts.weightUuid) ?? '') }, + { key: 'height', header: withUnit(t('height', 'Height'), conceptUnits.get(config.concepts.heightUuid) ?? '') }, + { key: 'bmi', header: `${t('bmi', 'BMI')} (${bmiUnit})` }, + { key: 'muac', header: withUnit(t('muac', 'MUAC'), conceptUnits.get(config.concepts.muacUuid) ?? '') }, ]; const tableRows = React.useMemo( diff --git a/packages/esm-patient-biometrics-app/src/biometrics/biometrics-chart.component.tsx b/packages/esm-patient-biometrics-app/src/biometrics/biometrics-chart.component.tsx index 096706aac0..a2a7d93e3b 100644 --- a/packages/esm-patient-biometrics-app/src/biometrics/biometrics-chart.component.tsx +++ b/packages/esm-patient-biometrics-app/src/biometrics/biometrics-chart.component.tsx @@ -33,7 +33,7 @@ const BiometricsChart: React.FC = ({ patientBiometrics, co const { t } = useTranslation(); const { bmiUnit } = config.biometrics; const [selectedBiometrics, setSelectedBiometrics] = React.useState({ - title: `Weight (${conceptUnits.get(config.concepts.weightUuid) ?? ''})`, + title: `${t('weight', 'Weight')} (${conceptUnits.get(config.concepts.weightUuid) ?? ''})`, value: 'weight', groupName: 'weight', }); @@ -62,7 +62,7 @@ const BiometricsChart: React.FC = ({ patientBiometrics, co title: selectedBiometrics.title, axes: { bottom: { - title: 'Date', + title: t('date', 'Date'), mapsTo: 'key', scaleType: ScaleTypes.LABELS, }, @@ -100,9 +100,12 @@ const BiometricsChart: React.FC = ({ patientBiometrics, co {[ - { id: 'weight', label: `Weight (${conceptUnits.get(config.concepts.weightUuid) ?? ''})` }, - { id: 'height', label: `Height (${conceptUnits.get(config.concepts.heightUuid) ?? ''})` }, - { id: 'bmi', label: `BMI (${bmiUnit})` }, + { + id: 'weight', + label: `${t('weight', 'Weight')} (${conceptUnits.get(config.concepts.weightUuid) ?? ''})`, + }, + { id: 'height', label: `{t('height',"Height")} (${conceptUnits.get(config.concepts.heightUuid) ?? ''})` }, + { id: 'bmi', label: `${t('bmi', 'BMI')} (${bmiUnit})` }, ].map(({ id, label }) => ( { const navMenuItems = useAssignedExtensions('patient-chart-dashboard-slot').map((extension) => extension.id); const { logo } = useConfig(); const { systemVisitEnabled } = useSystemVisitSetting(); + const isTablet = useLayoutType() === 'tablet'; const showHamburger = useLayoutType() !== 'large-desktop' && navMenuItems.length > 0; @@ -170,7 +171,7 @@ const VisitHeader: React.FC = () => { isActive={isSideMenuExpanded} /> )} - +
{logo?.src ? ( {logo.alt} diff --git a/packages/esm-patient-chart-app/src/visit-header/visit-header.scss b/packages/esm-patient-chart-app/src/visit-header/visit-header.scss index e81a2f2d54..7397c78cdc 100644 --- a/packages/esm-patient-chart-app/src/visit-header/visit-header.scss +++ b/packages/esm-patient-chart-app/src/visit-header/visit-header.scss @@ -21,6 +21,11 @@ margin-left: spacing.$spacing-04; } +.navLogoTablet { + margin-right: spacing.$spacing-04; + margin-left: spacing.$spacing-02; +} + .startVisitButton { @include brand-03(background-color); @@ -90,6 +95,7 @@ .tag { margin: 0.25rem 0; + min-width: 4rem; } .priorityTag { @@ -97,4 +103,5 @@ @include type.type-style('label-01'); color: #943d00; background-color: #ffc9a3; + min-width: 4rem; } diff --git a/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.component.tsx b/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.component.tsx index 8ad727199c..cfe552548e 100644 --- a/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.component.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { showModal } from '@openmrs/esm-framework'; +import { showModal, useLayoutType } from '@openmrs/esm-framework'; import styles from './edit-queue-entry.scss'; import { MappedVisitQueueEntry } from './queue.resource'; import { Edit } from '@carbon/react/icons'; @@ -12,6 +12,7 @@ interface EditQueueEntryProps { export const EditQueueEntry: React.FC = ({ queueEntry }) => { const { t } = useTranslation(); + const isTablet = useLayoutType() === 'tablet'; const launchEditPriorityModal = useCallback(() => { const dispose = showModal('edit-queue-entry-status-modal', { closeModal: () => dispose(), @@ -23,10 +24,11 @@ export const EditQueueEntry: React.FC = ({ queueEntry }) => ); }; diff --git a/packages/esm-patient-chart-app/src/visit/queue-entry/queue.resource.tsx b/packages/esm-patient-chart-app/src/visit/queue-entry/queue.resource.tsx index dd787bfa7f..f9a883a3e2 100644 --- a/packages/esm-patient-chart-app/src/visit/queue-entry/queue.resource.tsx +++ b/packages/esm-patient-chart-app/src/visit/queue-entry/queue.resource.tsx @@ -67,11 +67,12 @@ interface UseVisitQueueEntries { isLoading: boolean; isError: Error; isValidating?: boolean; + mutate: () => void; } export function useVisitQueueEntry(patientUuid, visitUuid): UseVisitQueueEntries { const apiUrl = `/ws/rest/v1/visit-queue-entry?patient=${patientUuid}`; - const { data, error, isLoading, isValidating } = useSWR<{ data: { results: Array } }, Error>( + const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: { results: Array } }, Error>( apiUrl, openmrsFetch, ); @@ -105,6 +106,7 @@ export function useVisitQueueEntry(patientUuid, visitUuid): UseVisitQueueEntries isLoading, isError: error, isValidating, + mutate, }; } diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx index 9cb82f9920..ebbbf27ec2 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx @@ -53,6 +53,7 @@ import BaseVisitType from './base-visit-type.component'; import LocationSelector from './location-selection.component'; import VisitAttributeTypeFields from './visit-attribute-type.component'; import styles from './visit-form.scss'; +import { useVisitQueueEntry } from '../queue-entry/queue.resource'; export type VisitFormData = { visitDate: Date; @@ -81,7 +82,7 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor const visitHeaderSlotState = useMemo(() => ({ patientUuid }), [patientUuid]); const { activePatientEnrollment, isLoading } = useActivePatientEnrollment(patientUuid); const allVisitTypes = useVisitTypes(); - const { mutate } = useVisit(patientUuid); + const { mutate: mutateVisit } = useVisit(patientUuid); const [ignoreChanges, setIgnoreChanges] = useState(true); const [errorFetchingResources, setErrorFetchingResources] = useState<{ blockSavingForm: boolean; @@ -89,6 +90,8 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor const [upcomingAppointment, setUpcomingAppointment] = useState(null); const upcomingAppointmentState = useMemo(() => ({ patientUuid, setUpcomingAppointment }), [patientUuid]); const visitQueueNumberAttributeUuid = config.visitQueueNumberAttributeUuid; + const [visitUuid, setVisitUuid] = useState(''); + const { mutate: mutateQueueEntry } = useVisitQueueEntry(patientUuid, visitUuid); const visitFormSchema = useMemo(() => { const visitAttributes = (config.visitAttributeTypes ?? [])?.reduce( @@ -173,7 +176,7 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor if (response.status === 201) { if (config.showServiceQueueFields) { // retrieve values from queue extension - + setVisitUuid(response.data.uuid); const queueLocation = event?.target['queueLocation']?.value; const serviceUuid = event?.target['service']?.value; const priority = event?.target['priority']?.value; @@ -193,7 +196,8 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor ).then( ({ status }) => { if (status === 201) { - mutate(); + mutateVisit(); + mutateQueueEntry(); showToast({ kind: 'success', title: t('visitStarted', 'Visit started'), @@ -225,7 +229,7 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor saveAppointment(appointmentPayload, abortController).then( ({ status }) => { if (status === 201) { - mutate(); + mutateVisit(); showToast({ critical: true, kind: 'success', @@ -244,7 +248,7 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor }, ); } - mutate(); + mutateVisit(); closeWorkspace(); showToast({ @@ -272,7 +276,7 @@ const StartVisitForm: React.FC = ({ patientUuid, closeWor config.showServiceQueueFields, config.showUpcomingAppointments, visitQueueNumberAttributeUuid, - mutate, + mutateVisit, patientUuid, upcomingAppointment, t, diff --git a/packages/esm-patient-chart-app/src/workspace/workspace-window.component.tsx b/packages/esm-patient-chart-app/src/workspace/workspace-window.component.tsx index c61c84fe13..43d4f9d53b 100644 --- a/packages/esm-patient-chart-app/src/workspace/workspace-window.component.tsx +++ b/packages/esm-patient-chart-app/src/workspace/workspace-window.component.tsx @@ -36,6 +36,7 @@ const WorkspaceWindow: React.FC = () => { }, [workspaces, patientUuid]); const workspaceTitle = workspaces[0]?.additionalProps?.['workspaceTitle'] ?? workspaces[0]?.title ?? ''; + const onCloseWorkspace = workspaces[0]?.additionalProps?.['onCloseWorkspace'] ?? null; const { canHide = false, canMaximize = false, @@ -59,7 +60,7 @@ const WorkspaceWindow: React.FC = () => { className={`${styles.header} ${maximized ? `${styles.fullWidth}` : `${styles.dynamicWidth}`}`} > {layout === 'tablet' && !canHide && ( - } onClick={closeWorkspace} /> + } onClick={onCloseWorkspace ?? closeWorkspace} /> )} {workspaceTitle} @@ -89,7 +90,7 @@ const WorkspaceWindow: React.FC = () => { closeWorkspace?.()} + onClick={() => onCloseWorkspace?.() ?? closeWorkspace?.()} size="lg" > @@ -98,7 +99,11 @@ const WorkspaceWindow: React.FC = () => { )} {layout === 'tablet' && canHide && ( - closeWorkspace?.()}> + onCloseWorkspace?.() ?? closeWorkspace?.()} + > )} diff --git a/packages/esm-patient-common-lib/src/form-entry/form-entry.ts b/packages/esm-patient-common-lib/src/form-entry/form-entry.ts index 27908b5c5b..022816f59d 100644 --- a/packages/esm-patient-common-lib/src/form-entry/form-entry.ts +++ b/packages/esm-patient-common-lib/src/form-entry/form-entry.ts @@ -5,4 +5,5 @@ export interface FormEntryProps { visitTypeUuid?: string; visitStartDatetime?: string; visitStopDatetime?: string; + additionalProps?: Record; } diff --git a/packages/esm-patient-forms-app/src/forms/form-entry.component.tsx b/packages/esm-patient-forms-app/src/forms/form-entry.component.tsx index c12a664bd3..e9972db637 100644 --- a/packages/esm-patient-forms-app/src/forms/form-entry.component.tsx +++ b/packages/esm-patient-forms-app/src/forms/form-entry.component.tsx @@ -8,7 +8,8 @@ interface FormEntryComponentProps extends DefaultWorkspaceProps { } const FormEntry: React.FC = ({ patientUuid, closeWorkspace, mutateForm, formInfo }) => { - const { encounterUuid, formUuid, visitStartDatetime, visitStopDatetime, visitTypeUuid, visitUuid } = formInfo || {}; + const { encounterUuid, formUuid, visitStartDatetime, visitStopDatetime, visitTypeUuid, visitUuid, additionalProps } = + formInfo || {}; const { patient } = usePatient(patientUuid); const { currentVisit } = useVisitOrOfflineVisit(patientUuid); const [showForm, setShowForm] = useState(true); @@ -27,6 +28,7 @@ const FormEntry: React.FC = ({ patientUuid, closeWorksp typeof mutateForm === 'function' && mutateForm(); closeWorkspace(); }, + additionalProps, }), [ formUuid, diff --git a/packages/esm-patient-immunizations-app/translations/km.json b/packages/esm-patient-immunizations-app/translations/km.json index 0b3b768fe0..66d4dfa647 100644 --- a/packages/esm-patient-immunizations-app/translations/km.json +++ b/packages/esm-patient-immunizations-app/translations/km.json @@ -1,9 +1,9 @@ { - "add": "Add", - "cancel": "Cancel", + "add": "បន្ថែម", + "cancel": "បោះបង់", "errorSaving": "Error saving vaccination", "expirationDate": "Expiration Date", - "goToSummary": "Go to Summary", + "goToSummary": "ចូលទៅកាន់តារាងសង្ខេប", "immunizations": "ការចាក់ថ្នាំបង្ការរោគ", "Immunizations": "ការចាក់ថ្នាំបង្ការរោគ", "lotNumber": "Lot Number", @@ -11,7 +11,7 @@ "pleaseSelect": "Please select", "recentVaccination": "Recent vaccination", "save": "Save", - "seeAll": "See all", + "seeAll": "ឃើញ​ទាំងអស់", "sequence": "Sequence", "singleDoseOn": "Single Dose on", "vaccinationDate": "Vaccination date", diff --git a/packages/esm-patient-labs-app/translations/km.json b/packages/esm-patient-labs-app/translations/km.json index c78e881b77..c64e6bd4bf 100644 --- a/packages/esm-patient-labs-app/translations/km.json +++ b/packages/esm-patient-labs-app/translations/km.json @@ -9,7 +9,7 @@ "ordered": "បានតម្រៀបតាមលំដាប់លំដោយ", "recentResults": "លទ្ធផលថ្មីៗ", "recentTestResults": "លទ្ធផលតេស្តថ្មីៗ", - "referenceRange": "Reference range", + "referenceRange": "ចន្លោះតម្លៃយោង", "removeFromBasket": "ដកចេញពីកញ្ចប់", "resetTreeText": "កំណត់មែកធាងទិន្នន័យ(Tree)ឡើងវិញ", "resulted": "លទ្ធផល", @@ -17,10 +17,10 @@ "returnToTimeline": "ត្រឡប់ទៅបន្ទាត់ពេលវេលា", "seeAllResults": "មើលលទ្ធផលទាំងអស់", "showTree": "Sបង្ហាញមែកធាងទិន្នន័យ(Tree)", - "Test Results": "លទ្ធផល\u200bតេ\u200bស្ត", + "Test Results": "លទ្ធផលតេស្ត", "testName": "ធ្វើតេស្តលើឈ្មោះ", "testResults": "លទ្ធផលតេស្ត", - "testResults_title": "លទ្ធផល\u200bតេ\u200bស្ត", + "testResults_title": "លទ្ធផលតេស្ត", "timeline": "បន្ទាត់ពេលវេលា", "timeOfTest": "ពេលវេលានៃការធ្វើតេស្ត", "tree": "មែកធាងទិន្នន័យ(Tree)", diff --git a/packages/esm-patient-medications-app/src/drug-order-basket-panel/drug-order-basket-panel.extension.tsx b/packages/esm-patient-medications-app/src/drug-order-basket-panel/drug-order-basket-panel.extension.tsx index 6eca3f7e08..f1d60f7840 100644 --- a/packages/esm-patient-medications-app/src/drug-order-basket-panel/drug-order-basket-panel.extension.tsx +++ b/packages/esm-patient-medications-app/src/drug-order-basket-panel/drug-order-basket-panel.extension.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'; import { Button, Tile } from '@carbon/react'; import { Add, ChevronDown, ChevronUp } from '@carbon/react/icons'; import { useLayoutType } from '@openmrs/esm-framework'; -import { launchPatientWorkspace, useOrderBasket } from '@openmrs/esm-patient-common-lib'; +import { closeWorkspace, launchPatientWorkspace, useOrderBasket } from '@openmrs/esm-patient-common-lib'; import { prepMedicationOrderPostData } from '../api/api'; import type { DrugOrderBasketItem } from '../types'; import OrderBasketItemTile from './order-basket-item-tile.component'; @@ -23,8 +23,15 @@ export default function DrugOrderBasketPanelExtension() { const revisedOrderBasketItems = orders.filter((x) => x.action === 'REVISE'); const discontinuedOrderBasketItems = orders.filter((x) => x.action === 'DISCONTINUE'); + const onClose = useCallback(() => { + closeWorkspace('add-drug-order', true); + launchPatientWorkspace('order-basket'); + }, []); + const openDrugSearch = () => { - launchPatientWorkspace('add-drug-order'); + launchPatientWorkspace('add-drug-order', { + onCloseWorkspace: onClose, + }); }; const openDrugForm = (order: DrugOrderBasketItem) => { diff --git a/packages/esm-patient-medications-app/translations/km.json b/packages/esm-patient-medications-app/translations/km.json index 3044444177..e44c797e07 100644 --- a/packages/esm-patient-medications-app/translations/km.json +++ b/packages/esm-patient-medications-app/translations/km.json @@ -5,19 +5,19 @@ "add": "បន្ថែម", "backToOrderBasket": "ត្រឡប់ទៅប្រអប់តម្រៀបតាមលំដាប់លំដោយវិញ", "clearSearchResults": "លទ្ធផលច្បាស់លាស់", - "decrement": "Decrement", + "decrement": "បន្ថយ", "details": "ព័ត៌មានលម្អិត", "directlyAddToBasket": "បញ្ចូលទៅក្នុង(ប្រអប់)កញ្ចប់ភ្លាមៗ", "discard": "បោះបង់", "discontinue": "បញ្ឈប់", - "dispensingInformation": "3. Dispensing instructions", + "dispensingInformation": "3. ការណែនាំអំពីការផ្តល់ឱសថ", "dosageInstructions": "1. ការណែនាំអំពីកំរិតប្រើប្រាស់", "dose": "កំរិតប្រើប្រាស់", - "drugAlreadyPrescribed": "Already prescribed", - "duration": "Duration", - "durationUnit": "Duration unit", - "durationUnitPlaceholder": "Duration Unit", - "editDispensingUnit": "Quantity unit", + "drugAlreadyPrescribed": "បានចេញវេជ្ជបញ្ជារួចហើយ", + "duration": "រយៈពេល", + "durationUnit": "ឯកតារយៈពេល", + "durationUnitPlaceholder": "ឯកតារយៈពេល", + "editDispensingUnit": "ឯកតាចំនួន", "editDosageUnitsPlaceholder": "ឯកតា", "editDosageUnitsTitle": "ឯកតាកំរិតប្រើប្រាស់", "editDoseComboBoxPlaceholder": "កំរិតប្រើប្រាស់", @@ -26,55 +26,55 @@ "editRouteComboBoxTitle": "បញ្ចូល ផ្លូវបញ្ចូលថ្នាំ", "endDate": "កាលបរិច្ឆេទបញ្ចប់", "error": "កំហុស/លំអៀង", - "errorFetchingDrugOrderTemplates": "Error fetching drug order templates", - "errorFetchingDrugResults": "Error fetching results for \"{{searchTerm}}\"", - "errorFetchingOrderConfig": "Error occured when fetching Order config", - "female": "Female", + "errorFetchingDrugOrderTemplates": "មាន​បញ្ហា​ក្នុង​ការ​ទាញយក​ទម្រង់គំរូវេជ្ជបញ្ជាសម្រាប់ឱសថ", + "errorFetchingDrugResults": "មាន​បញ្ហាក្នុងការទាញយកលទ្ធផលសម្រាប់ \"{{searchTerm}}\"", + "errorFetchingOrderConfig": "មាន​បញ្ហានៅពេលទាញយកការកំណត់វេជ្ជបញ្ជា", + "female": "ស្រី", "freeTextDosage": "លំហទំនេរសម្រាប់វាយអត្ថបទកំរិតប្រើប្រាស់", - "goToDrugOrderForm": "Order form", - "increment": "Increment", + "goToDrugOrderForm": "ទម្រង់គំរូវេជ្ជបញ្ជា", + "increment": "បង្កើន", "indication": "ការចង្អុលបង្ហាញ", "indicationPlaceholder": "ឧ. \"សម្ពាធឈាម\"", - "male": "Male", + "male": "ប្រុស", "medicationDurationAndUnit": "សម្រាប់ {រយៈពេល} {ឯកតារយៈពេល}", "medicationIndefiniteDuration": "រយៈពេលមិនកំណត់", "Medications": "ថ្នាំ", "modify": "កែប្រែ", "none": "គ្មាន", - "noResultsForDrugSearch": "No results to display for \"{{searchTerm}}\"", - "onDate": "on", + "noResultsForDrugSearch": "គ្មានលទ្ធផលដែលត្រូវបង្ហាញសម្រាប់ \"{{searchTerm}}\"", + "onDate": "នៅលើ", "orderActionDiscontinue": "បញ្ឈប់", "orderActionNew": "ថ្មី។", - "orderActionRenew": "Renew", - "orderActionRevise": "Modify", + "orderActionRenew": "បន្ត", + "orderActionRevise": "ផ្លាស់ប្តូរ", "orderForm": "ទម្រង់វេជ្ជបញ្ជា", - "other": "Other", + "other": "ផ្សេងៗ", "pastMedicationsDisplayText": "ថ្នាំដែលបានប្រើប្រាស់កន្លងមក", "pastMedicationsHeaderTitle": "ថ្នាំដែលបានប្រើប្រាស់កន្លងមក", "pastMedicationsTableTitle": "ថ្នាំដែលបានប្រើប្រាស់កន្លងមក", "patientInstructions": "ការណែនាំអ្នកជំងឺ", "patientInstructionsPlaceholder": "ការណែនាំកម្រិតថ្នាំបន្ថែម (ឧ. \"របៀបប្រើប្រាស់ថ្នាំក្រោយបរិភាគអាហារ\")", - "prescriptionDuration": "2. Prescription duration", - "prescriptionRefills": "Prescription refills", - "print": "Print", - "printedBy": "Printed by", + "prescriptionDuration": "2. រយៈពេលនៃវេជ្ជបញ្ជា", + "prescriptionRefills": "ការបំពេញវេជ្ជបញ្ជា", + "print": "បោះពុម្ព", + "printedBy": "បោះពុម្ពដោយ", "prn": "P.R.N.", "prnReason": "P.R.N. ហេតុផល", "prnReasonPlaceholder": "ហេតុផលដែលត្រូវយកថ្នាំ", "quantity": "បរិមាណ", - "quantityToDispense": "Quantity to dispense", + "quantityToDispense": "ចំនួនឱសថដែលត្រូវផ្តល់", "refills": "ការបំពេញបន្ថែម", "removeFromBasket": "យកចេញពីប្រអប់", "reorder": "ចេញវេជ្ជបញ្ជាជាថ្មីឡើងវិញ", "saveOrder": "រក្សាទុកវេជ្ជបញ្ជា", - "searchAgain": "search again", + "searchAgain": "ស្វែងរកម្តងទៀត", "searchFieldPlaceholder": "ស្វែងរកវេជ្ជបញ្ជា (ឧ. \"Aspirin\")", - "searchResultsMatchesForTerm_other": "{{count}} results for \"{{searchTerm}}\"", + "searchResultsMatchesForTerm_other": "{{count}} លទ្ធផលសម្រាប់ \"{{searchTerm}}\"", "startDate": "ថ្ងៃចាប់ផ្តើម", "takeAsNeeded": "យកតាមតម្រូវការ", - "tryReopeningTheForm": "Please try launching the form again", - "trySearchingAgain": "Please try searching again", - "tryTo": "Try to", - "unknown": "Unknown", - "usingADifferentTerm": "using a different term" + "tryReopeningTheForm": "សូមព្យាយាមបើកទម្រង់នេះម្តងទៀត", + "trySearchingAgain": "សូមព្យាយាមស្វែងរកម្តងទៀត", + "tryTo": "ព្យាយាម", + "unknown": "មិនដឹង", + "usingADifferentTerm": "ប្រើពាក្យផ្សេងទៀត" } diff --git a/packages/esm-patient-orders-app/translations/km.json b/packages/esm-patient-orders-app/translations/km.json index 209dc0813d..b6883be647 100644 --- a/packages/esm-patient-orders-app/translations/km.json +++ b/packages/esm-patient-orders-app/translations/km.json @@ -1,3 +1,3 @@ { - "Medications": "Medications" + "Medications": "ឱសថ" } diff --git a/packages/esm-patient-vitals-app/src/vitals/vitals-biometrics-form/vitals-biometrics-form.component.tsx b/packages/esm-patient-vitals-app/src/vitals/vitals-biometrics-form/vitals-biometrics-form.component.tsx index 33d1e0c11d..94ed297035 100644 --- a/packages/esm-patient-vitals-app/src/vitals/vitals-biometrics-form/vitals-biometrics-form.component.tsx +++ b/packages/esm-patient-vitals-app/src/vitals/vitals-biometrics-form/vitals-biometrics-form.component.tsx @@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import { Button, ButtonSet, Column, Form, Row, Stack, InlineNotification } from '@carbon/react'; +import { Button, ButtonSet, Column, Form, InlineNotification, Row, Stack } from '@carbon/react'; import { age, createErrorHandler, @@ -20,15 +20,21 @@ import { DefaultWorkspaceProps, useVitalsConceptMetadata } from '@openmrs/esm-pa import type { ConfigObject } from '../../config-schema'; import { calculateBodyMassIndex, - isValueWithinReferenceRange, extractNumbers, - getColorCode, + getMuacColorCode, + isValueWithinReferenceRange, } from './vitals-biometrics-form.utils'; -import { savePatientVitals, useVitals } from '../vitals.resource'; -import VitalsBiometricInput from './vitals-biometrics-input.component'; +import { + assessValue, + getReferenceRangesForConcept, + interpretBloodPressure, + savePatientVitals, + useVitals, +} from '../vitals.resource'; +import VitalsAndBiometricsInput from './vitals-biometrics-input.component'; import styles from './vitals-biometrics-form.scss'; -const vitalsBiometricsFormSchema = z +const VitalsAndBiometricFormSchema = z .object({ systolicBloodPressure: z.number(), diastolicBloodPressure: z.number(), @@ -48,14 +54,14 @@ const vitalsBiometricsFormSchema = z return Object.values(fields).some((value) => Boolean(value)); }, { - message: 'Atleast one fields is required', + message: 'Please fill at least one field', path: ['oneFieldRequired'], }, ); -export type VitalsBiometricsFormData = z.infer; +export type VitalsBiometricsFormData = z.infer; -const VitalsAndBiometricForms: React.FC = ({ patientUuid, closeWorkspace }) => { +const VitalsAndBiometricsForm: React.FC = ({ patientUuid, closeWorkspace }) => { const { t } = useTranslation(); const isTablet = useLayoutType() === 'tablet'; const config = useConfig(); @@ -67,14 +73,15 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, const { currentVisit } = useVisit(patientUuid); const { mutate } = useVitals(patientUuid); const { data: conceptUnits, conceptMetadata, conceptRanges } = useVitalsConceptMetadata(); - const [bodyMassIndex, setBodyMassIndex] = useState(); + const [hasInvalidVitals, setHasInvalidVitals] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const [muacColorCode, setMuacColorCode] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); const [showErrorNotification, setShowErrorNotification] = useState(false); + const [showErrorMessage, setShowErrorMessage] = useState(false); - const { control, handleSubmit, getValues, watch, setValue } = useForm({ + const { control, handleSubmit, watch, setValue } = useForm({ mode: 'all', - resolver: zodResolver(vitalsBiometricsFormSchema), + resolver: zodResolver(VitalsAndBiometricFormSchema), }); const encounterUuid = currentVisit?.encounters?.find((encounter) => encounter?.form?.uuid === config.vitals.formUuid) @@ -90,15 +97,23 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, const weight = watch('weight'); const height = watch('height'); - const isBodyMassIndexValueAbnormal = (bmi: number) => { - if (!bmi) return false; - return bmi < 18.5 || bmi > 24.9; - }; - useEffect(() => { - getColorCode(extractNumbers(age(patient.patient?.birthDate)), midUpperArmCircumference, setMuacColorCode); + getMuacColorCode(extractNumbers(age(patient.patient?.birthDate)), midUpperArmCircumference, setMuacColorCode); }, [watch, patient.patient?.birthDate, midUpperArmCircumference]); + useEffect(() => { + if (height && weight) { + const computedBodyMassIndex = calculateBodyMassIndex(weight, height); + setValue('computedBodyMassIndex', computedBodyMassIndex); + } + }, [weight, height, setValue]); + + function onError(err) { + if (err?.oneFieldRequired) { + setShowErrorNotification(true); + } + } + const concepts = useMemo( () => ({ midUpperArmCircumferenceRange: conceptRanges.get(config.concepts.midUpperArmCircumferenceUuid), @@ -126,36 +141,31 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, ); const savePatientVitalsAndBiometrics = (data: VitalsBiometricsFormData) => { + const formData = data; + setShowErrorMessage(true); + setShowErrorNotification(false); + data?.computedBodyMassIndex && delete data.computedBodyMassIndex; - const patientVitalAndBiometrics = data; - let isFieldValid = true; - for (const key in patientVitalAndBiometrics) { - if ( - isValueWithinReferenceRange(conceptMetadata, config.concepts[key + 'Uuid'], patientVitalAndBiometrics[key]) == - false - ) { - isFieldValid = false; - showNotification({ - title: t('vitalsAndBiometricsSaveError', 'Error saving vitals and biometrics'), - kind: 'error', - critical: true, - description: t('checkForValidity', 'Some of the values entered are invalid'), - }); - break; - } - } - if (isFieldValid) { + let allFieldsAreValid = false; + + allFieldsAreValid = Object.entries(formData) + .filter(([, value]) => Boolean(value)) + .every(([key, value]) => isValueWithinReferenceRange(conceptMetadata, config.concepts[`${key}Uuid`], value)); + + if (allFieldsAreValid) { setIsSubmitting(true); - const ac = new AbortController(); + setShowErrorMessage(false); + const abortController = new AbortController(); + savePatientVitals( config.vitals.encounterTypeUuid, config.vitals.formUuid, config.concepts, patientUuid, - patientVitalAndBiometrics, + formData, new Date(), - ac, + abortController, session?.sessionLocation?.uuid, ) .then((response) => { @@ -184,18 +194,13 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, }); }) .finally(() => { - ac.abort(); + abortController.abort(); }); + } else { + setHasInvalidVitals(true); } }; - useEffect(() => { - if (height && weight) { - const computedBodyMassIndex = calculateBodyMassIndex(weight, height); - setValue('computedBodyMassIndex', computedBodyMassIndex); - } - }, [weight, height, setValue]); - if (config.vitals.useFormEngine) { return ( = ({ patientUuid, ); } - const onError = (err) => { - if (err?.oneFieldRequired) { - setShowErrorNotification(true); - } - }; - return (
@@ -229,10 +228,38 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, - + + + = ({ patientUuid, id: 'diastolicBloodPressure', }, ]} - unitSymbol={conceptUnits.get(config.concepts.systolicBloodPressureUuid) ?? ''} - isWithinNormalRange={ + interpretation={ + systolicBloodPressure && + diastolicBloodPressure && + interpretBloodPressure( + systolicBloodPressure, + diastolicBloodPressure, + config.concepts, + conceptMetadata, + ) + } + isValueWithinReferenceRange={ + systolicBloodPressure && + diastolicBloodPressure && isValueWithinReferenceRange( conceptMetadata, config.concepts.systolicBloodPressureUuid, @@ -262,13 +300,14 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, diastolicBloodPressure, ) } + label={t('bloodPressure', 'Blood pressure')} + unitSymbol={conceptUnits.get(config.concepts.systolicBloodPressureUuid) ?? ''} /> - = ({ patientUuid, id: 'pulse', }, ]} + interpretation={ + pulse && assessValue(pulse, getReferenceRangesForConcept(config.concepts.pulseUuid, conceptMetadata)) + } + isValueWithinReferenceRange={ + pulse && isValueWithinReferenceRange(conceptMetadata, config.concepts['pulseUuid'], pulse) + } + label={t('heartRate', 'Heart rate')} unitSymbol={conceptUnits.get(config.concepts.pulseUuid) ?? ''} - isWithinNormalRange={isValueWithinReferenceRange(conceptMetadata, config.concepts['pulseUuid'], pulse)} /> - - - - - - - - @@ -372,10 +412,9 @@ const VitalsAndBiometricForms: React.FC = ({ patientUuid, - = ({ patientUuid, id: 'weight', }, ]} + interpretation={ + weight && + assessValue(weight, getReferenceRangesForConcept(config.concepts.weightUuid, conceptMetadata)) + } + isValueWithinReferenceRange={ + height && isValueWithinReferenceRange(conceptMetadata, config.concepts['weightUuid'], weight) + } + showErrorMessage={showErrorMessage} + label={t('weight', 'Weight')} unitSymbol={conceptUnits.get(config.concepts.weightUuid) ?? ''} - isWithinNormalRange={isValueWithinReferenceRange( - conceptMetadata, - config.concepts['weightUuid'], - weight, - )} /> - = ({ patientUuid, id: 'height', }, ]} + interpretation={ + height && + assessValue(height, getReferenceRangesForConcept(config.concepts.heightUuid, conceptMetadata)) + } + isValueWithinReferenceRange={ + weight && isValueWithinReferenceRange(conceptMetadata, config.concepts['heightUuid'], height) + } + showErrorMessage={showErrorMessage} + label={t('height', 'Height')} unitSymbol={conceptUnits.get(config.concepts.heightUuid) ?? ''} - isWithinNormalRange={isValueWithinReferenceRange( - conceptMetadata, - config.concepts['heightUuid'], - height, - )} /> - - = ({ patientUuid, id: 'midUpperArmCircumference', }, ]} + muacColorCode={muacColorCode} + isValueWithinReferenceRange={ + height && + weight && + isValueWithinReferenceRange( + conceptMetadata, + config.concepts['midUpperArmCircumferenceUuid'], + midUpperArmCircumference, + ) + } + showErrorMessage={showErrorMessage} + label={t('muac', 'MUAC')} unitSymbol={conceptUnits.get(config.concepts.midUpperArmCircumferenceUuid) ?? ''} - isWithinNormalRange={isValueWithinReferenceRange( - conceptMetadata, - config.concepts['midUpperArmCircumferenceUuid'], - midUpperArmCircumference, - )} + useMuacColors={useMuacColorStatus} /> - - {showErrorNotification && ( - - setShowErrorNotification(false)} - /> - - )} -
+ + {showErrorNotification && ( + + setShowErrorNotification(false)} + /> + + )} + + {hasInvalidVitals && ( + + + + )} +