diff --git a/.eslintrc b/.eslintrc index eb5aff8d7c..ecd2d2ec05 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,14 +2,22 @@ "env": { "node": true }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:jest-dom/recommended", - "plugin:testing-library/react" - ], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:jest-dom/recommended"], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "import", "jest-dom", "react-hooks", "testing-library"], + "overrides": [ + { + "files": ["**/*.test.tsx"], + "extends": ["plugin:testing-library/react"] + }, + { + "files": ["e2e/**/*.spec.ts"], + "extends": ["plugin:playwright/recommended"], + "rules": { + "testing-library/prefer-screen-queries": "off" + } + } + ], "rules": { "react-hooks/exhaustive-deps": "warn", "react-hooks/rules-of-hooks": "error", diff --git a/e2e/commands/visit-operations.ts b/e2e/commands/visit-operations.ts index 752a07137f..b28b227893 100644 --- a/e2e/commands/visit-operations.ts +++ b/e2e/commands/visit-operations.ts @@ -13,16 +13,16 @@ export const startVisit = async (api: APIRequestContext, patientId: string): Pro }, }); - await expect(visitRes.ok()).toBeTruthy(); + expect(visitRes.ok()).toBeTruthy(); return await visitRes.json(); }; -export const endVisit = async (api: APIRequestContext, uuid: string) => { - const visitRes = await api.post(`visit/${uuid}`, { +export const endVisit = async (api: APIRequestContext, visit: Visit) => { + const visitRes = await api.post(`visit/${visit.uuid}`, { data: { - location: process.env.E2E_LOGIN_DEFAULT_LOCATION_UUID, - startDatetime: dayjs().subtract(1, 'D').format('YYYY-MM-DDTHH:mm:ss.SSSZZ'), - visitType: '7b0f5697-27e3-40c4-8bae-f4049abfb4ed', + location: visit.location.uuid, + startDatetime: visit.startDatetime, + visitType: visit.visitType.uuid, stopDatetime: dayjs().format('YYYY-MM-DDTHH:mm:ss.SSSZZ'), }, }); diff --git a/e2e/specs/biometrics.spec.ts b/e2e/specs/biometrics.spec.ts index 9c76114378..44f3c484e3 100644 --- a/e2e/specs/biometrics.spec.ts +++ b/e2e/specs/biometrics.spec.ts @@ -67,6 +67,6 @@ test('Record biometrics', async ({ page }) => { }); test.afterEach(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/specs/clinical-forms.spec.ts b/e2e/specs/clinical-forms.spec.ts index a8941ff10c..748af46dd4 100644 --- a/e2e/specs/clinical-forms.spec.ts +++ b/e2e/specs/clinical-forms.spec.ts @@ -273,6 +273,6 @@ test('Form state is retained when minimizing a form in the workspace', async ({ }); test.afterEach(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/specs/drug-orders.spec.ts b/e2e/specs/drug-orders.spec.ts index 8f9b3518de..f5e1ce18a7 100644 --- a/e2e/specs/drug-orders.spec.ts +++ b/e2e/specs/drug-orders.spec.ts @@ -243,6 +243,6 @@ test('Record, edit and discontinue a drug order', async ({ page }) => { }); test.afterEach(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/specs/lab-orders.spec.ts b/e2e/specs/lab-orders.spec.ts index 09babe1ec4..6619c59f96 100644 --- a/e2e/specs/lab-orders.spec.ts +++ b/e2e/specs/lab-orders.spec.ts @@ -50,8 +50,12 @@ test.describe.serial('Running laboratory order tests sequentially', () => { await page.getByLabel(/additional instructions/i).fill(' N/A'); }); - await test.step('Add I save the lab order form', async () => { + await test.step('And I save the lab order form', async () => { await page.getByRole('button', { name: /save order/i }).click(); + }); + + await test.step('And I click the `Sign and close` button', async () => { + await expect(page.getByRole('status', { name: /new/i })).toBeVisible(); await page.getByRole('button', { name: /sign and close/i }).click(); }); @@ -70,7 +74,6 @@ test.describe.serial('Running laboratory order tests sequentially', () => { test('Modify a lab order', async ({ page }) => { const ordersPage = new OrdersPage(page); - const orderBasket = page.locator('[data-extension-slot-name="order-basket-slot"]'); await test.step('When I visit the orders page', async () => { await ordersPage.goTo(patient.uuid); @@ -101,8 +104,8 @@ test.describe.serial('Running laboratory order tests sequentially', () => { }); await test.step('Then the order status should be changed to `Modify`', async () => { - await expect(orderBasket.getByText(/new/i)).not.toBeVisible(); - await expect(orderBasket.getByText(/modify/i)).toBeVisible(); + await expect(page.getByRole('status', { name: /new/i })).not.toBeVisible(); + await expect(page.getByRole('status', { name: /modify/i })).toBeVisible(); }); await test.step('When I click on the `Sign and close` button', async () => { @@ -116,7 +119,6 @@ test.describe.serial('Running laboratory order tests sequentially', () => { test('Discontinue a lab order', async ({ page }) => { const ordersPage = new OrdersPage(page); - const orderBasket = page.locator('[data-extension-slot-name="order-basket-slot"]'); await test.step('When I visit the orders page', async () => { await ordersPage.goTo(patient.uuid); @@ -138,7 +140,7 @@ test.describe.serial('Running laboratory order tests sequentially', () => { }); await test.step('Then the order status should be changed to `Discontinue`', async () => { - await expect(orderBasket.getByText(/discontinue/i)).toBeVisible(); + await expect(page.getByRole('status', { name: /discontinue/i })).toBeVisible(); }); await test.step('And I click on the `Sign and close` button', async () => { @@ -156,6 +158,6 @@ test.describe.serial('Running laboratory order tests sequentially', () => { }); test.afterAll(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/specs/results-viewer.spec.ts b/e2e/specs/results-viewer.spec.ts index 4afba862c4..b4fdd00ada 100644 --- a/e2e/specs/results-viewer.spec.ts +++ b/e2e/specs/results-viewer.spec.ts @@ -1,8 +1,9 @@ +/* eslint-disable playwright/no-nested-step */ import { expect } from '@playwright/test'; import { type Visit } from '@openmrs/esm-framework'; import { generateRandomPatient, type Patient, startVisit, endVisit, deletePatient } from '../commands'; import { test } from '../core'; -import { ResultsViewerPage, VisitsPage } from '../pages'; +import { ChartPage, ResultsViewerPage, VisitsPage } from '../pages'; let patient: Patient; let visit: Visit; @@ -13,6 +14,7 @@ test.beforeEach(async ({ api }) => { }); test('Record and edit test results', async ({ page }) => { + const chartPage = new ChartPage(page); const resultsViewerPage = new ResultsViewerPage(page); const visitsPage = new VisitsPage(page); const form = page.locator('[data-extension-slot-name="form-widget-slot"]'); @@ -209,12 +211,12 @@ test('Record and edit test results', async ({ page }) => { }, ]; - await test.step('When I visit the results viewer page', async () => { - await resultsViewerPage.goTo(patient.uuid); + await test.step('When I visit the chart summary page', async () => { + await chartPage.goTo(patient.uuid); }); - await test.step('And I click on the `Clinical forms` button on the siderail', async () => { - await page.getByLabel(/clinical forms/i).click(); + await test.step('And I click the `Clinical forms` button on the siderail', async () => { + await page.getByLabel(/clinical forms/i, { exact: true }).click(); }); await test.step('Then I should see the clinical forms workspace', async () => { @@ -226,11 +228,15 @@ test('Record and edit test results', async ({ page }) => { await expect(page.getByRole('cell', { name: /laboratory test results/i })).toBeVisible(); }); - await test.step('When I launch the `Laboratory Test Results` form', async () => { + await test.step('When I click the `Laboratory Test Results` link to launch the form', async () => { await page.getByText(/laboratory test results/i).click(); }); - await test.step('And I fill the "Complete Blood Count" section', async () => { + await test.step('Then I should see the `Laboratory Test Results` form launch in the workspace', async () => { + await expect(page.getByText(/laboratory test results/i)).toBeVisible(); + }); + + await test.step('When I fill the "Complete Blood Count" section', async () => { for (const { label, value } of completeBloodCountData) { await test.step(label, async () => { await form.getByLabel(label, { exact: true }).fill(value); @@ -276,6 +282,7 @@ test('Record and edit test results', async ({ page }) => { }); } }); + for (const { resultsPageReference, value } of chemistryResultsData) { await test.step(resultsPageReference, async () => { const row = page.locator(`tr:has-text("${resultsPageReference}"):has(td:has-text("${value}"))`).first(); @@ -364,6 +371,6 @@ test('Record and edit test results', async ({ page }) => { }); test.afterEach(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/specs/visit-note.spec.ts b/e2e/specs/visit-note.spec.ts index 1ab5a7d24a..fd557d50c4 100644 --- a/e2e/specs/visit-note.spec.ts +++ b/e2e/specs/visit-note.spec.ts @@ -88,6 +88,6 @@ test('Add and delete a visit note', async ({ page }) => { }); test.afterEach(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/specs/vitals.spec.ts b/e2e/specs/vitals.spec.ts index d67f633332..1072c40c1f 100644 --- a/e2e/specs/vitals.spec.ts +++ b/e2e/specs/vitals.spec.ts @@ -80,6 +80,6 @@ test('Record vital signs', async ({ page }) => { }); test.afterEach(async ({ api }) => { - await endVisit(api, visit.uuid); + await endVisit(api, visit); await deletePatient(api, patient.uuid); }); diff --git a/e2e/support/bamboo/playwright.Dockerfile b/e2e/support/bamboo/playwright.Dockerfile index 761967921a..70548dae35 100644 --- a/e2e/support/bamboo/playwright.Dockerfile +++ b/e2e/support/bamboo/playwright.Dockerfile @@ -1,5 +1,5 @@ # The image version should match the Playwright version specified in the package.json file -FROM mcr.microsoft.com/playwright:v1.48.2-jammy +FROM mcr.microsoft.com/playwright:v1.49.0-jammy ARG USER_ID ARG GROUP_ID diff --git a/package.json b/package.json index 218909ae41..47e682a739 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@openmrs/esm-patient-chart", "private": true, - "version": "8.2.0", + "version": "9.0.0", "workspaces": [ "packages/*" ], @@ -25,7 +25,7 @@ }, "devDependencies": { "@openmrs/esm-framework": "next", - "@playwright/test": "1.48.2", + "@playwright/test": "1.49.0", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.89", "@swc/jest": "^0.2.29", diff --git a/packages/esm-form-engine-app/package.json b/packages/esm-form-engine-app/package.json index 641f06d894..7ab6f51dc6 100644 --- a/packages/esm-form-engine-app/package.json +++ b/packages/esm-form-engine-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-form-engine-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Wrapper ESM for the O3 React Form Engine", "browser": "dist/openmrs-esm-form-engine-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -43,8 +43,8 @@ "react-error-boundary": "^4.0.13" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-form-engine-app/translations/pt_BR.json b/packages/esm-form-engine-app/translations/pt_BR.json new file mode 100644 index 0000000000..15c56f807a --- /dev/null +++ b/packages/esm-form-engine-app/translations/pt_BR.json @@ -0,0 +1,15 @@ +{ + "cancel": "Cancel", + "closeThisPanel": "Close this panel", + "collapseAll": "Collapse all", + "deleteQuestion": "Delete question", + "deleteQuestionConfirmation": "Are you sure you want to delete this question?", + "deleteQuestionExplainerText": "This action cannot be undone.", + "errorTitle": "There was an error with this form", + "expandAll": "Expand all", + "loading": "Loading", + "or": "or", + "thisList": "this list", + "toggleCollapseOrExpand": "Toggle collapse or expand", + "tryAgainMessage": "Try opening another form from" +} diff --git a/packages/esm-form-entry-app/package.json b/packages/esm-form-entry-app/package.json index 1ce81394ed..3bc10fbe32 100644 --- a/packages/esm-form-entry-app/package.json +++ b/packages/esm-form-entry-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-form-entry-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Angular form engine for O3", "browser": "dist/openmrs-esm-form-entry-app.js", @@ -65,7 +65,7 @@ "zone.js": "~0.14.8" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", + "@openmrs/esm-framework": "6.x", "single-spa": "6.x" }, "devDependencies": { diff --git a/packages/esm-form-entry-app/translations/pt_BR.json b/packages/esm-form-entry-app/translations/pt_BR.json new file mode 100644 index 0000000000..140719aa15 --- /dev/null +++ b/packages/esm-form-entry-app/translations/pt_BR.json @@ -0,0 +1,70 @@ +{ + "aDayAgo": "um dia atrás", + "aFewSecondsAgo": "alguns segundos atrás", + "aMinuteAgo": "um minuto atrás", + "aMonthAgo": "um mês atrás", + "aYearAgo": "um ano atrás", + "add": "Adicionar", + "addHour": "Adiciona uma hora", + "addMinute": "dicionar um minuto", + "addSecond": "dicionar um segundo", + "anHourAgo": "uma hora atrás", + "cancel": "Cancelar", + "changeToMonthView": "Alterar visualização do mês", + "chooseMonthAndYear": "Escolher mês e ano", + "clearEntry": "Tem certeza que deseja limpar esse campo?", + "closeThisPanel": "Fechar esse painel", + "componentLoadingFailed": "Carregamento de Componente falhou...", + "daysAgo": "dias atrás", + "deleteEntry": "Tem certeza que deseja excluir esse item?", + "discardButton": "Descartar", + "disallowDecimals": "Valores decimais não permitido", + "enterMoreCharacters": "Por favor digite 2 ou mais caracteres", + "errorFetchingFormData": "Houve um erro ao carregar os dados de formulário, Detalhes: {detail}", + "errorLoadingForm": "Erro ao carregar formulário", + "errorWithForm": "Houve um erro com esse formulário", + "fix": "Corrigir", + "formSubmissionFailed": "Um erro ocorreu enquanto processava o envio do formulário. Favor notificar o administrador do sistema e encaminhar o erro a seguir: {error}", + "formSubmittedSuccessfully": "O formulário foi enviado com sucesso", + "from": "De", + "futureDateRestriction": "Não é permitido uma data futura!", + "hoursAgo": "horas atrás", + "invalidDate": "Data fornecida não é válida", + "loading": "Carregando...", + "loadingComponent": "Carregando componente", + "max": "Valor máximo deve ser {max}", + "maxDate": "Data máxima deve ser {maxDate}", + "maxEntries": "Não pode haver mais do que {max} entradas", + "maxLength": "Tamanho máximo deve ser {maxLength}", + "min": "Valor mínimo deve ser {min}", + "minDate": "Data mínima deve ser {minDate}", + "minLength": "Comprimento mínimo deve ser {minLength}", + "minusHour": "Menos uma hora", + "minusMinute": "Menos um minuto", + "minusSecond": "Menos um segundo", + "minutesAgo": "minutos atrás", + "monthsAgo": "meses atrás", + "next": "Próximo", + "next21Years": "Próximos 21 anos", + "nextMonth": "Próximo mês", + "nextYear": "Próximo ano", + "patientIdentifierDuplication": "Registro de Paciente duplicado", + "patientIdentifierDuplicationDescription": "O Registro informado já está associado com um paciente existente. Por favor verifique o código de registro e tente novamente.", + "orderNumber": "Número de pedido", + "previous": "Anterior", + "previous21Years": "21 anos prévios", + "previousMonth": "Mês anterior", + "previousValue": "Valor anterior", + "previousYear": "Ano anterior", + "remove": "Remover", + "requiredField": "Esse campo é obrigatório!", + "saveAndCloseButton": "Salvar e fechar", + "selectWeeks": "Selecionar semanas", + "set": "Configurar", + "submitting": "Enviando", + "to": "Para", + "tryOpeningAnotherForm": "Tente abrir outro formulário", + "useValue": "Usar Valor", + "weeks": "Semanas", + "yearsAgo": "Anos atrás" +} diff --git a/packages/esm-generic-patient-widgets-app/package.json b/packages/esm-generic-patient-widgets-app/package.json index dcf8314b2f..2ed4638d3e 100644 --- a/packages/esm-generic-patient-widgets-app/package.json +++ b/packages/esm-generic-patient-widgets-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-generic-patient-widgets-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Generic widgets for the patient chart", "browser": "dist/openmrs-esm-generic-patient-widgets-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -43,8 +43,8 @@ "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "^18.2.0", "react-i18next": "11.x", diff --git a/packages/esm-generic-patient-widgets-app/translations/pt_BR.json b/packages/esm-generic-patient-widgets-app/translations/pt_BR.json new file mode 100644 index 0000000000..7b2512d798 --- /dev/null +++ b/packages/esm-generic-patient-widgets-app/translations/pt_BR.json @@ -0,0 +1,7 @@ +{ + "chartView": "Chart View", + "dateAndTime": "Date and time", + "displaying": "Displaying", + "encounterType": "Encounter type", + "tableView": "Table View" +} diff --git a/packages/esm-patient-allergies-app/package.json b/packages/esm-patient-allergies-app/package.json index 71799db537..838dfeaa0c 100644 --- a/packages/esm-patient-allergies-app/package.json +++ b/packages/esm-patient-allergies-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-allergies-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient allergies microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-allergies-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -41,8 +41,8 @@ "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "^18.2.0", "react-i18next": "11.x", diff --git a/packages/esm-patient-allergies-app/translations/pt_BR.json b/packages/esm-patient-allergies-app/translations/pt_BR.json new file mode 100644 index 0000000000..9b96690677 --- /dev/null +++ b/packages/esm-patient-allergies-app/translations/pt_BR.json @@ -0,0 +1,47 @@ +{ + "add": "Adicionar", + "allergen": "Alérgeno", + "allergies": "Alergias", + "Allergies": "Alergias", + "allergyDeleted": "Alergia excluída", + "allergyIntolerances": "intolerâncias alérgicas", + "allergyNowVisible": "Agora está visível na página de Alergias", + "allergySaved": "Alergia gravada", + "allergySaveError": "Erro ao gravar alergia", + "allergyUpdated": "Alergia atualizada", + "cancel": "Cancelar", + "dateOfOnsetAndComments": "Data de início e comentários", + "delete": "Apagar", + "deleteModalConfirmationText": "Tem certeza de que deseja remover esta alergia?", + "deletePatientAllergy": "Apagar alergia", + "deleting": "Removendo", + "discard": "Descartar", + "edit": "Editar", + "editAllergy": "Editar uma Alergia", + "editOrDeleteAllergy": "Editar ou apagar alergia", + "errorDeletingAllergy": "Erro ao apagar alergia", + "invalidComment": "Comentário inválido, tente novamente", + "loading": "Carregando", + "mild": "Leve", + "moderate": "Moderado", + "name": "Nome", + "nonCodedAllergenWarningDescription": "Adicionar um alérgeno personalizado pode afetar as notificações de alergia em todo o sistema. É recomendado escolher na lista correta fornecida para melhores alertas. As entradas personalizadas podem não acionar notificações em todos os contextos relevantes.", + "nonCodedAllergenWarningTitle": "Aviso: Entrada personalizada de alérgenos", + "onsetDateAndComments": "Data de início e comentários", + "other": "Outro", + "otherNonCodedAllergen": "Outro alergeno não codificado", + "otherNonCodedAllergicReaction": "Outra reação alérgica não codificada", + "reaction": "Reação", + "reactions": "Reações", + "recordNewAllergy": "Registar nova alergia", + "saveAndClose": "Salvar e fechar", + "seeAll": "Ver tudo", + "selectAllergen": "Selecione o alérgeno", + "selectReactions": "Selecione as reações", + "severe": "Severa", + "severityandReaction": "Gravidade", + "severityOfWorstReaction": "Gravidade da pior reação", + "typeAdditionalComments": "Descreva qualquer comentário adicional aqui", + "typeAllergenName": "Por favor digite o nome do alérgeno", + "typeAllergicReactionName": "Por favor digite o nome da reação alérgica" +} diff --git a/packages/esm-patient-attachments-app/package.json b/packages/esm-patient-attachments-app/package.json index fd47a99258..3d33ea0788 100644 --- a/packages/esm-patient-attachments-app/package.json +++ b/packages/esm-patient-attachments-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-attachments-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient attachments microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-attachments-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,14 +38,14 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21", "react-grid-gallery": "^0.5.6", "react-html5-camera-photo": "^1.5.11" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "^18.2.0", "react-i18next": "11.x", diff --git a/packages/esm-patient-attachments-app/translations/pt_BR.json b/packages/esm-patient-attachments-app/translations/pt_BR.json new file mode 100644 index 0000000000..d5513a7af8 --- /dev/null +++ b/packages/esm-patient-attachments-app/translations/pt_BR.json @@ -0,0 +1,57 @@ +{ + "add": "Add", + "addAnImage": "Add image", + "addAttachment": "Add Attachment", + "addAttachment_title": "Add Attachment", + "addMoreAttachments": "Add more attachments", + "attachments": "Attachments", + "Attachments": "Attachments", + "attachmentsInLowerCase": "attachments", + "attachmentsInProperFormat": "Attachments", + "cameraAccessErrorMessage": "Please enable camera access in your browser settings and try again.", + "cameraError": "Camera Error", + "cancel": "Cancel", + "chooseAnAllowedFileType": "The file \"{{fileName}}\" cannot be uploaded. Please upload a file with one of the following extensions: {{supportedExtensions}}, or {{ lastExtension }}.", + "closeModal": "Close", + "closePreview": "Close preview", + "dateUploaded": "Date uploaded", + "delete": "Delete", + "deleteAttachmentConfirmationText": "Are you sure you want to delete this {{attachmentType}}? This action can't be undone.", + "deleteImage": "Delete image", + "deletePdf": "Delete PDF", + "edit": "Edit", + "enterAttachmentDescription": "Enter attachment description", + "enterAttachmentName": "Enter attachment name", + "error": "Error", + "failed": "failed", + "failedDeleting": "couldn't be deleted", + "file": "File", + "fileDeleted": "File deleted", + "fileName": "File name", + "fileSizeInstructions": "Drag and drop files here or click to upload", + "fileSizeLimitExceeded": "exceeds the size limit of", + "fileSizeLimitExceededText": "File size limit exceeded", + "fileUploadSizeConstraints": "File limit is {{fileSize}}MB", + "gridView": "Grid view", + "image": "Image", + "imageDescription": "Image description", + "imagePlaceholder": "Image placeholder", + "imagePreview": "Image preview", + "name": "name", + "nameIsRequired": "Name is required", + "noAttachmentsToDisplay": "There are no attachments to display for this patient", + "noImageToDisplay": "No image to display", + "options": "Options", + "successfullyDeleted": "successfully deleted", + "supportedFiletypes": "Supported files are {{supportedFiles}}", + "tableView": "Table view", + "type": "Type", + "unsupportedFileType": "Unsupported file type", + "uploadComplete": "Upload complete", + "uploadedSuccessfully": "uploaded successfully", + "uploadError": "Error uploading file", + "uploadFiles": "Upload files", + "uploading": "Uploading", + "uploadWillContinueInTheBackground": "Files will be uploaded in the background. You can close this modal.", + "webcam": "Webcam" +} diff --git a/packages/esm-patient-banner-app/package.json b/packages/esm-patient-banner-app/package.json index c8acee182c..320e88edb7 100644 --- a/packages/esm-patient-banner-app/package.json +++ b/packages/esm-patient-banner-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-banner-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient banner microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-banner-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,12 +38,12 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-banner-app/src/config-schema.ts b/packages/esm-patient-banner-app/src/config-schema.ts index 23725e4158..5730abe815 100644 --- a/packages/esm-patient-banner-app/src/config-schema.ts +++ b/packages/esm-patient-banner-app/src/config-schema.ts @@ -47,7 +47,8 @@ export const configSchema = { }, printScale: { _type: Type.String, - _description: 'Set the scale for the printed content. A value between 0 and 1 shrinks the content, while a value greater than 1 enlarges it. The scale must be greater than 0.', + _description: + 'Set the scale for the printed content. A value between 0 and 1 shrinks the content, while a value greater than 1 enlarges it. The scale must be greater than 0.', _default: '1', }, identifiersToDisplay: { diff --git a/packages/esm-patient-banner-app/translations/am.json b/packages/esm-patient-banner-app/translations/am.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/am.json +++ b/packages/esm-patient-banner-app/translations/am.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/ar.json b/packages/esm-patient-banner-app/translations/ar.json index 31eb1c0743..60b75bd039 100644 --- a/packages/esm-patient-banner-app/translations/ar.json +++ b/packages/esm-patient-banner-app/translations/ar.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "العنوان سطر 1", "address2": "العنوان سطر 2", "city": "المدينة", "cityVillage": "المدينة", "country": "الدولة", "countyDistrict": "المنطقة", + "deceased": "Deceased", "district": "المنطقة", + "from_lower": "from", "implementationLogo": "شعار التنفيذ", "patientAge": "العمر", "patientDateOfBirthWithSeparator": "تاريخ الميلاد", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "اسم المريض", "postalCode": "الرمز البريدي", "printIdentifierSticker": "ملصق تعريف المريض", + "started": "Started", "state": "الولاية", "stateProvince": "الولاية", "telephoneNumberWithSeparator": "رقم الهاتف" diff --git a/packages/esm-patient-banner-app/translations/de.json b/packages/esm-patient-banner-app/translations/de.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/de.json +++ b/packages/esm-patient-banner-app/translations/de.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/en.json b/packages/esm-patient-banner-app/translations/en.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/en.json +++ b/packages/esm-patient-banner-app/translations/en.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/es.json b/packages/esm-patient-banner-app/translations/es.json index ccf7485c76..50d0e66146 100644 --- a/packages/esm-patient-banner-app/translations/es.json +++ b/packages/esm-patient-banner-app/translations/es.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Línea de Dirección 1", "address2": "Línea de Dirección 2", "city": "Ciudad", "cityVillage": "ciudad", "country": "País", "countyDistrict": "Distrito", + "deceased": "Deceased", "district": "Distrito", + "from_lower": "from", "implementationLogo": "Logo de la implementación", "patientAge": "Edad:", "patientDateOfBirthWithSeparator": "Fecha de nacimiento:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Nombre del paciente:", "postalCode": "Código postal", "printIdentifierSticker": "Imprimir sticker de identificación", + "started": "Started", "state": "Estado", "stateProvince": "Estado", "telephoneNumberWithSeparator": "Número de teléfono:" diff --git a/packages/esm-patient-banner-app/translations/fr.json b/packages/esm-patient-banner-app/translations/fr.json index 5f367042a5..1b6de49850 100644 --- a/packages/esm-patient-banner-app/translations/fr.json +++ b/packages/esm-patient-banner-app/translations/fr.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Ligne d'adresse 1", "address2": "Ligne d'adresse 2", "city": "Ville", "cityVillage": "ville", "country": "Pays", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Logo de mise en œuvre", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date de Naissance:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Nom du Patient:", "postalCode": "Code postal", "printIdentifierSticker": "Imprimer l'autocollant d'identification", + "started": "Started", "state": "État", "stateProvince": "État", "telephoneNumberWithSeparator": "Numéro de téléphone:" diff --git a/packages/esm-patient-banner-app/translations/he.json b/packages/esm-patient-banner-app/translations/he.json index 2d492a95e2..c1c54a67d0 100644 --- a/packages/esm-patient-banner-app/translations/he.json +++ b/packages/esm-patient-banner-app/translations/he.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "כתובת רשומה 1", "address2": "כתובת רשומה 2", "city": "עיר", "cityVillage": "עיר", "country": "מדינה", "countyDistrict": "מחוז", + "deceased": "Deceased", "district": "מחוז", + "from_lower": "from", "implementationLogo": "לוגו הטמעה", "patientAge": "גיל:", "patientDateOfBirthWithSeparator": "תאריך לידה:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "שם המטופל/ת:", "postalCode": "מיקוד", "printIdentifierSticker": "הדפסת מדבקת זיהוי", + "started": "Started", "state": "מדינה", "stateProvince": "מדינה", "telephoneNumberWithSeparator": "מספר טלפון:" diff --git a/packages/esm-patient-banner-app/translations/hi.json b/packages/esm-patient-banner-app/translations/hi.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/hi.json +++ b/packages/esm-patient-banner-app/translations/hi.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/hi_IN.json b/packages/esm-patient-banner-app/translations/hi_IN.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/hi_IN.json +++ b/packages/esm-patient-banner-app/translations/hi_IN.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/id.json b/packages/esm-patient-banner-app/translations/id.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/id.json +++ b/packages/esm-patient-banner-app/translations/id.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/it.json b/packages/esm-patient-banner-app/translations/it.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/it.json +++ b/packages/esm-patient-banner-app/translations/it.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/km.json b/packages/esm-patient-banner-app/translations/km.json index 233d56abb5..3c53df1521 100644 --- a/packages/esm-patient-banner-app/translations/km.json +++ b/packages/esm-patient-banner-app/translations/km.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "អាសយដ្ឋានជួរទី១", "address2": "អសយដ្ឋានជួរទី 2", "city": "ទីក្រុង", "cityVillage": "ទីក្រុង", "country": "ប្រទេស", "countyDistrict": "ស្រុក/ខ័ណ្ឌ", + "deceased": "Deceased", "district": "ស្រុក/ខ័ណ្ឌ", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "លេខកូដតំបន់", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "រដ្ឋ", "stateProvince": "រដ្ឋ", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/ne.json b/packages/esm-patient-banner-app/translations/ne.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/ne.json +++ b/packages/esm-patient-banner-app/translations/ne.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/pt.json b/packages/esm-patient-banner-app/translations/pt.json index 47d6730993..5a0b17e8cc 100644 --- a/packages/esm-patient-banner-app/translations/pt.json +++ b/packages/esm-patient-banner-app/translations/pt.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Endereço linha 1", "address2": "Endereço linha 2", "city": "Cidade", "cityVillage": "cidade", "country": "País", "countyDistrict": "Distrito", + "deceased": "Deceased", "district": "Distrito", + "from_lower": "from", "implementationLogo": "Logotipo de implementação", "patientAge": "Idade:", "patientDateOfBirthWithSeparator": "Data de nascimento:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Nome do utente:", "postalCode": "Caixa postal", "printIdentifierSticker": "Imprimir etiqueta do identificador", + "started": "Started", "state": "Estado", "stateProvince": "Estado", "telephoneNumberWithSeparator": "Número de Telefone:" diff --git a/packages/esm-patient-banner-app/translations/pt_BR.json b/packages/esm-patient-banner-app/translations/pt_BR.json new file mode 100644 index 0000000000..d6f528ff35 --- /dev/null +++ b/packages/esm-patient-banner-app/translations/pt_BR.json @@ -0,0 +1,24 @@ +{ + "activeVisit": "Active Visit", + "address1": "Address line 1", + "address2": "Address line 2", + "city": "City", + "cityVillage": "city", + "country": "Country", + "countyDistrict": "District", + "deceased": "Deceased", + "district": "District", + "from_lower": "from", + "implementationLogo": "Implementation logo", + "patientAge": "Age:", + "patientDateOfBirthWithSeparator": "Date of birth:", + "patientGenderWithSeparator": "Gender:", + "patientIdentifierSticker": "Patient identifier sticker", + "patientNameWithSeparator": "Patient name:", + "postalCode": "Postal code", + "printIdentifierSticker": "Print identifier sticker", + "started": "Started", + "state": "State", + "stateProvince": "State", + "telephoneNumberWithSeparator": "Telephone number:" +} diff --git a/packages/esm-patient-banner-app/translations/qu.json b/packages/esm-patient-banner-app/translations/qu.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/qu.json +++ b/packages/esm-patient-banner-app/translations/qu.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/si.json b/packages/esm-patient-banner-app/translations/si.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/si.json +++ b/packages/esm-patient-banner-app/translations/si.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/sw.json b/packages/esm-patient-banner-app/translations/sw.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/sw.json +++ b/packages/esm-patient-banner-app/translations/sw.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/sw_KE.json b/packages/esm-patient-banner-app/translations/sw_KE.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/sw_KE.json +++ b/packages/esm-patient-banner-app/translations/sw_KE.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/tr.json b/packages/esm-patient-banner-app/translations/tr.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/tr.json +++ b/packages/esm-patient-banner-app/translations/tr.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/tr_TR.json b/packages/esm-patient-banner-app/translations/tr_TR.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/tr_TR.json +++ b/packages/esm-patient-banner-app/translations/tr_TR.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/uk.json b/packages/esm-patient-banner-app/translations/uk.json index 61c50e2670..d6f528ff35 100644 --- a/packages/esm-patient-banner-app/translations/uk.json +++ b/packages/esm-patient-banner-app/translations/uk.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Address line 1", "address2": "Address line 2", "city": "City", "cityVillage": "city", "country": "Country", "countyDistrict": "District", + "deceased": "Deceased", "district": "District", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "Postal code", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/vi.json b/packages/esm-patient-banner-app/translations/vi.json index c1abe8095a..59f68f52a1 100644 --- a/packages/esm-patient-banner-app/translations/vi.json +++ b/packages/esm-patient-banner-app/translations/vi.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "Địa chỉ 1", "address2": "Địa chỉ 2", "city": "Thành phố", "cityVillage": "thành phố", "country": "Đất nước", "countyDistrict": "Huyện", + "deceased": "Deceased", "district": "Huyện", + "from_lower": "from", "implementationLogo": "Logo triển khai", "patientAge": "Tuổi\"", "patientDateOfBirthWithSeparator": "Ngày sinh:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Tên bệnh nhân:", "postalCode": "Mã bưu chính", "printIdentifierSticker": "In nhãn dán nhận dạng", + "started": "Started", "state": "State", "stateProvince": "State", "telephoneNumberWithSeparator": "Số điện thoại:" diff --git a/packages/esm-patient-banner-app/translations/zh.json b/packages/esm-patient-banner-app/translations/zh.json index 2a7db4c417..0c64edf158 100644 --- a/packages/esm-patient-banner-app/translations/zh.json +++ b/packages/esm-patient-banner-app/translations/zh.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "地址行1", "address2": "地址行2", "city": "城市", "cityVillage": "城市", "country": "国家", "countyDistrict": "区县", + "deceased": "Deceased", "district": "区县", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "邮政编码", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "省份", "stateProvince": "省份", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-banner-app/translations/zh_CN.json b/packages/esm-patient-banner-app/translations/zh_CN.json index bd02eec04c..7b1421b452 100644 --- a/packages/esm-patient-banner-app/translations/zh_CN.json +++ b/packages/esm-patient-banner-app/translations/zh_CN.json @@ -1,11 +1,14 @@ { + "activeVisit": "Active Visit", "address1": "地址行1", "address2": "地址行2", "city": "城市", "cityVillage": "城市", "country": "国家", "countyDistrict": "区县", + "deceased": "Deceased", "district": "区县", + "from_lower": "from", "implementationLogo": "Implementation logo", "patientAge": "Age:", "patientDateOfBirthWithSeparator": "Date of birth:", @@ -14,6 +17,7 @@ "patientNameWithSeparator": "Patient name:", "postalCode": "邮政编码", "printIdentifierSticker": "Print identifier sticker", + "started": "Started", "state": "省份", "stateProvince": "省份", "telephoneNumberWithSeparator": "Telephone number:" diff --git a/packages/esm-patient-chart-app/package.json b/packages/esm-patient-chart-app/package.json index 0105ed8219..b47bbcb07a 100644 --- a/packages/esm-patient-chart-app/package.json +++ b/packages/esm-patient-chart-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-chart-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient dashboard microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-chart-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -42,7 +42,7 @@ }, "peerDependencies": { "@carbon/react": "1.x", - "@openmrs/esm-framework": "5.x", + "@openmrs/esm-framework": "6.x", "@openmrs/esm-patient-common-lib": "*", "dayjs": "1.x", "lodash-es": "4.x", diff --git a/packages/esm-patient-chart-app/src/actions-buttons/start-visit.component.tsx b/packages/esm-patient-chart-app/src/actions-buttons/start-visit.component.tsx index 08f57f984c..3d568f5833 100644 --- a/packages/esm-patient-chart-app/src/actions-buttons/start-visit.component.tsx +++ b/packages/esm-patient-chart-app/src/actions-buttons/start-visit.component.tsx @@ -14,9 +14,13 @@ const StartVisitOverflowMenuItem: React.FC = ({ const { currentVisit } = useVisit(patient?.id); const isDeceased = Boolean(patient?.deceasedDateTime); - const handleLaunchModal = useCallback(() => launchPatientWorkspace('start-visit-workspace-form', { - openedFrom: "patient-chart-start-visit", - }), []); + const handleLaunchModal = useCallback( + () => + launchPatientWorkspace('start-visit-workspace-form', { + openedFrom: 'patient-chart-start-visit', + }), + [], + ); return ( !currentVisit && diff --git a/packages/esm-patient-chart-app/src/config-schema.ts b/packages/esm-patient-chart-app/src/config-schema.ts index e21dac645f..0f2d4f2099 100644 --- a/packages/esm-patient-chart-app/src/config-schema.ts +++ b/packages/esm-patient-chart-app/src/config-schema.ts @@ -133,6 +133,21 @@ export const esmPatientChartSchema = { _default: '/etl-latest/etl/patient/', _description: 'Custom URL to load resources required for showing recommended visit types', }, + trueConceptUuid: { + _type: Type.String, + _description: 'Default concept uuid for true in forms', + _default: '1065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + }, + falseConceptUuid: { + _type: Type.String, + _description: 'Default concept uuid for false in forms', + _default: '1066AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + }, + otherConceptUuid: { + _type: Type.String, + _description: 'Default concept uuid for other in forms', + _default: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + }, }; export interface ChartConfig { @@ -161,4 +176,7 @@ export interface ChartConfig { uuid: string; }>; visitDiagnosisConceptUuid: string; + trueConceptUuid: string; + falseConceptUuid: string; + otherConceptUuid: string; } diff --git a/packages/esm-patient-chart-app/src/constants.ts b/packages/esm-patient-chart-app/src/constants.ts index d01f53acb8..b63b224f9f 100644 --- a/packages/esm-patient-chart-app/src/constants.ts +++ b/packages/esm-patient-chart-app/src/constants.ts @@ -1,3 +1,5 @@ +export const clinicalFormsWorkspace = 'clinical-forms-workspace'; +export const formEntryWorkspace = 'patient-form-entry-workspace'; export const spaRoot = window['getOpenmrsSpaBase'](); export const basePath = '/patient/:patientUuid/chart'; export const dashboardPath = `${basePath}/:view/*`; diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.component.tsx b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.component.tsx new file mode 100644 index 0000000000..647d788c22 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.component.tsx @@ -0,0 +1,69 @@ +import React, { useMemo } from 'react'; +import { useConfig, usePatient, useVisit } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; +import { Tabs, Tab, TabList, TabPanels, TabPanel } from '@carbon/react'; +import { EncounterList } from './encounter-list.component'; +import { getMenuItemTabsConfiguration } from '../utils/encounter-list-config-builder'; +import styles from './encounter-list-tabs.scss'; +import { filter } from '../utils/helpers'; +import { type Encounter } from '../types'; + +interface EncounterListTabsComponentProps { + patientUuid: string; +} + +const EncounterListTabsComponent: React.FC = ({ patientUuid }) => { + const config = useConfig(); + const { tabDefinitions = [] } = config; + const configConcepts = { + trueConceptUuid: config.trueConceptUuid, + falseConceptUuid: config.falseConceptUuid, + otherConceptUuid: config.otherConceptUuid, + }; + const { t } = useTranslation(); + const tabsConfig = getMenuItemTabsConfiguration(tabDefinitions, configConcepts); + const patient = usePatient(patientUuid); + const { currentVisit } = useVisit(patientUuid); + const tabFilters = useMemo(() => { + return tabsConfig.map((tab) => ({ + name: tab.name, + filter: tab.hasFilter ? (encounter: Encounter) => filter(encounter, tab.formList?.[0]?.uuid) : null, + })); + }, [tabsConfig]); + + return ( +
+ + + {tabsConfig.map((tab) => ( + {t(tab.name)} + ))} + + + {tabsConfig.map((tab) => { + const tabFilter = tabFilters.find((t) => t.name === tab.name)?.filter; + + return ( + + + + ); + })} + + +
+ ); +}; + +export default EncounterListTabsComponent; diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.scss b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.scss new file mode 100644 index 0000000000..89a85c7da5 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.scss @@ -0,0 +1,7 @@ +.tabContainer div[role='tabpanel'] { + padding: 0 !important; +} + +.tabContainer li button { + width: 100% !important; +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.component.tsx b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.component.tsx new file mode 100644 index 0000000000..127d1cbb11 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.component.tsx @@ -0,0 +1,299 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import { navigate, showModal, showSnackbar, type Visit } from '@openmrs/esm-framework'; +import { EmptyState } from '@openmrs/esm-patient-common-lib'; +import { useTranslation } from 'react-i18next'; +import { EncounterListDataTable } from './table.component'; +import { Button, Link, OverflowMenu, OverflowMenuItem, DataTableSkeleton, Pagination } from '@carbon/react'; +import { Add } from '@carbon/react/icons'; +import { launchEncounterForm } from '../utils/helpers'; +import { deleteEncounter } from '../encounter-list.resource'; +import { useEncounterRows, useFormsJson } from '../hooks'; + +import styles from './encounter-list.scss'; +import { type TableRow, type Encounter, type Mode, type ColumnValue } from '../types'; +import { type FormattedColumn } from '../utils/encounter-list-config-builder'; + +export interface EncounterListColumn { + key: string; + header: string; + getValue: (encounter: Encounter) => ColumnValue; + link?: any; +} + +export interface EncounterListProps { + patientUuid: string; + encounterType: string; + columns: Array; + headerTitle: string; + description: string; + formList?: Array<{ + name?: string; + uuid: string; + excludedIntents?: Array; + fixedIntent?: string; + isDefault?: boolean; + }>; + launchOptions: { + hideFormLauncher?: boolean; + displayText?: string; + workspaceWindowSize?: 'minimized' | 'maximized'; + }; + filter?: (encounter: Encounter) => boolean; + afterFormSaveAction?: () => void; + deathStatus?: boolean; + currentVisit: Visit; +} + +export const EncounterList: React.FC = ({ + patientUuid, + encounterType, + columns, + headerTitle, + description, + formList, + filter, + launchOptions, + afterFormSaveAction, + currentVisit, + deathStatus, +}) => { + const { t } = useTranslation(); + + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + const { formsJson, isLoading: isLoadingFormsJson } = useFormsJson(formList?.[0]?.uuid); + const { encounters, total, isLoading, onFormSave, mutate } = useEncounterRows( + patientUuid, + encounterType, + filter, + afterFormSaveAction, + pageSize, + currentPage, + ); + + const { displayText, hideFormLauncher } = launchOptions; + + const defaultActions = useMemo( + () => [ + { + label: t('viewEncounter', 'View'), + form: { + name: formsJson?.name, + }, + mode: 'view', + intent: '*', + }, + { + label: t('editEncounter', 'Edit'), + form: { + name: formsJson?.name, + }, + mode: 'edit', + intent: '*', + }, + { + label: t('deleteEncounter', 'Delete'), + form: { + name: formsJson?.name, + }, + mode: 'delete', + intent: '*', + }, + ], + [formsJson, t], + ); + + const createLaunchFormAction = useCallback( + (encounter: Encounter, mode: Mode) => () => { + launchEncounterForm(formsJson, currentVisit, mode, onFormSave, encounter.uuid, null, patientUuid); + }, + [formsJson, onFormSave, patientUuid, currentVisit], + ); + + const handleDeleteEncounter = useCallback( + (encounterUuid: string, encounterTypeName: string) => { + const close = showModal('delete-encounter-modal', { + close: () => close(), + encounterTypeName: encounterTypeName || '', + onConfirmation: () => { + const abortController = new AbortController(); + deleteEncounter(encounterUuid, abortController) + .then(() => { + onFormSave(); + mutate(); + showSnackbar({ + isLowContrast: true, + title: t('encounterDeleted', 'Encounter deleted'), + subtitle: t('encounterSuccessfullyDeleted', 'The encounter has been deleted successfully'), + kind: 'success', + }); + }) + .catch(() => { + showSnackbar({ + isLowContrast: false, + title: t('error', 'Error'), + subtitle: t( + 'encounterWithError', + 'The encounter could not be deleted successfully. If the error persists, please contact your system administrator.', + ), + kind: 'error', + }); + }) + .finally(() => { + close(); + }); + }, + }); + }, + [onFormSave, t, mutate], + ); + + const tableRows = useMemo(() => { + return encounters.map((encounter: Encounter) => { + const tableRow: TableRow = { id: encounter.uuid, actions: null }; + + encounter['launchFormActions'] = { + editEncounter: createLaunchFormAction(encounter, 'edit'), + viewEncounter: createLaunchFormAction(encounter, 'view'), + }; + + columns.forEach((column) => { + let val = column?.getValue(encounter); + if (column.link) { + val = ( + { + e.preventDefault(); + if (column.link.handleNavigate) { + column.link.handleNavigate(encounter); + } else { + column.link?.getUrl && navigate({ to: column.link.getUrl(encounter) }); + } + }} + > + {val} + + ); + } + tableRow[column.key] = val; + }); + + const actions = + Array.isArray(tableRow.actions) && tableRow.actions.length > 0 ? tableRow.actions : defaultActions; + + tableRow['actions'] = ( + + {actions.map((actionItem, index) => { + const form = formsJson && actionItem?.form?.name ? formsJson.name === actionItem.form.name : null; + + return ( + form && ( + { + e.preventDefault(); + actionItem.mode === 'delete' + ? handleDeleteEncounter(encounter.uuid, encounter.encounterType.name) + : launchEncounterForm( + formsJson, + currentVisit, + actionItem.mode === 'enter' ? 'add' : actionItem.mode, + onFormSave, + encounter.uuid, + actionItem.intent, + patientUuid, + ); + }} + /> + ) + ); + })} + + ); + + return tableRow; + }); + }, [ + encounters, + createLaunchFormAction, + columns, + defaultActions, + formsJson, + t, + handleDeleteEncounter, + onFormSave, + patientUuid, + currentVisit, + ]); + + const headers = useMemo(() => { + if (columns) { + return columns.map((column) => { + return { key: column.key, header: t(column.header) }; + }); + } + return []; + }, [columns, t]); + + const formLauncher = useMemo(() => { + if (formsJson && !formsJson['availableIntents']?.length) { + return ( + + ); + } + return null; + }, [formsJson, displayText, onFormSave, patientUuid, t, currentVisit]); + + if (isLoading === true || isLoadingFormsJson === true) { + return ; + } + + return ( + <> + {tableRows?.length > 0 || encounters.length > 0 ? ( + <> +
+
+

{t(headerTitle)}

+ {!(hideFormLauncher ?? deathStatus) &&
{formLauncher}
} +
+ + { + setCurrentPage(page); + setPageSize(pageSize); + }} + pageSize={pageSize} + totalItems={total} + /> +
+ + ) : ( + launchEncounterForm(formsJson, currentVisit, 'add', onFormSave, '', '*', patientUuid) + } + /> + )} + + ); +}; diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.scss b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.scss new file mode 100644 index 0000000000..3565416375 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.scss @@ -0,0 +1,28 @@ +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; + +.widgetContainer { + background-color: $ui-background; + border: 1px solid #e0e0e0; + margin-bottom: 1rem; +} + +.widgetHeaderContainer { + display: flex; + justify-content: space-between; + align-items: center; + padding: layout.$spacing-04 0 layout.$spacing-04 layout.$spacing-05; +} + +.widgetHeaderContainer > h4:after { + content: ''; + display: block; + width: 2rem; + padding-top: 0.188rem; + border-bottom: 0.375rem solid var(--brand-03); +} + +.widgetContainer :global(.cds--data-table) thead th button span { + height: unset !important; +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/table.component.tsx b/packages/esm-patient-chart-app/src/encounter-list/components/table.component.tsx new file mode 100644 index 0000000000..7ba30f4d78 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/table.component.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { + DataTable, + Table, + TableCell, + TableContainer, + TableBody, + TableHead, + TableHeader, + TableRow, +} from '@carbon/react'; + +import styles from './table.scss'; + +interface TableProps { + tableHeaders: any; + tableRows: any; +} + +export const EncounterListDataTable: React.FC = ({ tableHeaders, tableRows }) => { + return ( + + + {({ rows, headers, getHeaderProps, getTableProps }) => ( + + + + {headers.map((header, index) => ( + + {header.header?.content ?? header.header} + + ))} + + + + {rows.map((row) => ( + + {row.cells.map((cell) => ( + {cell.value?.content ?? cell.value} + ))} + + ))} + +
+ )} +
+
+ ); +}; diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/table.scss b/packages/esm-patient-chart-app/src/encounter-list/components/table.scss new file mode 100644 index 0000000000..0d1c910d7e --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/table.scss @@ -0,0 +1,7 @@ +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; + +.productiveHeading01 { + @include type.type-style('heading-compact-01'); +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/components/tag.component.tsx b/packages/esm-patient-chart-app/src/encounter-list/components/tag.component.tsx new file mode 100644 index 0000000000..5176174c6f --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/components/tag.component.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Tag } from '@carbon/react'; +import { getObsFromEncounter, findObs } from '../utils/helpers'; +import { type ConfigConcepts, type Encounter } from '../types'; + +export const renderTag = ( + encounter: Encounter, + concept: string, + statusColorMappings: Record, + config: ConfigConcepts, +) => { + const columnStatus = getObsFromEncounter(encounter, concept, false, false, undefined, undefined, undefined, config); + const columnStatusObs = findObs(encounter, concept); + + if (columnStatus == '--') { + return '--'; + } else { + return ( + + {columnStatus} + + ); + } +}; diff --git a/packages/esm-patient-chart-app/src/encounter-list/encounter-list.resource.ts b/packages/esm-patient-chart-app/src/encounter-list/encounter-list.resource.ts new file mode 100644 index 0000000000..f419d0c904 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/encounter-list.resource.ts @@ -0,0 +1,26 @@ +import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; + +export function fetchOpenMRSForms(formUuid: string) { + return openmrsFetch(`${restBaseUrl}/form/${formUuid}`).then(({ data }) => { + if (data.results.length) { + return data.results; + } + return null; + }); +} + +export function fetchPatientRelationships(patientUuid: string) { + return openmrsFetch(`${restBaseUrl}relationship?person=${patientUuid}&v=full`).then(({ data }) => { + if (data.results.length) { + return data.results; + } + return null; + }); +} + +export function deleteEncounter(encounterUuid: string, abortController: AbortController) { + return openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, { + method: 'DELETE', + signal: abortController.signal, + }); +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/hooks/index.ts b/packages/esm-patient-chart-app/src/encounter-list/hooks/index.ts new file mode 100644 index 0000000000..fd15cbc4d2 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './useEncounterRows'; +export * from './useFormsJson'; diff --git a/packages/esm-patient-chart-app/src/encounter-list/hooks/useEncounterRows.ts b/packages/esm-patient-chart-app/src/encounter-list/hooks/useEncounterRows.ts new file mode 100644 index 0000000000..0dcfb05da4 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/hooks/useEncounterRows.ts @@ -0,0 +1,58 @@ +import { useCallback, useEffect, useState } from 'react'; +import useSWR from 'swr'; +import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; +import { type Encounter } from '../types'; + +export const encounterRepresentation = + 'custom:(uuid,encounterDatetime,encounterType,location:(uuid,name),' + + 'patient:(uuid,display,age,identifiers,person),encounterProviders:(uuid,provider:(uuid,name)),' + + 'obs:(uuid,obsDatetime,voided,groupMembers,concept:(uuid,name:(uuid,name)),value:(uuid,name:(uuid,name),' + + 'names:(uuid,conceptNameType,name))),form:(uuid,name))'; + +interface EncounterResponse { + results: Encounter[]; + totalCount?: number; +} + +export function useEncounterRows( + patientUuid: string, + encounterType: string, + encounterFilter: (encounter) => boolean, + afterFormSaveAction: () => void, + pageSize?: number, + currentPage?: number, +) { + const startIndex = (currentPage - 1) * pageSize; + const [encounters, setEncounters] = useState([]); + const url = `${restBaseUrl}/encounter?encounterType=${encounterType}&patient=${patientUuid}&v=${encounterRepresentation}&totalCount=true&limit=${pageSize}&startIndex=${startIndex}`; + + const { data: response, error, isLoading, mutate } = useSWR<{ data: EncounterResponse }, Error>(url, openmrsFetch); + + useEffect(() => { + if (response) { + response.data.results.sort( + (a, b) => new Date(b.encounterDatetime).getTime() - new Date(a.encounterDatetime).getTime(), + ); + + if (encounterFilter) { + setEncounters(response.data.results.filter((encounter) => encounterFilter(encounter))); + } else { + setEncounters([...response.data.results]); + } + } + }, [encounterFilter, response]); + + const onFormSave = useCallback(() => { + mutate(); + afterFormSaveAction && afterFormSaveAction(); + }, [afterFormSaveAction, mutate]); + + return { + encounters, + total: response?.data?.totalCount, + isLoading, + error, + onFormSave, + mutate, + }; +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/hooks/useFormsJson.ts b/packages/esm-patient-chart-app/src/encounter-list/hooks/useFormsJson.ts new file mode 100644 index 0000000000..6027050da3 --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/hooks/useFormsJson.ts @@ -0,0 +1,15 @@ +import useSWR from 'swr'; + +import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; +import { type Form } from '../types'; + +export function useFormsJson(formUuid: string) { + const url = `${restBaseUrl}/form/${formUuid}`; + const { data, isLoading, error } = useSWR<{ data: Form }, Error>(url, openmrsFetch); + + return { + formsJson: data?.data ?? null, + isLoading, + error, + }; +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/types.ts b/packages/esm-patient-chart-app/src/encounter-list/types.ts new file mode 100644 index 0000000000..c11972e56c --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/types.ts @@ -0,0 +1,211 @@ +import { type OpenmrsResource } from '@openmrs/esm-framework'; +import { type ReactElement } from 'react'; + +export interface Encounter extends OpenmrsResource { + encounterDatetime: Date; + encounterType: { uuid: string; name: string }; + patient: { + uuid: string; + display: string; + age: number; + birthDate: string; + }; + location: { + uuid: string; + display: string; + name: string; + }; + encounterProviders?: Array<{ encounterRole: string; provider: { uuid: string; name: string } }>; + obs: Array; + form?: { + uuid: string; + }; + visit?: string; +} + +export interface Observation { + uuid: string; + concept: { uuid: string; name: string }; + value: + | { + uuid: string; + name: { + name: string; + }; + names?: Array<{ uuid: string; name: string; conceptNameType: string }>; + } + | string; + groupMembers?: Array; + obsDatetime: string; +} + +export interface ListResponse { + results: Array; +} + +export interface Privilege { + uuid: string; + name: string; + display?: string; + description?: string; +} + +export interface EncounterType { + uuid: string; + name: string; + viewPrivilege: Privilege | null; + editPrivilege: Privilege | null; +} + +export interface Form { + uuid: string; + encounterType?: EncounterType; + name: string; + display?: string; + version: string; + published: boolean; + retired: boolean; + resources: Array; + formCategory?: string; +} + +export interface FormEncounterResource { + uuid: string; + name: string; + dataType: string; + valueReference: string; +} + +export interface EncounterWithFormRef { + uuid: string; + encounterType?: EncounterType; + encounterDatetime: string; + form?: Form; +} + +export interface CompletedFormInfo { + form: Form; + associatedEncounters: Array; + lastCompleted?: Date; +} + +export interface HtmlFormEntryForm { + formUuid: string; + formName: string; + formUiResource: string; + formUiPage: 'enterHtmlFormWithSimpleUi' | 'enterHtmlFormWithStandardUi'; + formEditUiPage: 'editHtmlFormWithSimpleUi' | 'editHtmlFormWithStandardUi'; +} + +export interface FormsSection { + name: string; + forms: Array; +} + +export interface ConfigObject { + htmlFormEntryForms: Array; + formSections: Array; + customFormsUrl: string; + orderBy: 'name' | 'most-recent'; + showHtmlFormEntryForms: boolean; +} + +// encounter list types + +export interface ActionProps { + mode: string; + label: string; + formName: string; + intent?: string; +} + +export interface ConditionalActionProps { + mode: string; + label: string; + formName: string; + dependsOn?: string; + dependantConcept?: string; + dependantEncounter?: string; + intent?: string; +} + +export interface ColumnDefinition { + id: string; + title: string; + isComplex?: boolean; + concept?: string; + secondaryConcept?: string; + multipleConcepts?: Array; + fallbackConcepts?: Array; + actionOptions?: Array; + conditionalActionOptions?: Array; + isDate?: boolean; + isTrueFalseConcept?: boolean; + type?: string; + isLink?: boolean; + useMultipleObs?: boolean; + valueMappings?: Record; + conceptMappings?: Array; + statusColorMappings?: Record; + isConditionalConcept?: boolean; + conditionalConceptMappings?: Record; + conditionalEncounterMappings?: Record; +} + +export interface ConditionalEncounterMapping { + concept: string; + isDate?: boolean; +} + +interface LaunchOptions { + displayText: string; + hideFormLauncher?: boolean; +} +export interface TabSchema { + tabName: string; + hasFilter?: boolean; + headerTitle: string; + displayText: string; + encounterType: string; + columns: Array; + formList: Array<{ name: string; uuid: string; fixedIntent?: string; excludedIntents?: Array }>; + launchOptions: LaunchOptions; +} + +export type Mode = 'edit' | 'view'; + +export interface Action { + label: string; + mode: Mode; + form?: { name: string }; + intent?: string; +} + +export interface TableRow { + id: string; + actions: Action[] | ReactElement | null; +} + +export interface FormColumn { + form: { name: string }; + encounterUuid: string; + intent: string; + label: string; + mode: string; +} + +export type NamedColumn = + | string + | { + uuid: string; + name: { name: string }; + names?: { uuid: string; name: string; conceptNameType: string }[]; + }; + +export type ColumnValue = string | number | JSX.Element | NamedColumn | Array | Array | null; + +export interface ConfigConcepts { + trueConceptUuid: string; + falseConceptUuid: string; + otherConceptUuid: string; +} diff --git a/packages/esm-patient-chart-app/src/encounter-list/utils/encounter-list-config-builder.ts b/packages/esm-patient-chart-app/src/encounter-list/utils/encounter-list-config-builder.ts new file mode 100644 index 0000000000..6a0b25297c --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/utils/encounter-list-config-builder.ts @@ -0,0 +1,152 @@ +import { + getObsFromEncounter, + getMultipleObsFromEncounter, + resolveValueUsingMappings, + getConceptFromMappings, + getConditionalConceptValue, +} from './helpers'; +import { + type Encounter, + type ColumnDefinition, + type TabSchema, + type ActionProps, + type ConditionalActionProps, + type ColumnValue, + type NamedColumn, + type ConfigConcepts, +} from '../types'; +import { renderTag } from '../components/tag.component'; + +export interface FormattedColumn { + key: string; + header: string; + getValue: (encounter: Encounter) => ColumnValue; + link?: { getUrl: (encounter: Encounter) => string; handleNavigate?: (encounter: Encounter) => void }; + concept?: string; +} + +const getColumnValue = (encounter: Encounter, column: ColumnDefinition, config: ConfigConcepts): ColumnValue => { + if (column.id === 'actions') { + return getActions(encounter, column, config); + } + if (column.statusColorMappings) { + return renderTag(encounter, column.concept, column.statusColorMappings, config); + } + + if (column.isConditionalConcept) { + return getConditionalConceptValue(encounter, column.conditionalConceptMappings, column.isDate, config); + } + + if (column.useMultipleObs) { + return getMultipleObsFromEncounter(encounter, column.multipleConcepts, config); + } + + if (column.valueMappings) { + return resolveValueUsingMappings(encounter, column.concept, column.valueMappings); + } + + if (column.conceptMappings) { + return getMappedConceptValue(encounter, column, config); + } + + return getObsFromEncounter( + encounter, + column.concept, + column.isDate, + column.isTrueFalseConcept, + column.type, + column.fallbackConcepts, + column.secondaryConcept, + config, + ); +}; + +const createActionObject = (encounter: Encounter, action: ActionProps | ConditionalActionProps) => ({ + form: { name: action.formName }, + encounterUuid: encounter.uuid, + intent: action.intent || '*', + label: action.label, + mode: action.mode, +}); + +const getActions = (encounter: Encounter, column: ColumnDefinition, config: ConfigConcepts) => { + const baseActions = column.actionOptions?.map((action: ActionProps) => createActionObject(encounter, action)) || []; + + const conditionalActions = + column.conditionalActionOptions + ?.map((action) => createConditionalAction(encounter, action, config)) + .filter(Boolean) || []; + + return [...baseActions, ...conditionalActions]; +}; + +const createConditionalAction = (encounter: Encounter, action: ConditionalActionProps, config: ConfigConcepts) => { + const dependantObsValue = getObsFromEncounter( + encounter, + action.dependantConcept, + false, + false, + undefined, + undefined, + undefined, + config, + ); + if (dependantObsValue === action.dependsOn) { + return createActionObject(encounter, action); + } + + const dependantEncounterValue = encounter.encounterType?.uuid; + if (dependantEncounterValue === action.dependantEncounter) { + return createActionObject(encounter, action); + } + + return null; +}; + +const getMappedConceptValue = (encounter: Encounter, column: ColumnDefinition, config: ConfigConcepts): NamedColumn => { + const concept = getConceptFromMappings(encounter, column.conceptMappings); + return getObsFromEncounter( + encounter, + concept, + column.isDate, + column.isTrueFalseConcept, + column.type, + column.fallbackConcepts, + column.secondaryConcept, + config, + ); +}; + +export const getTabColumns = (columnsDefinition: Array, config: ConfigConcepts) => { + const columns: Array = columnsDefinition.map((column: ColumnDefinition) => ({ + key: column.id, + header: column.title, + concept: column.concept, + getValue: (encounter) => getColumnValue(encounter, column, config), + link: column.isLink + ? { + getUrl: (encounter) => encounter.url, + handleNavigate: (encounter) => encounter.launchFormActions?.viewEncounter(), + } + : null, + })); + + return columns; +}; + +export const getMenuItemTabsConfiguration = (tabDefinitions: Array, config: ConfigConcepts) => { + const tabs = tabDefinitions.map((tab) => { + return { + name: tab.tabName, + hasFilter: tab.hasFilter || false, + encounterType: tab.encounterType, + headerTitle: tab.headerTitle, + description: tab.displayText, + formList: tab.formList, + columns: getTabColumns(tab.columns, config), + launchOptions: tab.launchOptions, + }; + }); + + return tabs; +}; diff --git a/packages/esm-patient-chart-app/src/encounter-list/utils/helpers.ts b/packages/esm-patient-chart-app/src/encounter-list/utils/helpers.ts new file mode 100644 index 0000000000..b189eaea0c --- /dev/null +++ b/packages/esm-patient-chart-app/src/encounter-list/utils/helpers.ts @@ -0,0 +1,182 @@ +import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; +import { formatDate, parseDate, formatDatetime, type Concept, age, type Visit } from '@openmrs/esm-framework'; +import { type Observation, type Encounter, type Form, type ConfigConcepts } from '../types'; + +type LaunchAction = 'add' | 'view' | 'edit' | 'embedded-view'; + +export function launchEncounterForm( + form: Form, + visit: Visit, + action: LaunchAction = 'add', + onFormSave: () => void, + encounterUuid?: string, + intent: string = '*', + patientUuid?: string, +) { + launchPatientWorkspace('patient-form-entry-workspace', { + workspaceTitle: form?.name, + mutateForm: onFormSave, + formInfo: { + encounterUuid, + formUuid: form?.uuid, + patientUuid: patientUuid, + visit: visit, + additionalProps: { + mode: action === 'add' ? 'enter' : action, + formSessionIntent: intent, + openClinicalFormsWorkspaceOnFormClose: false, + }, + }, + }); +} + +export function formatDateTime(dateString: string): string { + const parsedDate = parseDate(dateString.includes('.') ? dateString.split('.')[0] : dateString); + return formatDatetime(parsedDate); +} + +export function obsArrayDateComparator(left: Observation, right: Observation): number { + const leftDate = new Date(left.obsDatetime); + const rightDate = new Date(right.obsDatetime); + return rightDate.getTime() - leftDate.getTime(); +} + +export function findObs(encounter: Encounter, obsConcept: string): Observation | undefined { + const allObs = encounter?.obs?.filter((observation) => observation.concept.uuid === obsConcept) || []; + return !allObs ? undefined : allObs.length === 1 ? allObs[0] : allObs.sort(obsArrayDateComparator)[0]; +} + +export function getObsFromEncounters(encounters: Encounter, obsConcept: Concept) { + const filteredEnc = encounters?.find((enc) => enc.obs.find((obs) => obs.concept.uuid === obsConcept)); + return getObsFromEncounter(filteredEnc, obsConcept); +} + +export function resolveValueUsingMappings(encounter: Encounter, concept: string, mappings) { + const obs = findObs(encounter, concept); + for (const key in mappings) { + if (typeof obs?.value === 'object' && 'uuid' in obs.value) { + if (mappings[key] === obs.value.uuid) { + return key; + } + } + } + return '--'; +} + +export function getConditionalConceptValue( + encounter: Encounter, + conditionalConceptMappings, + isDate: boolean, + config: ConfigConcepts, +) { + const { trueConcept, nonTrueConcept, dependantConcept, conditionalConcept } = conditionalConceptMappings; + const obsValue = findObs(encounter, dependantConcept)?.value; + const dependantUuid = typeof obsValue === 'object' && 'uuid' in obsValue ? obsValue.uuid : null; + const finalConcept = dependantUuid === conditionalConcept ? trueConcept : nonTrueConcept; + return getObsFromEncounter(encounter, finalConcept, isDate, false, undefined, undefined, undefined, config); +} + +export function getConceptFromMappings(encounter: Encounter, concepts) { + for (const concept of concepts) { + const obs = findObs(encounter, concept); + if (obs && obs.value) { + return concept; + } + } + return null; +} + +export function getMultipleObsFromEncounter(encounter: Encounter, obsConcepts: Array, config: ConfigConcepts) { + let observations = []; + obsConcepts.map((concept) => { + const obs = getObsFromEncounter(encounter, concept, false, false, undefined, undefined, undefined, config); + if (obs !== '--') { + observations.push(obs); + } + }); + + return observations.length ? observations.join(', ') : '--'; +} + +export function getObsFromEncounter( + encounter: Encounter, + obsConcept, + isDate?: Boolean, + isTrueFalseConcept?: Boolean, + type?: string, + fallbackConcepts?: Array, + secondaryConcept?: string, + config?: ConfigConcepts, +) { + let obs = findObs(encounter, obsConcept); + + if (!encounter || !obsConcept) { + return '--'; + } + + if (isTrueFalseConcept) { + if (typeof obs?.value === 'object') { + if (obs?.value?.uuid === config.falseConceptUuid || obs?.value?.uuid === config.trueConceptUuid) { + return obs?.value?.name?.name; + } + } + } + + if (type === 'location') { + return encounter.location.name; + } + + if (type === 'provider') { + return encounter.encounterProviders.map((p) => p.provider.name).join(' | '); + } + + // TODO: This needs to be added later + if (type === 'mothersName') { + return; + } + + if (type === 'visitType') { + return encounter.encounterType.name; + } + + // TODO: Need to get a better place for this + if (type === 'ageAtHivTest') { + return age(encounter.patient.birthDate, encounter.encounterDatetime); + } + + // + if (secondaryConcept && typeof obs.value === 'object' && obs.value.names) { + const primaryValue = + obs.value.names.find((conceptName) => conceptName.conceptNameType === 'SHORT')?.name || obs.value.name.name; + if (primaryValue === config.otherConceptUuid) { + const secondaryObs = findObs(encounter, secondaryConcept); + return secondaryObs ? secondaryObs.value : '--'; + } + } + + if (!obs && fallbackConcepts?.length) { + const concept = fallbackConcepts.find((c) => findObs(encounter, c) != null); + obs = findObs(encounter, concept); + } + + if (!obs) { + return '--'; + } + + if (isDate) { + if (typeof obs.value === 'object' && obs.value?.names) { + return formatDate(parseDate(obs.obsDatetime), { mode: 'wide' }); + } else { + return typeof obs.value === 'string' ? formatDate(parseDate(obs.value), { mode: 'wide' }) : '--'; + } + } + + if (typeof obs.value === 'object' && obs.value?.names) { + return ( + obs.value?.names?.find((conceptName) => conceptName.conceptNameType === 'SHORT')?.name || obs.value.name.name + ); + } + return obs.value; +} + +export const filter = (encounter: Encounter, formUuid: string) => encounter?.form?.uuid === formUuid; diff --git a/packages/esm-patient-chart-app/src/index.ts b/packages/esm-patient-chart-app/src/index.ts index 4e1566570e..d46d643695 100644 --- a/packages/esm-patient-chart-app/src/index.ts +++ b/packages/esm-patient-chart-app/src/index.ts @@ -225,3 +225,8 @@ export const activeVisitActionsComponent = getAsyncLifecycle( () => import('./visit/visits-widget/active-visit-buttons/active-visit-buttons'), { featureName: 'active-visit-actions', moduleName }, ); + +export const encounterListTableTabs = getAsyncLifecycle( + () => import('./encounter-list/components/encounter-list-tabs.component'), + { featureName: 'encounter-list-table-tabs', moduleName }, +); diff --git a/packages/esm-patient-chart-app/src/patient-chart/chart-review/chart-review.component.tsx b/packages/esm-patient-chart-app/src/patient-chart/chart-review/chart-review.component.tsx index 14353f6df7..b91354fb42 100644 --- a/packages/esm-patient-chart-app/src/patient-chart/chart-review/chart-review.component.tsx +++ b/packages/esm-patient-chart-app/src/patient-chart/chart-review/chart-review.component.tsx @@ -19,8 +19,8 @@ function makePath(target: DashboardConfig, params: Record = {}) return parts.join('/'); } -function getDashboardDefinition(meta: object, config: ConfigObject) { - return { ...meta, ...config }; +function getDashboardDefinition(meta: object, config: ConfigObject, moduleName: string) { + return { ...meta, ...config, moduleName }; } interface ChartReviewProps { @@ -35,11 +35,13 @@ const ChartReview: React.FC = ({ patientUuid, patient, view, s const { navGroups } = useNavGroups(); const ungroupedDashboards = extensionStore.slots['patient-chart-dashboard-slot'].assignedExtensions.map((e) => - getDashboardDefinition(e.meta, e.config), + getDashboardDefinition(e.meta, e.config, e.moduleName), ); const groupedDashboards = navGroups .map((slotName) => - extensionStore.slots[slotName].assignedExtensions.map((e) => getDashboardDefinition(e.meta, e.config)), + extensionStore.slots[slotName].assignedExtensions.map((e) => + getDashboardDefinition(e.meta, e.config, e.moduleName), + ), ) .flat(); const dashboards = ungroupedDashboards.concat(groupedDashboards) as Array; diff --git a/packages/esm-patient-chart-app/src/patient-chart/chart-review/dashboard-view.component.tsx b/packages/esm-patient-chart-app/src/patient-chart/chart-review/dashboard-view.component.tsx index 68df28ca6e..080eeeb1bc 100644 --- a/packages/esm-patient-chart-app/src/patient-chart/chart-review/dashboard-view.component.tsx +++ b/packages/esm-patient-chart-app/src/patient-chart/chart-review/dashboard-view.component.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useMemo, useState } from 'react'; +import classNames from 'classnames'; import { useMatch } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { Extension, ExtensionSlot, useExtensionSlotMeta } from '@openmrs/esm-framework'; +import { launchPatientWorkspace, launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib'; import { dashboardPath } from '../../constants'; import styles from './dashboard-view.scss'; -import { launchPatientWorkspace, launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib'; -import classNames from 'classnames'; /** * The layout mode dictates the width occuppied by the chart dashboard widgets. @@ -21,6 +22,7 @@ export interface DashboardConfig { path: string; hideDashboardTitle?: boolean; layoutMode?: LayoutMode; + moduleName: string; } interface DashboardViewProps { @@ -31,6 +33,7 @@ interface DashboardViewProps { export function DashboardView({ dashboard, patientUuid, patient }: DashboardViewProps) { const widgetMetas = useExtensionSlotMeta(dashboard.slot); + const { t } = useTranslation(dashboard.moduleName); const { params: { view }, } = useMatch(dashboardPath); @@ -61,7 +64,7 @@ export function DashboardView({ dashboard, patientUuid, patient }: DashboardView return ( <> - {!dashboard.hideDashboardTitle && resolvedTitle &&

{resolvedTitle}

} + {!dashboard.hideDashboardTitle && resolvedTitle &&

{t(resolvedTitle)}

}
{(extension) => { diff --git a/packages/esm-patient-chart-app/src/routes.json b/packages/esm-patient-chart-app/src/routes.json index 5973d07f33..4fa53595e5 100644 --- a/packages/esm-patient-chart-app/src/routes.json +++ b/packages/esm-patient-chart-app/src/routes.json @@ -23,6 +23,12 @@ "online": true, "offline": true }, + { + "name": "encounter-list", + "component": "encounterListTableTabs", + "online": true, + "offline": true + }, { "name": "start-visit-button", "component": "startVisitActionButton", diff --git a/packages/esm-patient-chart-app/src/visit-header/visit-header.component.tsx b/packages/esm-patient-chart-app/src/visit-header/visit-header.component.tsx index f91a20d637..1daae7d9b6 100644 --- a/packages/esm-patient-chart-app/src/visit-header/visit-header.component.tsx +++ b/packages/esm-patient-chart-app/src/visit-header/visit-header.component.tsx @@ -118,7 +118,7 @@ const PatientInfo: React.FC = ({ patient }) => { }; function launchStartVisitForm() { - launchPatientWorkspace('start-visit-workspace-form', {openedFrom: "patient-chart-start-visit"}); + launchPatientWorkspace('start-visit-workspace-form', { openedFrom: 'patient-chart-start-visit' }); } const VisitHeader: React.FC<{ patient: fhir.Patient }> = ({ patient }) => { diff --git a/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.scss b/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.scss index 3d8992a1d6..1fda6a8bd2 100644 --- a/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.scss +++ b/packages/esm-patient-chart-app/src/visit/queue-entry/edit-queue-entry.scss @@ -2,6 +2,7 @@ @use '@openmrs/esm-styleguide/src/vars' as *; .editStatusBtn { + white-space: nowrap; margin-left: layout.$spacing-04; border: none; } diff --git a/packages/esm-patient-chart-app/src/visit/start-visit-button.component.tsx b/packages/esm-patient-chart-app/src/visit/start-visit-button.component.tsx index 6f88435e10..dfee38e85f 100644 --- a/packages/esm-patient-chart-app/src/visit/start-visit-button.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/start-visit-button.component.tsx @@ -11,8 +11,8 @@ const StartVisitButton = ({ patientUuid }) => { patientUuid, workspaceName: 'start-visit-workspace-form', additionalProps: { - openedFrom: 'patient-chart-start-visit' - } + openedFrom: 'patient-chart-start-visit', + }, }); }, [patientUuid]); diff --git a/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx index 6d96a877e4..555e558c1b 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-action-items/edit-visit-details.component.tsx @@ -18,7 +18,7 @@ const EditVisitDetailsActionItem: React.FC = ({ launchPatientWorkspace('start-visit-workspace-form', { workspaceTitle: t('editVisitDetails', 'Edit visit details'), visitToEdit: visit, - openedFrom: 'patient-chart-edit-visit' + openedFrom: 'patient-chart-edit-visit', }); }; 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 49f83a2cf4..a8e4876bf0 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 @@ -14,6 +14,7 @@ import { } from '@carbon/react'; import { zodResolver } from '@hookform/resolvers/zod'; import { + type AssignedExtension, Extension, ExtensionSlot, formatDatetime, @@ -60,10 +61,10 @@ import VisitDateTimeField from './visit-date-time.component'; import { createVisitAttribute, deleteVisitAttribute, - type OnVisitCreatedOrUpdatedCallback, updateVisitAttribute, useConditionalVisitTypes, - useOnVisitCreatedOrUpdatedCallbacks, + useVisitFormCallbacks, + type VisitFormCallbacks, type VisitFormData, } from './visit-form.resource'; import styles from './visit-form.scss'; @@ -119,7 +120,7 @@ const StartVisitForm: React.FC = ({ const { visitAttributeTypes } = useVisitAttributeTypes(); const [extraVisitInfo, setExtraVisitInfo] = useState(null); - const [OnVisitCreatedOrUpdatedCallbacks, setOnVisitCreatedOrUpdatedCallbacks] = useOnVisitCreatedOrUpdatedCallbacks(); + const [visitFormCallbacks, setVisitFormCallbacks] = useVisitFormCallbacks(); const displayVisitStopDateTimeFields = useMemo( () => Boolean(visitToEdit?.uuid || showVisitEndDateTimeFields), [visitToEdit?.uuid, showVisitEndDateTimeFields], @@ -530,11 +531,11 @@ const StartVisitForm: React.FC = ({ }, ); - const OnVisitCreatedOrUpdatedRequests = [...OnVisitCreatedOrUpdatedCallbacks.values()].map( - (OnVisitCreatedOrUpdated) => OnVisitCreatedOrUpdated(visit, patientUuid), + const onVisitCreatedOrUpdatedRequests = [...visitFormCallbacks.values()].map((callbacks) => + callbacks.onVisitCreatedOrUpdated(visit), ); - return Promise.all([visitAttributesRequest, ...OnVisitCreatedOrUpdatedRequests]); + return Promise.all([visitAttributesRequest, ...onVisitCreatedOrUpdatedRequests]); }) .then(() => { closeWorkspace({ ignoreChanges: true }); @@ -590,7 +591,7 @@ const StartVisitForm: React.FC = ({ mutateCurrentVisit, mutateVisits, mutateInfiniteVisits, - OnVisitCreatedOrUpdatedCallbacks, + visitFormCallbacks, patientUuid, t, validateVisitStartStopDatetime, @@ -662,7 +663,7 @@ const StartVisitForm: React.FC = ({ name="visit-form-top-slot" patientUuid={patientUuid} visitFormOpenedFrom={openedFrom} - setOnVisitCreatedOrUpdatedCallbacks={setOnVisitCreatedOrUpdatedCallbacks} + setVisitFormCallbacks={setVisitFormCallbacks} />
@@ -773,7 +774,7 @@ const StartVisitForm: React.FC = ({ name="visit-form-bottom-slot" patientUuid={patientUuid} visitFormOpenedFrom={openedFrom} - setOnVisitCreatedOrUpdatedCallbacks={setOnVisitCreatedOrUpdatedCallbacks} + setVisitFormCallbacks={setVisitFormCallbacks} /> @@ -817,52 +818,47 @@ interface VisitFormExtensionSlotProps { name: string; patientUuid: string; visitFormOpenedFrom: string; - setOnVisitCreatedOrUpdatedCallbacks: React.Dispatch< - React.SetStateAction> - >; + setVisitFormCallbacks: React.Dispatch>>; } type VisitFormExtensionState = { patientUuid: string; /** - * This function allows an extension to register a callback to run after a visit has been created. - * This callback can be used to make further requests. The callback should handle its own UI notification + * This function allows an extension to register callbacks for visit form submission. + * This callbacks can be used to make further requests. The callbacks should handle its own UI notification * on success / failure, and its returned Promise MUST resolve on success and MUST reject on failure. * @param callback * @returns */ - setOnVisitCreatedOrUpdated: (callback: OnVisitCreatedOrUpdatedCallback) => void; + setVisitFormCallbacks(callbacks: VisitFormCallbacks); visitFormOpenedFrom: string; patientChartConfig: ChartConfig; }; -const VisitFormExtensionSlot: React.FC = ({ - name, - patientUuid, - visitFormOpenedFrom, - setOnVisitCreatedOrUpdatedCallbacks, -}) => { - const config = useConfig(); - - return ( - - {(extension) => { - const state: VisitFormExtensionState = { - patientUuid, - setOnVisitCreatedOrUpdated: (callback) => { - setOnVisitCreatedOrUpdatedCallbacks((old) => { - return new Map(old).set(extension.id, callback); - }); - }, - visitFormOpenedFrom, - patientChartConfig: config, - }; - return ; - }} - - ); -}; +const VisitFormExtensionSlot: React.FC = React.memo( + ({ name, patientUuid, visitFormOpenedFrom, setVisitFormCallbacks }) => { + const config = useConfig(); + + return ( + + {(extension: AssignedExtension) => { + const state: VisitFormExtensionState = { + patientUuid, + setVisitFormCallbacks: (callbacks) => { + setVisitFormCallbacks((old) => { + return new Map(old).set(extension.id, callbacks); + }); + }, + visitFormOpenedFrom, + patientChartConfig: config, + }; + return ; + }} + + ); + }, +); export default StartVisitForm; diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts index bb22db8f14..832c9b550f 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.resource.ts @@ -28,11 +28,12 @@ export function useConditionalVisitTypes() { return visitTypesHook(); } +export interface VisitFormCallbacks { + onVisitCreatedOrUpdated: (visit: Visit) => Promise; +} -export type OnVisitCreatedOrUpdatedCallback = (visit: Visit, patientUuid: string) => Promise; - -export function useOnVisitCreatedOrUpdatedCallbacks() { - return useState>(new Map()); +export function useVisitFormCallbacks() { + return useState>(new Map()); } export function createVisitAttribute(visitUuid: string, attributeType: string, value: string) { diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.test.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.test.tsx index da56aa39dc..915fbe0add 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.test.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.test.tsx @@ -24,7 +24,7 @@ import { createVisitAttribute, deleteVisitAttribute, updateVisitAttribute, - useOnVisitCreatedOrUpdatedCallbacks, + useVisitFormCallbacks, } from './visit-form.resource'; const visitUuid = 'test_visit_uuid'; @@ -76,9 +76,9 @@ const mockUseEmrConfiguration = jest.mocked(useEmrConfiguration); // from ./visit-form.resource const mockOnVisitCreatedOrUpdatedCallback = jest.fn(); -jest.mocked(useOnVisitCreatedOrUpdatedCallbacks).mockReturnValue([ - new Map([['test-extension-id', mockOnVisitCreatedOrUpdatedCallback]]), // OnVisitCreatedOrUpdatedCallbacks - jest.fn(), // setOnVisitCreatedOrUpdatedCallbacks +jest.mocked(useVisitFormCallbacks).mockReturnValue([ + new Map([['test-extension-id', { onVisitCreatedOrUpdated: mockOnVisitCreatedOrUpdatedCallback }]]), // visitFormCallbacks + jest.fn(), // setVisitFormCallbacks ]); const mockCreateVisitAttribute = jest.mocked(createVisitAttribute).mockResolvedValue({} as unknown as FetchResponse); const mockUpdateVisitAttribute = jest.mocked(updateVisitAttribute).mockResolvedValue({} as unknown as FetchResponse); @@ -164,7 +164,7 @@ jest.mock('./visit-form.resource', () => { const requireActual = jest.requireActual('./visit-form.resource'); return { ...requireActual, - useOnVisitCreatedOrUpdatedCallbacks: jest.fn(), + useVisitFormCallbacks: jest.fn(), createVisitAttribute: jest.fn(), updateVisitAttribute: jest.fn(), deleteVisitAttribute: jest.fn(), diff --git a/packages/esm-patient-chart-app/src/visit/visit-prompt/start-visit-dialog.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-prompt/start-visit-dialog.component.tsx index 561be2cbe0..a0c89135ea 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-prompt/start-visit-dialog.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-prompt/start-visit-dialog.component.tsx @@ -38,10 +38,10 @@ const StartVisitDialog: React.FC = ({ launchPatientChartWithWorkspaceOpen({ patientUuid, workspaceName: 'start-visit-workspace-form', - additionalProps: {openedFrom: 'patient-chart-start-visit'} + additionalProps: { openedFrom: 'patient-chart-start-visit' }, }); } else { - launchPatientWorkspace('start-visit-workspace-form', {openedFrom: 'patient-chart-start-visit'}); + launchPatientWorkspace('start-visit-workspace-form', { openedFrom: 'patient-chart-start-visit' }); } closeModal(); diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.component.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.component.tsx index aa473fd442..8dd30d1913 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/current-visit-summary.component.tsx @@ -33,7 +33,9 @@ const CurrentVisitSummary: React.FC = ({ patientUuid } launchPatientWorkspace('start-visit-workspace-form', {openedFrom: 'patient-chart-current-visit-summary'})} + launchForm={() => + launchPatientWorkspace('start-visit-workspace-form', { openedFrom: 'patient-chart-current-visit-summary' }) + } /> ); } diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx index 1a700f488c..2fdba8fe6c 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx @@ -147,7 +147,7 @@ const VisitTable: React.FC = ({ showAllEncounters, visits, pati showSnackbar({ isLowContrast: true, title: t('encounterDeleted', 'Encounter deleted'), - subtitle: `Encounter ${t('successfullyDeleted', 'successfully deleted')}`, + subtitle: t('encounterSuccessfullyDeleted', 'The encounter has been deleted successfully'), kind: 'success', }); }) @@ -155,7 +155,10 @@ const VisitTable: React.FC = ({ showAllEncounters, visits, pati showSnackbar({ isLowContrast: false, title: t('error', 'Error'), - subtitle: `Encounter ${t('failedDeleting', "couldn't be deleted")}`, + subtitle: t( + 'encounterWithError', + 'The encounter could not be deleted successfully. If the error persists, please contact your system administrator.', + ), kind: 'error', }); }); diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.resource.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.resource.tsx index a31d1544fe..ddec57b212 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.resource.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.resource.tsx @@ -3,9 +3,6 @@ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; export function deleteEncounter(encounterUuid: string, abortController: AbortController) { return openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, { method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - }, signal: abortController.signal, }); } diff --git a/packages/esm-patient-chart-app/translations/en.json b/packages/esm-patient-chart-app/translations/en.json index aaf1e2776a..f1ac5d0800 100644 --- a/packages/esm-patient-chart-app/translations/en.json +++ b/packages/esm-patient-chart-app/translations/en.json @@ -34,6 +34,7 @@ "diagnoses": "Diagnoses", "discard": "Discard", "dose": "Dose", + "editEncounter": "Edit", "editPastVisit": "Edit past visit", "editThisEncounter": "Edit this encounter", "editThisVisit": "Edit this visit", @@ -43,7 +44,9 @@ "encounters": "Encounters", "encounters__lower": "encounters", "encounters_title": "Encounters", + "encounterSuccessfullyDeleted": "The encounter has been deleted successfully", "encounterType": "Encounter type", + "encounterWithError": "The encounter could not be deleted successfully. If the error persists, please contact your system administrator.", "end": "End", "endActiveVisitConfirmation": "Are you sure you want to end this active visit?", "endDate": "End date", @@ -65,7 +68,6 @@ "errorUpdatingVisitAttribute": "Could not update {{attributeName}} attribute", "errorUpdatingVisitDetails": "Error updating visit details", "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", - "failedDeleting": "couldn't be deleted", "failedToLoadCurrentVisit": "Failed loading current visit", "fieldRequired": "This field is required", "filterByEncounterType": "Filter by encounter type", @@ -144,7 +146,6 @@ "startNewVisit": "Start new visit", "startVisit": "Start visit", "startVisitError": "Error starting visit", - "successfullyDeleted": "successfully deleted", "tests": "Tests", "time": "Time", "timeFormat ": "Time Format", @@ -152,6 +153,7 @@ "undo": "Undo", "updateVisit": "Update visit", "updatingVisit": "Updating visit", + "viewEncounter": "View", "visit": "Visit", "visitAttributes": "Visit attributes", "visitCancelled": "Visit cancelled", diff --git a/packages/esm-patient-chart-app/translations/pt_BR.json b/packages/esm-patient-chart-app/translations/pt_BR.json new file mode 100644 index 0000000000..aaf1e2776a --- /dev/null +++ b/packages/esm-patient-chart-app/translations/pt_BR.json @@ -0,0 +1,182 @@ +{ + "addAPastVisit": "Add a past visit", + "additionalVisitInformationUpdatedSuccessfully": "Additional visit information updated successfully", + "addPastVisit": "Add past visit", + "addPastVisitText": "You can add a new past visit or update an old one. Choose from one of the options below to continue.", + "all": "All", + "allEncounters": "All encounters", + "cancel": "Cancel", + "cancelActiveVisitConfirmation": "Are you sure you want to cancel this active visit?", + "cancellingVisit": "Cancelling visit", + "cancelVisit": "Cancel visit", + "cancelVisitExplainerMessage": "Cancelling this visit will delete its associated encounters", + "causeOfDeath": "Cause of death", + "causeOfDeath_lower": "cause of death concepts configured in the system", + "causeOfDeathIsRequired": "Please select the cause of death", + "checkFilters": "Check the filters above", + "close": "Close", + "confirm": "Confirm", + "confirmDeletingVisitTextWithStartAndEndDate": "Are you sure you want to delete {{visit}} which started {{visitStartDate}} and ended {{visitEndDate}}?", + "confirmModifyingVisitDateToAccomodateEncounter": "The encounter date falls outside the designated visit date range. Would you like to modify the visit date to accommodate the new encounter date?", + "currentVisit": "Current visit", + "date": "Date", + "dateAndTime": "Date & time", + "dateOfDeath": "Date of death", + "deathDateRequired": "Please select the date of death", + "delete": "Delete", + "deleteEncounter": "Delete encounter", + "deleteEncounterConfirmationText": "Are you sure you want to delete this encounter? This action can't be undone.", + "deleteThisEncounter": "Delete this encounter", + "deleteVisit": "Delete visit", + "deleteVisitDialogHeader": "Are you sure you want to delete visit?", + "deletingVisit": "Deleting visit", + "deletingVisitWillDeleteEncounters": "Deleting this visit will delete all associated encounters.", + "diagnoses": "Diagnoses", + "discard": "Discard", + "dose": "Dose", + "editPastVisit": "Edit past visit", + "editThisEncounter": "Edit this encounter", + "editThisVisit": "Edit this visit", + "editVisitDetails": "Edit visit details", + "emptyStateText": "There are no {{displayText}} to display for this patient", + "encounterDeleted": "Encounter deleted", + "encounters": "Encounters", + "encounters__lower": "encounters", + "encounters_title": "Encounters", + "encounterType": "Encounter type", + "end": "End", + "endActiveVisitConfirmation": "Are you sure you want to end this active visit?", + "endDate": "End date", + "endDate_title": "End date", + "endVisit": "End visit", + "endVisit_title": "End Visit", + "endVisitExplainerMessage": "Ending this visit means that you will no longer be able to add encounters to it. If you need to add an encounter, you can create a new visit for this patient or edit a past one.", + "enterNonCodedCauseOfDeath": "Enter non-coded cause of death", + "error": "Error", + "errorCancellingVisit": "Error cancelling active visit", + "errorCopy": "Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.", + "errorCreatingVisitAttribute": "Could not delete {{attributeName}} attribute", + "errorDeletingVisit": "Error deleting visit", + "errorDeletingVisitAttribute": "Could not delete {{attributeName}} attribute", + "errorEndingVisit": "Error ending visit", + "errorMarkingPatientAlive": "Error marking patient alive", + "errorMarkingPatientDeceased": "Error marking patient deceased", + "errorOccuredDeletingVisit": "An error occured when deleting visit", + "errorUpdatingVisitAttribute": "Could not update {{attributeName}} attribute", + "errorUpdatingVisitDetails": "Error updating visit details", + "errorWhenRestoringVisit": "Error occured when restoring {{visit}}", + "failedDeleting": "couldn't be deleted", + "failedToLoadCurrentVisit": "Failed loading current visit", + "fieldRequired": "This field is required", + "filterByEncounterType": "Filter by encounter type", + "form": "Form name", + "futureStartTime": "Visit start time cannot be in the future", + "goToThisEncounter": "Go to this encounter", + "indication": "Indication", + "invalidTimeFormat": "Invalid time format", + "invalidVisitStartDate": "Start date needs to be on or before {{firstEncounterDatetime}}", + "invalidVisitStopDate": "Visit stop date time cannot be on or before visit start date time", + "loading": "Loading", + "loadingVisit": "Loading current visit...", + "loadMore": "Load more", + "location": "Location", + "markAliveSuccessfully": "Patient marked alive succesfully", + "markDeceasedWarning": "Marking the patient as deceased will end any active visits for this patient", + "markPatientAlive": "Mark patient alive", + "markPatientAliveConfirmation": "Are you sure you want to mark this patient alive?", + "markPatientDeceased": "Mark patient deceased", + "medications": "Medications", + "medications__lower": "medications", + "missingVisitType": "Missing visit type", + "modifyVisitDate": "Modify visit date", + "movePatient": "Move patient", + "movePatientToNextService": "Move patient to next service", + "name": "Name", + "no": "No", + "noActiveVisit": "No Active Visit", + "noActiveVisitMessage": "active visit", + "noActiveVisitNoRDEText": "You can't add data to the patient chart without an active visit. Would you like to start a new visit?", + "noActiveVisitText": "You can't add data to the patient chart without an active visit. Choose from one of the options below to continue.", + "noDiagnosesFound": "No diagnoses found", + "noEncountersFound": "No encounters found", + "noEncountersToDisplay": "No encounters to display", + "noMatchingCodedCausesOfDeath": "No matching coded causes of death", + "nonCodedCauseOfDeath": "Non-coded cause of death", + "nonCodedCauseOfDeathRequired": "Please enter the non-coded cause of death", + "noObservationsFound": "No observations found", + "notes": "Notes", + "notes__lower": "notes", + "noVisitTypesToDisplay": "No visit types to display", + "optional": "optional", + "orderDurationAndUnit": "for {{duration}} {{durationUnit}}", + "orderIndefiniteDuration": "Indefinite duration", + "paginationItemsCount_one": "{{pageItemsCount}} / {{count}} item", + "paginationItemsCount_other": "{{pageItemsCount}} / {{count}} items", + "paginationPageText_one": "of {{count}} page", + "paginationPageText_other": "of {{count}} pages", + "partOfFormDidntLoad": "Part of the form did not load", + "pastVisitErrorText": "Past visit error", + "pastVisits": "Past visits", + "Patient Summary": "Patient summary", + "program": "Program", + "provider": "Provider", + "quantity": "Quantity", + "recommended": "Recommended", + "record": "Record", + "refills": "Refills", + "refreshToTryAgain": "Please refresh to try again", + "required": "Required", + "retrospectiveEntry": "Retrospective entry", + "saveAndClose": "Save and close", + "saving": "Saving", + "searchForAVisitType": "Search for a visit type", + "searchForCauseOfDeath": "Search for a cause of death", + "searchThisList": "Search this list", + "seeAll": "See all", + "selectAnOption": "Select an option", + "selectLocation": "Select a location", + "selectProgramType": "Select program type", + "selectVisitType": "Please select a visit type", + "start": "Start", + "startAVisit": "Start a visit", + "startDate": "Start date", + "startingVisit": "Starting visit", + "startNewVisit": "Start new visit", + "startVisit": "Start visit", + "startVisitError": "Error starting visit", + "successfullyDeleted": "successfully deleted", + "tests": "Tests", + "time": "Time", + "timeFormat ": "Time Format", + "type": "Type", + "undo": "Undo", + "updateVisit": "Update visit", + "updatingVisit": "Updating visit", + "visit": "Visit", + "visitAttributes": "Visit attributes", + "visitCancelled": "Visit cancelled", + "visitCancelSuccessMessage": "Active {{visit}} cancelled successfully", + "visitDeleted": "{{visit}} deleted", + "visitDeletedSuccessfully": "{{visit}} deleted successfully", + "visitDetailsUpdated": "Visit details updated", + "visitDetailsUpdatedSuccessfully": "{{visit}} updated successfully", + "visitEnded": "Visit ended", + "visitEndSuccessfully": "Ended current visit successfully", + "visitLocation": "Visit location", + "visitNotRestored": "Visit couldn't be restored", + "visitRestored": "Visit restored", + "visitRestoredSuccessfully": "{{visit}} restored successfully", + "visits": "visits", + "Visits": "Visits", + "visitStartDatetime": "Visit start date and time", + "visitStarted": "Visit started", + "visitStartedSuccessfully": "{{visit}} started successfully", + "visitStopDateMustBeAfterMostRecentEncounter": "Stop date needs to be on or after {{lastEncounterDatetime}}", + "visitStopDatetime": "Visit stop date and time", + "visitSummaries": "Visit summaries", + "visitType": "Visit type", + "visitType_title": "Visit Type", + "visitTypeRequired": "Visit type is required", + "warning": "Warning", + "yes": "Yes" +} diff --git a/packages/esm-patient-common-lib/package.json b/packages/esm-patient-common-lib/package.json index f54d158102..186ad2d992 100644 --- a/packages/esm-patient-common-lib/package.json +++ b/packages/esm-patient-common-lib/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-common-lib", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Library for common patient chart components", "browser": "dist/openmrs-esm-patient-common-lib.js", @@ -34,7 +34,7 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", + "@openmrs/esm-framework": "6.x", "react": "18.x", "single-spa": "6.x" } diff --git a/packages/esm-patient-common-lib/src/orders/postOrders.ts b/packages/esm-patient-common-lib/src/orders/postOrders.ts index 83b01b8286..404e54bf3e 100644 --- a/packages/esm-patient-common-lib/src/orders/postOrders.ts +++ b/packages/esm-patient-common-lib/src/orders/postOrders.ts @@ -71,8 +71,14 @@ export async function postOrders(encounterUuid: string, abortController: AbortCo const orders = patientItems[grouping]; for (let i = 0; i < orders.length; i++) { const order = orders[i]; - const dto = postDataPrepFunctions[grouping](order, patientUuid, encounterUuid); - await postOrder(dto, abortController).catch((error) => { + const dataPrepFn = postDataPrepFunctions[grouping]; + + if (typeof dataPrepFn !== 'function') { + console.warn(`The postDataPrep function registered for ${grouping} orders is not a function`); + continue; + } + + await postOrder(dataPrepFn(order, patientUuid, encounterUuid), abortController).catch((error) => { erroredItems.push({ ...order, orderError: error, diff --git a/packages/esm-patient-conditions-app/package.json b/packages/esm-patient-conditions-app/package.json index 2e3f25d984..a50d3651f4 100644 --- a/packages/esm-patient-conditions-app/package.json +++ b/packages/esm-patient-conditions-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-conditions-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient conditions microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-conditions-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,12 +38,12 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-conditions-app/translations/pt_BR.json b/packages/esm-patient-conditions-app/translations/pt_BR.json new file mode 100644 index 0000000000..1f6dbeb2cd --- /dev/null +++ b/packages/esm-patient-conditions-app/translations/pt_BR.json @@ -0,0 +1,41 @@ +{ + "active": "Active", + "add": "Add", + "cancel": "Cancel", + "checkFilters": "Check the filters above", + "clinicalStatus": "Clinical status", + "clinicalStatusRequired": "A clinical status is required", + "condition": "Condition", + "conditionDeleted": "Condition deleted", + "conditionNowVisible": "It is now visible on the Conditions page", + "conditionRequired": "A condition is required", + "conditions": "Conditions", + "Conditions": "Conditions", + "conditionSaved": "Condition saved successfully", + "conditionUpdated": "Condition updated", + "dateOfOnset": "Date of onset", + "delete": "Delete", + "deleteCondition": "Delete condition", + "deleteModalConfirmationText": "Are you sure you want to delete this condition?", + "deleting": "Deleting", + "edit": "Edit", + "editCondition": "Edit a Condition", + "endDate": "End date", + "enterCondition": "Enter condition", + "errorCreatingCondition": "Error creating condition", + "errorDeletingCondition": "Error deleting condition", + "errorUpdatingCondition": "Error updating condition", + "inactive": "Inactive", + "noConditionsToDisplay": "No conditions to display", + "noResultsFor": "No results for", + "onsetDate": "Onset date", + "recordCondition": "Record a Condition", + "required": "Required", + "saveAndClose": "Save & close", + "saving": "Saving", + "searchConditions": "Search conditions", + "searching": "Searching", + "seeAll": "See all", + "show": "Show", + "status": "Status" +} diff --git a/packages/esm-patient-flags-app/package.json b/packages/esm-patient-flags-app/package.json index 033716c82e..64963e52b3 100644 --- a/packages/esm-patient-flags-app/package.json +++ b/packages/esm-patient-flags-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-flags-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "An OpenMRS frontend module for managing patient flags", "browser": "dist/openmrs-esm-patient-flags-app.js", @@ -17,7 +17,7 @@ "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color", "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -40,8 +40,8 @@ "@carbon/react": "^1.33.1" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-flags-app/translations/pt_BR.json b/packages/esm-patient-flags-app/translations/pt_BR.json new file mode 100644 index 0000000000..69c9683858 --- /dev/null +++ b/packages/esm-patient-flags-app/translations/pt_BR.json @@ -0,0 +1,29 @@ +{ + "activeFirst": "Active first", + "alphabetically": "A - Z", + "clearSearch": "Clear search", + "closeFlagsBar": "Close flags bar", + "disableFlagError": "Disable flag error", + "disablingFlag": "Disabling flag...", + "discard": "Discard", + "edit": "Edit", + "editFlags": "Edit flags", + "editPatientFlags": "Edit patient flags", + "enabledFlag": "Enabled flag", + "enablingFlag": "Enabling flag...", + "flagCount_one": "{{count}} risk flag", + "flagCount_other": "{{count}} risk flags", + "flagDisabled": "Flag disabled", + "flagDisabledSuccessfully": "Flag successfully disabled", + "flagDisableError": "Error disabling the flag", + "flagEnabled": "flag enabled", + "flagEnabledSuccessfully": "Flag successfully enabled", + "flagEnableError": "Error enabling flag", + "loading": "Loading", + "matchesForSearchTerm_one": "{{count}} flag", + "matchesForSearchTerm_other": "{{count}} flags", + "noFlagsFound": "Sorry, no flags found matching your search", + "retiredFirst": "Retired first", + "saveAndClose": "Save & close", + "searchForAFlag": "Search for a flag" +} diff --git a/packages/esm-patient-forms-app/package.json b/packages/esm-patient-forms-app/package.json index 71013750d5..24ebdac511 100644 --- a/packages/esm-patient-forms-app/package.json +++ b/packages/esm-patient-forms-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-forms-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient forms microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-forms-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,13 +38,13 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "fuzzy": "^0.1.3", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-forms-app/src/routes.json b/packages/esm-patient-forms-app/src/routes.json index 149deb8675..17fd41e0cf 100644 --- a/packages/esm-patient-forms-app/src/routes.json +++ b/packages/esm-patient-forms-app/src/routes.json @@ -52,7 +52,7 @@ "type": "clinical-form", "canMaximize": true, "canHide": true, - "width": "wider" + "width": "extra-wide" }, { "name": "ward-patient-form-entry-workspace", diff --git a/packages/esm-patient-forms-app/translations/pt_BR.json b/packages/esm-patient-forms-app/translations/pt_BR.json new file mode 100644 index 0000000000..7cf1901ce6 --- /dev/null +++ b/packages/esm-patient-forms-app/translations/pt_BR.json @@ -0,0 +1,25 @@ +{ + "clinicalForm": "Clinical Form", + "clinicalForms": "Clinical forms", + "editForm": "Edit form", + "formName": "Form Name (A-Z)", + "forms": "Forms", + "formSearchHint": "Try searching for the form using an alternative name or keyword", + "homeOverviewCardView": "View", + "lastCompleted": "Last completed", + "never": "Never", + "noFormsToDisplay": "There are no forms to display.", + "noMatchingFormsAvailable": "There are no {{formCategory}} forms to display", + "noMatchingFormsToDisplay": "No matching forms to display", + "offlineForms": "Offline forms", + "offlineForms__lower": "offline forms", + "offlineFormsOverviewCardAvailableOffline": "Available offline", + "offlineFormsTableFormAvailableOffline": "Offline", + "offlineFormsTableFormNameHeader": "Form name", + "offlineFormsTitle": "Offline forms", + "offlinePatientsTableSearchLabel": "Search this list", + "offlinePatientsTableSearchPlaceholder": "Search this list", + "offlineToggle": "Offline toggle", + "searchForAForm": "Search for a form", + "searchThisList": "Search this list" +} diff --git a/packages/esm-patient-immunizations-app/package.json b/packages/esm-patient-immunizations-app/package.json index ade3cd8b1a..b6859728cd 100644 --- a/packages/esm-patient-immunizations-app/package.json +++ b/packages/esm-patient-immunizations-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-immunizations-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient immunizations microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-immunizations-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -44,8 +44,8 @@ "zod": "^3.22.2" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-immunizations-app/translations/pt_BR.json b/packages/esm-patient-immunizations-app/translations/pt_BR.json new file mode 100644 index 0000000000..510dd0e3b5 --- /dev/null +++ b/packages/esm-patient-immunizations-app/translations/pt_BR.json @@ -0,0 +1,31 @@ +{ + "add": "Add", + "cancel": "Cancel", + "doseNumber": "Dose number within series", + "doseNumberWithinSeries": "Dose number within series", + "edit": "Edit", + "error": "Error", + "errorSaving": "Error saving vaccination", + "expirationDate": "Expiration Date", + "goToSummary": "Go to Summary", + "immunization": "Immunization", + "immunizations": "Immunizations", + "Immunizations": "Immunizations", + "immunizations__lower": "immunizations", + "immunizationWorkspaceTitle": "Immunization Form", + "lotNumber": "Lot Number", + "manufacturer": "Manufacturer", + "pleaseSelect": "Please select", + "recentVaccination": "Recent vaccination", + "save": "Save", + "seeAll": "See all", + "sequence": "Sequence", + "singleDoseOn": "Single Dose on", + "time": "Time", + "timeFormat ": "Time Format", + "vaccinationDate": "Vaccination date", + "vaccinationDateRequired": "Vaccination date required", + "vaccinationSaved": "Vaccination saved successfully", + "vaccine": "Vaccine", + "vaccineRequired": "Vaccine required" +} diff --git a/packages/esm-patient-lists-app/package.json b/packages/esm-patient-lists-app/package.json index b9c1f2ef37..a087119382 100644 --- a/packages/esm-patient-lists-app/package.json +++ b/packages/esm-patient-lists-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-lists-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "An OpenMRS frontend module for managing patient lists in the Patient Chart", "browser": "dist/openmrs-esm-patient-lists-app.js", @@ -17,7 +17,7 @@ "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color", "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -40,8 +40,8 @@ "@carbon/react": "^1.33.1" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-lists-app/translations/pt_BR.json b/packages/esm-patient-lists-app/translations/pt_BR.json new file mode 100644 index 0000000000..90c336895d --- /dev/null +++ b/packages/esm-patient-lists-app/translations/pt_BR.json @@ -0,0 +1,21 @@ +{ + "backToPatientLists": "Back to patient lists", + "checkFilters": "Check the filters above", + "createdOn": "Created on", + "identifier": "Identifier", + "listName": "List name", + "listType": "List type", + "name": "Name", + "noMatchingListsFound": "No matching lists to display", + "noMatchingPatients": "No matching patients to display", + "noPatientListsToDisplay": "No patient lists to display", + "noPatientsInList": "There are no patients in this list", + "numberOfPatients": "No. of patients", + "patientListDetailWorkspaceTitle": "Patient List Details", + "patientLists": "Patient lists", + "patientListsWorkspaceTitle": "Patient Lists", + "patients": "patients", + "searchThisList": "Search this list", + "sex": "Sex", + "startDate": "Start Date" +} diff --git a/packages/esm-patient-medications-app/package.json b/packages/esm-patient-medications-app/package.json index b1210bb2f2..7dd8ae14d6 100644 --- a/packages/esm-patient-medications-app/package.json +++ b/packages/esm-patient-medications-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-medications-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient medications microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-medications-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,12 +38,12 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx b/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx index d2bf770cc5..b32a720cda 100644 --- a/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx +++ b/packages/esm-patient-medications-app/src/components/medications-details-table.component.tsx @@ -45,7 +45,7 @@ import { type ConfigObject } from '../config-schema'; import PrintComponent from '../print/print.component'; import styles from './medications-details-table.scss'; -export interface ActiveMedicationsProps { +export interface MedicationsDetailsTableProps { isValidating?: boolean; title?: string; medications?: Array | null; @@ -56,7 +56,7 @@ export interface ActiveMedicationsProps { patient: fhir.Patient; } -const MedicationsDetailsTable: React.FC = ({ +const MedicationsDetailsTable: React.FC = ({ isValidating, title, medications, diff --git a/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.component.tsx b/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.component.tsx index ba029cb484..ef392f3aa1 100644 --- a/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.component.tsx +++ b/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.component.tsx @@ -126,18 +126,58 @@ function OrderActionLabel({ orderBasketItem }: { orderBasketItem: DrugOrderBaske const { t } = useTranslation(); if (orderBasketItem.isOrderIncomplete) { - return {t('orderActionIncomplete', 'Incomplete')}; + return ( + + {t('orderActionIncomplete', 'Incomplete')} + + ); } switch (orderBasketItem.action) { case 'NEW': - return {t('orderActionNew', 'New')}; + return ( + + {t('orderActionNew', 'New')} + + ); case 'RENEW': - return {t('orderActionRenew', 'Renew')}; + return ( + + {t('orderActionRenew', 'Renew')} + + ); case 'REVISE': - return {t('orderActionRevise', 'Modify')}; + return ( + + {t('orderActionRevise', 'Modify')} + + ); case 'DISCONTINUE': - return {t('orderActionDiscontinue', 'Discontinue')}; + return ( + + {t('orderActionDiscontinue', 'Discontinue')} + + ); default: return <>; } diff --git a/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.scss b/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.scss index 4f234bce28..6106d84264 100644 --- a/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.scss +++ b/packages/esm-patient-medications-app/src/drug-order-basket-panel/order-basket-item-tile.scss @@ -1,3 +1,4 @@ +@use '@carbon/colors'; @use '@carbon/layout'; @use '@carbon/type'; @use '@openmrs/esm-styleguide/src/vars' as *; @@ -27,29 +28,27 @@ .orderActionNewLabel { @extend .label; - color: $support-02; + background-color: colors.$green-10-hover; + padding: 0 layout.$spacing-02; } -.orderActionRenewLabel { - @extend .label; - color: $support-02; +.orderActionIncompleteLabel { + @extend .orderActionNewLabel; + background-color: colors.$red-60; + color: white; } -.orderActionRevisedLabel { - @extend .label; - color: #943d00; +.orderActionRenewLabel { + @extend .orderActionNewLabel; } -.orderActionDiscontinueLabel { - @extend .label; - color: $danger; +.orderActionReviseLabel { + @extend .orderActionNewLabel; } -.orderActionIncompleteLabel { - @extend .label; - color: white; - background-color: $danger; - padding: 0 layout.$spacing-02; +.orderActionDiscontinueLabel { + @extend .orderActionNewLabel; + background-color: colors.$gray-20; } .orderErrorText { diff --git a/packages/esm-patient-medications-app/src/medications-summary/medications-summary.component.tsx b/packages/esm-patient-medications-app/src/medications-summary/medications-summary.component.tsx index 9875c4c779..45e85b9aa7 100644 --- a/packages/esm-patient-medications-app/src/medications-summary/medications-summary.component.tsx +++ b/packages/esm-patient-medications-app/src/medications-summary/medications-summary.component.tsx @@ -62,7 +62,7 @@ export default function MedicationsSummary({ patient }: MedicationsSummaryProps) const displayText = t('pastMedicationsDisplayText', 'Past medications'); const headerTitle = t('pastMedicationsHeaderTitle', 'past medications'); - if (isValidatingPastOrders) return ; + if (isLoadingPastOrders) return ; if (pastOrdersError) return ; @@ -72,6 +72,7 @@ export default function MedicationsSummary({ patient }: MedicationsSummaryProps) isValidating={isValidatingPastOrders} title={t('pastMedicationsTableTitle', 'Past Medications')} medications={pastOrders} + showAddButton={false} showDiscontinueButton={false} showModifyButton={false} showReorderButton={true} diff --git a/packages/esm-patient-medications-app/translations/ar.json b/packages/esm-patient-medications-app/translations/ar.json index 862a27e506..94d57e0f0c 100644 --- a/packages/esm-patient-medications-app/translations/ar.json +++ b/packages/esm-patient-medications-app/translations/ar.json @@ -43,7 +43,7 @@ "male": "ذكر", "medicationDurationAndUnit": "لمدة {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "مدة غير محددة", - "Medications": "Medications", + "Medications": "الأدوية", "modify": "تعديل", "none": "لا شيء", "noResultsForDrugSearch": "لا توجد نتائج لـ \"{{searchTerm}}\"", diff --git a/packages/esm-patient-medications-app/translations/pt_BR.json b/packages/esm-patient-medications-app/translations/pt_BR.json new file mode 100644 index 0000000000..5eb48ed2c4 --- /dev/null +++ b/packages/esm-patient-medications-app/translations/pt_BR.json @@ -0,0 +1,95 @@ +{ + "activeMedicationsDisplayText": "Active medications", + "activeMedicationsHeaderTitle": "active medications", + "activeMedicationsTableTitle": "Active Medications", + "add": "Add", + "addDrugOrderWorkspaceTitle": "Add drug order", + "backToOrderBasket": "Back to order basket", + "clearSearchResults": "Clear Results", + "decrement": "Decrement", + "details": "Details", + "directlyAddToBasket": "Add to basket", + "discard": "Discard", + "discontinue": "Discontinue", + "discontinued": "Discontinued", + "dispensingInformation": "3. Dispensing instructions", + "dosageInstructions": "1. Dosage instructions", + "dosageRequiredErrorMessage": "Dosage is required", + "dose": "Dose", + "drugAlreadyPrescribed": "Already prescribed", + "drugOrders": "Drug orders", + "duration": "Duration", + "durationUnit": "Duration unit", + "durationUnitPlaceholder": "Duration Unit", + "editDispensingUnit": "Quantity unit", + "editDosageUnitsPlaceholder": "Unit", + "editDosageUnitsTitle": "Dose unit", + "editDoseComboBoxPlaceholder": "Dose", + "editDoseComboBoxTitle": "Dose", + "editFrequencyComboBoxTitle": "Frequency", + "editRouteComboBoxTitle": "Route", + "error": "Error", + "errorFetchingDrugOrderTemplates": "Error fetching drug order templates", + "errorFetchingDrugResults": "Error fetching results for \"{{searchTerm}}\"", + "errorFetchingOrderConfig": "Error occured when fetching Order config", + "female": "Female", + "freeDosageErrorMessage": "Add free dosage note", + "freeTextDosage": "Free text dosage", + "goToDrugOrderForm": "Order form", + "increment": "Increment", + "indication": "Indication", + "indicationErrorMessage": "Indication is required", + "indicationPlaceholder": "e.g. \"Hypertension\"", + "male": "Male", + "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", + "medicationIndefiniteDuration": "Indefinite duration", + "Medications": "Medications", + "modify": "Modify", + "none": "None", + "noResultsForDrugSearch": "No results to display for \"{{searchTerm}}\"", + "numRefillsErrorMessage": "Number of refills is required", + "onDate": "on", + "or": "or", + "orderActionDiscontinue": "Discontinue", + "orderActionIncomplete": "Incomplete", + "orderActionNew": "New", + "orderActionRenew": "Renew", + "orderActionRevise": "Modify", + "orderForm": "Order Form", + "other": "Other", + "pastMedicationsDisplayText": "Past medications", + "pastMedicationsHeaderTitle": "past medications", + "pastMedicationsTableTitle": "Past Medications", + "patientInstructions": "Patient instructions", + "patientInstructionsPlaceholder": "Additional dosing instructions (e.g. \"Take after eating\")", + "pillDispensedErrorMessage": "Quantity to dispense is required", + "prescriptionDuration": "2. Prescription duration", + "prescriptionRefills": "Prescription refills", + "print": "Print", + "printedBy": "Printed by", + "prn": "P.R.N.", + "prnReason": "P.R.N. reason", + "prnReasonPlaceholder": "Reason to take medicine", + "quantity": "Quantity", + "quantityToDispense": "Quantity to dispense", + "refills": "Refills", + "removeFromBasket": "Remove from basket", + "reorder": "Reorder", + "returnToOrderBasket": "Return to order basket", + "saveOrder": "Save order", + "searchAgain": "search again", + "searchFieldPlaceholder": "Search for a drug or orderset (e.g. \"Aspirin\")", + "searchResultsMatchesForTerm_one": "{{count}} result for \"{{searchTerm}}\"", + "searchResultsMatchesForTerm_other": "{{count}} results for \"{{searchTerm}}\"", + "selectFrequencyErrorMessage": "Frequency is required", + "selectQuantityUnitsErrorMessage": "Quantity unit is required", + "selectRouteErrorMessage": "Route is required", + "selectUnitErrorMessage": "Dose unit is required", + "startDate": "Start date", + "takeAsNeeded": "Take as needed", + "tryReopeningTheForm": "Please try launching the form again", + "trySearchingAgain": "Please try searching again", + "tryTo": "Try to", + "unknown": "Unknown", + "usingADifferentTerm": "using a different term" +} diff --git a/packages/esm-patient-notes-app/package.json b/packages/esm-patient-notes-app/package.json index 9f26a11e5a..6661e7c0e0 100644 --- a/packages/esm-patient-notes-app/package.json +++ b/packages/esm-patient-notes-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-notes-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient notes microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-notes-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -37,12 +37,12 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-chart/issues" }, "dependencies": { - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-notes-app/translations/pt_BR.json b/packages/esm-patient-notes-app/translations/pt_BR.json new file mode 100644 index 0000000000..51df0d8d62 --- /dev/null +++ b/packages/esm-patient-notes-app/translations/pt_BR.json @@ -0,0 +1,38 @@ +{ + "add": "Add", + "addImage": "Add image", + "addVisitNote": "Add a visit note", + "clinicalNoteLabel": "Write your notes", + "clinicalNotePlaceholder": "Write any notes here", + "date": "Date", + "diagnoses": "Diagnoses", + "discard": "Discard", + "emptyDiagnosisText": "No diagnosis selected — Enter a diagnosis below", + "enterPrimaryDiagnoses": "Enter Primary diagnoses", + "enterSecondaryDiagnoses": "Enter Secondary diagnoses", + "error": "Error", + "errorFetchingConcepts": "There was a problem fetching concepts", + "image": "Image", + "imageRemoved": "Image removed", + "imageUploadHelperText": "Upload an image or use this device's camera to capture an image", + "noMatchingDiagnoses": "No diagnoses found matching", + "note": "Note", + "noVisitNoteToDisplay": "No visit note to display", + "primaryDiagnosis": "Primary diagnosis", + "primaryDiagnosisInputPlaceholder": "Choose a primary diagnosis", + "primaryDiagnosisRequired": "Choose at least one primary diagnosis", + "saveAndClose": "Save and close", + "saving": "Saving", + "searchForPrimaryDiagnosis": "Search for a primary diagnosis", + "searchForSecondaryDiagnosis": "Search for a secondary diagnosis", + "secondaryDiagnosis": "Secondary diagnosis", + "secondaryDiagnosisInputPlaceholder": "Choose a secondary diagnosis", + "seeAll": "See all", + "visitDate": "Visit date", + "visitNote": "Visit note", + "visitNoteNowVisible": "It is now visible on the Encounters page", + "visitNotes": "Visit notes", + "visitNoteSaved": "Visit note saved", + "visitNoteSaveError": "Error saving visit note", + "visitNoteWorkspaceTitle": "Visit Note" +} diff --git a/packages/esm-patient-orders-app/package.json b/packages/esm-patient-orders-app/package.json index a2df28ea71..7bc06fa625 100644 --- a/packages/esm-patient-orders-app/package.json +++ b/packages/esm-patient-orders-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-orders-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Microfrontend for the OpenMRS SPA providing the order basket for the patient chart", "browser": "dist/openmrs-esm-patient-orders-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,12 +38,12 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-orders-app/src/components/test-order.component.tsx b/packages/esm-patient-orders-app/src/components/test-order.component.tsx index 5d107fb4db..43a46cd9d5 100644 --- a/packages/esm-patient-orders-app/src/components/test-order.component.tsx +++ b/packages/esm-patient-orders-app/src/components/test-order.component.tsx @@ -56,7 +56,7 @@ const TestOrder: React.FC = ({ testOrder }) => { result: isLoadingResult ? ( ) : ( - testResultObs?.groupMembers?.find((obs) => obs.concept.uuid === memberConcept.uuid)?.value.display ?? '--' + testResultObs?.groupMembers?.find((obs) => obs.concept.uuid === memberConcept.uuid)?.value ?? '--' ), normalRange: memberConcept.hiNormal && memberConcept.lowNormal @@ -68,14 +68,14 @@ const TestOrder: React.FC = ({ testOrder }) => { { id: concept.uuid, testType:
{concept.display}
, - result: isLoadingResult ? : testResultObs?.value.display ?? '--', + result: isLoadingResult ? : testResultObs?.value ?? '--', normalRange: concept.hiNormal && concept.lowNormal ? `${concept.lowNormal} - ${concept.hiNormal}` : 'N/A', }, ]; } else { return []; } - }, [concept, isLoadingResult, testResultObs?.groupMembers, testResultObs?.value?.display]); + }, [concept, isLoadingResult, testResultObs?.groupMembers, testResultObs?.value]); return (
diff --git a/packages/esm-patient-orders-app/src/index.ts b/packages/esm-patient-orders-app/src/index.ts index 636608939a..2e63d7c116 100644 --- a/packages/esm-patient-orders-app/src/index.ts +++ b/packages/esm-patient-orders-app/src/index.ts @@ -55,6 +55,7 @@ export const ordersDashboardLink = ); export const ordersDashboard = getSyncLifecycle(OrdersSummary, options); +export const labResult = getAsyncLifecycle(() => import('./lab-results/lab-result.component'), options); // t('searchOrderables','Search orderables') export const orderableConceptSearch = getAsyncLifecycle( diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-result.component.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-result.component.tsx new file mode 100644 index 0000000000..1d9fad213b --- /dev/null +++ b/packages/esm-patient-orders-app/src/lab-results/lab-result.component.tsx @@ -0,0 +1,46 @@ +import { Tile, InlineLoading , InlineNotification } from '@carbon/react'; +import { type Order } from '@openmrs/esm-patient-common-lib'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCompletedLabResults, useOrderConceptByUuid } from './lab-results.resource'; +import styles from './lab-result.scss'; +import TestOrder from '../components/test-order.component'; + +type LabResultsProps = { + order: Order; +}; +const LabResults: React.FC = ({ order }) => { + const { t } = useTranslation(); + const { concept, isLoading: isLoadingConcepts, error: conceptError } = useOrderConceptByUuid(order.concept.uuid); + const { isLoading, error, completeLabResult, mutate } = useCompletedLabResults(order); + + if (isLoading || isLoadingConcepts) + return ( + + ); + + if (error || conceptError) + return ( + + ); + + return ( + + + + ); +}; + +export default LabResults; + +const OrderDetail = ({ order }: { order: Order }) => { + return ; +}; diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-result.scss b/packages/esm-patient-orders-app/src/lab-results/lab-result.scss new file mode 100644 index 0000000000..048d14bc7d --- /dev/null +++ b/packages/esm-patient-orders-app/src/lab-results/lab-result.scss @@ -0,0 +1,18 @@ +@use '@carbon/layout'; +@use '@carbon/type'; + +.resultsCiontainer { + margin-top: layout.$spacing-05; +} + +.detailsContainer { + display: flex; + flex-direction: column; + gap: layout.$spacing-03; +} + +.resultDetail { + display: flex; + flex-direction: row; + gap: layout.$spacing-05; +} diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx index 1d36efe273..2adf6d6e72 100644 --- a/packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx +++ b/packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { NumberInput, Select, SelectItem, TextInput } from '@carbon/react'; import { useTranslation } from 'react-i18next'; import { type Control, Controller, type FieldErrors } from 'react-hook-form'; -import { type LabOrderConcept } from './lab-results.resource'; +import { isCoded, isNumeric, isPanel, isText, type LabOrderConcept } from './lab-results.resource'; import styles from './lab-results-form.scss'; interface ResultFormFieldProps { @@ -15,11 +15,6 @@ interface ResultFormFieldProps { const ResultFormField: React.FC = ({ concept, control, defaultValue, errors }) => { const { t } = useTranslation(); - const isCoded = (concept: LabOrderConcept) => concept.datatype?.display === 'Coded'; - const isNumeric = (concept: LabOrderConcept) => concept.datatype?.display === 'Numeric'; - const isPanel = (concept: LabOrderConcept) => concept.setMembers?.length > 0; - const isText = (concept: LabOrderConcept) => concept.datatype?.display === 'Text'; - const printValueRange = (concept: LabOrderConcept) => { if (concept?.datatype?.display === 'Numeric') { const maxVal = Math.max(concept?.hiAbsolute, concept?.hiCritical, concept?.hiNormal); diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx index 1745cfed17..964e255a8d 100644 --- a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx +++ b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx @@ -1,21 +1,25 @@ -import React, { useEffect, useState, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useForm } from 'react-hook-form'; -import { mutate } from 'swr'; import { Button, ButtonSet, Form, InlineLoading, InlineNotification, Stack } from '@carbon/react'; -import { type DefaultPatientWorkspaceProps, type Order } from '@openmrs/esm-patient-common-lib'; +import { zodResolver } from '@hookform/resolvers/zod'; import { restBaseUrl, showSnackbar, useAbortController, useLayoutType } from '@openmrs/esm-framework'; +import { type DefaultPatientWorkspaceProps, type Order } from '@openmrs/esm-patient-common-lib'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { mutate } from 'swr'; +import ResultFormField from './lab-results-form-field.component'; +import styles from './lab-results-form.scss'; import { - useOrderConceptByUuid, - updateOrderResult, - useLabEncounter, - useObservation, createObservationPayload, + isCoded, + isNumeric, + isPanel, + isText, + updateObservation, + updateOrderResult, + useCompletedLabResults, + useOrderConceptByUuid, } from './lab-results.resource'; -import ResultFormField from './lab-results-form-field.component'; -import styles from './lab-results-form.scss'; import { useLabResultsFormSchema } from './useLabResultsFormSchema'; -import { zodResolver } from '@hookform/resolvers/zod'; export interface LabResultsFormProps extends DefaultPatientWorkspaceProps { order: Order; @@ -30,19 +34,22 @@ const LabResultsForm: React.FC = ({ const { t } = useTranslation(); const abortController = useAbortController(); const isTablet = useLayoutType() === 'tablet'; - const [obsUuid, setObsUuid] = useState(''); - const [isEditing, setIsEditing] = useState(false); - const [initialValues, setInitialValues] = useState(null); - const [isLoadingInitialValues, setIsLoadingInitialValues] = useState(false); const { concept, isLoading: isLoadingConcepts } = useOrderConceptByUuid(order.concept.uuid); - const { encounter, isLoading: isLoadingEncounter, mutate: mutateLabOrders } = useLabEncounter(order.encounter.uuid); - const { data, isLoading: isLoadingObs, error: isErrorObs } = useObservation(obsUuid); const [showEmptyFormErrorNotification, setShowEmptyFormErrorNotification] = useState(false); const schema = useLabResultsFormSchema(order.concept.uuid); + const { completeLabResult, error, isLoading, mutate: mutateResults } = useCompletedLabResults(order); + const mutateOrderData = useCallback(() => { + mutate( + (key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/order?patient=${order.patient.uuid}`), + undefined, + { revalidate: true }, + ); + }, [order.patient.uuid]); const { control, formState: { errors, isDirty, isSubmitting }, + setValue, handleSubmit, } = useForm<{ testResult: Record }>({ defaultValues: {}, @@ -50,37 +57,30 @@ const LabResultsForm: React.FC = ({ mode: 'all', }); - const mutateOrderData = useCallback(() => { - mutate( - (key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/order?patient=${order.patient.uuid}`), - undefined, - { revalidate: true }, - ); - }, [order.patient.uuid]); - useEffect(() => { - if (!isLoadingEncounter && encounter?.obs?.length > 0 && !isEditing) { - const obs = encounter.obs.find((obs) => obs.concept?.uuid === order?.concept.uuid); - if (obs) { - setObsUuid(obs.uuid); - setIsEditing(true); + if (concept && completeLabResult && order?.fulfillerStatus === 'COMPLETED') { + if (isCoded(concept) && completeLabResult?.value?.uuid) { + setValue(concept.uuid as any, completeLabResult?.value?.uuid); + } else if (isNumeric(concept) && completeLabResult?.value) { + setValue(concept.uuid as any, parseFloat(completeLabResult?.value as any)); + } else if (isText(concept) && completeLabResult?.value) { + setValue(concept.uuid as any, completeLabResult?.value); + } else if (isPanel(concept)) { + concept.setMembers.forEach((member) => { + const obs = completeLabResult.groupMembers.find((v) => v.concept.uuid === member.uuid); + let value: any; + if (isCoded(member)) { + value = obs?.value?.uuid; + } else if (isNumeric(member)) { + value = obs?.value ? parseFloat(obs?.value as any) : undefined; + } else if (isText(member)) { + value = obs?.value; + } + if (value) setValue(member.uuid as any, value); + }); } } - }, [isLoadingEncounter, encounter, isEditing, order]); - - useEffect(() => { - const loadInitialValues = async () => { - if (isEditing && obsUuid) { - setIsLoadingInitialValues(true); - if (data && !isLoadingObs) { - setInitialValues(data); - } - setIsLoadingInitialValues(false); - } - }; - - loadInitialValues(); - }, [isEditing, obsUuid, data, isLoadingObs]); + }, [concept, completeLabResult, order, setValue]); useEffect(() => { promptBeforeClosing(() => isDirty); @@ -107,6 +107,49 @@ const LabResultsForm: React.FC = ({ setShowEmptyFormErrorNotification(true); return; } + + const showNotification = (kind: 'error' | 'success', message: string) => { + showSnackbar({ + title: + kind === 'success' + ? t('saveLabResults', 'Save lab results') + : t('errorSavingLabResults', 'Error saving lab results'), + kind: kind, + subtitle: message, + }); + }; + + // Handle update operation for completed lab order results + if (order.fulfillerStatus === 'COMPLETED') { + const updateTasks = Object.entries(formValues).map(([conceptUuid, value]) => { + const obs = completeLabResult?.groupMembers?.find((v) => v.concept.uuid === conceptUuid) ?? completeLabResult; + return updateObservation(obs?.uuid, { value }); + }); + const updateResults = await Promise.allSettled(updateTasks); + const failedObsconceptUuids = updateResults.reduce((prev, curr, index) => { + if (curr.status === 'rejected') { + return [...prev, Object.keys(formValues).at(index)]; + } + return prev; + }, []); + + if (failedObsconceptUuids.length) { + showNotification('error', 'Could not save obs with concept uuids ' + failedObsconceptUuids.join(', ')); + } else { + closeWorkspaceWithSavedChanges(); + showNotification( + 'success', + t('successfullySavedLabResults', 'Lab results for {{orderNumber}} have been successfully updated', { + orderNumber: order?.orderNumber, + }), + ); + } + mutateResults(); + return setShowEmptyFormErrorNotification(false); + } + + // Handle Creation logic + // Set the observation status to 'FINAL' as we're not capturing it in the form const obsPayload = createObservationPayload(concept, order, formValues, 'FINAL'); const orderDiscontinuationPayload = { @@ -134,21 +177,16 @@ const LabResultsForm: React.FC = ({ abortController, ); closeWorkspaceWithSavedChanges(); - mutateLabOrders(); + mutateResults(); mutateOrderData(); - showSnackbar({ - title: t('saveLabResults', 'Save lab results'), - kind: 'success', - subtitle: t('successfullySavedLabResults', 'Lab results for {{orderNumber}} have been successfully updated', { + showNotification( + 'success', + t('successfullySavedLabResults', 'Lab results for {{orderNumber}} have been successfully updated', { orderNumber: order?.orderNumber, }), - }); + ); } catch (err) { - showSnackbar({ - title: t('errorSavingLabResults', 'Error saving lab results'), - kind: 'error', - subtitle: err?.message, - }); + showNotification('error', err?.message); } finally { setShowEmptyFormErrorNotification(false); } @@ -160,8 +198,8 @@ const LabResultsForm: React.FC = ({ {concept.setMembers.length > 0 &&

{concept.display}

} {concept && ( - {!isLoadingInitialValues ? ( - + {!isLoading ? ( + ) : ( )} diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx index 5f9cafbaa0..f21915620e 100644 --- a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx +++ b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.test.tsx @@ -8,6 +8,7 @@ import { type LabOrderConcept, updateOrderResult, type Datatype, + useCompletedLabResults, } from './lab-results.resource'; import LabResultsForm from './lab-results-form.component'; import { type Order } from '@openmrs/esm-patient-common-lib'; @@ -16,6 +17,7 @@ import { type Encounter } from '../types/encounter'; const mockUseOrderConceptByUuid = jest.mocked(useOrderConceptByUuid); const mockUseLabEncounter = jest.mocked(useLabEncounter); const mockUseObservation = jest.mocked(useObservation); +const mockUseCompletedLabResults = jest.mocked(useCompletedLabResults); jest.mock('./lab-results.resource', () => ({ ...jest.requireActual('./lab-results.resource'), @@ -23,6 +25,11 @@ jest.mock('./lab-results.resource', () => ({ useLabEncounter: jest.fn(), useObservation: jest.fn(), updateOrderResult: jest.fn().mockResolvedValue({}), + useCompletedLabResults: jest.fn(), + isCoded: (concept) => concept?.datatype?.display === 'Coded', + isText: (concept) => concept?.datatype?.display === 'Text', + isNumeric: (concept) => concept?.datatype?.display === 'Numeric', + isPanel: (concept) => concept?.setMembers?.length > 0, })); const mockOrder = { @@ -80,6 +87,12 @@ describe('LabResultsForm', () => { isValidating: false, mutate: jest.fn(), }); + mockUseCompletedLabResults.mockReturnValue({ + completeLabResult: null, + isLoading: false, + error: null, + mutate: jest.fn(), + }); }); test('validates numeric input correctly', async () => { @@ -609,4 +622,26 @@ describe('LabResultsForm', () => { expect.anything(), ); }); + + test('should handle empty form submission', async () => { + const user = userEvent.setup(); + render(); + + const saveButton = screen.getByRole('button', { name: /Save and close/i }); + await user.click(saveButton); + + expect(screen.getByText('Please fill at least one field.')).toBeInTheDocument(); + expect(updateOrderResult).not.toHaveBeenCalled(); + }); + + test('should disable save button when form has validation errors', async () => { + const user = userEvent.setup(); + render(); + + const input = await screen.findByLabelText(`Test Concept (0 - 100 mg/dL)`); + await user.type(input, '150'); + + const saveButton = screen.getByRole('button', { name: /Save and close/i }); + expect(saveButton).toBeDisabled(); + }); }); diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts b/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts index 569dc1ae6b..241c9297a6 100644 --- a/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts +++ b/packages/esm-patient-orders-app/src/lab-results/lab-results.resource.ts @@ -1,8 +1,8 @@ +import { openmrsFetch, restBaseUrl, type FetchResponse, type OpenmrsResource } from '@openmrs/esm-framework'; +import { type Order } from '@openmrs/esm-patient-common-lib'; import useSWR from 'swr'; -import { type OpenmrsResource, openmrsFetch, restBaseUrl, type FetchResponse } from '@openmrs/esm-framework'; -import { type Observation, type Encounter } from '../types/encounter'; +import { type Encounter, type Observation } from '../types/encounter'; import { type OrderDiscontinuationPayload } from '../types/order'; -import { type Order } from '@openmrs/esm-patient-common-lib'; const labEncounterRepresentation = 'custom:(uuid,encounterDatetime,encounterType,location:(uuid,name),' + @@ -108,7 +108,10 @@ export function useLabEncounter(encounterUuid: string) { export function useObservation(obsUuid: string) { const url = `${restBaseUrl}/obs/${obsUuid}?v=${conceptObsRepresentation}`; - const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: Observation }, Error>(url, openmrsFetch); + const { data, error, isLoading, isValidating, mutate } = useSWR<{ data: Observation }, Error>( + obsUuid ? url : null, + openmrsFetch, + ); return { data: data?.data, isLoading, @@ -118,6 +121,31 @@ export function useObservation(obsUuid: string) { }; } +export function useCompletedLabResults(order: Order) { + const { + encounter, + isLoading: isLoadingEncounter, + mutate: mutateLabOrders, + error: encounterError, + } = useLabEncounter(order.encounter.uuid); + const { + data: observation, + isLoading: isLoadingObs, + error: isErrorObs, + mutate: mutateObs, + } = useObservation(encounter?.obs.find((obs) => obs?.concept?.uuid === order?.concept?.uuid)?.uuid ?? ''); + + return { + isLoading: isLoadingEncounter || isLoadingObs, + completeLabResult: observation, + mutate: () => { + mutateLabOrders(); + mutateObs(); + }, + error: isErrorObs ?? encounterError, + }; +} + // TODO: the calls to update order and observations for results should be transactional to allow for rollback export async function updateOrderResult( orderUuid: string, @@ -186,6 +214,16 @@ export function createObservationPayload( } } +export function updateObservation(observationUuid: string, payload: Record) { + return openmrsFetch(`${restBaseUrl}/obs/${observationUuid}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); +} + function createGroupMember(member: LabOrderConcept, order: Order, values: Record, status: string) { const value = getValue(member, values); if (value === null || value === undefined) { @@ -228,3 +266,8 @@ function getValue(concept: LabOrderConcept, values: Record) { return null; } + +export const isCoded = (concept: LabOrderConcept) => concept.datatype?.display === 'Coded'; +export const isNumeric = (concept: LabOrderConcept) => concept.datatype?.display === 'Numeric'; +export const isPanel = (concept: LabOrderConcept) => concept.setMembers?.length > 0; +export const isText = (concept: LabOrderConcept) => concept.datatype?.display === 'Text'; diff --git a/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.component.tsx b/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.component.tsx index 541bc47d1e..780ae2f350 100644 --- a/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.component.tsx +++ b/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.component.tsx @@ -78,18 +78,58 @@ function OrderActionLabel({ orderBasketItem }: { orderBasketItem: OrderBasketIte const { t } = useTranslation(); if (orderBasketItem.isOrderIncomplete) { - return {t('orderActionIncomplete', 'Incomplete')}; + return ( + + {t('orderActionIncomplete', 'Incomplete')} + + ); } switch (orderBasketItem.action) { case 'NEW': - return {t('orderActionNew', 'New')}; + return ( + + {t('orderActionNew', 'New')} + + ); case 'RENEW': - return {t('orderActionRenew', 'Renew')}; + return ( + + {t('orderActionRenew', 'Renew')} + + ); case 'REVISE': - return {t('orderActionRevise', 'Modify')}; + return ( + + {t('orderActionRevise', 'Modify')} + + ); case 'DISCONTINUE': - return {t('orderActionDiscontinue', 'Discontinue')}; + return ( + + {t('orderActionDiscontinue', 'Discontinue')} + + ); default: return <>; } diff --git a/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.scss b/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.scss index 2fc8dde661..3c20972285 100644 --- a/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.scss +++ b/packages/esm-patient-orders-app/src/order-basket/general-order-type/order-basket-item-tile.scss @@ -1,3 +1,4 @@ +@use '@carbon/colors'; @use '@carbon/layout'; @use '@carbon/type'; @use '@openmrs/esm-styleguide/src/vars' as *; @@ -27,31 +28,27 @@ .orderActionNewLabel { @extend .label; - color: black; - background-color: #c4f4cc; + background-color: colors.$green-10-hover; padding: 0 layout.$spacing-02; } .orderActionIncompleteLabel { - @extend .label; + @extend .orderActionNewLabel; + background-color: colors.$red-60; color: white; - background-color: $danger; - padding: 0 layout.$spacing-02; } .orderActionRenewLabel { - @extend .label; - color: $support-02; + @extend .orderActionNewLabel; } -.orderActionRevisedLabel { - @extend .label; - color: #943d00; +.orderActionReviseLabel { + @extend .orderActionNewLabel; } .orderActionDiscontinueLabel { - @extend .label; - color: $danger; + @extend .orderActionNewLabel; + background-color: colors.$gray-20; } .orderErrorText { diff --git a/packages/esm-patient-orders-app/src/order-basket/general-order-type/orderable-concept-search/orderable-concept-search.workspace.tsx b/packages/esm-patient-orders-app/src/order-basket/general-order-type/orderable-concept-search/orderable-concept-search.workspace.tsx index 94ab945d1f..9620c18236 100644 --- a/packages/esm-patient-orders-app/src/order-basket/general-order-type/orderable-concept-search/orderable-concept-search.workspace.tsx +++ b/packages/esm-patient-orders-app/src/order-basket/general-order-type/orderable-concept-search/orderable-concept-search.workspace.tsx @@ -1,3 +1,6 @@ +import React, { type ComponentProps, useCallback, useMemo, useRef, useState } from 'react'; +import { Button, Search } from '@carbon/react'; +import { useTranslation } from 'react-i18next'; import { ArrowLeftIcon, ResponsiveWrapper, @@ -13,14 +16,11 @@ import { useOrderType, usePatientChartStore, } from '@openmrs/esm-patient-common-lib'; -import React, { type ComponentProps, useCallback, useMemo, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import styles from './orderable-concept-search.scss'; -import { Button, Search } from '@carbon/react'; -import OrderableConceptSearchResults from './search-results.component'; -import { type ConfigObject } from '../../../config-schema'; import { OrderForm } from '../general-order-form/general-order-form.component'; import { prepOrderPostData } from '../resources'; +import { type ConfigObject } from '../../../config-schema'; +import OrderableConceptSearchResults from './search-results.component'; +import styles from './orderable-concept-search.scss'; interface OrderableConceptSearchWorkspaceProps extends DefaultWorkspaceProps { order: OrderBasketItem; diff --git a/packages/esm-patient-orders-app/src/routes.json b/packages/esm-patient-orders-app/src/routes.json index 98eca5e422..7e0328a018 100644 --- a/packages/esm-patient-orders-app/src/routes.json +++ b/packages/esm-patient-orders-app/src/routes.json @@ -27,6 +27,14 @@ "slot": "action-menu-patient-chart-items-slot", "order": 0 }, + { + "name": "lab-result", + "component": "labResult", + "slot": "completed-lab-order-results-slot", + "meta": { + "fullWidth": false + } + }, { "name": "patient-orders-summary-dashboard", "component": "ordersDashboardLink", @@ -76,4 +84,4 @@ "type": "order" } ] -} +} \ No newline at end of file diff --git a/packages/esm-patient-orders-app/translations/am.json b/packages/esm-patient-orders-app/translations/am.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/am.json +++ b/packages/esm-patient-orders-app/translations/am.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/ar.json b/packages/esm-patient-orders-app/translations/ar.json index 7a60686a18..2cd81f4079 100644 --- a/packages/esm-patient-orders-app/translations/ar.json +++ b/packages/esm-patient-orders-app/translations/ar.json @@ -16,7 +16,7 @@ "dateCannotBeBeforeToday": "Date cannot be before today", "dateOfOrder": "Date of order", "dateRange": "النطاق الزمني", - "directlyAddToBasket": "Add to basket", + "directlyAddToBasket": "أضف إلى السلة", "discard": "Discard", "discontinued": "Discontinued", "dosage": "الجرعة", @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", @@ -51,8 +53,8 @@ "orderActionNew": "New", "orderActionRenew": "Renew", "orderActionRevise": "Modify", - "orderBasket": "Order basket", - "orderBasketWorkspaceTitle": "Order basket", + "orderBasket": "سلة الطلب", + "orderBasketWorkspaceTitle": "سلة الطلب", "orderCancellation": "Order cancellation", "orderCancelled": "Order cancelled", "orderCompleted": "Placed orders", diff --git a/packages/esm-patient-orders-app/translations/de.json b/packages/esm-patient-orders-app/translations/de.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/de.json +++ b/packages/esm-patient-orders-app/translations/de.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/en.json b/packages/esm-patient-orders-app/translations/en.json index 9d3d18d84c..9878c76994 100644 --- a/packages/esm-patient-orders-app/translations/en.json +++ b/packages/esm-patient-orders-app/translations/en.json @@ -32,9 +32,11 @@ "indication": "Indication", "inStock": "In stock", "instructions": "Instructions", + "labResultError": "Error loading lab results", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/es.json b/packages/esm-patient-orders-app/translations/es.json index f44c7abdcb..30f5360172 100644 --- a/packages/esm-patient-orders-app/translations/es.json +++ b/packages/esm-patient-orders-app/translations/es.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error al guardar los resultados de laboratorio", "goToDrugOrderForm": "Order form", "indication": "Indicación", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Iniciar cesta de órdenes", "loading": "Cargando", "loadingInitialValues": "Cargando valores iniciales", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Cargando detalles de la prueba", "medicationDurationAndUnit": "por {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Duración indefinida", diff --git a/packages/esm-patient-orders-app/translations/fr.json b/packages/esm-patient-orders-app/translations/fr.json index e0dbe331b4..2c5fdae830 100644 --- a/packages/esm-patient-orders-app/translations/fr.json +++ b/packages/esm-patient-orders-app/translations/fr.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Erreur lors de la sauvegarde du résultat de laboratoire", "goToDrugOrderForm": "Bon de commande", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "En stock", "instructions": "Instructions", "launchOrderBasket": "Charger le panier de commande", "loading": "Chargement", "loadingInitialValues": "Chargement des valeurs initiales", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Chargement des détails du test", "medicationDurationAndUnit": "pour {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Durée indéfinie", diff --git a/packages/esm-patient-orders-app/translations/he.json b/packages/esm-patient-orders-app/translations/he.json index 11030a4774..4a14d1ddd1 100644 --- a/packages/esm-patient-orders-app/translations/he.json +++ b/packages/esm-patient-orders-app/translations/he.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/hi.json b/packages/esm-patient-orders-app/translations/hi.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/hi.json +++ b/packages/esm-patient-orders-app/translations/hi.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/hi_IN.json b/packages/esm-patient-orders-app/translations/hi_IN.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/hi_IN.json +++ b/packages/esm-patient-orders-app/translations/hi_IN.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/id.json b/packages/esm-patient-orders-app/translations/id.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/id.json +++ b/packages/esm-patient-orders-app/translations/id.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/it.json b/packages/esm-patient-orders-app/translations/it.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/it.json +++ b/packages/esm-patient-orders-app/translations/it.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/km.json b/packages/esm-patient-orders-app/translations/km.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/km.json +++ b/packages/esm-patient-orders-app/translations/km.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/ne.json b/packages/esm-patient-orders-app/translations/ne.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/ne.json +++ b/packages/esm-patient-orders-app/translations/ne.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/pt.json b/packages/esm-patient-orders-app/translations/pt.json index 92b0c1f98c..f6d5512fc9 100644 --- a/packages/esm-patient-orders-app/translations/pt.json +++ b/packages/esm-patient-orders-app/translations/pt.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Erro ao salvar resultados laboratoriais", "goToDrugOrderForm": "Order form", "indication": "Indicação", + "labResultError": "Error loading lab results", "inStock": "Em stock", "instructions": "Instructions", "launchOrderBasket": "Iniciar carrinho de pedido", "loading": "Carregando", "loadingInitialValues": "Carregando valores iniciais", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Carregando detalhes do teste", "medicationDurationAndUnit": "para {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Duração indefinida", diff --git a/packages/esm-patient-orders-app/translations/pt_BR.json b/packages/esm-patient-orders-app/translations/pt_BR.json new file mode 100644 index 0000000000..136a4c3b14 --- /dev/null +++ b/packages/esm-patient-orders-app/translations/pt_BR.json @@ -0,0 +1,113 @@ +{ + "actionsMenu": "Actions menu", + "activeVisitRequired": "An active visit is required to make orders", + "add": "Add", + "additionalInstructions": "Additional instructions", + "addResults": "Add results", + "allOrders": "All orders", + "backToOrderBasket": "Back to order basket", + "cancel": "Cancel", + "cancellationDate": "Cancellation date", + "cancellationDateRequired": "Cancellation date is required", + "cancelOrder": "Cancel order", + "checkFilters": "Check the filters above", + "chooseAnOption": "Choose an option", + "clearSearchResults": "Clear results", + "dateCannotBeBeforeToday": "Date cannot be before today", + "dateOfOrder": "Date of order", + "dateRange": "Date range", + "directlyAddToBasket": "Add to basket", + "discard": "Discard", + "discontinued": "Discontinued", + "dosage": "Dosage", + "dose": "Dose", + "editResults": "Edit results", + "endDate": "End date", + "enterTestResults": "Enter test results", + "error": "Error", + "errorCancellingOrder": "Error cancelling order", + "errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"", + "errorSavingLabResults": "Error saving lab results", + "goToDrugOrderForm": "Order form", + "indication": "Indication", + "labResultError": "Error loading lab results", + "inStock": "In stock", + "instructions": "Instructions", + "launchOrderBasket": "Launch order basket", + "loading": "Loading", + "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", + "loadingTestDetails": "Loading test details", + "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", + "medicationIndefiniteDuration": "Indefinite duration", + "medications": "Medications", + "modifyOrder": "Modify order", + "noMatchingOrdersToDisplay": "No matching orders to display", + "noResultsForTestTypeSearch": "No results to display for \"{{searchTerm}}\"", + "normalRange": "Normal range", + "onDate": "on", + "or": "or", + "order": "Order", + "orderActionDiscontinue": "Discontinue", + "orderActionIncomplete": "Incomplete", + "orderActionNew": "New", + "orderActionRenew": "Renew", + "orderActionRevise": "Modify", + "orderBasket": "Order basket", + "orderBasketWorkspaceTitle": "Order basket", + "orderCancellation": "Order cancellation", + "orderCancelled": "Order cancelled", + "orderCompleted": "Placed orders", + "orderDetails": "Order details", + "ordered": "Placed order for", + "orderedBy": "Ordered by", + "ordererInformation": "Orderer information", + "orderNumber": "Order number", + "orders": "Orders", + "Orders": "Orders", + "orderType": "Order type", + "outOfStock": "Out of stock", + "pleaseFillField": "Please fill at least one field", + "pleaseFillRequiredFields": "Please fill all the required fields", + "pleaseRequiredFields": "Please fill all required fields", + "price": "Price", + "priceDisclaimer": "This price is indicative and may not reflect final costs, which could vary due to discounts, insurance coverage, or other pricing rules", + "print": "Print", + "printedBy": "Printed by", + "printOrder": "Print order", + "priority": "Priority", + "quantity": "Quantity", + "reasonForCancellation": "Reason for cancellation", + "reasonForCancellationRequired": "Reason for cancellation is required", + "referenceNumber": "Reference number", + "referenceNumberTableHeader": "{{orderType}} reference number", + "refills": "Refills", + "removeFromBasket": "Remove from basket", + "result": "Result", + "returnToOrderBasket": "Return to order basket", + "saveAndClose": "Save and close", + "saveDrugOrderFailed": "Error ordering {{orderName}}", + "saveLabResults": "Save lab results", + "saveOrder": "Save order", + "saving": "Saving", + "searchAgain": "search again", + "searchFieldOrder": "Search for {{orderType}} order", + "searchOrderables": "Search orderables", + "searchResultsMatchesForTerm_one": "{{count}} results for \"{{searchTerm}}\"", + "searchResultsMatchesForTerm_other": "{{count}} results for \"{{searchTerm}}\"", + "searchTable": "Search table", + "selectOrderType": "Select order type", + "signAndClose": "Sign and close", + "startAVisitToRecordOrders": "Start a visit to order", + "startVisit": "Start visit", + "status": "Status", + "successfullyCancelledOrder": "Order {{orderNumber}} has been cancelled successfully", + "successfullySavedLabResults": "Lab results for {{orderNumber}} have been successfully updated", + "Test Order_few": "Test orders", + "testType": "Test type", + "tryReopeningTheWorkspaceAgain": "Please try launching the workspace again", + "trySearchingAgain": "Please try searching again", + "tryTo": "Try to", + "updated": "Updated", + "usingADifferentTerm": "using a different term" +} diff --git a/packages/esm-patient-orders-app/translations/qu.json b/packages/esm-patient-orders-app/translations/qu.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/qu.json +++ b/packages/esm-patient-orders-app/translations/qu.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/si.json b/packages/esm-patient-orders-app/translations/si.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/si.json +++ b/packages/esm-patient-orders-app/translations/si.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/sw.json b/packages/esm-patient-orders-app/translations/sw.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/sw.json +++ b/packages/esm-patient-orders-app/translations/sw.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/sw_KE.json b/packages/esm-patient-orders-app/translations/sw_KE.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/sw_KE.json +++ b/packages/esm-patient-orders-app/translations/sw_KE.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/tr.json b/packages/esm-patient-orders-app/translations/tr.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/tr.json +++ b/packages/esm-patient-orders-app/translations/tr.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/tr_TR.json b/packages/esm-patient-orders-app/translations/tr_TR.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/tr_TR.json +++ b/packages/esm-patient-orders-app/translations/tr_TR.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/uk.json b/packages/esm-patient-orders-app/translations/uk.json index 9d3d18d84c..136a4c3b14 100644 --- a/packages/esm-patient-orders-app/translations/uk.json +++ b/packages/esm-patient-orders-app/translations/uk.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Indication", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Loading", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/vi.json b/packages/esm-patient-orders-app/translations/vi.json index 9ed13ee549..b28b89db02 100644 --- a/packages/esm-patient-orders-app/translations/vi.json +++ b/packages/esm-patient-orders-app/translations/vi.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "Error saving lab results", "goToDrugOrderForm": "Order form", "indication": "Chỉ định", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "Launch order basket", "loading": "Đang tải", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "Loading test details", "medicationDurationAndUnit": "for {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "Indefinite duration", diff --git a/packages/esm-patient-orders-app/translations/zh.json b/packages/esm-patient-orders-app/translations/zh.json index 67546dee1f..9a6ff1abc6 100644 --- a/packages/esm-patient-orders-app/translations/zh.json +++ b/packages/esm-patient-orders-app/translations/zh.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "保存检验结果时出现错误", "goToDrugOrderForm": "Order form", "indication": "适应症", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "开启医嘱篮", "loading": "加载中", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "正在加载检验信息", "medicationDurationAndUnit": "持续 {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "无限期", diff --git a/packages/esm-patient-orders-app/translations/zh_CN.json b/packages/esm-patient-orders-app/translations/zh_CN.json index 67546dee1f..9a6ff1abc6 100644 --- a/packages/esm-patient-orders-app/translations/zh_CN.json +++ b/packages/esm-patient-orders-app/translations/zh_CN.json @@ -30,11 +30,13 @@ "errorSavingLabResults": "保存检验结果时出现错误", "goToDrugOrderForm": "Order form", "indication": "适应症", + "labResultError": "Error loading lab results", "inStock": "In stock", "instructions": "Instructions", "launchOrderBasket": "开启医嘱篮", "loading": "加载中", "loadingInitialValues": "Loading initial values", + "loadinglabresults": "Loading lab results", "loadingTestDetails": "正在加载检验信息", "medicationDurationAndUnit": "持续 {{duration}} {{durationUnit}}", "medicationIndefiniteDuration": "无限期", diff --git a/packages/esm-patient-programs-app/package.json b/packages/esm-patient-programs-app/package.json index b1eae3ee08..17f854efea 100644 --- a/packages/esm-patient-programs-app/package.json +++ b/packages/esm-patient-programs-app/package.json @@ -1,6 +1,6 @@ { "name": "@openmrs/esm-patient-programs-app", - "version": "8.2.0", + "version": "9.0.0", "license": "MPL-2.0", "description": "Patient programs microfrontend for the OpenMRS SPA", "browser": "dist/openmrs-esm-patient-programs-app.js", @@ -17,7 +17,7 @@ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color", "coverage": "yarn test --coverage", "typescript": "tsc", - "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -38,12 +38,12 @@ }, "dependencies": { "@carbon/react": "^1.12.0", - "@openmrs/esm-patient-common-lib": "^8.2.0", + "@openmrs/esm-patient-common-lib": "^9.0.0", "lodash-es": "^4.17.21" }, "peerDependencies": { - "@openmrs/esm-framework": "5.x", - "@openmrs/esm-patient-common-lib": "8.x", + "@openmrs/esm-framework": "6.x", + "@openmrs/esm-patient-common-lib": "9.x", "dayjs": "1.x", "react": "18.x", "react-i18next": "11.x", diff --git a/packages/esm-patient-programs-app/src/programs/programs-form.scss b/packages/esm-patient-programs-app/src/programs/programs-form.scss index 8b266bd808..446323c5ef 100644 --- a/packages/esm-patient-programs-app/src/programs/programs-form.scss +++ b/packages/esm-patient-programs-app/src/programs/programs-form.scss @@ -15,6 +15,10 @@ align-items: baseline; min-width: 50%; + :global(.cds--inline-loading) { + min-height: layout.$spacing-05 !important; + } + :global(.cds--inline-loading__text) { @include type.type-style('body-01'); } @@ -40,12 +44,6 @@ height: 100%; } -.errorMessage { - @include type.type-style('label-02'); - margin-top: layout.$spacing-03; - color: $danger; -} - .notification { margin: 0; min-width: 100%; diff --git a/packages/esm-patient-programs-app/src/programs/programs-form.workspace.tsx b/packages/esm-patient-programs-app/src/programs/programs-form.workspace.tsx index 926f3e74c9..47965b73b5 100644 --- a/packages/esm-patient-programs-app/src/programs/programs-form.workspace.tsx +++ b/packages/esm-patient-programs-app/src/programs/programs-form.workspace.tsx @@ -57,7 +57,6 @@ const ProgramsForm: React.FC = ({ const availableLocations = useLocations(); const { data: availablePrograms } = useAvailablePrograms(); const { data: enrollments, mutateEnrollments } = useEnrollments(patientUuid); - const [isSubmittingForm, setIsSubmittingForm] = useState(false); const { showProgramStatusField } = useConfig(); const programsFormSchema = useMemo(() => createProgramsFormSchema(t), [t]); @@ -90,7 +89,7 @@ const ProgramsForm: React.FC = ({ control, handleSubmit, watch, - formState: { isDirty }, + formState: { errors, isDirty, isSubmitting }, } = useForm({ mode: 'all', resolver: zodResolver(programsFormSchema), @@ -126,8 +125,6 @@ const ProgramsForm: React.FC = ({ }; try { - setIsSubmittingForm(true); - const abortController = new AbortController(); if (currentEnrollment) { @@ -155,8 +152,6 @@ const ProgramsForm: React.FC = ({ subtitle: error instanceof Error ? error.message : 'An unknown error occurred', }); } - - setIsSubmittingForm(false); }, [closeWorkspaceWithSavedChanges, currentEnrollment, currentState, mutateEnrollments, patientUuid, t], ); @@ -165,12 +160,13 @@ const ProgramsForm: React.FC = ({ ( + render={({ field: { onChange, value } }) => ( <> -

{fieldState?.error?.message}

)} /> @@ -267,12 +262,13 @@ const ProgramsForm: React.FC = ({ ( + render={({ field: { onChange, value } }) => ( <> -

{fieldState?.error?.message}

)} /> @@ -343,8 +338,8 @@ const ProgramsForm: React.FC = ({ -