diff --git a/.eslintrc b/.eslintrc index 992c5621d..485b7ea01 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,17 +2,23 @@ "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"], + "overrides": [ + { + "files": ["**/*.test.tsx"], + "extends": ["plugin:testing-library/react"] + }, + { + "files": ["e2e/**/*.spec.ts"], + "extends": ["plugin:playwright/recommended"], + "rules": { + "testing-library/prefer-screen-queries": "off" + } + } ], "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "import", "jest-dom", "react-hooks", "testing-library"], "rules": { - "import/no-duplicates": "error", - "react-hooks/rules-of-hooks": "error", // Disabling these rules for now just to keep the diff small. We'll enable them one by one as we go. "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-types": "off", @@ -32,7 +38,7 @@ "fixStyle": "inline-type-imports" } ], - "prefer-const": "off", + "import/no-duplicates": "error", "no-console": [ "error", { @@ -69,36 +75,9 @@ } ] } - ] - }, - "overrides": [ - { - "files": ["**/e2e/**"], - "rules": { - "testing-library/await-async-events": "off", - "testing-library/await-async-query": "off", - "testing-library/await-async-utils": "off", - "testing-library/no-await-sync-events": "off", - "testing-library/no-await-sync-queries": "off", - "testing-library/no-container": "off", - "testing-library/no-debugging-utils": "off", - "testing-library/no-dom-import": "off", - "testing-library/no-global-regexp-flag-in-query": "off", - "testing-library/no-manual-cleanup": "off", - "testing-library/no-node-access": "off", - "testing-library/no-promise-in-fire-event": "off", - "testing-library/no-render-in-lifecycle": "off", - "testing-library/no-unnecessary-act": "off", - "testing-library/no-wait-for-multiple-assertions": "off", - "testing-library/no-wait-for-side-effects": "off", - "testing-library/no-wait-for-snapshot": "off", - "testing-library/prefer-find-by": "off", - "testing-library/prefer-implicit-assert": "off", - "testing-library/prefer-presence-queries": "off", - "testing-library/prefer-query-by-disappearance": "off", - "testing-library/prefer-screen-queries": "off", - "testing-library/render-result-naming-convention": "off" - } - } - ] + ], + "prefer-const": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn" + } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d22aae375..2ad3945f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: OpenMRS CI on: + workflow_dispatch: push: branches: [main] pull_request: diff --git a/__mocks__/appointments.mock.ts b/__mocks__/appointments.mock.ts index e852e520f..a2a76e0a0 100644 --- a/__mocks__/appointments.mock.ts +++ b/__mocks__/appointments.mock.ts @@ -368,10 +368,12 @@ export const mockProviders = { { uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66', person: { uuid: '24252571-dd5a-11e6-9d9c-0242ac150002', display: 'Dr James Cook' }, + display: 'doctor - James Cook', }, { uuid: '3191eddf-5cc5-4fa4-94ef-dfc25e8d33e4', person: { uuid: '89af8ba6-2ec5-4d77-b3ed-7e9e02448e96', display: 'Dr Amstrong Neil' }, + display: 'doctor - Amstrong Neil', }, ], }; diff --git a/__mocks__/index.ts b/__mocks__/index.ts index 0fc40834c..15692d4f8 100644 --- a/__mocks__/index.ts +++ b/__mocks__/index.ts @@ -8,7 +8,7 @@ export * from './identifiers.mock'; export * from './locations.mock'; export * from './metrics.mock'; export * from './patient.mock'; -export * from './patient-visits.mock'; +export * from './patient-appointments.mock'; export * from './patient-registration.mock'; export * from './queue-entry.mock'; export * from './queue-rooms.mock'; diff --git a/__mocks__/patient-visits.mock.ts b/__mocks__/patient-appointments.mock.ts similarity index 97% rename from __mocks__/patient-visits.mock.ts rename to __mocks__/patient-appointments.mock.ts index e5b106e5c..07055ca5d 100644 --- a/__mocks__/patient-visits.mock.ts +++ b/__mocks__/patient-appointments.mock.ts @@ -1,5 +1,5 @@ -export const mockPatientsVisits = { - recentVisits: [ +export const mockPatientAppointments = { + recentAppointments: [ { uuid: '6baa7963-68ea-497e-b258-6fb82382bd07', appointmentNumber: '0000', @@ -59,7 +59,7 @@ export const mockPatientsVisits = { recurring: false, }, ], - futureVisits: [ + futureAppointments: [ { uuid: '6baa7963-68ea-497e-b258-6fb82382bd07', appointmentNumber: '0000', diff --git a/__mocks__/queue-entry.mock.ts b/__mocks__/queue-entry.mock.ts index dc36d30c7..138eaf43b 100644 --- a/__mocks__/queue-entry.mock.ts +++ b/__mocks__/queue-entry.mock.ts @@ -125,7 +125,7 @@ export const mockQueueEntryAlice: QueueEntry = { endedAt: null, locationWaitingFor: null, patient: mockPatientAlice, - priority: mockPriorityNonUrgent, + priority: mockPriorityUrgent, priorityComment: null, providerWaitingFor: null, queue: mockQueueSurgery, diff --git a/__mocks__/search.mock.ts b/__mocks__/search.mock.ts index 783ca54f7..fdaf71236 100644 --- a/__mocks__/search.mock.ts +++ b/__mocks__/search.mock.ts @@ -33,3 +33,116 @@ export const mockSearchResults = { ], }, }; + +export const mockAdvancedSearchResults = [ + { + patientId: 14, + uuid: 'e46dfea6-f32d-4b61-bc7d-e79fd35332a4', + identifiers: [ + { + identifier: '100008E', + identifierType: { + uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + display: 'OpenMRS ID', + }, + }, + ], + display: '100008E - Joshua Johnson', + patientIdentifier: { + uuid: '1e6c2da6-f63f-4ea5-a595-ded69df9f882', + identifier: '100008E', + }, + person: { + gender: 'M', + age: 5, + birthdate: '2019-09-25T00:00:00.000+0000', + birthdateEstimated: false, + personName: { + display: 'Joshua Johnson', + givenName: 'Joshua', + familyName: 'Johnson', + }, + addresses: [ + { + address1: 'Address16442', + cityVillage: 'City6442', + stateProvince: 'State6442', + country: 'Country6442', + postalCode: '20839', + preferred: true, + }, + ], + dead: false, + deathDate: null, + }, + attributes: [ + { + value: '0785434125', + attributeType: { + uuid: '14d4f066-15f5-102d-96e4-000c29c2a5d7', + display: 'Telephone Number', + }, + }, + ], + }, + { + patientId: 42, + uuid: 'a83747aa-3041-489a-a112-1c024582c83d', + identifiers: [ + { + identifier: '100016H', + identifierType: { + uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + display: 'OpenMRS ID', + }, + }, + ], + display: '100016H - Joseph Davis', + patientIdentifier: { + uuid: '0ac0a9a0-b040-4c0a-9c35-c4e0bb52a570', + identifier: '100016H', + }, + person: { + gender: 'M', + age: 30, + birthdate: '1994-10-13T00:00:00.000+0000', + birthdateEstimated: false, + personName: { + display: 'Joseph Davis', + givenName: 'Joseph', + familyName: 'Davis', + }, + addresses: [ + { + address1: 'Address19050', + cityVillage: 'City9050', + stateProvince: 'State9050', + country: 'Country9050', + postalCode: '46548', + preferred: true, + }, + ], + dead: false, + deathDate: null, + }, + attributes: [ + { + value: { + uuid: '1ce1b7d4-c865-4178-82b0-5932e51503d6', + display: 'Community Outreach', + links: [ + { + rel: 'self', + uri: 'http://dev3.openmrs.org/openmrs/ws/rest/v1/location/1ce1b7d4-c865-4178-82b0-5932e51503d6', + resourceAlias: 'location', + }, + ], + }, + attributeType: { + uuid: '8d87236c-c2cc-11de-8d13-0010c6dffd0f', + display: 'Health Center', + }, + }, + ], + }, +]; diff --git a/e2e/commands/cohort-operations.ts b/e2e/commands/cohort-operations.ts index 496038970..2a7042abd 100644 --- a/e2e/commands/cohort-operations.ts +++ b/e2e/commands/cohort-operations.ts @@ -1,43 +1,5 @@ import { type APIRequestContext, expect } from '@playwright/test'; -import { type Patient } from './patient-operations'; - -export interface CohortType { - uuid: string; - name: string; - description: string; - display: string; - links: { rel: string; uri: string; resourceAlias: string }[]; - resourceVersion: string; -} - -export interface Cohort { - uuid: string; - name: string; - description: string; - attributes: any[]; - links: any[]; - location: any; - groupCohort: boolean | null; - startDate: Date; - endDate: Date; - voidReason: string | null; - voided: boolean; - isStarred?: boolean; - type?: string; - size: number; - cohortType?: CohortType; - resourceVersion: string; -} - -export interface CohortMember { - attributes: Array; - description: string; - endDate: string; - startDate: string; - name: string; - uuid: string; - patient: Patient; -} +import { type Cohort, type CohortMember } from '../types'; export const generateRandomCohort = async (api: APIRequestContext): Promise => { const cohortRes = await api.post('cohortm/cohort', { diff --git a/e2e/commands/encounter-operations.ts b/e2e/commands/encounter-operations.ts index aeb991013..efccad25e 100644 --- a/e2e/commands/encounter-operations.ts +++ b/e2e/commands/encounter-operations.ts @@ -1,6 +1,6 @@ -import { Encounter } from './../../packages/esm-active-visits-app/src/visits-summary/visit.resource'; -import { APIRequestContext, expect } from '@playwright/test'; +import { type APIRequestContext, expect } from '@playwright/test'; import dayjs from 'dayjs'; +import { type Encounter } from '../types'; export const createEncounter = async ( api: APIRequestContext, diff --git a/e2e/commands/patient-operations.ts b/e2e/commands/patient-operations.ts index cc9fe99ff..e2efa2491 100644 --- a/e2e/commands/patient-operations.ts +++ b/e2e/commands/patient-operations.ts @@ -1,50 +1,5 @@ import { type APIRequestContext, expect } from '@playwright/test'; - -export interface Patient { - uuid: string; - identifiers: Identifier[]; - display: string; - person: { - uuid: string; - display: string; - gender: string; - age: number; - birthdate: string; - birthdateEstimated: boolean; - dead: boolean; - deathDate?: any; - causeOfDeath?: any; - preferredAddress: { - address1: string; - cityVillage: string; - country: string; - postalCode: string; - stateProvince: string; - countyDistrict: string; - }; - attributes: any[]; - voided: boolean; - birthtime?: any; - deathdateEstimated: boolean; - resourceVersion: string; - }; - attributes: { value: string; attributeType: { uuid: string; display: string } }[]; - voided: boolean; -} - -export interface Address { - preferred: boolean; - address1: string; - cityVillage: string; - country: string; - postalCode: string; - stateProvince: string; -} - -export interface Identifier { - uuid: string; - display: string; -} +import { type Patient } from '../types'; export const generateRandomPatient = async (api: APIRequestContext): Promise => { const identifierRes = await api.post('idgen/identifiersource/8549f706-7e85-4c1d-9424-217d50a2988b/identifier', { diff --git a/e2e/commands/provider-operations.ts b/e2e/commands/provider-operations.ts index c925313dc..8c056665d 100644 --- a/e2e/commands/provider-operations.ts +++ b/e2e/commands/provider-operations.ts @@ -1,5 +1,5 @@ -import { Provider } from '../../packages/esm-appointments-app/src/types/index'; -import { APIRequestContext, expect } from '@playwright/test'; +import { type APIRequestContext, expect } from '@playwright/test'; +import { type Provider } from '../types'; export const getProvider = async (api: APIRequestContext): Promise => { const providerRes = await api.get('provider?q=admin', { diff --git a/e2e/commands/visit-operations.ts b/e2e/commands/visit-operations.ts index 2091740ab..4316c395d 100644 --- a/e2e/commands/visit-operations.ts +++ b/e2e/commands/visit-operations.ts @@ -1,6 +1,6 @@ -import { APIRequestContext, expect } from '@playwright/test'; -import { Visit } from '@openmrs/esm-framework'; +import { type APIRequestContext, expect } from '@playwright/test'; import dayjs from 'dayjs'; +import { type Visit } from '@openmrs/esm-framework'; export const startVisit = async (api: APIRequestContext, patientId: string): Promise => { const visitRes = await api.post('visit', { diff --git a/e2e/pages/home-page.ts b/e2e/pages/home-page.ts index 8359dd9a5..bb5ff1700 100644 --- a/e2e/pages/home-page.ts +++ b/e2e/pages/home-page.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test'; +import { type Page } from '@playwright/test'; export class HomePage { constructor(readonly page: Page) {} diff --git a/e2e/pages/patient-lists-page.ts b/e2e/pages/patient-lists-page.ts index b5ac5f7e4..8e0922d10 100644 --- a/e2e/pages/patient-lists-page.ts +++ b/e2e/pages/patient-lists-page.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test'; +import { type Page } from '@playwright/test'; export class PatientListsPage { constructor(readonly page: Page) {} diff --git a/e2e/pages/registration-and-edit-page.ts b/e2e/pages/registration-and-edit-page.ts index 3041689a2..753b6d194 100644 --- a/e2e/pages/registration-and-edit-page.ts +++ b/e2e/pages/registration-and-edit-page.ts @@ -1,27 +1,5 @@ import { type Locator, type Page, expect } from '@playwright/test'; - -export type PatientRegistrationSex = 'male' | 'female' | 'other' | 'unknown'; - -export interface PatientRegistrationFormValues { - givenName?: string; - middleName?: string; - familyName?: string; - sex?: PatientRegistrationSex; - birthdate?: { - day: string; - month: string; - year: string; - }; - postalCode?: string; - address1?: string; - address2?: string; - country?: string; - countyDistrict?: string; - stateProvince?: string; - cityVillage?: string; - phone?: string; - email?: string; -} +import { type PatientRegistrationFormValues, type PatientRegistrationSex } from '../types'; export class RegistrationAndEditPage { constructor(readonly page: Page) {} diff --git a/e2e/specs/active-visits.spec.ts b/e2e/specs/active-visits.spec.ts index fb70f607b..1ba677c18 100644 --- a/e2e/specs/active-visits.spec.ts +++ b/e2e/specs/active-visits.spec.ts @@ -1,19 +1,18 @@ import { expect } from '@playwright/test'; import type { Visit } from '@openmrs/esm-framework'; import { test } from '../core'; -import type { Provider } from '../../packages/esm-appointments-app/src/types/index'; -import type { Encounter } from '../../packages/esm-active-visits-app/src/visits-summary/visit.resource'; + import { createEncounter, deleteEncounter, deletePatient, endVisit, generateRandomPatient, - type Patient, - startVisit, getProvider, + startVisit, } from '../commands'; import { HomePage } from '../pages'; +import { type Encounter, type Patient, type Provider } from '../types'; let patient: Patient; let visit: Visit; diff --git a/e2e/specs/appointments.spec.ts b/e2e/specs/appointments.spec.ts index 87d387989..7705ea0a9 100644 --- a/e2e/specs/appointments.spec.ts +++ b/e2e/specs/appointments.spec.ts @@ -1,8 +1,9 @@ import { expect } from '@playwright/test'; -import { generateRandomPatient, deletePatient, type Patient, startVisit, endVisit } from '../commands'; +import { generateRandomPatient, deletePatient, startVisit, endVisit } from '../commands'; +import { type Visit } from '@openmrs/esm-framework'; +import { type Patient } from '../types'; import { test } from '../core'; import { AppointmentsPage } from '../pages'; -import { type Visit } from '@openmrs/esm-framework'; let patient: Patient; let visit: Visit; @@ -111,7 +112,7 @@ test('Add, edit and cancel an appointment', async ({ page, api }) => { await page.getByRole('button', { name: 'Options' }).click(); }); - await test.step('And I choose the "Cancel" option ', async () => { + await test.step('And I choose the "Cancel" option', async () => { await page.getByRole('menuitem', { name: 'Cancel' }).click(); }); diff --git a/e2e/specs/edit-patient.spec.ts b/e2e/specs/edit-patient.spec.ts index c76d228aa..0aa701512 100644 --- a/e2e/specs/edit-patient.spec.ts +++ b/e2e/specs/edit-patient.spec.ts @@ -1,8 +1,9 @@ import dayjs from 'dayjs'; import { expect } from '@playwright/test'; import { test } from '../core'; -import { deletePatient, generateRandomPatient, getPatient, type Patient } from '../commands'; -import { type PatientRegistrationFormValues, RegistrationAndEditPage } from '../pages'; +import { deletePatient, generateRandomPatient, getPatient } from '../commands'; +import { RegistrationAndEditPage } from '../pages'; +import { type Patient, type PatientRegistrationFormValues } from '../types'; let patient: Patient; diff --git a/e2e/specs/patient-list.spec.ts b/e2e/specs/patient-list.spec.ts index 69092ccea..1591b4b90 100644 --- a/e2e/specs/patient-list.spec.ts +++ b/e2e/specs/patient-list.spec.ts @@ -2,9 +2,6 @@ import { test } from '../core'; import { PatientListsPage } from '../pages'; import { expect } from '@playwright/test'; import { - type Cohort, - type CohortMember, - type Patient, addPatientToCohort, deleteCohort, deletePatient, @@ -12,6 +9,7 @@ import { generateRandomPatient, removePatientFromCohort, } from '../commands'; +import { type Cohort, type CohortMember, type Patient } from '../types'; let cohortMember: CohortMember; let cohortUuid: string; @@ -46,7 +44,8 @@ test('Create and edit a patient list', async ({ page }) => { await test.step('Then I should see the information about the list', async () => { await expect(page).toHaveURL(new RegExp('^[\\w\\d:\\/.-]+\\/patient-lists\\/[\\w\\d-]+$')); - cohortUuid = /patient-lists\/([\w\d-]+)/.exec(page.url())?.[1] ?? null; + const [, extractedUuid] = /patient-lists\/([\w\d-]+)/.exec(page.url()); + cohortUuid = extractedUuid; await expect(patientListPage.patientListHeader()).toHaveText(new RegExp(patientListName)); await expect(patientListPage.patientListHeader()).toHaveText(new RegExp(patientListDescription)); diff --git a/e2e/specs/patient-search.spec.ts b/e2e/specs/patient-search.spec.ts index b91f53c67..54c13b1ac 100644 --- a/e2e/specs/patient-search.spec.ts +++ b/e2e/specs/patient-search.spec.ts @@ -1,7 +1,8 @@ import { expect } from '@playwright/test'; import { test } from '../core'; import { HomePage } from '../pages'; -import { generateRandomPatient, deletePatient, type Patient } from '../commands'; +import { generateRandomPatient, deletePatient } from '../commands'; +import { type Patient } from '../types'; let patient: Patient; diff --git a/e2e/specs/register-new-patient.spec.ts b/e2e/specs/register-new-patient.spec.ts index 365b0bdf8..ed4fca8ae 100644 --- a/e2e/specs/register-new-patient.spec.ts +++ b/e2e/specs/register-new-patient.spec.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { test } from '../core'; -import { type PatientRegistrationFormValues, RegistrationAndEditPage } from '../pages'; +import { RegistrationAndEditPage } from '../pages'; +import { type PatientRegistrationFormValues } from '../types'; import { deletePatient } from '../commands'; let patientUuid: string; @@ -101,7 +102,7 @@ test('Register an unknown patient', async ({ api, page }) => { }); await test.step('And then I fill in 25 as the estimated age in years', async () => { - const estimatedAgeField = await page.getByLabel(/estimated age in years/i); + const estimatedAgeField = page.getByLabel(/estimated age in years/i); await estimatedAgeField.clear(); await estimatedAgeField.fill('25'); }); @@ -123,7 +124,7 @@ test('Register an unknown patient', async ({ api, page }) => { await test.step("And I should see the newly registered patient's details displayed in the patient banner", async () => { const patientBanner = page.locator('header[aria-label="patient banner"]'); - expect(patientBanner).toBeVisible(); + await expect(patientBanner).toBeVisible(); await expect(patientBanner.getByText('Unknown Unknown')).toBeVisible(); await expect(patientBanner.getByText(/female/i)).toBeVisible(); await expect(patientBanner.getByText(/25 yrs/i)).toBeVisible(); diff --git a/e2e/specs/return-to-patient-list.spec.ts b/e2e/specs/return-to-patient-list.spec.ts index 2023f0840..a86c56572 100644 --- a/e2e/specs/return-to-patient-list.spec.ts +++ b/e2e/specs/return-to-patient-list.spec.ts @@ -3,15 +3,13 @@ import { PatientListsPage } from '../pages'; import { expect } from '@playwright/test'; import { addPatientToCohort, - type Cohort, - type CohortMember, deleteCohort, deletePatient, generateRandomCohort, generateRandomPatient, - type Patient, removePatientFromCohort, } from '../commands'; +import { type Cohort, type CohortMember, type Patient } from '../types'; let cohortMembership: CohortMember; let cohort: Cohort; @@ -123,7 +121,7 @@ test('Return to patient list after navigating to visits and refreshing the page' test('Return to patient list from the patient chart on a new tab', async ({ page, context }) => { const patientListPage = new PatientListsPage(page); - const locator = await page.locator('table tbody tr td:nth-child(1) a'); + const locator = page.locator('table tbody tr td:nth-child(1) a'); const pagePromise = context.waitForEvent('page'); await test.step('When I navigate to the patient list', async () => { diff --git a/e2e/types/index.ts b/e2e/types/index.ts new file mode 100644 index 000000000..540f16211 --- /dev/null +++ b/e2e/types/index.ts @@ -0,0 +1,218 @@ +import { type OpenmrsResource } from '@openmrs/esm-framework'; + +export type PatientRegistrationSex = 'male' | 'female' | 'other' | 'unknown'; + +export interface PatientRegistrationFormValues { + givenName?: string; + middleName?: string; + familyName?: string; + sex?: PatientRegistrationSex; + birthdate?: { + day: string; + month: string; + year: string; + }; + postalCode?: string; + address1?: string; + address2?: string; + country?: string; + countyDistrict?: string; + stateProvince?: string; + cityVillage?: string; + phone?: string; + email?: string; +} + +export interface Encounter { + uuid: string; + encounterDateTime: string; + encounterProviders: Array<{ + uuid: string; + display: string; + encounterRole: { + uuid: string; + display: string; + }; + provider: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; + }>; + encounterType: { + uuid: string; + display: string; + }; + obs: Array; + orders: Array; +} + +export interface Observation { + uuid: string; + concept: { + uuid: string; + display: string; + conceptClass: { + uuid: string; + display: string; + }; + }; + display: string; + groupMembers: null | Array<{ + uuid: string; + concept: { + uuid: string; + display: string; + }; + value: { + uuid: string; + display: string; + }; + }>; + value: any; + obsDatetime: string; +} + +export interface Order { + uuid: string; + dateActivated: string; + dateStopped?: Date | null; + dose: number; + dosingInstructions: string | null; + dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions'; + doseUnits: { + uuid: string; + display: string; + }; + drug: { + uuid: string; + name: string; + strength: string; + display: string; + }; + duration: number; + durationUnits: { + uuid: string; + display: string; + }; + frequency: { + uuid: string; + display: string; + }; + numRefills: number; + orderNumber: string; + orderReason: string | null; + orderReasonNonCoded: string | null; + orderer: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; + orderType: { + uuid: string; + display: string; + }; + route: { + uuid: string; + display: string; + }; + quantity: number; + quantityUnits: OpenmrsResource; +} + +export interface Provider { + uuid: string; + display: string; + comments?: string; + response?: string; + person: OpenmrsResource; + name?: string; +} + +export interface CohortType { + uuid: string; + name: string; + description: string; + display: string; + links: { rel: string; uri: string; resourceAlias: string }[]; + resourceVersion: string; +} + +export interface Cohort { + uuid: string; + name: string; + description: string; + attributes: any[]; + links: any[]; + location: any; + groupCohort: boolean | null; + startDate: Date; + endDate: Date; + voidReason: string | null; + voided: boolean; + isStarred?: boolean; + type?: string; + size: number; + cohortType?: CohortType; + resourceVersion: string; +} + +export interface CohortMember { + attributes: Array; + description: string; + endDate: string; + startDate: string; + name: string; + uuid: string; + patient: Patient; +} + +export interface Patient { + uuid: string; + identifiers: Identifier[]; + display: string; + person: { + uuid: string; + display: string; + gender: string; + age: number; + birthdate: string; + birthdateEstimated: boolean; + dead: boolean; + deathDate?: any; + causeOfDeath?: any; + preferredAddress: { + address1: string; + cityVillage: string; + country: string; + postalCode: string; + stateProvince: string; + countyDistrict: string; + }; + attributes: any[]; + voided: boolean; + birthtime?: any; + deathdateEstimated: boolean; + resourceVersion: string; + }; + attributes: { value: string; attributeType: { uuid: string; display: string } }[]; + voided: boolean; +} + +export interface Address { + preferred: boolean; + address1: string; + cityVillage: string; + country: string; + postalCode: string; + stateProvince: string; +} + +export interface Identifier { + uuid: string; + display: string; +} diff --git a/package.json b/package.json index 65701f7cf..fce37340b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "@babel/core": "^7.11.6", - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "@openmrs/esm-framework": "next", "@openmrs/esm-patient-common-lib": "next", "@playwright/test": "1.48.2", @@ -55,6 +55,7 @@ "eslint": "^8.55.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jest-dom": "^5.4.0", + "eslint-plugin-playwright": "^2.1.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-testing-library": "^6.2.2", "husky": "^8.0.3", diff --git a/packages/esm-active-visits-app/package.json b/packages/esm-active-visits-app/package.json index 4a9e4bb51..fd2b237ca 100644 --- a/packages/esm-active-visits-app/package.json +++ b/packages/esm-active-visits-app/package.json @@ -18,7 +18,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/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" + "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.resource.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js" }, "browserslist": [ "extends browserslist-config-openmrs" @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "lodash-es": "^4.17.15" }, "peerDependencies": { diff --git a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.component.tsx b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.component.tsx index 08e3e53a3..f7ba849fb 100644 --- a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.component.tsx +++ b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.component.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState, useCallback } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { DataTable, DataTableSkeleton, @@ -20,88 +20,53 @@ import { } from '@carbon/react'; import { useTranslation } from 'react-i18next'; import { - useLayoutType, + ConfigurableLink, + ErrorState, + ExtensionSlot, isDesktop, useConfig, + useLayoutType, usePagination, - ExtensionSlot, - ErrorState, - ConfigurableLink, } from '@openmrs/esm-framework'; import { EmptyDataIllustration } from './empty-data-illustration.component'; -import { useActiveVisits } from './active-visits.resource'; +import { useActiveVisits, useActiveVisitsSorting, useObsConcepts, useTableHeaders } from './active-visits.resource'; import styles from './active-visits.scss'; - -function generateTableHeaders(t, config) { - let headersIndex = 0; - - const headers = [ - { - id: headersIndex++, - header: t('visitStartTime', 'Visit Time'), - key: 'visitStartTime', - }, - ]; - - config?.activeVisits?.identifiers?.map((identifier) => { - headers.push({ - id: headersIndex++, - header: t(identifier?.header?.key, identifier?.header?.default), - key: identifier?.header?.key, - }); - }); - - if (!config?.activeVisit?.identifiers) { - headers.push({ - id: headersIndex++, - header: t('idNumber', 'ID Number'), - key: 'idNumber', - }); - } - - config?.activeVisits?.attributes?.map((attribute) => { - headers.push({ - id: headersIndex++, - header: t(attribute?.header?.key, attribute?.header?.default), - key: attribute?.header?.key, - }); - }); - - headers.push( - { - id: headersIndex++, - header: t('name', 'Name'), - key: 'name', - }, - { - id: headersIndex++, - header: t('gender', 'Gender'), - key: 'gender', - }, - { - id: headersIndex++, - header: t('age', 'Age'), - key: 'age', - }, - { - id: headersIndex++, - header: t('visitType', 'Visit Type'), - key: 'visitType', - }, - ); - - return headers; -} +import { type ActiveVisitsConfigSchema } from '../config-schema'; +import { type ActiveVisit } from '../types'; const ActiveVisitsTable = () => { const { t } = useTranslation(); - const config = useConfig(); + const config = useConfig(); const layout = useLayoutType(); const pageSizes = config?.activeVisits?.pageSizes ?? [10, 20, 30, 40, 50]; const [pageSize, setPageSize] = useState(config?.activeVisits?.pageSize ?? 10); + const { obsConcepts, isLoadingObsConcepts } = useObsConcepts(config.activeVisits.obs); const { activeVisits, isLoading, isValidating, error } = useActiveVisits(); const [searchString, setSearchString] = useState(''); - const headerData = useMemo(() => generateTableHeaders(t, config), [config, t]); + const headerData = useTableHeaders(obsConcepts); + + const transformVisitForDisplay = useCallback( + (visit: ActiveVisit) => { + const displayData = { ...visit }; + + // Add observation values to the display data + obsConcepts?.forEach((concept) => { + const obsValues = visit?.observations?.[concept.uuid] ?? []; + const latestObs = obsValues[0]; + + if (latestObs) { + // Handle both string and object values + displayData[`obs-${concept.uuid}`] = + typeof latestObs.value === 'object' ? latestObs.value.display : latestObs.value; + } else { + displayData[`obs-${concept.uuid}`] = '--'; + } + }); + + return displayData; + }, + [obsConcepts], + ); const searchResults = useMemo(() => { if (activeVisits !== undefined && activeVisits.length > 0) { @@ -118,10 +83,11 @@ const ActiveVisitsTable = () => { } } - return activeVisits; - }, [searchString, activeVisits]); + return activeVisits.map(transformVisitForDisplay); + }, [searchString, activeVisits, transformVisitForDisplay]); - const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize); + const { sortedRows, sortRow } = useActiveVisitsSorting(searchResults); + const { paginated, goTo, results, currentPage } = usePagination(sortedRows, pageSize as number); const handleSearch = useCallback( (e) => { @@ -131,7 +97,7 @@ const ActiveVisitsTable = () => { [goTo, setSearchString], ); - if (isLoading) { + if (isLoading || isLoadingObsConcepts) { return (
@@ -187,125 +153,128 @@ const ActiveVisitsTable = () => {
); - } else { - return ( -
-
-
-

{t('activeVisits', 'Active Visits')}

-
-
- {isValidating ? : null} -
+ } + + return ( +
+
+
+

{t('activeVisits', 'Active Visits')}

- - 1 ? true : false}> - {({ rows, headers, getHeaderProps, getTableProps, getRowProps, getExpandHeaderProps }) => ( - - - - - - {headers.map((header) => ( - {header.header} - ))} - - - - {rows.map((row, index) => { - const currentVisit = activeVisits.find((visit) => visit.id === row.id); +
+ {isValidating ? : null} +
+ + + 1}> + {({ rows, headers, getHeaderProps, getTableProps, getRowProps, getExpandHeaderProps }) => ( + +
+ + + + {headers.map((header) => ( + {header.header} + ))} + + + + {rows.map((row, index) => { + const currentVisit = activeVisits.find((visit) => visit.id === row.id); - if (!currentVisit) { - return null; - } + if (!currentVisit) { + return null; + } - const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart/Patient%20Summary'; + const patientChartUrl = '${openmrsSpaBase}/patient/${patientUuid}/chart'; - return ( - - - {row.cells.map((cell) => ( - - {cell.info.header === 'name' && currentVisit.patientUuid ? ( - - {cell.value} - - ) : ( - cell.value - )} - - ))} - - {row.isExpanded ? ( - - - - ) : ( - - )} - - ); - })} - -
- -
-
- )} -
- {searchResults?.length === 0 && ( -
- - -

{t('noVisitsToDisplay', 'No visits to display')}

-

{t('checkFilters', 'Check the filters above')}

-
-
-
+ return ( + + + {row.cells.map((cell) => ( + + {cell.info.header === 'name' && currentVisit.patientUuid ? ( + + {cell.value} + + ) : ( + cell.value + )} + + ))} + + {row.isExpanded ? ( + + + + + + ) : ( + + )} + + ); + })} + + + )} - {paginated && ( - { - if (newPageSize !== pageSize) { - setPageSize(newPageSize); - } - if (newPage !== currentPage) { - goTo(newPage); - } - }} - /> - )} -
- ); - } + + {searchResults?.length === 0 && ( +
+ + +

{t('noVisitsToDisplay', 'No visits to display')}

+

{t('checkFilters', 'Check the filters above')}

+
+
+
+ )} + {paginated && ( + { + if (newPageSize !== pageSize) { + setPageSize(newPageSize); + } + if (newPage !== currentPage) { + goTo(newPage); + } + }} + /> + )} +
+ ); }; export default ActiveVisitsTable; diff --git a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.resource.tsx b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.resource.tsx index 765d8e872..975bfb653 100644 --- a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.resource.tsx +++ b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.resource.tsx @@ -1,39 +1,24 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import useSWRInfinite from 'swr/infinite'; import dayjs from 'dayjs'; import isToday from 'dayjs/plugin/isToday'; import last from 'lodash-es/last'; import { - openmrsFetch, - type Visit, - useSession, type FetchResponse, formatDatetime, + openmrsFetch, + type OpenmrsResource, parseDate, - useConfig, restBaseUrl, + useConfig, + useSession, + type Visit, } from '@openmrs/esm-framework'; -dayjs.extend(isToday); +import useSWR from 'swr'; +import { type ActiveVisit, type VisitResponse } from '../types'; +import { useTranslation } from 'react-i18next'; -export interface ActiveVisit { - age: string; - id: string; - idNumber: string; - gender: string; - location: string; - name: string; - patientUuid: string; - visitStartTime: string; - visitType: string; - visitUuid: string; - [identifier: string]: string; -} - -interface VisitResponse { - results: Array; - links: Array<{ rel: 'prev' | 'next' }>; - totalCount: number; -} +dayjs.extend(isToday); export function useActiveVisits() { const session = useSession(); @@ -41,8 +26,10 @@ export function useActiveVisits() { const sessionLocation = session?.sessionLocation?.uuid; const customRepresentation = - 'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),person:(age,display,gender,uuid,attributes:(value,attributeType:(uuid,display)))),' + - 'visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)'; + 'custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),' + + 'person:(age,display,gender,uuid,attributes:(value,attributeType:(uuid,display)))),' + + 'visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime,' + + 'encounters:(encounterDatetime,obs:(uuid,concept:(uuid,display),value)))'; const getUrl = (pageIndex, previousPageData: FetchResponse) => { if (pageIndex && !previousPageData?.data?.links?.some((link) => link.rel === 'next')) { @@ -76,7 +63,7 @@ export function useActiveVisits() { if (data && data?.[pageNumber - 1]?.data?.links?.some((link) => link.rel === 'next')) { setSize((currentSize) => currentSize + 1); } - }, [data, pageNumber]); + }, [data, pageNumber, setSize]); const mapVisitProperties = (visit: Visit): ActiveVisit => { // create base object @@ -126,6 +113,23 @@ export function useActiveVisits() { activeVisits[header?.key] = personAttributes?.value ?? '--'; }); + // Add flattened observations + const allObs = visit.encounters.reduce((accumulator, encounter) => { + return [...accumulator, ...(encounter.obs || [])]; + }, []); + + activeVisits.observations = allObs.reduce((map, obs) => { + const key = obs.concept.uuid; + if (!map[key]) { + map[key] = []; + } + map[key].push({ + value: obs.value, + uuid: obs.uuid, + }); + return map; + }, {}); + return activeVisits; }; @@ -142,6 +146,176 @@ export function useActiveVisits() { }; } +export function useObsConcepts(uuids: Array): { + obsConcepts: Array | undefined; + isLoadingObsConcepts: boolean; +} { + const fetchConcept = async (uuid: string): Promise => { + try { + const response = await openmrsFetch(`${restBaseUrl}/concept/${uuid}?v=custom:(uuid,display)`); + return response?.data; + } catch (error) { + console.error(`Error fetching concept for UUID: ${uuid}`, error); + return null; + } + }; + + const { data, isLoading, error } = useSWR(uuids.length > 0 ? ['obs-concepts', uuids] : null, async () => { + const results = await Promise.all(uuids.map(fetchConcept)); + return results.filter((concept) => concept !== null); + }); + + return useMemo( + () => ({ + obsConcepts: data ?? [], + isLoadingObsConcepts: isLoading, + }), + [data, isLoading], + ); +} + +export function useActiveVisitsSorting(tableRows: Array) { + const [sortParams, setSortParams] = useState<{ + key: string; + sortDirection: 'ASC' | 'DESC' | 'NONE'; + }>({ key: 'visitStartTime', sortDirection: 'DESC' }); + + const sortRow = (cellA, cellB, { key, sortDirection }) => { + setSortParams({ key, sortDirection }); + }; + + const getSortValue = (item: any, key: string) => { + // For observation columns + if (key.startsWith('obs-')) { + const conceptUuid = key.replace('obs-', ''); + const obsValue = item?.observations?.[conceptUuid]?.[0]?.value; + + if (!obsValue) return null; + if (typeof obsValue === 'object' && obsValue.display) { + return obsValue.display.toLowerCase(); + } + return obsValue; + } + + const value = item[key]; + if (value == null) return null; + + if (key === 'visitStartTime') { + return new Date(value).getTime(); + } + + if (key === 'age' && !isNaN(value)) { + return Number(value); + } + + return String(value).toLowerCase(); + }; + + const sortedRows = useMemo(() => { + if (sortParams.sortDirection === 'NONE') { + return tableRows; + } + + return [...tableRows].sort((a, b) => { + const valueA = getSortValue(a, sortParams.key); + const valueB = getSortValue(b, sortParams.key); + + if (valueA === null && valueB === null) return 0; + if (valueA === null) return 1; + if (valueB === null) return -1; + + if (typeof valueA === 'number' && typeof valueB === 'number') { + return sortParams.sortDirection === 'DESC' ? valueB - valueA : valueA - valueB; + } + + const compareResult = String(valueA).localeCompare(String(valueB), undefined, { + numeric: true, + }); + + return sortParams.sortDirection === 'DESC' ? -compareResult : compareResult; + }); + }, [sortParams, tableRows]); + + return { + sortedRows, + sortRow, + }; +} + +export function useTableHeaders(obsConcepts: OpenmrsResource[]) { + const { t } = useTranslation(); + const config = useConfig(); + return useMemo(() => { + let headersIndex = 0; + + const headers = [ + { + id: headersIndex++, + header: t('visitStartTime', 'Visit Time'), + key: 'visitStartTime', + }, + ]; + + config?.activeVisits?.identifiers?.forEach((identifier) => { + headers.push({ + id: headersIndex++, + header: t(identifier?.header?.key, identifier?.header?.default), + key: identifier?.header?.key, + }); + }); + + if (!config?.activeVisits?.identifiers) { + headers.push({ + id: headersIndex++, + header: t('idNumber', 'ID Number'), + key: 'idNumber', + }); + } + + config?.activeVisits?.attributes?.forEach((attribute) => { + headers.push({ + id: headersIndex++, + header: t(attribute?.header?.key, attribute?.header?.default), + key: attribute?.header?.key, + }); + }); + + // Add headers for obs concepts + obsConcepts?.forEach((concept) => { + headers.push({ + id: headersIndex++, + header: concept.display, + key: `obs-${concept.uuid}`, + }); + }); + + headers.push( + { + id: headersIndex++, + header: t('name', 'Name'), + key: 'name', + }, + { + id: headersIndex++, + header: t('gender', 'Gender'), + key: 'gender', + }, + { + id: headersIndex++, + header: t('age', 'Age'), + key: 'age', + }, + { + id: headersIndex++, + header: t('visitType', 'Visit Type'), + key: 'visitType', + }, + ); + + return headers; + }, [t, config, obsConcepts]); +} + export const getOriginFromPathName = (pathname = '') => { const from = pathname.split('/'); return last(from); diff --git a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.scss b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.scss index 99752b452..92ee6dc8a 100644 --- a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.scss +++ b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.scss @@ -24,6 +24,14 @@ } .activeVisitsTable { + width: 100%; + + th, + td { + white-space: nowrap; + text-align: left; + } + tbody tr[data-parent-row] { // Don't show a bottom border on the last row so we don't end up with a double border from the activeVisitContainer &:nth-last-of-type(2) > td { @@ -154,12 +162,14 @@ html[dir='rtl'] { .activeVisitsDetailHeaderContainer { padding: layout.$spacing-04 layout.$spacing-05 layout.$spacing-04 0; } + .desktopHeading, .tabletHeading { h4 { text-align: right; } } + div[role='search'] { & :first-child { svg { @@ -167,21 +177,29 @@ html[dir='rtl'] { right: layout.$spacing-03; } } + & :last-child { right: unset; left: 0; } } + .tableContainer { + overflow-x: auto; + text-wrap: nowrap; + th > div { text-align: right; } + td { text-align: right; + .serviceColor { margin-right: 0; margin-left: layout.$spacing-03; } + button { text-align: right; } diff --git a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx index 2b5b5fbfd..38977c3bf 100644 --- a/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx +++ b/packages/esm-active-visits-app/src/active-visits-widget/active-visits.test.tsx @@ -1,41 +1,72 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; -import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; -import { mockPatient, mockSession } from '__mocks__'; -import { configSchema, type SectionDefinition } from '../config-schema'; -import { useActiveVisits } from './active-visits.resource'; +import { getDefaultsFromConfigSchema, type OpenmrsResource, useConfig } from '@openmrs/esm-framework'; +import { mockSession } from '__mocks__'; +import { type ActiveVisitsConfigSchema, configSchema } from '../config-schema'; +import { useActiveVisits, useObsConcepts } from './active-visits.resource'; import ActiveVisitsTable from './active-visits.component'; +import { type ActiveVisit, type Observation } from '../types'; const mockUseActiveVisits = jest.mocked(useActiveVisits); -const mockUseConfig = jest.mocked(useConfig); +const mockUseObsConcepts = jest.mocked(useObsConcepts); +const mockIsDesktop = jest.mocked(useObsConcepts); +const mockUseConfig = jest.mocked(useConfig); jest.mock('./active-visits.resource', () => ({ ...jest.requireActual('./active-visits.resource'), useActiveVisits: jest.fn(), + useObsConcepts: jest.fn(), })); +const mockObsConcepts: Array = [ + { uuid: '160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Sickle cell screening test' }, + { uuid: '5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Nutritional support' }, +]; + +const mockConfig: ActiveVisitsConfigSchema = { + activeVisits: { + ...getDefaultsFromConfigSchema(configSchema).activeVisits, + obs: ['160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', '5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'], + }, +}; + +const mockActiveVisits: ActiveVisit[] = [ + { + age: '20', + gender: 'male', + id: '1', + idNumber: '000001A', + location: mockSession.data.sessionLocation.uuid, + name: 'John Doe', + patientUuid: 'uuid1', + visitStartTime: '', + visitType: 'Checkup', + visitUuid: 'visit-uuid-1', + }, + { + age: '25', + gender: 'female', + id: '2', + idNumber: '000001B', + location: mockSession.data.sessionLocation.uuid, + name: 'Some One', + patientUuid: 'uuid2', + visitStartTime: '', + visitType: 'Checkup', + visitUuid: 'visit-uuid-2', + }, +]; + describe('ActiveVisitsTable', () => { beforeEach(() => { - mockUseConfig.mockReturnValue({ - ...getDefaultsFromConfigSchema(configSchema), + mockUseConfig.mockReturnValue(mockConfig); + mockUseObsConcepts.mockReturnValue({ + obsConcepts: mockObsConcepts, + isLoadingObsConcepts: false, }); - mockUseActiveVisits.mockReturnValue({ - activeVisits: [ - { - age: '20', - gender: 'male', - id: '1', - idNumber: mockPatient.uuid, - location: mockSession.data.sessionLocation.uuid, - name: 'John Doe', - patientUuid: 'uuid1', - visitStartTime: '', - visitType: 'Checkup', - visitUuid: 'visit-uuid-1', - }, - ], + activeVisits: mockActiveVisits, isLoading: false, isValidating: false, error: undefined, @@ -43,51 +74,82 @@ describe('ActiveVisitsTable', () => { }); }); - it('renders data table with active visits', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders data table with standard and observation columns', () => { + mockUseActiveVisits.mockReturnValue({ + activeVisits: mockActiveVisits.map((visit) => ({ + ...visit, + observations: { + '160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [ + { + value: { uuid: '1065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Patient is sick' }, + uuid: 'obs-uuid-1', + } as unknown as Observation, + ], + '5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [ + { + value: 'Not done', + uuid: 'obs-uuid-2', + } as unknown as Observation, + ], + }, + })), + isLoading: false, + isValidating: false, + error: undefined, + totalResults: 0, + }); + render(); - expect(screen.getByText('Visit Time')).toBeInTheDocument(); - expect(screen.getByText('ID Number')).toBeInTheDocument(); - const expectedColumnHeaders = [/Visit Time/, /ID Number/, /Name/, /Gender/, /Age/, /Visit Type/]; - expectedColumnHeaders.forEach((header) => { - expect(screen.getByRole('columnheader', { name: new RegExp(header, 'i') })).toBeInTheDocument(); + const standardColumnHeaders = [/Visit Time/, /ID Number/, /Name/, /Gender/, /Age/, /Visit Type/]; + standardColumnHeaders.forEach((header) => { + expect(screen.getByRole('columnheader', { name: header })).toBeInTheDocument(); }); - const patientNameLink = screen.getByText('John Doe'); - expect(patientNameLink).toBeInTheDocument(); - expect(patientNameLink.tagName).toBe('A'); + expect(screen.getByRole('columnheader', { name: /Sickle cell screening test/ })).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: /Nutritional support/ })).toBeInTheDocument(); + }); + + it('displays observation values correctly', () => { + mockUseActiveVisits.mockReturnValue({ + activeVisits: mockActiveVisits.map((visit) => ({ + ...visit, + observations: { + '160225AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [ + { + value: { uuid: '1065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Patient is sick' }, + uuid: 'obs-uuid-1', + } as unknown as Observation, + ], + '5484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA': [ + { + value: 'Not done', + uuid: 'obs-uuid-2', + } as unknown as Observation, + ], + }, + })), + isLoading: false, + isValidating: false, + error: undefined, + totalResults: 0, + }); + + render(); + + expect(screen.getAllByRole('cell', { name: /Patient is sick/ }).length).toBe(2); + expect(screen.getAllByRole('cell', { name: /Not done/ }).length).toBe(2); }); it('filters active visits based on search input', async () => { const user = userEvent.setup(); mockUseActiveVisits.mockReturnValue({ - activeVisits: [ - { - age: '20', - gender: 'male', - id: '1', - idNumber: '000001A', - location: mockSession.data.sessionLocation.uuid, - name: 'John Doe', - patientUuid: 'uuid1', - visitStartTime: '', - visitType: 'Checkup', - visitUuid: 'visit-uuid-1', - }, - { - age: '25', - gender: 'female', - id: '2', - idNumber: '000001B', - location: mockSession.data.sessionLocation.uuid, - name: 'Some One', - patientUuid: 'uuid2', - visitStartTime: '', - visitType: 'Checkup', - visitUuid: 'visit-uuid-2', - }, - ], + activeVisits: mockActiveVisits, isLoading: false, isValidating: false, error: undefined, @@ -151,32 +213,7 @@ describe('ActiveVisitsTable', () => { it('should display the pagination when pagination is true', () => { mockUseActiveVisits.mockReturnValue({ - activeVisits: [ - { - age: '20', - gender: 'male', - id: '1', - idNumber: '000001A', - location: mockSession.data.sessionLocation.uuid, - name: 'John Doe', - patientUuid: 'uuid1', - visitStartTime: '', - visitType: 'Checkup', - visitUuid: 'visit-uuid-1', - }, - { - age: '25', - gender: 'female', - id: '2', - idNumber: '000001B', - location: mockSession.data.sessionLocation.uuid, - name: 'Some One', - patientUuid: 'uuid2', - visitStartTime: '', - visitType: 'Checkup', - visitUuid: 'visit-uuid-2', - }, - ], + activeVisits: mockActiveVisits, isLoading: false, isValidating: false, error: undefined, diff --git a/packages/esm-active-visits-app/src/config-schema.ts b/packages/esm-active-visits-app/src/config-schema.ts index fedc9d50e..9f144e10f 100644 --- a/packages/esm-active-visits-app/src/config-schema.ts +++ b/packages/esm-active-visits-app/src/config-schema.ts @@ -1,10 +1,11 @@ import { Type } from '@openmrs/esm-framework'; -export interface SectionDefinition { +export interface ActiveVisitsConfigSchema { activeVisits: { pageSize: Number; pageSizes: Array; identifiers: Array; + obs: Array; }; } @@ -53,5 +54,14 @@ export const configSchema = { _description: 'Customizable page sizes that user can choose', _default: [10, 20, 50], }, + obs: { + _type: Type.Array, + _description: 'Array of observation concept UUIDs to be displayed on the active visits table.', + _elements: { + _type: Type.UUID, + _description: 'UUID of an observation concept.', + }, + _default: [], + }, }, }; diff --git a/packages/esm-active-visits-app/src/types/index.ts b/packages/esm-active-visits-app/src/types/index.ts index 6910c23eb..55fce7015 100644 --- a/packages/esm-active-visits-app/src/types/index.ts +++ b/packages/esm-active-visits-app/src/types/index.ts @@ -1,4 +1,4 @@ -import { type OpenmrsResource } from '@openmrs/esm-framework'; +import { type OpenmrsResource, type Visit } from '@openmrs/esm-framework'; export interface SearchedPatient { patientId: number; @@ -28,3 +28,158 @@ export interface Identifier { preferred: boolean; voided: boolean; } + +export interface Encounter { + uuid: string; + encounterDateTime: string; + encounterProviders: Array<{ + uuid: string; + display: string; + encounterRole: { + uuid: string; + display: string; + }; + provider: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; + }>; + encounterType: { + uuid: string; + display: string; + }; + obs: Array; + orders: Array; +} + +export interface EncounterProvider { + uuid: string; + display: string; + encounterRole: { + uuid: string; + display: string; + }; + provider: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; +} + +export interface Observation { + uuid: string; + concept: { + uuid: string; + display: string; + conceptClass: { + uuid: string; + display: string; + }; + }; + display: string; + groupMembers: null | Array<{ + uuid: string; + concept: { + uuid: string; + display: string; + }; + value: { + uuid: string; + display: string; + }; + }>; + value: any; + obsDatetime: string; +} + +export interface Order { + uuid: string; + dateActivated: string; + dateStopped?: Date | null; + dose: number; + dosingInstructions: string | null; + dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions'; + doseUnits: { + uuid: string; + display: string; + }; + drug: { + uuid: string; + name: string; + strength: string; + display: string; + }; + duration: number; + durationUnits: { + uuid: string; + display: string; + }; + frequency: { + uuid: string; + display: string; + }; + numRefills: number; + orderNumber: string; + orderReason: string | null; + orderReasonNonCoded: string | null; + orderer: { + uuid: string; + person: { + uuid: string; + display: string; + }; + }; + orderType: { + uuid: string; + display: string; + }; + route: { + uuid: string; + display: string; + }; + quantity: number; + quantityUnits: OpenmrsResource; +} + +export interface Note { + note: string; + provider: { + name: string; + role: string; + }; + time: string; +} + +export interface OrderItem { + order: Order; + provider: { + name: string; + role: string; + }; +} + +export interface ActiveVisit { + age: string; + id: string; + idNumber: string; + gender: string; + location: string; + name: string; + patientUuid: string; + visitStartTime: string; + visitType: string; + visitUuid: string; + observations?: Record; + [identifier: string]: string | Record; +} + +export interface VisitResponse { + results: Array; + links: Array<{ rel: 'prev' | 'next' }>; + totalCount: number; +} diff --git a/packages/esm-active-visits-app/src/visits-summary/visit-detail.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visit-detail.component.tsx index 427045e32..27c7f228f 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visit-detail.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visit-detail.component.tsx @@ -2,8 +2,9 @@ import React, { useMemo, useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { ContentSwitcher, DataTableSkeleton, Switch } from '@carbon/react'; -import { type Encounter, useVisit } from './visit.resource'; -import { formatTime, formatDatetime, parseDate } from '@openmrs/esm-framework'; +import { useVisit } from './visit.resource'; +import { type Encounter } from '../types'; +import { formatDatetime, formatTime, parseDate } from '@openmrs/esm-framework'; import EncounterList from './visits-components/encounter-list.component'; import VisitSummary from './visits-components/visit-summary.component'; import styles from './visit-detail-overview.scss'; diff --git a/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx b/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx index 0b5b32439..3103efbdf 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visit-detail.test.tsx @@ -1,21 +1,23 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; -import { formatDate } from '@openmrs/esm-framework'; import { useVisit } from './visit.resource'; import VisitDetailComponent from './visit-detail.component'; +const mockUseVisit = jest.mocked(useVisit); const defaultProps = { patientUuid: '691eed12-c0f1-11e2-94be-8c13b969e334', visitUuid: '497b8b17-54ec-4726-87ec-3c4da8cdcaeb', }; -const mockUseVisit = jest.mocked(useVisit); -jest.mock('./visit.resource'); +jest.mock('./visit.resource', () => ({ + ...jest.requireActual('./visit.resource'), + useVisit: jest.fn(), +})); describe('VisitDetail', () => { it('renders a loading spinner when data is loading', () => { - mockUseVisit.mockReturnValueOnce({ + mockUseVisit.mockReturnValue({ visit: null, error: undefined, isLoading: true, @@ -29,7 +31,8 @@ describe('VisitDetail', () => { it('renders a visit detail overview when data is available', () => { const mockVisitDate = new Date(); - mockUseVisit.mockReturnValueOnce({ + + mockUseVisit.mockReturnValue({ visit: { encounters: [], startDatetime: mockVisitDate.toISOString(), @@ -43,13 +46,14 @@ describe('VisitDetail', () => { render(); - expect(screen.getByText(/Some Visit Type/)).toBeInTheDocument(); - expect(screen.getByText(formatDate(mockVisitDate), { collapseWhitespace: false })).toBeInTheDocument(); - expect(screen.getByText('All Encounters')).toBeInTheDocument(); - expect(screen.getByText('Visit Summary')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /some visit type/i })).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: /no encounters found/i })).toBeInTheDocument(); + expect(screen.getByText(/there is no information to display here/i)).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: /all encounters/i })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: /visit summary/i })).toBeInTheDocument(); }); - it('render the Encounter Lists view when the "All Encounters" tab is clicked', async () => { + it('renders the Encounter Lists view when the "All Encounters" tab is clicked', async () => { const user = userEvent.setup(); mockUseVisit.mockReturnValue({ @@ -74,8 +78,12 @@ describe('VisitDetail', () => { render(); - await user.click(screen.getByText('All Encounters')); - expect(screen.getByTestId('encountersTable')).toBeInTheDocument(); + await user.click(screen.getByRole('tab', { name: /all encounters/i })); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: /time/i })).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: /encounter type/i })).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: /provider/i })).toBeInTheDocument(); + expect(screen.getByRole('row', { name: /12:34 pm encounter type/i })).toBeInTheDocument(); }); it('renders the Visit Summaries view when the "Visit Summary" tab is clicked', async () => { @@ -112,7 +120,12 @@ describe('VisitDetail', () => { render(); - await user.click(screen.getByText('Visit Summary')); - expect(screen.getByRole('tablist', { name: 'Visit summary tabs' })).toBeInTheDocument(); + await user.click(screen.getByRole('tab', { name: /visit summary/i })); + expect(screen.getByRole('tablist', { name: /visit summary tabs/i })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: /notes/i })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: /tests/i })).toBeInTheDocument(); + expect(screen.getByRole('tab', { name: /medications/i })).toBeInTheDocument(); + expect(screen.getByText(/no diagnoses found/i)).toBeInTheDocument(); + expect(screen.getByText(/there are no notes to display for this patient/i)).toBeInTheDocument(); }); }); diff --git a/packages/esm-active-visits-app/src/visits-summary/visit.resource.ts b/packages/esm-active-visits-app/src/visits-summary/visit.resource.ts index 74c76caac..ff98833ea 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visit.resource.ts +++ b/packages/esm-active-visits-app/src/visits-summary/visit.resource.ts @@ -1,139 +1,5 @@ import useSWR from 'swr'; -import { openmrsFetch, restBaseUrl, type OpenmrsResource, type Visit } from '@openmrs/esm-framework'; - -export interface Encounter { - uuid: string; - encounterDateTime: string; - encounterProviders: Array<{ - uuid: string; - display: string; - encounterRole: { - uuid: string; - display: string; - }; - provider: { - uuid: string; - person: { - uuid: string; - display: string; - }; - }; - }>; - encounterType: { - uuid: string; - display: string; - }; - obs: Array; - orders: Array; -} - -export interface EncounterProvider { - uuid: string; - display: string; - encounterRole: { - uuid: string; - display: string; - }; - provider: { - uuid: string; - person: { - uuid: string; - display: string; - }; - }; -} - -export interface Observation { - uuid: string; - concept: { - uuid: string; - display: string; - conceptClass: { - uuid: string; - display: string; - }; - }; - display: string; - groupMembers: null | Array<{ - uuid: string; - concept: { - uuid: string; - display: string; - }; - value: { - uuid: string; - display: string; - }; - }>; - value: any; - obsDatetime: string; -} - -export interface Order { - uuid: string; - dateActivated: string; - dateStopped?: Date | null; - dose: number; - dosingInstructions: string | null; - dosingType?: 'org.openmrs.FreeTextDosingInstructions' | 'org.openmrs.SimpleDosingInstructions'; - doseUnits: { - uuid: string; - display: string; - }; - drug: { - uuid: string; - name: string; - strength: string; - display: string; - }; - duration: number; - durationUnits: { - uuid: string; - display: string; - }; - frequency: { - uuid: string; - display: string; - }; - numRefills: number; - orderNumber: string; - orderReason: string | null; - orderReasonNonCoded: string | null; - orderer: { - uuid: string; - person: { - uuid: string; - display: string; - }; - }; - orderType: { - uuid: string; - display: string; - }; - route: { - uuid: string; - display: string; - }; - quantity: number; - quantityUnits: OpenmrsResource; -} - -export interface Note { - note: string; - provider: { - name: string; - role: string; - }; - time: string; -} - -export interface OrderItem { - order: Order; - provider: { - name: string; - role: string; - }; -} +import { openmrsFetch, restBaseUrl, type Visit } from '@openmrs/esm-framework'; export function useVisit(visitUuid: string) { const customRepresentation = diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-list.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-list.component.tsx index 4d2b42a88..7479eb66f 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-list.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-list.component.tsx @@ -1,21 +1,21 @@ -import React, { useEffect, useState, useMemo, useRef } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { DataTable, - TableContainer, Table, - TableHead, - TableExpandHeader, - TableRow, - TableHeader, TableBody, - TableExpandRow, TableCell, + TableContainer, TableExpandedRow, + TableExpandHeader, + TableExpandRow, + TableHead, + TableHeader, + TableRow, } from '@carbon/react'; -import { useLayoutType, isDesktop } from '@openmrs/esm-framework'; -import { type Observation } from '../visit.resource'; +import { isDesktop, useLayoutType } from '@openmrs/esm-framework'; +import { type Observation } from '../../types'; import EncounterObservations from './encounter-observations.component'; import styles from '../visit-detail-overview.scss'; diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.component.tsx index 747951188..efe158eac 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.component.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { SkeletonText } from '@carbon/react'; -import { type Observation } from '../visit.resource'; +import { type Observation } from '../../types'; import styles from '../visit-detail-overview.scss'; interface EncounterObservationsProps { diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.test.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.test.tsx index ff58eb842..4cd4a913e 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.test.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/encounter-observations.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { type Observation } from '../visit.resource'; +import { type Observation } from '../../types'; import EncounterObservations from './encounter-observations.component'; describe('EncounterObservations', () => { diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/medications-summary.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/medications-summary.component.tsx index 56c253396..7f96ed284 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/medications-summary.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/medications-summary.component.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import capitalize from 'lodash-es/capitalize'; import { useTranslation } from 'react-i18next'; import { formatDate, formatTime, parseDate } from '@openmrs/esm-framework'; -import { type OrderItem, getDosage } from '../visit.resource'; +import { type OrderItem } from '../../types'; import styles from '../visit-detail-overview.scss'; interface MedicationSummaryProps { diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/notes-summary.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/notes-summary.component.tsx index a0b0b3796..2543bbb5d 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/notes-summary.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/notes-summary.component.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { Layer, Tile } from '@carbon/react'; import { isDesktop, useLayoutType } from '@openmrs/esm-framework'; -import type { Note } from '../visit.resource'; +import type { Note } from '../../types'; import { EmptyDataIllustration } from '../../active-visits-widget/empty-data-illustration.component'; import styles from '../visit-detail-overview.scss'; diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/tests-summary.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/tests-summary.component.tsx index 43434cf3c..70779f655 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/tests-summary.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/tests-summary.component.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; import { ExtensionSlot } from '@openmrs/esm-framework'; -import { type Encounter } from '../visit.resource'; +import { type Encounter } from '../../types'; import styles from '../visit-detail-overview.scss'; const TestsSummary = ({ patientUuid, encounters }: { patientUuid: string; encounters: Array }) => { diff --git a/packages/esm-active-visits-app/src/visits-summary/visits-components/visit-summary.component.tsx b/packages/esm-active-visits-app/src/visits-summary/visits-components/visit-summary.component.tsx index 0cb59b83c..c5cc7854f 100644 --- a/packages/esm-active-visits-app/src/visits-summary/visits-components/visit-summary.component.tsx +++ b/packages/esm-active-visits-app/src/visits-summary/visits-components/visit-summary.component.tsx @@ -1,12 +1,12 @@ -import React, { useState, useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; -import { Tab, Tabs, TabList, TabPanel, TabPanels, Tag } from '@carbon/react'; -import { type OpenmrsResource, formatTime, parseDate } from '@openmrs/esm-framework'; +import { Tab, TabList, TabPanel, TabPanels, Tabs, Tag } from '@carbon/react'; +import { formatTime, type OpenmrsResource, parseDate } from '@openmrs/esm-framework'; import NotesSummary from './notes-summary.component'; import MedicationSummary from './medications-summary.component'; import TestsSummary from './tests-summary.component'; -import { type Order, type Encounter, type Note, type Observation, type OrderItem } from '../visit.resource'; +import type { Encounter, Note, Observation, Order, OrderItem } from '../../types'; import styles from '../visit-detail-overview.scss'; interface DiagnosisItem { diff --git a/packages/esm-active-visits-app/translations/es.json b/packages/esm-active-visits-app/translations/es.json index 08ec3ff89..616651dc5 100644 --- a/packages/esm-active-visits-app/translations/es.json +++ b/packages/esm-active-visits-app/translations/es.json @@ -1,5 +1,5 @@ { - "activeVisits": "Visitas Activas", + "activeVisits": "Consultas Activas", "age": "Edad", "allEncounters": "Todos Los Encuentros", "checkFilters": "Compruebe los filtros anteriores", @@ -9,29 +9,29 @@ "endDate": "Fecha de finalización", "filterTable": "Filtrar la tabla", "gender": "Género", - "idNumber": "Número ID", + "idNumber": "Número de ID", "indication": "Indicación", - "medications": "Medicaciones", + "medications": "Medicamentos", "name": "Nombre", - "noActiveVisitsForLocation": "No hay visitas activas para esta ubicación.", + "noActiveVisitsForLocation": "No hay visitas activas para mostrar para esta ubicación.", "noDiagnosesFound": "No se encontraron diagnósticos", "noEncountersFound": "No se encontraron encuentros", "noMedicationsFound": "No se encontraron medicamentos", "noNotesToShowForPatient": "No hay notas para mostrar para este paciente", "noObservationsFound": "No se encontraron observaciones", "notes": "Notas", - "noVisitsToDisplay": "No hay visitas para mostrar", + "noVisitsToDisplay": "No hay consultas para mostrar", "orderDurationAndUnit": "por {{duration}} {{durationUnit}}", "orderIndefiniteDuration": "Duración indefinida", "patients": "Pacientes", - "provider": "Proveedor", + "provider": "Personal de la Salud", "quantity": "Cantidad", - "refills": "Reposiciones", + "refills": "Reabastecimientos", "tests": "Pruebas", "thereIsNoInformationToDisplayHere": "No hay información para mostrar aquí", "time": "Hora", - "totalVisits": "Total de Visitas Hoy", - "visitStartTime": "Tiempo de Visita", - "visitSummary": "Resumen de Visita", - "visitType": "Tipo de Visita" + "totalVisits": "Total de Consultas Hoy", + "visitStartTime": "Tiempo de la Consulta", + "visitSummary": "Resumen de la Consulta", + "visitType": "Tipo de Consulta" } diff --git a/packages/esm-active-visits-app/translations/ne.json b/packages/esm-active-visits-app/translations/ne.json new file mode 100644 index 000000000..2016fc430 --- /dev/null +++ b/packages/esm-active-visits-app/translations/ne.json @@ -0,0 +1,37 @@ +{ + "activeVisits": "Active Visits", + "age": "Age", + "allEncounters": "All Encounters", + "checkFilters": "Check the filters above", + "diagnoses": "Diagnoses", + "dose": "Dose", + "encounterType": "Encounter Type", + "endDate": "End date", + "filterTable": "Filter table", + "gender": "Gender", + "idNumber": "ID Number", + "indication": "Indication", + "medications": "Medications", + "name": "Name", + "noActiveVisitsForLocation": "There are no active visits to display for this location.", + "noDiagnosesFound": "No diagnoses found", + "noEncountersFound": "No encounters found", + "noMedicationsFound": "No medications found", + "noNotesToShowForPatient": "There are no notes to display for this patient", + "noObservationsFound": "No observations found", + "notes": "Notes", + "noVisitsToDisplay": "No visits to display", + "orderDurationAndUnit": "for {{duration}} {{durationUnit}}", + "orderIndefiniteDuration": "Indefinite duration", + "patients": "Patients", + "provider": "Provider", + "quantity": "Quantity", + "refills": "Refills", + "tests": "Tests", + "thereIsNoInformationToDisplayHere": "There is no information to display here", + "time": "Time", + "totalVisits": "Total Visits Today", + "visitStartTime": "Visit Time", + "visitSummary": "Visit Summary", + "visitType": "Visit Type" +} diff --git a/packages/esm-active-visits-app/translations/pt.json b/packages/esm-active-visits-app/translations/pt.json index 8e0c99edc..d717f9bf7 100644 --- a/packages/esm-active-visits-app/translations/pt.json +++ b/packages/esm-active-visits-app/translations/pt.json @@ -1,34 +1,34 @@ { "activeVisits": "Visitas Ativas", "age": "Idade", - "allEncounters": "Todas Consultas", + "allEncounters": "Todas as consultas", "checkFilters": "Verifique os filtros acima", "diagnoses": "Diagnósticos", "dose": "Dose", "encounterType": "Tipo de Consulta", "endDate": "Data Final", - "filterTable": "Tabela de filtro", + "filterTable": "Filtrar tabela", "gender": "Sexo", "idNumber": "Número de Identificação", "indication": "Indicação", "medications": "Medicamentos", "name": "Nome", - "noActiveVisitsForLocation": "Não há visitas ativas por visualizar para este local.", + "noActiveVisitsForLocation": "Não há visitas ativas para visualizar neste local.", "noDiagnosesFound": "Nenhum diagnóstico encontrado", "noEncountersFound": "Nenhuma consulta encontrada", "noMedicationsFound": "Nenhuma medicação encontrada", - "noNotesToShowForPatient": "Não há notas por mostrar para este utente", + "noNotesToShowForPatient": "Não há notas para esse paciente", "noObservationsFound": "Nenhuma observação encontrada", "notes": "Notas", "noVisitsToDisplay": "Nenhuma visita para mostrar", "orderDurationAndUnit": "para {{duration}} {{durationUnit}}", "orderIndefiniteDuration": "Duração indefinida", - "patients": "Utentes", + "patients": "Pacientes", "provider": "Provedor", "quantity": "Quantidade", - "refills": "Repor", + "refills": "Recargas", "tests": "Testes", - "thereIsNoInformationToDisplayHere": "Sem informações por mostrar aqui", + "thereIsNoInformationToDisplayHere": "Sem informações para mostrar aqui", "time": "Tempo", "totalVisits": "Total de visitas hoje", "visitStartTime": "Hora da Visita", diff --git a/packages/esm-active-visits-app/translations/zh.json b/packages/esm-active-visits-app/translations/zh.json index c98732b7c..b876984f6 100644 --- a/packages/esm-active-visits-app/translations/zh.json +++ b/packages/esm-active-visits-app/translations/zh.json @@ -9,7 +9,7 @@ "endDate": "结束日期", "filterTable": "筛选表格", "gender": "性别", - "idNumber": "ID", + "idNumber": "ID Number", "indication": "适应症", "medications": "药物", "name": "姓名", diff --git a/packages/esm-appointments-app/package.json b/packages/esm-appointments-app/package.json index 2c67163be..c91ba8a0e 100644 --- a/packages/esm-appointments-app/package.json +++ b/packages/esm-appointments-app/package.json @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "formik": "^2.2.9", "lodash-es": "^4.17.15", "yup": "^0.32.11" diff --git a/packages/esm-appointments-app/src/appointments.component.tsx b/packages/esm-appointments-app/src/appointments.component.tsx index 1ca3ac6c2..b9f8e4868 100644 --- a/packages/esm-appointments-app/src/appointments.component.tsx +++ b/packages/esm-appointments-app/src/appointments.component.tsx @@ -1,19 +1,19 @@ import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { omrsDateFormat } from './constants'; import AppointmentTabs from './appointments/appointment-tabs.component'; import AppointmentsHeader from './header/appointments-header.component'; import AppointmentMetrics from './metrics/appointments-metrics.component'; -import { useParams } from 'react-router-dom'; import SelectedDateContext from './hooks/selectedDateContext'; -import { omrsDateFormat } from './constants'; const Appointments: React.FC = () => { const { t } = useTranslation(); - const [appointmentServiceType, setAppointmentServiceType] = useState(''); - const [selectedDate, setSelectedDate] = useState(dayjs().startOf('day').format(omrsDateFormat)); + const [appointmentServiceType, setAppointmentServiceType] = useState(''); + const [selectedDate, setSelectedDate] = useState(dayjs().startOf('day').format(omrsDateFormat)); - let params = useParams(); + const params = useParams(); useEffect(() => { if (params.date) { @@ -30,9 +30,9 @@ const Appointments: React.FC = () => { return ( diff --git a/packages/esm-appointments-app/src/appointments.test.tsx b/packages/esm-appointments-app/src/appointments.test.tsx index 972843323..ce43724c6 100644 --- a/packages/esm-appointments-app/src/appointments.test.tsx +++ b/packages/esm-appointments-app/src/appointments.test.tsx @@ -2,14 +2,24 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import Appointments from './appointments.component'; +// TODO: Tweak the ExtensionSlot stub in the framework to not return a function. Functions are not valid React children. describe('Appointments', () => { it('renders the appointments dashboard', async () => { render(); await screen.findByText(/^appointments$/i); + expect(screen.getByRole('button', { name: /appointments calendar/i })).toBeInTheDocument(); expect(screen.getByPlaceholderText(/dd-mmm-yyyy/i)).toBeInTheDocument(); - expect(screen.getByRole('combobox', { name: /view/i })).toBeInTheDocument(); + expect( + screen.getByRole('combobox', { + name: /select service type/i, + }), + ).toBeInTheDocument(); + expect(screen.getByRole('listbox', { name: /view/i })).toBeInTheDocument(); expect(screen.getByText(/appointment metrics/i)).toBeInTheDocument(); + expect(screen.getByText(/scheduled appointments/i)).toBeInTheDocument(); + expect(screen.getByText(/highest volume service/i)).toBeInTheDocument(); + expect(screen.getByText(/providers booked/i)).toBeInTheDocument(); }); }); diff --git a/packages/esm-appointments-app/src/appointments/appointment-tabs.component.tsx b/packages/esm-appointments-app/src/appointments/appointment-tabs.component.tsx index a59b453ef..a8f566476 100644 --- a/packages/esm-appointments-app/src/appointments/appointment-tabs.component.tsx +++ b/packages/esm-appointments-app/src/appointments/appointment-tabs.component.tsx @@ -13,8 +13,8 @@ interface AppointmentTabsProps { const AppointmentTabs: React.FC = ({ appointmentServiceType }) => { const { t } = useTranslation(); - const [activeTabIndex, setActiveTabIndex] = useState(0); const { showUnscheduledAppointmentsTab } = useConfig(); + const [activeTabIndex, setActiveTabIndex] = useState(0); const handleTabChange = ({ selectedIndex }: { selectedIndex: number }) => { setActiveTabIndex(selectedIndex); diff --git a/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx b/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx index 1a5e8a6b0..01d8a5ef5 100644 --- a/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx +++ b/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx @@ -44,7 +44,6 @@ import { type ConfigObject } from '../../config-schema'; import { getPageSizes, useAppointmentSearchResults } from '../utils'; import AppointmentActions from './appointments-actions.component'; import AppointmentDetails from '../details/appointment-details.component'; -import PatientSearch from '../../patient-search/patient-search.component'; import styles from './appointments-table.scss'; dayjs.extend(utc); diff --git a/packages/esm-appointments-app/src/appointments/common-components/appointments-table.test.tsx b/packages/esm-appointments-app/src/appointments/common-components/appointments-table.test.tsx index 65739d604..d0e9c4321 100644 --- a/packages/esm-appointments-app/src/appointments/common-components/appointments-table.test.tsx +++ b/packages/esm-appointments-app/src/appointments/common-components/appointments-table.test.tsx @@ -61,11 +61,15 @@ const mockAppointments = [ }, ] as unknown as Array; -const mockExportAppointmentsToSpreadsheet = jest.mocked(exportAppointmentsToSpreadsheet); const mockUseConfig = jest.mocked(useConfig); +const mockExportAppointmentsToSpreadsheet = jest.mocked(exportAppointmentsToSpreadsheet); -jest.mock('../../helpers/excel'); -jest.mock('../../hooks/useOverlay'); +jest.mock('../../helpers/excel', () => { + return { + ...jest.requireActual('../../helpers/excel'), + exportAppointmentsToSpreadsheet: jest.fn(), + }; +}); describe('AppointmentsTable', () => { beforeEach(() => { diff --git a/packages/esm-appointments-app/src/appointments/common-components/checkin-button.component.tsx b/packages/esm-appointments-app/src/appointments/common-components/checkin-button.component.tsx index 85ac5c605..173428f77 100644 --- a/packages/esm-appointments-app/src/appointments/common-components/checkin-button.component.tsx +++ b/packages/esm-appointments-app/src/appointments/common-components/checkin-button.component.tsx @@ -31,7 +31,11 @@ const CheckInButton: React.FC = ({ appointment, patientUuid to: checkInButton.customUrl, templateParams: { patientUuid: appointment.patient.uuid, appointmentUuid: appointment.uuid }, }) - : launchWorkspace('start-visit-workspace-form', { patientUuid: patientUuid, showPatientHeader: true }) + : launchWorkspace('start-visit-workspace-form', { + patientUuid: patientUuid, + showPatientHeader: true, + openedFrom: 'appointments-check-in', + }) }> {t('checkIn', 'Check in')} diff --git a/packages/esm-appointments-app/src/appointments/common-components/end-appointment.modal.tsx b/packages/esm-appointments-app/src/appointments/common-components/end-appointment.modal.tsx index 0325a4ab4..25e84d049 100644 --- a/packages/esm-appointments-app/src/appointments/common-components/end-appointment.modal.tsx +++ b/packages/esm-appointments-app/src/appointments/common-components/end-appointment.modal.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework'; import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; +import { showSnackbar, updateVisit, useVisit } from '@openmrs/esm-framework'; import { changeAppointmentStatus } from '../../patient-appointments/patient-appointments.resource'; import { useMutateAppointments } from '../../form/appointments-form.resource'; @@ -68,7 +68,7 @@ const EndAppointmentModal: React.FC = ({ patientUuid, .finally(() => { closeModal(); }); - }, [activeVisit, mutate, mutateAppointments, closeModal, patientUuid, appointmentUuid]); + }, [activeVisit, appointmentUuid, closeModal, mutate, mutateAppointments, t]); return (
diff --git a/packages/esm-appointments-app/src/appointments/scheduled/scheduled-appointments.component.tsx b/packages/esm-appointments-app/src/appointments/scheduled/scheduled-appointments.component.tsx index 553d1376a..a40795d2c 100644 --- a/packages/esm-appointments-app/src/appointments/scheduled/scheduled-appointments.component.tsx +++ b/packages/esm-appointments-app/src/appointments/scheduled/scheduled-appointments.component.tsx @@ -35,7 +35,7 @@ const ScheduledAppointments: React.FC = ({ appointme // t('checkedIn', 'Checked in'); // t('expected', 'Expected'); - const [currentTab, setCurrentTab] = useState(null); + const [currentTab, setCurrentTab] = useState(null); const [dateType, setDateType] = useState('today'); const scheduledAppointmentPanels = useConnectedExtensions(scheduledAppointmentsPanelsSlot); const { allowedExtensions, showExtension, hideExtension } = useAllowedExtensions(); diff --git a/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.component.tsx b/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.component.tsx index 718e20465..f126171fd 100644 --- a/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.component.tsx +++ b/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.component.tsx @@ -1,20 +1,20 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { + Button, DataTable, - TableContainer, + DataTableSkeleton, + Pagination, Table, + TableBody, + TableCell, + TableContainer, TableHead, TableHeader, TableRow, - TableBody, - TableCell, TableToolbar, TableToolbarContent, TableToolbarSearch, - Pagination, - DataTableSkeleton, - Button, } from '@carbon/react'; import { Download } from '@carbon/react/icons'; import { ConfigurableLink, useConfig, usePagination } from '@openmrs/esm-framework'; diff --git a/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.test.tsx b/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.test.tsx index bb8e01173..6e5449f23 100644 --- a/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.test.tsx +++ b/packages/esm-appointments-app/src/appointments/unscheduled/unscheduled-appointments.test.tsx @@ -3,18 +3,28 @@ import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; import { type ConfigObject, configSchema } from '../../config-schema'; +import { exportUnscheduledAppointmentsToSpreadsheet } from '../../helpers/excel'; import { getByTextWithMarkup } from 'tools'; import { useUnscheduledAppointments } from '../../hooks/useUnscheduledAppointments'; -import { exportUnscheduledAppointmentsToSpreadsheet } from '../../helpers/excel'; import UnscheduledAppointments from './unscheduled-appointments.component'; const mockExportUnscheduledAppointmentsToSpreadsheet = jest.mocked(exportUnscheduledAppointmentsToSpreadsheet); const mockUseUnscheduledAppointments = jest.mocked(useUnscheduledAppointments); const mockUseConfig = jest.mocked(useConfig); -jest.mock('../../helpers/excel'); -jest.mock('../../hooks/useOverlay'); -jest.mock('../../hooks/useUnscheduledAppointments'); +jest.mock('../../helpers/excel', () => { + return { + ...jest.requireActual('../../helpers/excel'), + exportUnscheduledAppointmentsToSpreadsheet: jest.fn(), + }; +}); + +jest.mock('../../hooks/useUnscheduledAppointments', () => { + return { + ...jest.requireActual('../../hooks/useUnscheduledAppointments'), + useUnscheduledAppointments: jest.fn(), + }; +}); const mockUnscheduledAppointments = [ { @@ -45,6 +55,11 @@ describe('UnscheduledAppointments', () => { ...getDefaultsFromConfigSchema(configSchema), customPatientChartUrl: 'someUrl', }); + mockUseUnscheduledAppointments.mockReturnValue({ + isLoading: false, + data: mockUnscheduledAppointments, + error: null, + }); }); it('renders the component correctly', async () => { diff --git a/packages/esm-appointments-app/src/calendar/appointments-calendar-view.component.tsx b/packages/esm-appointments-app/src/calendar/appointments-calendar-view.component.tsx index af6901140..9c2799e60 100644 --- a/packages/esm-appointments-app/src/calendar/appointments-calendar-view.component.tsx +++ b/packages/esm-appointments-app/src/calendar/appointments-calendar-view.component.tsx @@ -2,16 +2,16 @@ import React, { useEffect, useState } from 'react'; import dayjs from 'dayjs'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { omrsDateFormat } from '../constants'; import { useAppointmentsCalendar } from '../hooks/useAppointmentsCalendar'; import AppointmentsHeader from '../header/appointments-header.component'; import CalendarHeader from './header/calendar-header.component'; import MonthlyCalendarView from './monthly/monthly-calendar-view.component'; import SelectedDateContext from '../hooks/selectedDateContext'; -import { omrsDateFormat } from '../constants'; const AppointmentsCalendarView: React.FC = () => { const { t } = useTranslation(); - const [selectedDate, setSelectedDate] = useState(dayjs().startOf('day').format(omrsDateFormat)); + const [selectedDate, setSelectedDate] = useState(dayjs().startOf('day').format(omrsDateFormat)); const { calendarEvents } = useAppointmentsCalendar(dayjs(selectedDate).toISOString(), 'monthly'); let params = useParams(); diff --git a/packages/esm-appointments-app/src/calendar/monthly/monthly-workload-view.component.tsx b/packages/esm-appointments-app/src/calendar/monthly/monthly-workload-view.component.tsx index 044b0da07..f06b47cbd 100644 --- a/packages/esm-appointments-app/src/calendar/monthly/monthly-workload-view.component.tsx +++ b/packages/esm-appointments-app/src/calendar/monthly/monthly-workload-view.component.tsx @@ -25,7 +25,7 @@ const MonthlyWorkloadView: React.FC = ({ dateTime, eve events?.find( (event) => dayjs(event.appointmentDate)?.format('YYYY-MM-DD') === dayjs(dateTime)?.format('YYYY-MM-DD'), ), - [events], + [dateTime, events], ); const visibleServices = useMemo(() => { @@ -34,7 +34,7 @@ const MonthlyWorkloadView: React.FC = ({ dateTime, eve return currentData.services.slice(0, layout === 'small-desktop' ? 2 : 4); } return []; - }, [currentData, showAllServices, layout, currentData]); + }, [currentData, showAllServices, layout]); const hasHiddenServices = useMemo(() => { if (currentData?.services) { @@ -42,7 +42,7 @@ const MonthlyWorkloadView: React.FC = ({ dateTime, eve return layout === 'small-desktop' ? currentData.services.length > 2 : currentData.services.length > 4; } return false; - }, [layout, currentData, currentData]); + }, [currentData?.services, layout, showAllServices]); const navigateToAppointmentsByDate = (serviceUuid: string) => { navigate({ to: `${spaHomePage}/appointments/${dayjs(dateTime).format('YYYY-MM-DD')}/${serviceUuid}` }); @@ -61,8 +61,8 @@ const MonthlyWorkloadView: React.FC = ({ dateTime, eve }, )}> {isSameMonth(dateTime, dayjs(selectedDate)) && ( -

-

+
+ {currentData?.services ? (
@@ -72,7 +72,7 @@ const MonthlyWorkloadView: React.FC = ({ dateTime, eve
)} {dateTime.format('D')} -
+ {currentData?.services && (
{visibleServices.map(({ serviceName, serviceUuid, count }, i) => ( @@ -100,7 +100,7 @@ const MonthlyWorkloadView: React.FC = ({ dateTime, eve )}
)} -

+
)}
); diff --git a/packages/esm-appointments-app/src/config-schema.ts b/packages/esm-appointments-app/src/config-schema.ts index 2cc9de9eb..c74e49599 100644 --- a/packages/esm-appointments-app/src/config-schema.ts +++ b/packages/esm-appointments-app/src/config-schema.ts @@ -1,22 +1,11 @@ -import { Type, restBaseUrl, validators } from '@openmrs/esm-framework'; -import { spaHomePage } from './constants'; +import { Type, validators } from '@openmrs/esm-framework'; export const configSchema = { - includePhoneNumberInExcelSpreadsheet: { - _type: Type.Boolean, - _description: 'Whether to include phone numbers in the exported Excel spreadsheet', - _default: false, - }, allowAllDayAppointments: { _type: Type.Boolean, _description: 'Whether to allow scheduling of all-day appointments (vs appointments with start time and end time)', _default: false, }, - appointmentsBaseUrl: { - _type: Type.String, - _description: 'Configurable alternative URL for the Appointments UI. Eg, the Bahmni Appointments UI URL', - _default: `${spaHomePage}`, - }, appointmentStatuses: { _type: Type.Array, _description: 'Configurable appointment status (status of appointments)', @@ -27,11 +16,6 @@ export const configSchema = { _description: 'Configurable appointment types (types of appointments)', _default: ['Scheduled'], }, - bahmniAppointmentsUiBaseUrl: { - _type: Type.String, - _description: 'Configurable base URL that points to the Bahmni Appointments UI', - _default: '/appointments', - }, checkInButton: { enabled: { _type: Type.Boolean, @@ -61,13 +45,6 @@ export const configSchema = { _default: '', }, }, - concepts: { - visitQueueNumberAttributeUuid: { - _type: Type.String, - _description: 'The UUID of the visit attribute that contains the visit queue number.', - _default: 'c61ce16f-272a-41e7-9924-4c555d0932c5', - }, - }, customPatientChartUrl: { _type: Type.String, _description: `Template URL that will be used when clicking on the patient name in the queues table. @@ -76,57 +53,26 @@ export const configSchema = { _default: '${openmrsSpaBase}/patient/${patientUuid}/chart', _validators: [validators.isUrlWithTemplateParameters(['patientUuid'])], }, - daysOfTheWeek: { - _type: Type.Array, - _description: 'Configurable days of the week', - _default: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - }, - defaultFacilityUrl: { - _type: Type.String, - _default: `${restBaseUrl}/kenyaemr/default-facility`, - _description: 'Custom URL to load default facility if it is not in the session', - }, - fullViewPrivilege: { - _type: Type.String, - _description: 'Name of the privilege to display the full view of the Appointments dashboard widget.', - _default: "Today's Appointments Widget: Display Full View", - }, - hiddenFormFields: { - _type: Type.Array, - _description: 'Array of form controls to be hidden on form load', - _default: [], + includePhoneNumberInExcelSpreadsheet: { + _type: Type.Boolean, + _description: 'Whether to include phone numbers in the exported Excel spreadsheet', + _default: false, }, patientIdentifierType: { _type: Type.String, _description: 'The name of the patient identifier type to be used for the patient identifier field', _default: '', }, - showServiceQueueFields: { - _type: Type.Boolean, - _description: 'Whether start visit form should display service queue fields`', - _default: false, - }, showUnscheduledAppointmentsTab: { _type: Type.Boolean, _description: 'Whether to show the Unscheduled Appointments tab. Note that configuring this to true requires a custom unscheduledAppointment endpoint not currently available', _default: false, }, - useBahmniAppointmentsUI: { - _type: Type.Boolean, - _description: 'Open links in Bahmni Appointments UI instead of O3 UI', - _default: false, - }, - useFullViewPrivilege: { - _type: Type.Boolean, - _description: "If set to 'false', will always display the full view, disregarding any privilege", - _default: false, - }, }; export interface ConfigObject { allowAllDayAppointments: boolean; - appointmentComments: Array; appointmentStatuses: Array; appointmentTypes: Array; checkInButton: { @@ -138,18 +84,8 @@ export interface ConfigObject { enabled: boolean; customUrl: string; }; - concepts: { - visitQueueNumberAttributeUuid: string; - }; customPatientChartUrl: string; - daysOfTheWeek: Array; - defaultFacilityUrl: string; - fullViewPrivilege: string; - hiddenFormFields: Array; + includePhoneNumberInExcelSpreadsheet: boolean; patientIdentifierType: string; - showServiceQueueFields: boolean; showUnscheduledAppointmentsTab: boolean; - useBahmniAppointmentsUI: boolean; - useFullViewPrivilege: boolean; - includePhoneNumberInExcelSpreadsheet: boolean; } diff --git a/packages/esm-appointments-app/src/form/appointments-form.component.tsx b/packages/esm-appointments-app/src/form/appointments-form.component.tsx index 611d0f7ed..501b682ba 100644 --- a/packages/esm-appointments-app/src/form/appointments-form.component.tsx +++ b/packages/esm-appointments-app/src/form/appointments-form.component.tsx @@ -1,3 +1,7 @@ +import React, { useContext, useEffect, useState } from 'react'; +import dayjs from 'dayjs'; +import { Controller, useController, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { Button, ButtonSet, @@ -31,12 +35,15 @@ import { type DefaultWorkspaceProps, type FetchResponse, } from '@openmrs/esm-framework'; -import dayjs from 'dayjs'; -import React, { useContext, useEffect, useState } from 'react'; -import { Controller, useController, useForm } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; import { z } from 'zod'; import { type ConfigObject } from '../config-schema'; +import { + checkAppointmentConflict, + saveAppointment, + saveRecurringAppointments, + useAppointmentService, + useMutateAppointments, +} from './appointments-form.resource'; import { appointmentLocationTagName, dateFormat, @@ -45,25 +52,12 @@ import { moduleName, weekDays, } from '../constants'; -import SelectedDateContext from '../hooks/selectedDateContext'; import { useProviders } from '../hooks/useProviders'; import type { Appointment, AppointmentPayload, RecurringPattern } from '../types'; +import SelectedDateContext from '../hooks/selectedDateContext'; import Workload from '../workload/workload.component'; -import { - checkAppointmentConflict, - saveAppointment, - saveRecurringAppointments, - useAppointmentService, - useMutateAppointments, -} from './appointments-form.resource'; import styles from './appointments-form.scss'; -const time12HourFormatRegexPattern = '^(1[0-2]|0?[1-9]):[0-5][0-9]$'; - -function isValidTime(timeStr) { - return timeStr.match(new RegExp(time12HourFormatRegexPattern)); -} - interface AppointmentsFormProps { appointment?: Appointment; recurringPattern?: RecurringPattern; @@ -71,6 +65,10 @@ interface AppointmentsFormProps { context: string; } +const time12HourFormatRegexPattern = '^(1[0-2]|0?[1-9]):[0-5][0-9]$'; + +const isValidTime = (timeStr: string) => timeStr.match(new RegExp(time12HourFormatRegexPattern)); + const AppointmentsForm: React.FC = ({ appointment, recurringPattern, @@ -127,7 +125,7 @@ const AppointmentsForm: React.FC const defaultDuration = appointment?.startDateTime && appointment?.endDateTime ? dayjs(appointment.endDateTime).diff(dayjs(appointment.startDateTime), 'minutes') - : null; + : undefined; // t('durationErrorMessage', 'Duration should be greater than zero') const appointmentsFormSchema = z @@ -156,7 +154,9 @@ const AppointmentsForm: React.FC recurringPatternPeriod: z.number(), recurringPatternDaysOfWeek: z.array(z.string()), selectedDaysOfWeekText: z.string().optional(), - startTime: z.string().refine((value) => isValidTime(value)), + startTime: z.string().refine((value) => isValidTime(value), { + message: translateFrom(moduleName, 'invalidTime', 'Invalid time'), + }), timeFormat: z.enum(['AM', 'PM']), appointmentDateTime: z.object({ startDate: z.date(), @@ -220,7 +220,7 @@ const AppointmentsForm: React.FC watch, handleSubmit, reset, - formState: { isDirty }, + formState: { errors, isDirty }, } = useForm({ mode: 'all', resolver: zodResolver(appointmentsFormSchema), @@ -251,7 +251,7 @@ const AppointmentsForm: React.FC }, }); - useEffect(() => setValue('formIsRecurringAppointment', isRecurringAppointment), [isRecurringAppointment]); + useEffect(() => setValue('formIsRecurringAppointment', isRecurringAppointment), [isRecurringAppointment, setValue]); // Retrive ref callback for appointmentDateTime (startDate & recurringPatternEndDate) const { @@ -277,14 +277,14 @@ const AppointmentsForm: React.FC return; } promptBeforeClosing(() => isDirty); - }, [isDirty, promptBeforeClosing, isSuccessful]); + }, [isDirty, promptBeforeClosing, isSuccessful, reset, closeWorkspace]); const handleWorkloadDateChange = (date: Date) => { const appointmentDate = getValues('appointmentDateTime'); setValue('appointmentDateTime', { ...appointmentDate, startDate: date }); }; - const handleMultiselectChange = (e) => { + const handleSelectChange = (e) => { setValue( 'selectedDaysOfWeekText', (() => { @@ -354,7 +354,9 @@ const AppointmentsForm: React.FC appointmentRequest: appointmentPayload, recurringPattern: constructRecurringPattern(data), }; + const abortController = new AbortController(); + (isRecurringAppointment ? saveRecurringAppointments(recurringAppointmentPayload, abortController) : saveAppointment(appointmentPayload, abortController) @@ -458,15 +460,13 @@ const AppointmentsForm: React.FC }; }; - const onError = (error) => console.error(error); - if (isLoading) return ( ); return ( -
+ {patient && ( ( + render={({ field: { onChange, value, onBlur, ref } }) => ( { if (context === 'creating') { setValue( @@ -534,11 +537,8 @@ const AppointmentsForm: React.FC } onChange(event); }} - onBlur={onBlur} - value={value} ref={ref} - invalid={!!fieldState?.error?.message} - invalidText={fieldState?.error?.message}> + value={value}> {services?.length > 0 && services.map((service) => ( @@ -557,17 +557,17 @@ const AppointmentsForm: React.FC ( + render={({ field: { onBlur, onChange, value, ref } }) => ( ( + render={({ field: { onChange, value, ref } }) => (
onChange(date)} - invalid={!!fieldState?.error?.message} - invalidText={fieldState?.error?.message}> + value={value}> ref={ref} /> - {fieldState?.error?.message &&
{fieldState.error.message}
}
)} /> @@ -900,7 +899,7 @@ const AppointmentsForm: React.FC ); }; -function TimeAndDuration({ isTablet, t, watch, control, services }) { +function TimeAndDuration({ t, watch, control, services, errors }) { const defaultDuration = services?.find((service) => service.name === watch('selectedService'))?.durationMins || null; return ( @@ -913,12 +912,14 @@ function TimeAndDuration({ isTablet, t, watch, control, services }) { onChange(event.target.value)} - value={value} + invalid={!!errors?.startTime} + invalidText={errors?.startTime?.message} + labelText={t('time', 'Time')} + onChange={(event) => { + onChange(event.target.value); + }} style={{ marginLeft: '0.125rem', flex: 'none' }} - labelText={t('time', 'Time')}> + value={value}> ( + render={({ field: { onChange, onBlur, value, ref } }) => ( onChange(Number(event.target.value))} - value={value} ref={ref} - invalid={fieldState?.error?.message} + size="md" + value={value} /> )} /> diff --git a/packages/esm-appointments-app/src/form/appointments-form.test.tsx b/packages/esm-appointments-app/src/form/appointments-form.test.tsx index 033bb185e..1e4c3f5f5 100644 --- a/packages/esm-appointments-app/src/form/appointments-form.test.tsx +++ b/packages/esm-appointments-app/src/form/appointments-form.test.tsx @@ -5,14 +5,16 @@ import { type FetchResponse, getDefaultsFromConfigSchema, openmrsFetch, + showSnackbar, useConfig, useLocations, useSession, } from '@openmrs/esm-framework'; -import { mockUseAppointmentServiceData, mockSession, mockLocations } from '__mocks__'; +import { configSchema, type ConfigObject } from '../config-schema'; +import { mockUseAppointmentServiceData, mockSession, mockLocations, mockProviders } from '__mocks__'; import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools'; import { saveAppointment } from './appointments-form.resource'; -import { configSchema, type ConfigObject } from '../config-schema'; +import { useProviders } from '../hooks/useProviders'; import AppointmentForm from './appointments-form.component'; const defaultProps = { @@ -20,79 +22,40 @@ const defaultProps = { closeWorkspace: jest.fn(), patientUuid: mockPatient.id, promptBeforeClosing: jest.fn(), + closeWorkspaceWithSavedChanges: jest.fn(), + setTitle: jest.fn(), }; -const mockCreateAppointment = jest.mocked(saveAppointment); const mockOpenmrsFetch = jest.mocked(openmrsFetch); +const mockSaveAppointment = jest.mocked(saveAppointment); +const mockShowSnackbar = jest.mocked(showSnackbar); const mockUseConfig = jest.mocked(useConfig); const mockUseLocations = jest.mocked(useLocations); +const mockUseProviders = jest.mocked(useProviders); const mockUseSession = jest.mocked(useSession); -jest.mock('react-hook-form', () => ({ - ...jest.requireActual('react-hook-form'), - useForm: jest.fn().mockImplementation(() => ({ - handleSubmit: () => jest.fn(), - control: { - register: jest.fn(), - unregister: jest.fn(), - getFieldState: jest.fn(), - _names: { - array: new Set('test'), - mount: new Set('test'), - unMount: new Set('test'), - watch: new Set('test'), - focus: 'test', - watchAll: false, - }, - _subjects: { - watch: jest.fn(), - array: jest.fn(), - state: jest.fn(), - }, - _getWatch: jest.fn(), - _formValues: [], - _defaultValues: [], - }, - getValues: (str) => { - if (str === 'recurringPatternDaysOfWeek') { - return []; - } else { - return null; - } - }, - setValue: () => jest.fn(), - formState: () => jest.fn(), - watch: () => jest.fn(), - })), - Controller: ({ render }) => - render({ - field: { - onChange: jest.fn(), - onBlur: jest.fn(), - value: '', - ref: jest.fn(), - }, - formState: { - isSubmitted: false, - }, - fieldState: { - isTouched: false, - }, - }), - useSubscribe: () => ({ - r: { current: { subject: { subscribe: () => jest.fn() } } }, - }), - useController: () => ({ - field: { ref: jest.fn() }, - }), -})); - jest.mock('./appointments-form.resource', () => ({ ...jest.requireActual('./appointments-form.resource'), saveAppointment: jest.fn(), })); +jest.mock('../hooks/useProviders', () => ({ + ...jest.requireActual('../hooks/useProviders'), + useProviders: jest.fn(), +})); + +jest.mock('../workload/workload.resource', () => ({ + ...jest.requireActual('../workload/workload.resource'), + getMonthlyCalendarDistribution: jest.fn(), + useAppointmentSummary: jest.fn(), + useCalendarDistribution: jest.fn(), + useMonthlyCalendarDistribution: jest.fn().mockReturnValue([]), + useMonthlyAppointmentSummary: jest.fn().mockReturnValue([]), +})); + describe('AppointmentForm', () => { + const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3}Z|\+00:00)$/; + beforeEach(() => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), @@ -100,36 +63,42 @@ describe('AppointmentForm', () => { }); mockUseLocations.mockReturnValue(mockLocations.data.results); mockUseSession.mockReturnValue(mockSession.data); + mockUseProviders.mockReturnValue({ + providers: mockProviders.data, + isLoading: false, + error: null, + isValidating: false, + }); }); - it('renders the appointments form showing all the relevant fields and values', async () => { + it('renders the appointments form', async () => { mockOpenmrsFetch.mockResolvedValue(mockUseAppointmentServiceData as unknown as FetchResponse); renderWithSwr(); await waitForLoadingToFinish(); - expect(screen.getByLabelText(/Select a location/i)).toBeInTheDocument(); - expect(screen.getByLabelText(/^Date$/i)).toBeInTheDocument(); - expect(screen.getByLabelText(/Select a service/i)).toBeInTheDocument(); - expect(screen.getByLabelText(/Select the type of appointment/i)).toBeInTheDocument(); - expect(screen.getByLabelText(/Write an additional note/i)).toBeInTheDocument(); - expect(screen.getByPlaceholderText(/Write any additional points here/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/select a location/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/select a service/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/select the type of appointment/i)).toBeInTheDocument(); + expect(screen.getByLabelText(/write an additional note/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/write any additional points here/i)).toBeInTheDocument(); expect(screen.getAllByPlaceholderText(/dd\/mm\/yyyy/i).length).toBe(2); - expect(screen.getByRole('option', { name: /Mosoriot/i })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /Inpatient Ward/i })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /AM/i })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /PM/i })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /Choose appointment type/i })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /Scheduled/i })).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /WalkIn/i })).toBeInTheDocument(); - expect(screen.getByRole('textbox', { name: /^Date$/i })).toBeInTheDocument(); - expect(screen.getByRole('textbox', { name: /Time/i })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Discard/i })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /Save and close/i })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /mosoriot/i })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /inpatient ward/i })).toBeInTheDocument(); + expect(screen.getByLabelText(/^date$/i)).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /^am$/i })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /^pm$/i })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /choose appointment type/i })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /scheduled/i })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /walkin/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /^date$/i })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: /time/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /discard/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /save and close/i })).toBeInTheDocument(); }); - it('closes the form and the workspace when the cancel button is clicked', async () => { + it('closes the workspace when the cancel button is clicked', async () => { const user = userEvent.setup(); mockOpenmrsFetch.mockResolvedValueOnce(mockUseAppointmentServiceData as unknown as FetchResponse); @@ -144,34 +113,64 @@ describe('AppointmentForm', () => { expect(defaultProps.closeWorkspace).toHaveBeenCalledTimes(1); }); - it('renders a success snackbar upon successfully scheduling an appointment', async () => { + it('renders a success snackbar upon successfully scheduling an appointment', async () => { const user = userEvent.setup(); mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse); - mockCreateAppointment.mockResolvedValue({ status: 200, statusText: 'Ok' } as FetchResponse); + mockSaveAppointment.mockResolvedValue({ status: 200, statusText: 'Ok' } as FetchResponse); renderWithSwr(); await waitForLoadingToFinish(); - const saveButton = screen.getByRole('button', { name: /Save and close/i }); - const dateInput = screen.getByRole('textbox', { name: /^Date$/i }); - const timeInput = screen.getByRole('textbox', { name: /Time/i }); - const timeFormat = screen.getByRole('combobox', { name: /Time/i }); - const serviceSelect = screen.getByRole('combobox', { name: /Select a service/i }); - const appointmentTypeSelect = screen.getByRole('combobox', { name: /Select the type of appointment/i }); - - expect(saveButton).toBeEnabled(); + const locationSelect = screen.getByRole('combobox', { name: /select a location/i }); + const serviceSelect = screen.getByRole('combobox', { name: /select a service/i }); + const appointmentTypeSelect = screen.getByRole('combobox', { name: /select the type of appointment/i }); + const providerSelect = screen.getByRole('combobox', { name: /select a provider/i }); + const dateInput = screen.getByRole('textbox', { name: /^date$/i }); + const timeInput = screen.getByRole('textbox', { name: /time/i }); + const timeFormat = screen.getByRole('combobox', { name: /time/i }); + const saveButton = screen.getByRole('button', { name: /save and close/i }); - await user.clear(dateInput); - await user.type(dateInput, '4/4/2021'); - await user.clear(timeInput); - await user.type(timeInput, '09:30'); - await user.selectOptions(timeFormat, 'AM'); + await user.selectOptions(locationSelect, ['Inpatient Ward']); await user.selectOptions(serviceSelect, ['Outpatient']); await user.selectOptions(appointmentTypeSelect, ['Scheduled']); + await user.selectOptions(providerSelect, ['doctor - James Cook']); + + const date = '4/4/2021'; + const time = '09:30'; + await user.type(dateInput, date); + await user.type(timeInput, time); + await user.tab(); + await user.selectOptions(timeFormat, 'AM'); await user.click(saveButton); + + expect(mockSaveAppointment).toHaveBeenCalledTimes(1); + expect(mockSaveAppointment).toHaveBeenCalledWith( + { + appointmentKind: 'Scheduled', + comments: '', + dateAppointmentScheduled: expect.stringMatching(dateTimeRegex), + endDateTime: expect.stringMatching(dateTimeRegex), + locationUuid: 'b1a8b05e-3542-4037-bbd3-998ee9c40574', + patientUuid: mockPatient.id, + providers: [{ uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66' }], + serviceUuid: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90', + startDateTime: expect.stringMatching(dateTimeRegex), + status: '', + uuid: undefined, + }, + new AbortController(), + ); + + expect(mockShowSnackbar).toHaveBeenCalledTimes(1); + expect(mockShowSnackbar).toHaveBeenCalledWith({ + kind: 'success', + isLowContrast: true, + subtitle: 'It is now visible on the Appointments page', + title: 'Appointment scheduled', + }); }); it('renders an error snackbar if there was a problem scheduling an appointment', async () => { @@ -185,27 +184,60 @@ describe('AppointmentForm', () => { }, }; - mockOpenmrsFetch.mockResolvedValueOnce({ data: mockUseAppointmentServiceData } as unknown as FetchResponse); - mockCreateAppointment.mockRejectedValueOnce(error); + mockOpenmrsFetch.mockResolvedValue({ data: mockUseAppointmentServiceData } as unknown as FetchResponse); + mockSaveAppointment.mockRejectedValue(error); renderWithSwr(); await waitForLoadingToFinish(); - const saveButton = screen.getByRole('button', { name: /Save and close/i }); - const dateInput = screen.getByRole('textbox', { name: /^Date$/i }); - const timeInput = screen.getByRole('textbox', { name: /Time/i }); - const timeFormat = screen.getByRole('combobox', { name: /Time/i }); - const serviceSelect = screen.getByRole('combobox', { name: /Select a service/i }); - const appointmentTypeSelect = screen.getByRole('combobox', { name: /Select the type of appointment/i }); - - await user.clear(dateInput); - await user.type(dateInput, '4/4/2021'); - await user.clear(timeInput); - await user.type(timeInput, '09:30'); - await user.selectOptions(timeFormat, 'AM'); + const locationSelect = screen.getByRole('combobox', { name: /select a location/i }); + const serviceSelect = screen.getByRole('combobox', { name: /select a service/i }); + const appointmentTypeSelect = screen.getByRole('combobox', { name: /select the type of appointment/i }); + const providerSelect = screen.getByRole('combobox', { name: /select a provider/i }); + const dateInput = screen.getByRole('textbox', { name: /^date$/i }); + const timeInput = screen.getByRole('textbox', { name: /time/i }); + const timeFormat = screen.getByRole('combobox', { name: /time/i }); + const saveButton = screen.getByRole('button', { name: /save and close/i }); + + await user.selectOptions(locationSelect, ['Inpatient Ward']); await user.selectOptions(serviceSelect, ['Outpatient']); await user.selectOptions(appointmentTypeSelect, ['Scheduled']); + await user.selectOptions(providerSelect, ['doctor - James Cook']); + + const date = '4/4/2021'; + const time = '09:30'; + + await user.type(dateInput, date); + await user.type(timeInput, time); + await user.tab(); + await user.selectOptions(timeFormat, 'AM'); await user.click(saveButton); + + expect(mockSaveAppointment).toHaveBeenCalledTimes(1); + expect(mockSaveAppointment).toHaveBeenCalledWith( + { + appointmentKind: 'Scheduled', + comments: '', + dateAppointmentScheduled: expect.stringMatching(dateTimeRegex), + endDateTime: expect.stringMatching(dateTimeRegex), + locationUuid: 'b1a8b05e-3542-4037-bbd3-998ee9c40574', + patientUuid: mockPatient.id, + providers: [{ uuid: 'f9badd80-ab76-11e2-9e96-0800200c9a66' }], + serviceUuid: 'e2ec9cf0-ec38-4d2b-af6c-59c82fa30b90', + startDateTime: expect.stringMatching(dateTimeRegex), + status: '', + uuid: undefined, + }, + new AbortController(), + ); + + expect(mockShowSnackbar).toHaveBeenCalledTimes(1); + expect(mockShowSnackbar).toHaveBeenCalledWith({ + isLowContrast: false, + kind: 'error', + subtitle: 'Internal Server Error', + title: 'Error scheduling appointment', + }); }); }); diff --git a/packages/esm-appointments-app/src/hooks/useDefaultLocation.ts b/packages/esm-appointments-app/src/hooks/useDefaultLocation.ts deleted file mode 100644 index 59be7209c..000000000 --- a/packages/esm-appointments-app/src/hooks/useDefaultLocation.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { type FetchResponse, openmrsFetch, useConfig } from '@openmrs/esm-framework'; -import useSWRImmutable from 'swr/immutable'; - -export const useDefaultLoginLocation = () => { - const config = useConfig(); - const apiUrl = config.defaultFacilityUrl; - const { data, error, isLoading } = useSWRImmutable(apiUrl, openmrsFetch); - - return { - defaultFacility: data ? data?.data : null, - isLoading: isLoading, - error, - }; -}; diff --git a/packages/esm-appointments-app/src/patient-appointments/patient-appointments-action-menu.component.tsx b/packages/esm-appointments-app/src/patient-appointments/patient-appointments-action-menu.component.tsx index 62a3187f9..db8f997c0 100644 --- a/packages/esm-appointments-app/src/patient-appointments/patient-appointments-action-menu.component.tsx +++ b/packages/esm-appointments-app/src/patient-appointments/patient-appointments-action-menu.component.tsx @@ -1,13 +1,11 @@ import React, { useCallback, useContext } from 'react'; import { useTranslation } from 'react-i18next'; - import { Layer, OverflowMenu, OverflowMenuItem } from '@carbon/react'; import { launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; import { launchWorkspace, showModal, useLayoutType } from '@openmrs/esm-framework'; import type { Appointment } from '../types'; -import styles from './patient-appointments-action-menu.scss'; - import PatientAppointmentContext, { PatientAppointmentContextTypes } from '../hooks/patientAppointmentContext'; +import styles from './patient-appointments-action-menu.scss'; interface appointmentsActionMenuProps { appointment: Appointment; @@ -32,7 +30,7 @@ export const PatientAppointmentsActionMenu = ({ appointment, patientUuid }: appo appointment, }); } - }, [appointment, t]); + }, [appointment, patientAppointmentContext, t]); const launchCancelAppointmentDialog = () => { const dispose = showModal('patient-appointment-cancel-confirmation-dialog', { diff --git a/packages/esm-appointments-app/src/patient-appointments/patient-appointments-base.test.tsx b/packages/esm-appointments-app/src/patient-appointments/patient-appointments-base.test.tsx index 2ef78d564..7e509c33b 100644 --- a/packages/esm-appointments-app/src/patient-appointments/patient-appointments-base.test.tsx +++ b/packages/esm-appointments-app/src/patient-appointments/patient-appointments-base.test.tsx @@ -14,7 +14,7 @@ const testProps = { const mockOpenmrsFetch = jest.mocked(openmrsFetch); -describe('AppointmensOverview', () => { +describe('AppointmentsOverview', () => { it('renders an empty state if appointments data is unavailable', async () => { mockOpenmrsFetch.mockResolvedValueOnce({ data: [], diff --git a/packages/esm-appointments-app/src/patient-appointments/patient-upcoming-appointments-card.component.tsx b/packages/esm-appointments-app/src/patient-appointments/patient-upcoming-appointments-card.component.tsx index 7c070be92..ace547b74 100644 --- a/packages/esm-appointments-app/src/patient-appointments/patient-upcoming-appointments-card.component.tsx +++ b/packages/esm-appointments-app/src/patient-appointments/patient-upcoming-appointments-card.component.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { InlineLoading, @@ -10,31 +10,84 @@ import { StructuredListRow, StructuredListWrapper, } from '@carbon/react'; -import { formatDate, parseDate } from '@openmrs/esm-framework'; -import { usePatientAppointments } from './patient-appointments.resource'; +import { formatDate, parseDate, showSnackbar, type Visit } from '@openmrs/esm-framework'; +import { changeAppointmentStatus, usePatientAppointments } from './patient-appointments.resource'; import { ErrorState } from '@openmrs/esm-patient-common-lib'; import styles from './patient-upcoming-appointments-card.scss'; import dayjs from 'dayjs'; import { type Appointment } from '../types'; +import { useMutateAppointments } from '../form/appointments-form.resource'; -interface PatientUpcomingAppointmentsProps { +interface VisitFormCallbacks { + onVisitCreatedOrUpdated: (visit: Visit) => Promise; +} + +// See VisitFormExtensionState in esm-patient-chart-app +export interface PatientUpcomingAppointmentsProps { + setVisitFormCallbacks(callbacks: VisitFormCallbacks); + visitFormOpenedFrom: string; + patientChartConfig?: { + showUpcomingAppointments: boolean; + }; patientUuid: string; - setUpcomingAppointment: (value: Appointment) => void; } +/** + * This is an extension that gets slotted into the patient chart start visit form when + * the appropriate config values are enabled. + * @param param0 + * @returns + */ const PatientUpcomingAppointmentsCard: React.FC = ({ patientUuid, - setUpcomingAppointment, + setVisitFormCallbacks, + patientChartConfig, }) => { const { t } = useTranslation(); const startDate = dayjs(new Date().toISOString()).subtract(6, 'month').toISOString(); const headerTitle = t('upcomingAppointments', 'Upcoming appointments'); - const [selectedAppointment, setSelectedAppointment] = useState(null); + const [selectedAppointment, setSelectedAppointment] = useState(null); + const { mutateAppointments } = useMutateAppointments(); + const memoMutateAppointments = useMemo(() => mutateAppointments, [mutateAppointments]); const ac = useMemo(() => new AbortController(), []); useEffect(() => () => ac.abort(), [ac]); const { data: appointmentsData, error, isLoading } = usePatientAppointments(patientUuid, startDate, ac); + const onVisitCreatedOrUpdated = useMemo( + () => ({ + onVisitCreatedOrUpdated: () => { + if (selectedAppointment) { + return changeAppointmentStatus('CheckedIn', selectedAppointment.uuid) + .then(() => { + memoMutateAppointments(); + showSnackbar({ + isLowContrast: true, + kind: 'success', + subtitle: t('appointmentMarkedChecked', 'Appointment marked as Checked In'), + title: t('appointmentCheckedIn', 'Appointment Checked In'), + }); + }) + .catch((error) => { + showSnackbar({ + title: t('updateError', 'Error updating upcoming appointment'), + kind: 'error', + isLowContrast: false, + subtitle: error?.message, + }); + }); + } else { + return Promise.resolve(); + } + }, + }), + [selectedAppointment, memoMutateAppointments, t], + ); + + useEffect(() => { + setVisitFormCallbacks(onVisitCreatedOrUpdated); + }, [onVisitCreatedOrUpdated, setVisitFormCallbacks]); + const todaysAppointments = appointmentsData?.todaysAppointments?.length ? appointmentsData?.todaysAppointments : []; const futureAppointments = appointmentsData?.upcomingAppointments?.length ? appointmentsData?.upcomingAppointments @@ -46,9 +99,12 @@ const PatientUpcomingAppointmentsCard: React.FC { setSelectedAppointment(appointment); - setUpcomingAppointment(appointment); }; + if (!patientChartConfig.showUpcomingAppointments) { + return <>; + } + if (error) { return ; } diff --git a/packages/esm-appointments-app/src/routes.json b/packages/esm-appointments-app/src/routes.json index fbe5d8fac..0e81492e0 100644 --- a/packages/esm-appointments-app/src/routes.json +++ b/packages/esm-appointments-app/src/routes.json @@ -99,7 +99,7 @@ { "name": "patient-upcoming-appointment-widget", "component": "patientUpcomingAppointmentsWidget", - "slot": "upcoming-appointment-slot" + "slot": "visit-form-top-slot" }, { "name": "edit-appointments-form", diff --git a/packages/esm-appointments-app/src/types/index.ts b/packages/esm-appointments-app/src/types/index.ts index 0fa56ba89..dcb5a8bb5 100644 --- a/packages/esm-appointments-app/src/types/index.ts +++ b/packages/esm-appointments-app/src/types/index.ts @@ -149,7 +149,7 @@ export interface AppointmentSummary { export interface Provider { uuid: string; display: string; - comments: string; + comments?: string; response?: string; person: OpenmrsResource; name?: string; diff --git a/packages/esm-appointments-app/src/workload/monthly-view-workload/monthly-view.component.tsx b/packages/esm-appointments-app/src/workload/monthly-view-workload/monthly-view.component.tsx index 13c6091b6..670a7c775 100644 --- a/packages/esm-appointments-app/src/workload/monthly-view-workload/monthly-view.component.tsx +++ b/packages/esm-appointments-app/src/workload/monthly-view-workload/monthly-view.component.tsx @@ -1,11 +1,11 @@ -import dayjs from 'dayjs'; import React, { useContext } from 'react'; +import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; -import DaysOfWeekCard from '../../calendar/monthly/days-of-week.component'; import { monthDays } from '../../helpers'; +import DaysOfWeekCard from '../../calendar/monthly/days-of-week.component'; +import MonthlyWorkloadCard from './monthlyWorkCard'; import SelectedDateContext from '../../hooks/selectedDateContext'; import styles from './monthly-workload.scss'; -import MonthlyWorkloadCard from './monthlyWorkCard'; interface MonthlyCalendarViewProps { calendarWorkload: Array<{ count: number; date: string }>; @@ -13,22 +13,24 @@ interface MonthlyCalendarViewProps { onDateClick?: (pickedDate: Date) => void; } -const monthFormat = 'MMMM, YYYY'; const MonthlyCalendarView: React.FC = ({ calendarWorkload, dateToDisplay = '', onDateClick, }) => { + const monthFormat = 'MMMM, YYYY'; + const { t } = useTranslation(); const { selectedDate } = useContext(SelectedDateContext); const daysInWeek = ['SUN', 'MON', 'TUE', 'WED', 'THUR', 'FRI', 'SAT']; const monthViewDate = dateToDisplay === '' ? selectedDate : dateToDisplay; + const daysInWeeks = daysInWeek.map((day) => t(day)); + const handleClick = (date: Date) => { if (onDateClick) { onDateClick(date); } }; - const { t } = useTranslation(); - const daysInWeeks = daysInWeek.map((day) => t(day)); + return (
<> diff --git a/packages/esm-appointments-app/src/workload/monthly-view-workload/monthlyWorkCard.tsx b/packages/esm-appointments-app/src/workload/monthly-view-workload/monthlyWorkCard.tsx index 2a9c40f18..8193eea01 100644 --- a/packages/esm-appointments-app/src/workload/monthly-view-workload/monthlyWorkCard.tsx +++ b/packages/esm-appointments-app/src/workload/monthly-view-workload/monthlyWorkCard.tsx @@ -15,6 +15,7 @@ interface MonthlyWorkloadComponentProps { const MonthlyWorkloadCard: React.FC = ({ date, count, isActive, selectedDate }) => { const layout = useLayoutType(); const isToday = date.isSame(dayjs(), 'day'); + return (
= ({ date, co [styles.largeDesktop]: layout !== 'small-desktop', }, )}> -

+

{date.format('D')}
@@ -38,7 +39,7 @@ const MonthlyWorkloadCard: React.FC = ({ date, co {count}
-

+
); }; diff --git a/packages/esm-appointments-app/src/workload/workload-card.component.tsx b/packages/esm-appointments-app/src/workload/workload-card.component.tsx index 8f547cced..b62937280 100644 --- a/packages/esm-appointments-app/src/workload/workload-card.component.tsx +++ b/packages/esm-appointments-app/src/workload/workload-card.component.tsx @@ -2,13 +2,14 @@ import React from 'react'; import classNames from 'classnames'; import styles from './workload.scss'; -interface WorkloadCardProp { +interface WorkloadCardProps { + count: number; day: string; date: string; - count: number; isActive: boolean; } -const WorkloadCard: React.FC = ({ day, date, count, isActive }) => { + +const WorkloadCard = ({ count, day, date, isActive }: WorkloadCardProps) => { return (
= ({ day, date, count, isActive }
); }; + export default WorkloadCard; diff --git a/packages/esm-appointments-app/src/workload/workload.component.tsx b/packages/esm-appointments-app/src/workload/workload.component.tsx index b1f77e9e1..8f5c65fe0 100644 --- a/packages/esm-appointments-app/src/workload/workload.component.tsx +++ b/packages/esm-appointments-app/src/workload/workload.component.tsx @@ -1,12 +1,8 @@ import React, { useState } from 'react'; -import { Tabs, Tab, TabPanel, TabPanels, TabList } from '@carbon/react'; -import WorkloadCard from './workload-card.component'; -import dayjs from 'dayjs'; -import { useTranslation } from 'react-i18next'; -import { useCalendarDistribution, useMonthlyCalendarDistribution } from './workload.resource'; -import styles from './workload.scss'; import { useAppointmentService } from '../form/appointments-form.resource'; +import { useCalendarDistribution, useMonthlyCalendarDistribution } from './workload.resource'; import MonthlyCalendarView from './monthly-view-workload/monthly-view.component'; +import styles from './workload.scss'; interface WorkloadProps { selectedService: string; @@ -15,31 +11,28 @@ interface WorkloadProps { } const Workload: React.FC = ({ selectedService, appointmentDate, onWorkloadDateChange }) => { - const { t } = useTranslation(); - const [selectedTab, setSelectedTab] = useState(0); const { data: services } = useAppointmentService(); const serviceUuid = services?.find((service) => service.name === selectedService)?.uuid; + + const [selectedTab, setSelectedTab] = useState(0); + const calendarWorkload = useCalendarDistribution(serviceUuid, selectedTab === 0 ? 'week' : 'month', appointmentDate); + const monthlyCalendarWorkload = useMonthlyCalendarDistribution( serviceUuid, selectedTab === 0 ? 'week' : 'month', appointmentDate, ); - const handleDateClick = (pickedDate: Date) => { - onWorkloadDateChange(pickedDate); - }; + + const handleDateClick = (pickedDate: Date) => onWorkloadDateChange(pickedDate); return (
-
-
- -
-
+
); }; diff --git a/packages/esm-appointments-app/src/workload/workload.resource.ts b/packages/esm-appointments-app/src/workload/workload.resource.ts index bc80fb38f..fbd31171a 100644 --- a/packages/esm-appointments-app/src/workload/workload.resource.ts +++ b/packages/esm-appointments-app/src/workload/workload.resource.ts @@ -4,6 +4,7 @@ import first from 'lodash-es/first'; import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { type AppointmentSummary } from '../types'; import { omrsDateFormat } from '../constants'; + interface AppointmentCount { date: string; count: number; @@ -40,6 +41,7 @@ export const useAppointmentSummary = (fromDate: Date, serviceUuid: string): Arra const { data } = useSWR<{ data: Array }>(url, openmrsFetch); const results = first(data?.data?.filter(({ appointmentService }) => appointmentService.uuid === serviceUuid)); const appointmentCountMap = results?.appointmentCountMap; + return Object.entries(appointmentCountMap ?? []) .map(([key, value]) => ({ date: key, @@ -47,6 +49,7 @@ export const useAppointmentSummary = (fromDate: Date, serviceUuid: string): Arra })) .sort((dateA, dateB) => new Date(dateA.date).getTime() - new Date(dateB.date).getTime()); }; + export const useMonthlyCalendarDistribution = ( serviceUuid: string, distributionType: 'month' | 'week', diff --git a/packages/esm-appointments-app/translations/am.json b/packages/esm-appointments-app/translations/am.json index 8e551fe63..72d4ba088 100644 --- a/packages/esm-appointments-app/translations/am.json +++ b/packages/esm-appointments-app/translations/am.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/ar.json b/packages/esm-appointments-app/translations/ar.json index 2a6c0ce9c..532d10393 100644 --- a/packages/esm-appointments-app/translations/ar.json +++ b/packages/esm-appointments-app/translations/ar.json @@ -7,6 +7,7 @@ "appointmentCancelError": "خطأ في إلغاء الموعد", "appointmentCancelled": "تم إلغاء الموعد", "appointmentCancelledSuccessfully": "تم إلغاء الموعد بنجاح", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "لون الموعد", "appointmentConflict": "تعارض بالمواعيد", "appointmentEdited": "تم تعديل الموعد", @@ -18,6 +19,7 @@ "appointmentEndError": "خطأ في انهاء الموعد", "appointmentFormError": "خطأ في جدولة الموعد", "appointmentHistory": "تاريخ الموعد", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "مقاييس الموعد", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "كتابة ملاحظة اضافية", @@ -32,8 +34,8 @@ "appointmentService": "خدمة الموعد", "appointmentServiceCreate": "تم إنشاء خدمة الموعد بنجاح", "appointmentServiceName": "اسم خدمة الموعد", - "appointmentsScheduledForToday": "appointments scheduled for today", - "appointmentsTable": "Appointments table", + "appointmentsScheduledForToday": "المواعيد المقررة لهذا اليوم", + "appointmentsTable": "جدول المواعيد", "appointmentStatus": "Appointment status", "appointmentToFulfill": "Select appointment to fulfill", "appointmentType": "نوع الموعد", @@ -63,7 +65,7 @@ "date": "تاريخ", "date&Time": "Date & time", "dateAppointmentIssuedCannotBeAfterAppointmentDate": "Date appointment issued cannot be after the appointment date", - "dateOfBirth": "Date of birth", + "dateOfBirth": "تاريخ الميلاد", "dateScheduled": "Date appointment issued", "dateScheduledDetail": "Date appointment issued", "dateTime": "Date & Time", @@ -92,9 +94,8 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "معرف", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", - "itemsPerPage": "Items per page", + "itemsPerPage": "العناصر لكل صفحة", "loading": "Loading", "location": "الموقع", "medications": "الأدوية", @@ -103,7 +104,7 @@ "nextMonth": "Next month", "nextPage": "Next page", "no": "لا", - "noAppointmentsToDisplay": "No appointments to display", + "noAppointmentsToDisplay": "لا توجد مواعيد لعرضها", "noContent": "No Content", "noCurrentAppointments": "لا توجد مواعيد مجدولة لهذا المريض اليوم", "noEncountersFound": "لم يتم العثور على لقاءات", @@ -131,10 +132,10 @@ "recurringAppointmentShouldHaveEndDate": "A recurring appointment should have an end date", "repeatEvery": "Repeat every", "save": "حفظ", - "saveAndClose": "Save and close", + "saveAndClose": "حفظ وإغلاق", "scheduled": "مجدول", "scheduledAppointments": "المواعيد المجدولة", - "scheduledForToday": "Scheduled For Today", + "scheduledForToday": "مجدول لليوم", "selectALocation": "Select a location", "selectAppointmentStatus": "اختر الحالة", "selectAppointmentType": "اختر نوع الموعد", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "مواعيد غير مجدولة", "upcoming": "القادمة", "upcomingAppointments": "المواعيد القادمة", + "updateError": "Error updating upcoming appointment", "view": "عرض", "vitals": "العلامات الحيوية", "week": "Week", diff --git a/packages/esm-appointments-app/translations/de.json b/packages/esm-appointments-app/translations/de.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/de.json +++ b/packages/esm-appointments-app/translations/de.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/en.json b/packages/esm-appointments-app/translations/en.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/en.json +++ b/packages/esm-appointments-app/translations/en.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/es.json b/packages/esm-appointments-app/translations/es.json index 0517bae97..03c0b87be 100644 --- a/packages/esm-appointments-app/translations/es.json +++ b/packages/esm-appointments-app/translations/es.json @@ -3,26 +3,28 @@ "actions": "Acciones", "add": "Agregar", "age": "Edad", - "allDay": "All day", + "allDay": "Todo el día", "appointmentCancelError": "Error al cancelar la cita", - "appointmentCancelled": "Cita cancelada", + "appointmentCancelled": "Cita Cancelada", "appointmentCancelledSuccessfully": "Cita cancelada con éxito", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Color de la cita", "appointmentConflict": "Conflicto en las citas", "appointmentEdited": "Cita editada", - "appointmentEditError": "Error al editar cita", + "appointmentEditError": "Error al editar la cita", "appointmentEnded": "Cita finalizada", - "appointmentEndedAndVisitClosedSuccessfully": "Appointment successfully ended and visit successfully closed", - "appointmentEndedButVisitNotClosedError": "Cita finalizada, pero hubo un error al cerrar la visita", - "appointmentEndedSuccessfully": "Cita finalizada con éxito ", - "appointmentEndError": "Error al finalizar cita", + "appointmentEndedAndVisitClosedSuccessfully": "Cita finalizada exitosamente y consulta cerrada exitosamente", + "appointmentEndedButVisitNotClosedError": "Cita finalizada, pero hubo un error al cerrar la consulta", + "appointmentEndedSuccessfully": "Cita finalizada con éxito.", + "appointmentEndError": "Error al finalizar la cita", "appointmentFormError": "Error al programar la cita", - "appointmentHistory": "Historial de citas", + "appointmentHistory": "Historial de Citas", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Métricas de citas", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Escriba una nota adicional", - "appointmentNotePlaceholder": "Escriba algún punto adicional aquí", - "appointmentNotes": "Notas de cita", + "appointmentNotePlaceholder": "Escriba cualquier punto adicional aquí", + "appointmentNotes": "Notas de la Cita", "appointmentNowVisible": "Ahora es visible en la página de Citas", "appointments": "Citas", "Appointments": "Citas", @@ -34,25 +36,25 @@ "appointmentServiceName": "Nombre del servicio de citas", "appointmentsScheduledForToday": "citas agendadas para hoy", "appointmentsTable": "Tabla de citas", - "appointmentStatus": "Appointment status", - "appointmentToFulfill": "Seleccione cita para completar", + "appointmentStatus": "Estado de la cita", + "appointmentToFulfill": "Seleccione una cita para completar", "appointmentType": "Tipo de cita", - "appointmentType_title": "Tipo de cita", + "appointmentType_title": "Tipo de Cita", "back": "Atrás", "calendar": "Calendario", - "cameEarly": "Llegó temprano", + "cameEarly": "Llegó Temprano", "cancel": "Cancelar", - "cancelAppointment": "Cancelar cita", + "cancelAppointment": "Cancelar Cita", "cancelAppointmentModalConfirmationText": "¿Está seguro de que desea cancelar esta cita?", "cancelled": "Cancelada", - "checkedIn": "Checked in", - "checkedOut": "Completada", - "checkFilters": "Compruebe los filtros anteriores", - "checkIn": "Registrar", - "checkOut": "Finalizar", - "chooseAppointmentType": "Escoja un tipo de cita", - "chooseLocation": "Escoja una ubicación", - "chooseProvider": "Escoja un proveedor", + "checkedIn": "Llegada Registrada", + "checkedOut": "Salida Registrada", + "checkFilters": "Revise los filtros arriba", + "checkIn": "Registrar Entrada", + "checkOut": "Registrar Salida", + "chooseAppointmentType": "Elija un tipo de cita", + "chooseLocation": "Elija una ubicación", + "chooseProvider": "Elija un proveedor de la salud", "chooseService": "Seleccionar servicio", "completed": "Completado", "Contact": "Contacto {{index}}", @@ -61,41 +63,40 @@ "createAppointmentService": "Crear servicios de citas", "createNewAppointment": "Crear nueva cita", "date": "Fecha", - "date&Time": "Date & time", - "dateAppointmentIssuedCannotBeAfterAppointmentDate": "La fecha en que se emitió la cita no puede ser posterior a la fecha de la cita", + "date&Time": "Fecha y hora", + "dateAppointmentIssuedCannotBeAfterAppointmentDate": "La fecha de emisión de la cita no puede ser posterior a la fecha de la cita", "dateOfBirth": "Fecha de nacimiento", "dateScheduled": "Fecha de emisión de la cita", "dateScheduledDetail": "Fecha de emisión de la cita", - "dateTime": "Fecha y hora", + "dateTime": "Fecha y Hora", "day": "Día", "daysOfWeek": "Días de la semana", "discard": "Descartar", "download": "Descargar", - "durationErrorMessage": "La duración debería ser mayor a cero", + "durationErrorMessage": "La duración debe ser mayor que cero", "durationInMinutes": "Duración (minutos)", "durationMins": "Duración (min)", "edit": "Editar", - "editAppointment": "Editar cita", - "editAppointments": "Editar cita", + "editAppointment": "Editar Cita", + "editAppointments": "Editar Cita", "emptyStateText": "No hay <1>{{displayText}} para mostrar", "encounters": "Encuentros", "encounterType": "Tipo de encuentro", - "endAppointmentAndVisitConfirmationMessage": "Marcar al paciente como dado de alta indicará que la cita está completa y cerrará la visita activa para este paciente.", - "endAppointmentConfirmation": "¿Está seguro de que desea marcar al paciente como dado de alta para esta cita?", - "endAppointmentConfirmationMessage": "Marcar al paciente como dado de alta indicará que la cita está completa.", + "endAppointmentAndVisitConfirmationMessage": "Registrar la salida del paciente marcará la cita como completada y cerrará la consulta activa para este paciente.", + "endAppointmentConfirmation": "¿Está seguro de que desea registrar la salida del paciente para esta cita?", + "endAppointmentConfirmationMessage": "Registrar la salida del paciente marcará la cita como completada.", "endDate": "Fecha de finalización", "endTime": "Hora de finalización", - "errorCreatingAppointmentService": "Error creating appointment service", - "expected": "Expected", + "errorCreatingAppointmentService": "Error al crear el servicio de cita", + "expected": "Esperado", "filterTable": "Filtrar tabla", "gender": "Género", "highestServiceVolume": "Servicio de mayor volumen: {{time}}", "identifier": "Identificador", "invalidNumber": "El número no es válido", - "invalidTime": "Hora inválida", "isRecurringAppointment": "¿Esta es una cita recurrente?", "itemsPerPage": "Elementos por página", - "loading": "Loading", + "loading": "Cargando", "location": "Ubicación", "medications": "Medicamentos", "missed": "Perdida", @@ -104,19 +105,19 @@ "nextPage": "Siguiente página", "no": "No", "noAppointmentsToDisplay": "No hay citas para mostrar", - "noContent": "No hay contenido", - "noCurrentAppointments": "No hay citas programadas que mostrar para hoy para este paciente", + "noContent": "No hay Contenido", + "noCurrentAppointments": "No hay citas programadas para hoy para mostrar para este paciente.", "noEncountersFound": "No se encontraron encuentros", "noPastAppointments": "No hay citas anteriores para mostrar para este paciente", - "noPreviousVisitFound": "No se encontraron visitas anteriores", + "noPreviousVisitFound": "No se encontraron consultas anteriores", "notArrived": "No ha llegado", "note": "Nota", "notes": "Notas", - "noUpcomingAppointments": "No hay próximas citas para mostrar para este paciente", - "noUpcomingAppointmentsForPatient": "No hay próximas citas para mostrar para este paciente", + "noUpcomingAppointments": "No se encontraron próximas citas", + "noUpcomingAppointmentsForPatient": "No hay próximas citas para mostrar para este paciente.", "pageNumber": "Número de página", - "past": "Anterior", - "patientDetails": "Detalles del paciente", + "past": "Pasado", + "patientDetails": "Detalles del Paciente", "patientDoubleBooking": "El paciente ya agendó una cita para esta hora", "patientName": "Nombre del paciente", "patients": "Pacientes", @@ -124,28 +125,28 @@ "prev": "Ant", "previousMonth": "Mes anterior", "previousPage": "Página anterior", - "provider": "Proveedor", - "providers": "Proveedores", - "providersBooked": "Proveedores agendados: {{time}}", - "recurringAppointment": "Cita recurrente", - "recurringAppointmentShouldHaveEndDate": "Una cita recurrente debería tener una fecha de finalización", + "provider": "Proveedor de la Salud", + "providers": "Personal de la Salud", + "providersBooked": "Personal de la Salud agendado: {{time}}", + "recurringAppointment": "Cita Recurrente", + "recurringAppointmentShouldHaveEndDate": "Una cita recurrente debe tener una fecha de finalización", "repeatEvery": "Repetir cada", "save": "Guardar", "saveAndClose": "Guardar y cerrar", "scheduled": "Programada", "scheduledAppointments": "Citas programadas", - "scheduledForToday": "Agendada Para Hoy", + "scheduledForToday": "Programada Para Hoy", "selectALocation": "Seleccionar una ubicación", "selectAppointmentStatus": "Seleccionar estado", - "selectAppointmentType": "Seleccionar tipo de cita", + "selectAppointmentType": "Seleccione un tipo de cita", "selectLocation": "Seleccionar ubicación", - "selectOption": "Seleccionar una opción", - "selectProvider": "Seleccionar un proveedor", - "selectService": "Seleccionar servicio", + "selectOption": "Seleccione una opción", + "selectProvider": "Seleccione un proveedor de la salud", + "selectService": "Seleccione un servicio", "selectServiceType": "Seleccionar tipo de servicio", "service": "Servicio", "serviceName": "Nombre del servicio", - "serviceType": "Tipo de servicio", + "serviceType": "Tipo de Servicio", "serviceUnavailable": "La hora de la cita está fuera de las horas de servicio", "startDate": "Fecha de inicio", "startTime": "Hora de inicio", @@ -153,14 +154,15 @@ "time": "Tiempo", "today": "Hoy", "todays": "Hoy día es", - "type": "Tip", + "type": "Tipo", "unscheduled": "No programada", "unscheduledAppointments": "Citas no programadas", "unscheduledAppointments_lower": "citas no programadas", "upcoming": "Próximas", "upcomingAppointments": "Próximas citas", + "updateError": "Error updating upcoming appointment", "view": "Ver", - "vitals": "Signos vitales", + "vitals": "Signos Vitales", "week": "Semana", "yes": "Sí" } diff --git a/packages/esm-appointments-app/translations/fr.json b/packages/esm-appointments-app/translations/fr.json index 46cf3efa3..e09b12bc4 100644 --- a/packages/esm-appointments-app/translations/fr.json +++ b/packages/esm-appointments-app/translations/fr.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Erreur d’annulation du rendez-vous", "appointmentCancelled": "Rendez-vous annulé", "appointmentCancelledSuccessfully": "Rendez-vous annulé avec succès", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Couleur du rendez-vous", "appointmentConflict": "Conflit du rendez-vous", "appointmentEdited": "Rendez-vous modifié", @@ -18,6 +19,7 @@ "appointmentEndError": "Erreur lors de la clôture du rendez-vous", "appointmentFormError": "Erreur lors de la planification du rendez-vous", "appointmentHistory": "Historique de Rendez-vous", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Métriques de rendez-vous", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Écrire une note additionnelle", @@ -92,7 +94,6 @@ "highestServiceVolume": "Service le plus utilisé: {{time}}", "identifier": "Identifiant", "invalidNumber": "Le numéro n'est pas valide", - "invalidTime": "Heure non valide", "isRecurringAppointment": "S'agit-il d'un rendez-vous récurrent?", "itemsPerPage": "Eléments par page", "loading": "En cours de chargement", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "rendez-vous non-planifiés", "upcoming": "À venir", "upcomingAppointments": "À venir rendez-vous", + "updateError": "Error updating upcoming appointment", "view": "Vue", "vitals": "Signes Vitaux", "week": "Semaine", diff --git a/packages/esm-appointments-app/translations/he.json b/packages/esm-appointments-app/translations/he.json index 792b610dd..ee77235c6 100644 --- a/packages/esm-appointments-app/translations/he.json +++ b/packages/esm-appointments-app/translations/he.json @@ -1,165 +1,167 @@ { "action": "פעולה", "actions": "פעולות", - "add": "הוסף", + "add": "הוספה", "age": "גיל", "allDay": "כל היום", "appointmentCancelError": "שגיאה בביטול התור", "appointmentCancelled": "התור בוטל", "appointmentCancelledSuccessfully": "התור בוטל בהצלחה", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "צבע התור", - "appointmentConflict": "Appointment conflict", - "appointmentEdited": "Appointment edited", - "appointmentEditError": "Error editing appointment", - "appointmentEnded": "Appointment ended", - "appointmentEndedAndVisitClosedSuccessfully": "Appointment successfully ended and visit successfully closed", - "appointmentEndedButVisitNotClosedError": "Appointment ended, but error closing visit", - "appointmentEndedSuccessfully": "Appointment successfully ended.", - "appointmentEndError": "Error ending appointment", + "appointmentConflict": "סתירת תורים", + "appointmentEdited": "התור נערך", + "appointmentEditError": "שגיאה בעריכת תור", + "appointmentEnded": "התור הסתיים", + "appointmentEndedAndVisitClosedSuccessfully": "התור הסתיים והביקור נסגר בהצלחה", + "appointmentEndedButVisitNotClosedError": "התור הסתיים אבל אירעה שגיאה בסגירת הביקור", + "appointmentEndedSuccessfully": "התור הסתיים בהצלחה.", + "appointmentEndError": "שגיאה בסיום תור", "appointmentFormError": "שגיאה בתזמון התור", "appointmentHistory": "היסטוריית התורים", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "מדדי התורים", "appointmentMetricsLoadError": "", - "appointmentNoteLabel": "Write an additional note", - "appointmentNotePlaceholder": "Write any additional points here", + "appointmentNoteLabel": "הצגת הערה נוספ", + "appointmentNotePlaceholder": "כאן ניתן לכתוב נקודות נוספות", "appointmentNotes": "הערות לתור", - "appointmentNowVisible": "התור כעת נראה בעמוד התורים", + "appointmentNowVisible": "התור מופיע כעת בעמוד התורים", "appointments": "תורים", - "Appointments": "Appointments", - "appointments_lower": "appointments", - "appointmentsCalendar": "Appointments calendar", + "Appointments": "תורים", + "appointments_lower": "תורים", + "appointmentsCalendar": "לוח תורים", "appointmentScheduled": "התור נקבע", - "appointmentService": "שירות התור", - "appointmentServiceCreate": "שירות התור נוצר בהצלחה", - "appointmentServiceName": "שם שירות התור", - "appointmentsScheduledForToday": "appointments scheduled for today", - "appointmentsTable": "Appointments table", - "appointmentStatus": "סטטוס התור", - "appointmentToFulfill": "Select appointment to fulfill", + "appointmentService": "שירות התורים", + "appointmentServiceCreate": "שירות התורים נוצר בהצלחה", + "appointmentServiceName": "שם שירות התורים", + "appointmentsScheduledForToday": "תורים שתוזמנו להיום", + "appointmentsTable": "טבלת תורים", + "appointmentStatus": "מצב התור", + "appointmentToFulfill": "נא לבחור תור למילוי", "appointmentType": "סוג התור", "appointmentType_title": "סוג התור", - "back": "חזור", + "back": "חזרה", "calendar": "לוח שנה", - "cameEarly": "הגיע מוקדם", + "cameEarly": "הקדמה", "cancel": "ביטול", "cancelAppointment": "ביטול תור", - "cancelAppointmentModalConfirmationText": "?האם אתה בטוח שברצונך לבטל תור זה", + "cancelAppointmentModalConfirmationText": "לבטל את התור הזה?", "cancelled": "בוטל", "checkedIn": "נרשם", "checkedOut": "נסגר", - "checkFilters": "Check the filters above", + "checkFilters": "נא לסמן את המסננים לעיל", "checkIn": "הרשמה", - "checkOut": "לראות", - "chooseAppointmentType": "Choose appointment type", - "chooseLocation": "Choose a location", - "chooseProvider": "Choose a provider", + "checkOut": "סגירה", + "chooseAppointmentType": "בחירת סוג תור", + "chooseLocation": "נא לבחור מקום", + "chooseProvider": "נא לבחור ספק", "chooseService": "בחירת שירות", "completed": "הושלם", - "Contact": "Contact {{index}}", - "countMore_one": "{{count}} more", - "countMore_other": "{{count}} more", + "Contact": "יצירת קשר עם {{index}}", + "countMore_one": "עוד {{count}}", + "countMore_other": "עוד {{count}}", "createAppointmentService": "יצירת שירותי תורים", "createNewAppointment": "יצירת תור חדש", "date": "תאריך", "date&Time": "תאריך ושעה", - "dateAppointmentIssuedCannotBeAfterAppointmentDate": "Date appointment issued cannot be after the appointment date", - "dateOfBirth": "Date of birth", - "dateScheduled": "Date appointment issued", - "dateScheduledDetail": "Date appointment issued", - "dateTime": "Date & Time", - "day": "Day", - "daysOfWeek": "Days of the week", + "dateAppointmentIssuedCannotBeAfterAppointmentDate": "תאריך התור שהונפק לא יכול להיות אחרי תאריך התור", + "dateOfBirth": "תאריך לידה", + "dateScheduled": "הונפק תאריך לתור", + "dateScheduledDetail": "הונפק תאריך לתור", + "dateTime": "תאריך ושעה", + "day": "יום", + "daysOfWeek": "ימי השבוע", "discard": "סגירה מבלי לשמור", "download": "הורדה", - "durationErrorMessage": "Duration should be greater than zero", - "durationInMinutes": "Duration (minutes)", + "durationErrorMessage": "המשך צריך להיות גדול מאפס", + "durationInMinutes": "משך (דקות)", "durationMins": "משך בדקות", - "edit": "ערוך", + "edit": "עריכה", "editAppointment": "עריכת תור", - "editAppointments": "עריכת תורים", - "emptyStateText": "There are no <1>{{displayText}} to display", + "editAppointments": "עריכת תור", + "emptyStateText": "אין <1>{{displayText}} להצגה", "encounters": "פגישות", "encounterType": "סוג הפגישה", "endAppointmentAndVisitConfirmationMessage": "Checking the patient out will mark the appointment as complete, and close out the active visit for this patient.", "endAppointmentConfirmation": "Are you sure you want to check the patient out for this appointment?", "endAppointmentConfirmationMessage": "Checking the patient out will mark the appointment as complete.", - "endDate": "End date", + "endDate": "תאריך סיום", "endTime": "שעת סיום", "errorCreatingAppointmentService": "שגיאה ביצירת שירות התור", "expected": "צפוי", - "filterTable": "Filter table", + "filterTable": "סינון הטבלה", "gender": "מגדר", - "highestServiceVolume": "Highest volume service: {{time}}", + "highestServiceVolume": "השירות הכי עמוס: {{time}}", "identifier": "מזהה", - "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", - "isRecurringAppointment": "Is this a recurring appointment?", - "itemsPerPage": "Items per page", - "loading": "טוען", - "location": "מיקום", + "invalidNumber": "המספר שגוי", + "isRecurringAppointment": "זה תור חוזר?", + "itemsPerPage": "פריטים בעמוד", + "loading": "בטעינה", + "location": "מקום", "medications": "תרופות", - "missed": "לא התקיים", - "next": "Next", - "nextMonth": "Next month", - "nextPage": "Next page", + "missed": "הוחמץ", + "next": "הבא", + "nextMonth": "חודש הבא", + "nextPage": "העמוד הבא", "no": "לא", - "noAppointmentsToDisplay": "No appointments to display", - "noContent": "No Content", + "noAppointmentsToDisplay": "אין תורים להצגה", + "noContent": "אין תוכן", "noCurrentAppointments": "אין תורים מתוזמנים להצגה עבור מטופל זה ליום הזה", "noEncountersFound": "לא נמצאו פגישות", - "noPastAppointments": "אין תורים עבריים להצגה עבור מטופל זה", + "noPastAppointments": "אין תורי עבר להצגה עבור מטופל/ת אלה", "noPreviousVisitFound": "לא נמצא ביקור קודם", - "notArrived": "לא הגיע", - "note": "Note", + "notArrived": "לא הגיע/ה", + "note": "הערה", "notes": "הערות", - "noUpcomingAppointments": "אין תורים עתידיים להצגה עבור מטופל זה", - "noUpcomingAppointmentsForPatient": "אין תורים עתידיים להצגה עבור מטופל זה", - "pageNumber": "Page number", + "noUpcomingAppointments": "לא נמצאו תורים עתידיים", + "noUpcomingAppointmentsForPatient": "לא נמצאו תורים עתידיים למטפל/ת האלה", + "pageNumber": "מספר עמוד", "past": "עבר", "patientDetails": "פרטי המטופל", - "patientDoubleBooking": "Patient already booked for an appointment at this time", + "patientDoubleBooking": "למטופל/ת כבר הוזמן תור במועד הזה", "patientName": "שם המטופל", "patients": "מטופלים", - "period": "Period", - "prev": "Prev", - "previousMonth": "Previous month", - "previousPage": "Previous page", + "period": "תקופה", + "prev": "הקודם", + "previousMonth": "החודש הקודם", + "previousPage": "העמוד הקודם", "provider": "ספק", "providers": "ספקים", - "providersBooked": "Providers booked: {{time}}", - "recurringAppointment": "Recurring Appointment", - "recurringAppointmentShouldHaveEndDate": "A recurring appointment should have an end date", - "repeatEvery": "Repeat every", + "providersBooked": "ספקים שהוזמנו: {{time}}", + "recurringAppointment": "תור חוזר", + "recurringAppointmentShouldHaveEndDate": "לתור חוזר צריך להיות תאריך סיום", + "repeatEvery": "חוזר כל", "save": "שמירה", - "saveAndClose": "Save and close", + "saveAndClose": "שמירה וסגירה", "scheduled": "מתוזמן", "scheduledAppointments": "תורים מתוזמנים", - "scheduledForToday": "Scheduled For Today", - "selectALocation": "Select a location", - "selectAppointmentStatus": "בחר סטטוס", - "selectAppointmentType": "בחר סוג תור", - "selectLocation": "בחר מיקום", - "selectOption": "בחר אפשרות", - "selectProvider": "Select a provider", - "selectService": "בחר שירות", - "selectServiceType": "בחר סוג שירות", + "scheduledForToday": "מתוזמן להיום", + "selectALocation": "בחירת מקום", + "selectAppointmentStatus": "בחירת מצב", + "selectAppointmentType": "בחירת סוג תור", + "selectLocation": "בחירת מקום", + "selectOption": "בחירת אפשרות", + "selectProvider": "בחירת ספק", + "selectService": "בחירת שירות", + "selectServiceType": "בחירת סוג שירות", "service": "שירות", "serviceName": "שם השירות", "serviceType": "סוג השירות", - "serviceUnavailable": "Appointment time is outside of service hours", - "startDate": "Start date", + "serviceUnavailable": "זמן התור הוא מחוץ לשעות השירות", + "startDate": "תאריך התחלה", "startTime": "שעת התחלה", - "status": "Status", + "status": "מצב", "time": "זמן", "today": "היום", - "todays": "Today's", - "type": "Type", + "todays": "להיום", + "type": "סוג", "unscheduled": "לא מתוזמן", "unscheduledAppointments": "תורים שאינם מתוזמנים", "unscheduledAppointments_lower": "תורים שאינם מתוזמנים", "upcoming": "בקרוב", "upcomingAppointments": "תורים עתידיים", - "view": "הצג", + "updateError": "Error updating upcoming appointment", + "view": "הצגה", "vitals": "מדדים", "week": "Week", "yes": "כן" diff --git a/packages/esm-appointments-app/translations/hi.json b/packages/esm-appointments-app/translations/hi.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/hi.json +++ b/packages/esm-appointments-app/translations/hi.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/hi_IN.json b/packages/esm-appointments-app/translations/hi_IN.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/hi_IN.json +++ b/packages/esm-appointments-app/translations/hi_IN.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/id.json b/packages/esm-appointments-app/translations/id.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/id.json +++ b/packages/esm-appointments-app/translations/id.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/it.json b/packages/esm-appointments-app/translations/it.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/it.json +++ b/packages/esm-appointments-app/translations/it.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/km.json b/packages/esm-appointments-app/translations/km.json index 9bb690ae2..1350440e4 100644 --- a/packages/esm-appointments-app/translations/km.json +++ b/packages/esm-appointments-app/translations/km.json @@ -7,6 +7,7 @@ "appointmentCancelError": "កំហុសក្នុងការបោះបង់ការណាត់ជួប", "appointmentCancelled": "ការណាត់ជួបត្រូវបានលុបចោល", "appointmentCancelledSuccessfully": "ការណាត់ជួបបានលុបចោលដោយជោគជ័យ", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "ពណ៌ណាត់ជួប", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "កំហុសក្នុងការកំណត់ពេលណាត់ជួប", "appointmentHistory": "ប្រវត្តិនៃការណាត់ជួប", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "មាត្រដ្ឋានណាត់ជួប", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "អ្នកកំណត់អត្តសញ្ញាណ", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "ការណាត់ជួបនាពេលខាងមុខ", + "updateError": "Error updating upcoming appointment", "view": "ពិនិត្យមើល", "vitals": "សញ្ញាជីវិត", "week": "Week", diff --git a/packages/esm-appointments-app/translations/ne.json b/packages/esm-appointments-app/translations/ne.json new file mode 100644 index 000000000..d8bf71b55 --- /dev/null +++ b/packages/esm-appointments-app/translations/ne.json @@ -0,0 +1,168 @@ +{ + "action": "Action", + "actions": "Actions", + "add": "Add", + "age": "Age", + "allDay": "All day", + "appointmentCancelError": "Error cancelling appointment", + "appointmentCancelled": "Appointment Cancelled", + "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", + "appointmentColor": "Appointment color", + "appointmentConflict": "Appointment conflict", + "appointmentEdited": "Appointment edited", + "appointmentEditError": "Error editing appointment", + "appointmentEnded": "Appointment ended", + "appointmentEndedAndVisitClosedSuccessfully": "Appointment successfully ended and visit successfully closed", + "appointmentEndedButVisitNotClosedError": "Appointment ended, but error closing visit", + "appointmentEndedSuccessfully": "Appointment successfully ended.", + "appointmentEndError": "Error ending appointment", + "appointmentFormError": "Error scheduling appointment", + "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", + "appointmentMetrics": "Appointment metrics", + "appointmentMetricsLoadError": "", + "appointmentNoteLabel": "Write an additional note", + "appointmentNotePlaceholder": "Write any additional points here", + "appointmentNotes": "Appointment Notes", + "appointmentNowVisible": "It is now visible on the Appointments page", + "appointments": "Appointments", + "Appointments": "Appointments", + "appointments_lower": "appointments", + "appointmentsCalendar": "Appointments calendar", + "appointmentScheduled": "Appointment scheduled", + "appointmentService": "Appointment service", + "appointmentServiceCreate": "Appointment service created successfully", + "appointmentServiceName": "Appointment service name", + "appointmentsScheduledForToday": "appointments scheduled for today", + "appointmentsTable": "Appointments table", + "appointmentStatus": "Appointment status", + "appointmentToFulfill": "Select appointment to fulfill", + "appointmentType": "Appointment type", + "appointmentType_title": "Appointment Type", + "back": "Back", + "calendar": "Calendar", + "cameEarly": "Came Early", + "cancel": "Cancel", + "cancelAppointment": "Cancel Appointment", + "cancelAppointmentModalConfirmationText": "Are you sure you want to cancel this appointment?", + "cancelled": "Cancelled", + "checkedIn": "Checked in", + "checkedOut": "Checked out", + "checkFilters": "Check the filters above", + "checkIn": "Check In", + "checkOut": "Check out", + "chooseAppointmentType": "Choose appointment type", + "chooseLocation": "Choose a location", + "chooseProvider": "Choose a provider", + "chooseService": "Select service", + "completed": "Completed", + "Contact": "Contact {{index}}", + "countMore_one": "{{count}} more", + "countMore_other": "{{count}} more", + "createAppointmentService": "Create appointment services", + "createNewAppointment": "Create new appointment", + "date": "Date", + "date&Time": "Date & time", + "dateAppointmentIssuedCannotBeAfterAppointmentDate": "Date appointment issued cannot be after the appointment date", + "dateOfBirth": "Date of birth", + "dateScheduled": "Date appointment issued", + "dateScheduledDetail": "Date appointment issued", + "dateTime": "Date & Time", + "day": "Day", + "daysOfWeek": "Days of the week", + "discard": "Discard", + "download": "Download", + "durationErrorMessage": "Duration should be greater than zero", + "durationInMinutes": "Duration (minutes)", + "durationMins": "Duration min", + "edit": "Edit", + "editAppointment": "Edit Appointment", + "editAppointments": "Edit Appointment", + "emptyStateText": "There are no <1>{{displayText}} to display", + "encounters": "Encounters", + "encounterType": "Encounter type", + "endAppointmentAndVisitConfirmationMessage": "Checking the patient out will mark the appointment as complete, and close out the active visit for this patient.", + "endAppointmentConfirmation": "Are you sure you want to check the patient out for this appointment?", + "endAppointmentConfirmationMessage": "Checking the patient out will mark the appointment as complete.", + "endDate": "End date", + "endTime": "End Time", + "errorCreatingAppointmentService": "Error creating appointment service", + "expected": "Expected", + "filterTable": "Filter table", + "gender": "Gender", + "highestServiceVolume": "Highest volume service: {{time}}", + "identifier": "Identifier", + "invalidNumber": "Number is not valid", + "isRecurringAppointment": "Is this a recurring appointment?", + "itemsPerPage": "Items per page", + "loading": "Loading", + "location": "Location", + "medications": "Medications", + "missed": "Missed", + "next": "Next", + "nextMonth": "Next month", + "nextPage": "Next page", + "no": "No", + "noAppointmentsToDisplay": "No appointments to display", + "noContent": "No Content", + "noCurrentAppointments": "There are no appointments scheduled for today to display for this patient", + "noEncountersFound": "No encounters found", + "noPastAppointments": "There are no past appointments to display for this patient", + "noPreviousVisitFound": "No previous visit found", + "notArrived": "Not arrived", + "note": "Note", + "notes": "Notes", + "noUpcomingAppointments": "No upcoming appointments found", + "noUpcomingAppointmentsForPatient": "There are no upcoming appointments to display for this patient", + "pageNumber": "Page number", + "past": "Past", + "patientDetails": "Patient Details", + "patientDoubleBooking": "Patient already booked for an appointment at this time", + "patientName": "Patient name", + "patients": "Patients", + "period": "Period", + "prev": "Prev", + "previousMonth": "Previous month", + "previousPage": "Previous page", + "provider": "Provider", + "providers": "Providers", + "providersBooked": "Providers booked: {{time}}", + "recurringAppointment": "Recurring Appointment", + "recurringAppointmentShouldHaveEndDate": "A recurring appointment should have an end date", + "repeatEvery": "Repeat every", + "save": "Save", + "saveAndClose": "Save and close", + "scheduled": "Scheduled", + "scheduledAppointments": "Scheduled appointments", + "scheduledForToday": "Scheduled For Today", + "selectALocation": "Select a location", + "selectAppointmentStatus": "Select status", + "selectAppointmentType": "Select an appointment type", + "selectLocation": "Select location", + "selectOption": "Select an option", + "selectProvider": "Select a provider", + "selectService": "Select a service", + "selectServiceType": "Select service type", + "service": "Service", + "serviceName": "Service name", + "serviceType": "Service Type", + "serviceUnavailable": "Appointment time is outside of service hours", + "startDate": "Start date", + "startTime": "Start Time", + "status": "Status", + "time": "Time", + "today": "Today", + "todays": "Today's", + "type": "Type", + "unscheduled": "Unscheduled", + "unscheduledAppointments": "Unscheduled appointments", + "unscheduledAppointments_lower": "unscheduled appointments", + "upcoming": "Upcoming", + "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", + "view": "View", + "vitals": "Vitals", + "week": "Week", + "yes": "Yes" +} diff --git a/packages/esm-appointments-app/translations/pt.json b/packages/esm-appointments-app/translations/pt.json index 983e4fb0c..9514c523f 100644 --- a/packages/esm-appointments-app/translations/pt.json +++ b/packages/esm-appointments-app/translations/pt.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Erro ao cancelar a marcação de consulta", "appointmentCancelled": "Consulta Cancelada ", "appointmentCancelledSuccessfully": "Consulta cancelada com sucesso", + "appointmentCheckedIn": "Marcação de consulta registada", "appointmentColor": "Cor da consulta", "appointmentConflict": "Conflito no agendamento da consulta", "appointmentEdited": "Agendamento da consulta editado", @@ -16,8 +17,9 @@ "appointmentEndedButVisitNotClosedError": "Consulta finalizada, mas houve erro ao fechar a visita", "appointmentEndedSuccessfully": "Consulta fechada com sucesso.", "appointmentEndError": "Erro ao encerrar a consulta", - "appointmentFormError": "Erro ao cancelar a marcação de consulta", + "appointmentFormError": "Erro ao agendar a consulta", "appointmentHistory": "Historico de Consultas", + "appointmentMarkedChecked": "Consulta marcada como registada.", "appointmentMetrics": "Metricas de Consulta", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Escreva uma nota adicional", @@ -92,7 +94,6 @@ "highestServiceVolume": "Serviço de maior volume: {{time}}", "identifier": "Identificador", "invalidNumber": "Número não é válido", - "invalidTime": "Hora inválida", "isRecurringAppointment": "Esta é uma consulta recorrente?", "itemsPerPage": "Itens por página", "loading": "Carregando", @@ -113,7 +114,7 @@ "note": "Nota", "notes": "Notas", "noUpcomingAppointments": "Nenhuma próxima consulta foi encontrada", - "noUpcomingAppointmentsForPatient": "Não há consultas marcada por mostrar para este utente", + "noUpcomingAppointmentsForPatient": "Não há consultas marcadas por mostrar para este utente", "pageNumber": "Número da página", "past": "Passado", "patientDetails": "Detalhes do utente", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "consultas não marcadas", "upcoming": "Por vir", "upcomingAppointments": "Próximas consultas", + "updateError": "Error updating upcoming appointment", "view": "Visualizar", "vitals": "Sinais vitais", "week": "Semana", diff --git a/packages/esm-appointments-app/translations/qu.json b/packages/esm-appointments-app/translations/qu.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/qu.json +++ b/packages/esm-appointments-app/translations/qu.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/si.json b/packages/esm-appointments-app/translations/si.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/si.json +++ b/packages/esm-appointments-app/translations/si.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/sw.json b/packages/esm-appointments-app/translations/sw.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/sw.json +++ b/packages/esm-appointments-app/translations/sw.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/sw_KE.json b/packages/esm-appointments-app/translations/sw_KE.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/sw_KE.json +++ b/packages/esm-appointments-app/translations/sw_KE.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/tr.json b/packages/esm-appointments-app/translations/tr.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/tr.json +++ b/packages/esm-appointments-app/translations/tr.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/tr_TR.json b/packages/esm-appointments-app/translations/tr_TR.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/tr_TR.json +++ b/packages/esm-appointments-app/translations/tr_TR.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/uk.json b/packages/esm-appointments-app/translations/uk.json index e26cc97bd..d8bf71b55 100644 --- a/packages/esm-appointments-app/translations/uk.json +++ b/packages/esm-appointments-app/translations/uk.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Error cancelling appointment", "appointmentCancelled": "Appointment Cancelled", "appointmentCancelledSuccessfully": "Appointment cancelled successfully", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Appointment color", "appointmentConflict": "Appointment conflict", "appointmentEdited": "Appointment edited", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "Error scheduling appointment", "appointmentHistory": "Appointment History", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Appointment metrics", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Write an additional note", @@ -92,7 +94,6 @@ "highestServiceVolume": "Highest volume service: {{time}}", "identifier": "Identifier", "invalidNumber": "Number is not valid", - "invalidTime": "Invalid time", "isRecurringAppointment": "Is this a recurring appointment?", "itemsPerPage": "Items per page", "loading": "Loading", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "unscheduled appointments", "upcoming": "Upcoming", "upcomingAppointments": "Upcoming appointments", + "updateError": "Error updating upcoming appointment", "view": "View", "vitals": "Vitals", "week": "Week", diff --git a/packages/esm-appointments-app/translations/vi.json b/packages/esm-appointments-app/translations/vi.json index f88c95541..1db048cb6 100644 --- a/packages/esm-appointments-app/translations/vi.json +++ b/packages/esm-appointments-app/translations/vi.json @@ -7,6 +7,7 @@ "appointmentCancelError": "Lỗi khi hủy cuộc hẹn", "appointmentCancelled": "Cuộc hẹn dã hủy", "appointmentCancelledSuccessfully": "Hủy cuộc hẹn thành công", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "Màu cuộc hẹn", "appointmentConflict": "Xung đột cuộc hẹn", "appointmentEdited": "Cuộc hẹn đã được chỉnh sửa", @@ -18,6 +19,7 @@ "appointmentEndError": "Lỗi khi kết thúc cuộc hẹn", "appointmentFormError": "Lỗi khi lên lịch hẹn", "appointmentHistory": "Lịch sử cuộc hẹn", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "Số liệu cuộc hẹn", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "Viết thêm một ghi chú", @@ -92,7 +94,6 @@ "highestServiceVolume": "Dịch vụ có khối lượng cao nhất: {{hour}}", "identifier": "Mã định danh", "invalidNumber": "Số không hợp lệ", - "invalidTime": "Thời gian không hợp lệ", "isRecurringAppointment": "Đây có phải là cuộc hẹn định kỳ không?", "itemsPerPage": "Số mục trên mỗi trang", "loading": "Đang tải", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "cuộc hẹn không theo lịch trình", "upcoming": "Sắp tới", "upcomingAppointments": "Cuộc hẹn sắp tới", + "updateError": "Error updating upcoming appointment", "view": "Xem", "vitals": "Các yếu tố quan trọng", "week": "Tuần", diff --git a/packages/esm-appointments-app/translations/zh.json b/packages/esm-appointments-app/translations/zh.json index ffc469fc6..13f6fd726 100644 --- a/packages/esm-appointments-app/translations/zh.json +++ b/packages/esm-appointments-app/translations/zh.json @@ -7,6 +7,7 @@ "appointmentCancelError": "取消预约时出现错误", "appointmentCancelled": "预约已取消", "appointmentCancelledSuccessfully": "预约已成功取消", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "预约颜色", "appointmentConflict": "Appointment conflict", "appointmentEdited": "预约已编辑", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "安排预约时出现错误", "appointmentHistory": "预约历史", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "预约指标", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "输入附加备注", @@ -92,7 +94,6 @@ "highestServiceVolume": "最高量服务:{{time}}", "identifier": "ID", "invalidNumber": "数值无效", - "invalidTime": "无效时间", "isRecurringAppointment": "这是一个定期预约吗?", "itemsPerPage": "每页条目数", "loading": "加载中", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "未安排的预约", "upcoming": "即将到来", "upcomingAppointments": "即将到来的预约", + "updateError": "Error updating upcoming appointment", "view": "查看", "vitals": "生命体征", "week": "周", diff --git a/packages/esm-appointments-app/translations/zh_CN.json b/packages/esm-appointments-app/translations/zh_CN.json index ffc469fc6..13f6fd726 100644 --- a/packages/esm-appointments-app/translations/zh_CN.json +++ b/packages/esm-appointments-app/translations/zh_CN.json @@ -7,6 +7,7 @@ "appointmentCancelError": "取消预约时出现错误", "appointmentCancelled": "预约已取消", "appointmentCancelledSuccessfully": "预约已成功取消", + "appointmentCheckedIn": "Appointment Checked In", "appointmentColor": "预约颜色", "appointmentConflict": "Appointment conflict", "appointmentEdited": "预约已编辑", @@ -18,6 +19,7 @@ "appointmentEndError": "Error ending appointment", "appointmentFormError": "安排预约时出现错误", "appointmentHistory": "预约历史", + "appointmentMarkedChecked": "Appointment marked as Checked In", "appointmentMetrics": "预约指标", "appointmentMetricsLoadError": "", "appointmentNoteLabel": "输入附加备注", @@ -92,7 +94,6 @@ "highestServiceVolume": "最高量服务:{{time}}", "identifier": "ID", "invalidNumber": "数值无效", - "invalidTime": "无效时间", "isRecurringAppointment": "这是一个定期预约吗?", "itemsPerPage": "每页条目数", "loading": "加载中", @@ -159,6 +160,7 @@ "unscheduledAppointments_lower": "未安排的预约", "upcoming": "即将到来", "upcomingAppointments": "即将到来的预约", + "updateError": "Error updating upcoming appointment", "view": "查看", "vitals": "生命体征", "week": "周", diff --git a/packages/esm-bed-management-app/package.json b/packages/esm-bed-management-app/package.json index fe6cb162f..2772a26dc 100644 --- a/packages/esm-bed-management-app/package.json +++ b/packages/esm-bed-management-app/package.json @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "lodash-es": "^4.17.15" }, "peerDependencies": { diff --git a/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx b/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx index 6fb0fed9c..11e6f44eb 100644 --- a/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx +++ b/packages/esm-bed-management-app/src/bed-administration/bed-administration-form.component.tsx @@ -249,7 +249,7 @@ const BedAdministrationForm: React.FC = ({ defaultValue={selectedBedType} id="bedType" invalidText={t('required', 'Required')} - labelText={t('bedType', 'Bed type')} + labelText={t('bedTypes', 'Bed types')} {...field}> {availableBedTypes.map((bedType, index) => ( diff --git a/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.component.tsx b/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.component.tsx index 00649f232..b8cf88253 100644 --- a/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.component.tsx +++ b/packages/esm-bed-management-app/src/bed-administration/bed-administration-table.component.tsx @@ -29,7 +29,7 @@ import styles from './bed-administration-table.scss'; const BedAdministrationTable: React.FC = () => { const { t } = useTranslation(); - const headerTitle = t('wardAllocation', 'Ward Allocation'); + const headerTitle = t('wardAllocation', 'Ward allocation'); const layout = useLayoutType(); const isTablet = layout === 'tablet'; const responsiveSize = isTablet ? 'lg' : 'sm'; @@ -72,7 +72,7 @@ const BedAdministrationTable: React.FC = () => { const { results, currentPage, totalPages, goTo } = usePagination( filterOption === 'ALL' ? bedsGroupedByLocation - : bedsGroupedByLocation.filter((bed) => bed.status === filterOption) ?? [], + : bedsGroupedByLocation.flat().filter((bed) => bed.status === filterOption) ?? [], pageSize, ); @@ -131,7 +131,7 @@ const BedAdministrationTable: React.FC = () => { if (isLoadingBedsGroupedByLocation && !bedsGroupedByLocation.length) { return ( <> -
+
@@ -142,7 +142,7 @@ const BedAdministrationTable: React.FC = () => { if (errorFetchingBedsGroupedByLocation) { return ( <> -
+
@@ -152,7 +152,7 @@ const BedAdministrationTable: React.FC = () => { return ( <> -
+
{results?.length ? (
diff --git a/packages/esm-bed-management-app/src/bed-tag/bed-tag-administration-table.component.tsx b/packages/esm-bed-management-app/src/bed-tag/bed-tag-administration-table.component.tsx index 4720d04e6..5b7943787 100644 --- a/packages/esm-bed-management-app/src/bed-tag/bed-tag-administration-table.component.tsx +++ b/packages/esm-bed-management-app/src/bed-tag/bed-tag-administration-table.component.tsx @@ -27,7 +27,7 @@ import styles from '../bed-administration/bed-administration-table.scss'; const BedTagAdministrationTable: React.FC = () => { const { t } = useTranslation(); - const headerTitle = t('bedTag', 'Bed tag'); + const headerTitle = t('bedTags', 'Bed tags'); const layout = useLayoutType(); const isTablet = layout === 'tablet'; const responsiveSize = isTablet ? 'lg' : 'sm'; @@ -43,7 +43,7 @@ const BedTagAdministrationTable: React.FC = () => { const tableHeaders = [ { - header: t('ids', 'Id'), + header: t('ids', 'ID'), key: 'ids', }, { @@ -85,7 +85,7 @@ const BedTagAdministrationTable: React.FC = () => { if (isBedDataLoading || isLoadingBedTags) { return ( <> -
+
@@ -96,7 +96,7 @@ const BedTagAdministrationTable: React.FC = () => { if (errorLoadingBedTags) { return ( <> -
+
@@ -106,7 +106,7 @@ const BedTagAdministrationTable: React.FC = () => { return ( <> -
+
{showBedTagsModal ? ( diff --git a/packages/esm-bed-management-app/src/bed-tag/bed-tags-admin-form.component.tsx b/packages/esm-bed-management-app/src/bed-tag/bed-tags-admin-form.component.tsx index 200a7ef76..3e9cbd94e 100644 --- a/packages/esm-bed-management-app/src/bed-tag/bed-tags-admin-form.component.tsx +++ b/packages/esm-bed-management-app/src/bed-tag/bed-tags-admin-form.component.tsx @@ -88,7 +88,7 @@ const BedTagsAdministrationForm: React.FC = ({ <> { const { t } = useTranslation(); - const headerTitle = t('bedType', 'Bed type'); + const headerTitle = t('bedTypes', 'Bed types'); const layout = useLayoutType(); const isTablet = layout === 'tablet'; const responsiveSize = isTablet ? 'lg' : 'sm'; @@ -91,7 +91,7 @@ const BedTypeAdministrationTable: React.FC = () => { if (isLoadingBedTypes) { return ( <> -
+
@@ -102,7 +102,7 @@ const BedTypeAdministrationTable: React.FC = () => { if (errorLoadingBedTypes) { return ( <> -
+
@@ -112,7 +112,7 @@ const BedTypeAdministrationTable: React.FC = () => { return ( <> -
+
{showBedTypeModal ? ( diff --git a/packages/esm-bed-management-app/src/header/header.component.tsx b/packages/esm-bed-management-app/src/header/header.component.tsx index 1f0ba249f..c3032bcd5 100644 --- a/packages/esm-bed-management-app/src/header/header.component.tsx +++ b/packages/esm-bed-management-app/src/header/header.component.tsx @@ -1,31 +1,36 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Calendar, Location } from '@carbon/react/icons'; -import { ConfigurableLink, formatDate, useSession } from '@openmrs/esm-framework'; -import Illustration from './illustration.component'; +import { + ConfigurableLink, + formatDate, + InPatientPictogram, + PageHeader, + PageHeaderContent, + useSession, +} from '@openmrs/esm-framework'; import styles from './header.scss'; type HeaderProps = { - route: string; - headerTitle?: string; + title: string; }; -const Header: React.FC = ({ route, headerTitle }) => { +const Header: React.FC = ({ title }) => { const { t } = useTranslation(); const userSession = useSession(); const userLocation = userSession?.sessionLocation?.display; return ( -
-
- - - -
-

{t(headerTitle ?? 'bedManagement', headerTitle ?? 'Bed management')}

-

{route}

-
-
+ + + + + } + title={title} + className={styles.leftJustifiedItems} + />
@@ -35,7 +40,7 @@ const Header: React.FC = ({ route, headerTitle }) => { {formatDate(new Date(), { mode: 'standard' })}
-
+ ); }; diff --git a/packages/esm-bed-management-app/src/header/header.scss b/packages/esm-bed-management-app/src/header/header.scss index a06b94ffc..11b4c4cb2 100644 --- a/packages/esm-bed-management-app/src/header/header.scss +++ b/packages/esm-bed-management-app/src/header/header.scss @@ -18,6 +18,21 @@ flex-direction: row; align-items: center; cursor: pointer; + + & > div:nth-child(2) { + margin: layout.$spacing-05; + + & > p:last-child { + white-space: nowrap; + @include type.type-style('heading-04'); + } + } +} + +.inPatientPictogram { + width: 4.5rem; + height: 4.5rem; + fill: var(--brand-03); } .rightJustifiedItems { @@ -27,19 +42,6 @@ padding-top: layout.$spacing-04; } -.pageName { - white-space: nowrap; - @include type.type-style('heading-04'); -} - -.pageLabels { - margin: layout.$spacing-05; -} - -.middot { - margin: 0 layout.$spacing-03; -} - .dateAndLocation { display: flex; justify-content: flex-end; @@ -54,19 +56,3 @@ .middot { margin: 0 layout.$spacing-03; } - -.view { - @include type.type-style('label-01'); -} - -svg.iconOverrides { - width: 4.5rem !important; - height: 4.5rem !important; - fill: var(--brand-03); -} - -.svgContainer svg { - width: 4.5rem; - height: 4.5rem; - fill: var(--brand-03); -} diff --git a/packages/esm-bed-management-app/src/header/illustration.component.tsx b/packages/esm-bed-management-app/src/header/illustration.component.tsx deleted file mode 100644 index 210ae6a59..000000000 --- a/packages/esm-bed-management-app/src/header/illustration.component.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { Stethoscope } from '@carbon/react/icons'; -import styles from './header.scss'; - -const Illustration: React.FC = () => { - return ( -
- -
- ); -}; - -export default Illustration; diff --git a/packages/esm-bed-management-app/src/home.component.tsx b/packages/esm-bed-management-app/src/home.component.tsx index 0d8e79a19..f2f761223 100644 --- a/packages/esm-bed-management-app/src/home.component.tsx +++ b/packages/esm-bed-management-app/src/home.component.tsx @@ -1,12 +1,15 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import BedManagementSummary from './summary/summary.component'; import Header from './header/header.component'; import styles from './home.scss'; const Home: React.FC = () => { + const { t } = useTranslation(); + return (
-
+
); diff --git a/packages/esm-bed-management-app/src/index.ts b/packages/esm-bed-management-app/src/index.ts index a42726af6..36c0226fa 100644 --- a/packages/esm-bed-management-app/src/index.ts +++ b/packages/esm-bed-management-app/src/index.ts @@ -22,6 +22,7 @@ export const adminCardLink = getAsyncLifecycle(() => import('./admin-card-link.c export const summaryLeftPanelLink = getSyncLifecycle( createLeftPanelLink({ name: 'bed-management', + // t('summary', 'Summary') title: 'Summary', }), options, @@ -29,24 +30,27 @@ export const summaryLeftPanelLink = getSyncLifecycle( export const adminLeftPanelLink = getSyncLifecycle( createLeftPanelLink({ - name: 'administration', - title: 'Ward Allocation', + name: 'bed-administration', + // t('wardAllocation', 'Ward allocation') + title: 'Ward allocation', }), options, ); export const bedTypeLeftPanelLink = getSyncLifecycle( createLeftPanelLink({ - name: 'bed-type', - title: 'Bed Type', + name: 'bed-types', + // t('bedTypes', 'Bed types') + title: 'Bed types', }), options, ); export const bedTagLeftPanelLink = getSyncLifecycle( createLeftPanelLink({ - name: 'bed-tag', - title: 'Bed Tag', + name: 'bed-tags', + // t('bedTags', 'Bed tags') + title: 'Bed tags', }), options, ); diff --git a/packages/esm-bed-management-app/src/root.component.tsx b/packages/esm-bed-management-app/src/root.component.tsx index 993eeb2ea..f1d04fa16 100644 --- a/packages/esm-bed-management-app/src/root.component.tsx +++ b/packages/esm-bed-management-app/src/root.component.tsx @@ -28,9 +28,9 @@ const Root: React.FC = () => { } /> } /> - } /> - } /> - } /> + } /> + } /> + } /> diff --git a/packages/esm-bed-management-app/src/routes.json b/packages/esm-bed-management-app/src/routes.json index 7cfba80a5..fb76dd360 100644 --- a/packages/esm-bed-management-app/src/routes.json +++ b/packages/esm-bed-management-app/src/routes.json @@ -36,21 +36,6 @@ "name": "bed-management-home-dashboard-link", "slot": "bed-management-left-panel-slot", "order": 0 - }, - { - "component": "bedAdmission", - "name": "bed-admission-dashboard", - "slot": "bed-admission-dashboard-slot" - }, - { - "name": "bed-admission-dashboard-link", - "component": "bedAdmissionDashboardLink", - "slot": "homepage-dashboard-slot", - "meta": { - "name": "bed-admission", - "slot": "bed-admission-dashboard-slot", - "title": "Bed-admission" - } } ] } diff --git a/packages/esm-bed-management-app/src/summary/summary.resource.ts b/packages/esm-bed-management-app/src/summary/summary.resource.ts index 48e8e1e97..240493d88 100644 --- a/packages/esm-bed-management-app/src/summary/summary.resource.ts +++ b/packages/esm-bed-management-app/src/summary/summary.resource.ts @@ -142,7 +142,7 @@ export function useBedsGroupedByLocation() { isValidatingBedsGroupedByLocation: isValidating, mutateBedsGroupedByLocation: mutate, }), - [result, error, isLoading, isValidating, mutate], + [error, isLoading, isLoadingAdmissionLocations, isValidating, mutate, result], ); return results; diff --git a/packages/esm-bed-management-app/src/ward-with-beds/ward-with-beds.component.tsx b/packages/esm-bed-management-app/src/ward-with-beds/ward-with-beds.component.tsx index 10bb2b380..6219cf862 100644 --- a/packages/esm-bed-management-app/src/ward-with-beds/ward-with-beds.component.tsx +++ b/packages/esm-bed-management-app/src/ward-with-beds/ward-with-beds.component.tsx @@ -93,7 +93,7 @@ const WardWithBeds: React.FC = () => { return ( <> -
+
{isLoadingBeds && (
diff --git a/packages/esm-bed-management-app/translations/am.json b/packages/esm-bed-management-app/translations/am.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/am.json +++ b/packages/esm-bed-management-app/translations/am.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/ar.json b/packages/esm-bed-management-app/translations/ar.json index dba1d2c0e..0c5600155 100644 --- a/packages/esm-bed-management-app/translations/ar.json +++ b/packages/esm-bed-management-app/translations/ar.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "إنشاء علامة السرير", + "addBedType": "أضف نوع السرير", "allocationStatus": "Allocated", + "bedDescription": "وصف السرير", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "إدارة السرير", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "الموقع", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", - "save": "Save", - "saveSuccessMessage": "was saved successfully.", + "save": "حفظ", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/de.json b/packages/esm-bed-management-app/translations/de.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/de.json +++ b/packages/esm-bed-management-app/translations/de.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/en.json b/packages/esm-bed-management-app/translations/en.json index ac668f7d9..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/en.json +++ b/packages/esm-bed-management-app/translations/en.json @@ -12,16 +12,16 @@ "bedName": "Bed name", "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed tag", "bedTagCreated": "Bed tag created", "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", + "bedTags": "Bed tags", "bedTagUpdated": "Bed tag updated", "bedTagUpdatedSuccessfully": "", - "bedType": "Bed type", "bedTypeCreated": "Bed type created", "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", "bedTypeUpdated": "Bed type updated", "bedTypeUpdatedSuccessfully": "", "bedUpdated": "Bed updated", @@ -60,7 +60,8 @@ "required": "Required", "save": "Save", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/es.json b/packages/esm-bed-management-app/translations/es.json index 3a191820d..bfff0a577 100644 --- a/packages/esm-bed-management-app/translations/es.json +++ b/packages/esm-bed-management-app/translations/es.json @@ -1,50 +1,66 @@ { "actions": "Acciones", - "addBed": "Añadir cama", - "addBedTag": "Crear Etiqueta de Cama", - "addBedtype": "Añadir Tipo de Cama", - "addBedType": "Crear Tipo de Cama", - "allocationStatus": "Asignado", + "addBed": "Agregar cama", + "addBedTag": "Crear etiqueta de cama", + "addBedType": "Agregar tipo de cama", + "allocationStatus": "Asignada", + "bedDescription": "Descripción de la cama", "bedId": "ID de la Cama", - "bedIdPlaceholder": "p. ej. BMW-201", "bedLocation": "Ubicación", - "bedManagement": "Administración de Camas", - "bedName": "Nombre de la Cama", + "bedManagement": "Administración de camas", + "bedManagementLeftPanel": "Panel izquierdo de administración de camas", + "bedName": "Nombre de cama", + "bedNumber": "Número de cama", "beds": "Camas", - "bedTag": "Nombre de la Etiqueta de la Cama", + "bedTagCreated": "Etiqueta de cama creada", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Añadir Tipo de Cama", + "bedTags": "Bed tags", + "bedTagUpdated": "Etiqueta de cama actualizada", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Tipo de cama creado", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", - "checkFilters": "Compruebe los filtros anteriores", + "bedTypes": "Bed types", + "bedTypeUpdated": "Tipo de cama actualizado", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Cama actualizada", + "bedUpdatedSuccessfully": "", + "checkFilters": "Revise los filtros de arriba", "chooseBedtype": "Elija un tipo de cama", "chooseOccupiedStatus": "Elija el estado ocupado", + "createBedTag": "Crear etiqueta de cama", + "createBedType": "Crear tipo de cama", "createNewBed": "Crear una nueva cama", "description": "Descripción", - "displayName": "Mostrar Nombre", + "displayName": "Nombre para mostrar", "displayNamePlaceholder": "", - "editBed": "Editar etiqueta", + "editBed": "Editar cama", "editBedTag": "Editar Etiqueta de la Cama", - "editBedType": "Editar Tipo de Cama", + "editBedType": "Editar tipo de cama", + "editTag": "Editar Etiqueta", + "enterBedDescription": "Ingrese la descripción de la cama", + "enterBedNumber": "p. ej. BMW-201", "error": "Error", - "errorCreatingForm": "Error al crear cama", + "errorCreatingBedTag": "Error al crear la etiqueta de la cama", + "errorCreatingForm": "Error al crear la cama", "errorFetchingbedInformation": "Error al obtener información de la cama", "filterByOccupancyStatus": "Filtrar por estado de ocupación", - "formCreated": "Añadir Tipo de Cama", - "formSaved": "Tipo de Cama", - "headerTitle": "", "ids": "Id", - "location": "Ubicaciones", - "manageBeds": "Administrar Camas", + "location": "Ubicación", + "manageBeds": "Administrar camas", "name": "Nombre", + "newBedCreated": "Nueva cama creada", "no": "No", "No data": "No hay datos para mostrar", + "noDataToDisplay": "No hay datos para mostrar", "occupancyStatus": "Ocupada", "or": "o", "pleaseFillField": "", - "required": "Obligatorio", + "required": "Requerido", "save": "Guardar", - "saveSuccessMessage": "fue guardado exitosamente.", "selectBedLocation": "Seleccione una ubicación para la cama", + "summary": "Summary", "viewBeds": "Ver camas", "wardAllocation": "Asignación de sala", "yes": "Sí" diff --git a/packages/esm-bed-management-app/translations/fr.json b/packages/esm-bed-management-app/translations/fr.json index 2ed9929f3..397fddeb6 100644 --- a/packages/esm-bed-management-app/translations/fr.json +++ b/packages/esm-bed-management-app/translations/fr.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Ajouter un lit", - "addBedTag": "Créer une étiquette de lit", - "addBedtype": "Ajouter type de lit", - "addBedType": "Créer Type de lit", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Attribué", + "bedDescription": "Bed description", "bedId": "Identifiant du Lit", - "bedIdPlaceholder": "exemple : BMW-201", "bedLocation": "Emplacement", - "bedManagement": "Gestion du lit", - "bedName": "Nom du lit", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Lits", - "bedTag": "Nom de l'étiquette du lit", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Ajouter un type de lit", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Vérifiez les filtres ci-dessus", "chooseBedtype": "Choisir un type de lit", "chooseOccupiedStatus": " Choisissez le statut occupé", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Créer un nouveau lit", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Afficher un tag", + "editBed": "Edit bed", "editBedTag": "Afficher un Tag de lit", - "editBedType": "Afficher un type de lit", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Erreur", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Erreur lors de la création du lit", "errorFetchingbedInformation": " Erreur lors de la récupération des informations sur le lit", "filterByOccupancyStatus": "Filtrer par statut d'occupation", - "formCreated": "Ajouter un type de lit", - "formSaved": "Type de lit", - "headerTitle": "", "ids": "Identifiant", - "location": "Emplacements", - "manageBeds": "Gestion des lits", + "location": "Location", + "manageBeds": "Manage beds", "name": "Nom", + "newBedCreated": "New bed created", "no": "Non", "No data": "Pas de données à afficher", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupé", "or": "ou", "pleaseFillField": "", "required": "Requis", "save": "Enregistrer", - "saveSuccessMessage": "a été enregistré avec succès.", "selectBedLocation": "Sélectionner un emplacement de lit", + "summary": "Summary", "viewBeds": "Voir lits", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Oui" } diff --git a/packages/esm-bed-management-app/translations/he.json b/packages/esm-bed-management-app/translations/he.json index dba1d2c0e..1ac562924 100644 --- a/packages/esm-bed-management-app/translations/he.json +++ b/packages/esm-bed-management-app/translations/he.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "יצירת תגית מיטה", + "addBedType": "הוספת סוג מיטה", "allocationStatus": "Allocated", + "bedDescription": "תיאור מיטה", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "ניהול מיטות", + "bedManagementLeftPanel": "לוח ימני לניהול מיטות", + "bedName": "שם מיטה", + "bedNumber": "מספר מיטה", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "נוצרה תגית מיטה", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "תגיות מיטות", + "bedTagUpdated": "תגית מיטה עודכנה", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "נוצר סוג מיטה", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "סוג המיטה עודכן", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "המיטה עודכנה", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "יצירת תגית מיטה", + "createBedType": "יצירת סוג מיטה", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "שם תצוגה", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "עריכת מיטה", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "עריכת סוג מיטה", + "editTag": "עריכת תגית", + "enterBedDescription": "נא למלא תיאור מיטה", + "enterBedNumber": "למשל: ילד-201", "error": "Error", + "errorCreatingBedTag": "שגיאה ביצירת תגית מיטה", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "מקום", + "manageBeds": "ניהול מיטות", "name": "Name", + "newBedCreated": "נוצרה מיטה חדשה", "no": "No", "No data": "No data to display", + "noDataToDisplay": "אין נתונים להצגה", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/hi.json b/packages/esm-bed-management-app/translations/hi.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/hi.json +++ b/packages/esm-bed-management-app/translations/hi.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/hi_IN.json b/packages/esm-bed-management-app/translations/hi_IN.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/hi_IN.json +++ b/packages/esm-bed-management-app/translations/hi_IN.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/id.json b/packages/esm-bed-management-app/translations/id.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/id.json +++ b/packages/esm-bed-management-app/translations/id.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/it.json b/packages/esm-bed-management-app/translations/it.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/it.json +++ b/packages/esm-bed-management-app/translations/it.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/km.json b/packages/esm-bed-management-app/translations/km.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/km.json +++ b/packages/esm-bed-management-app/translations/km.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/ne.json b/packages/esm-bed-management-app/translations/ne.json new file mode 100644 index 000000000..d19a3e425 --- /dev/null +++ b/packages/esm-bed-management-app/translations/ne.json @@ -0,0 +1,67 @@ +{ + "actions": "Actions", + "addBed": "Add bed", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", + "allocationStatus": "Allocated", + "bedDescription": "Bed description", + "bedId": "Bed ID", + "bedLocation": "Location", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", + "beds": "Beds", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", + "bedTagPlaceholder": "", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", + "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", + "checkFilters": "Check the filters above", + "chooseBedtype": "Choose a bed type", + "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", + "createNewBed": "Create a new bed", + "description": "Description", + "displayName": "Display name", + "displayNamePlaceholder": "", + "editBed": "Edit bed", + "editBedTag": "Edit Bed Tag", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", + "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", + "errorCreatingForm": "Error creating bed", + "errorFetchingbedInformation": "Error fetching bed information", + "filterByOccupancyStatus": "Filter by occupancy status", + "ids": "Id", + "location": "Location", + "manageBeds": "Manage beds", + "name": "Name", + "newBedCreated": "New bed created", + "no": "No", + "No data": "No data to display", + "noDataToDisplay": "No data to display", + "occupancyStatus": "Occupied", + "or": "or", + "pleaseFillField": "", + "required": "Required", + "save": "Save", + "selectBedLocation": "Select a bed location", + "summary": "Summary", + "viewBeds": "View beds", + "wardAllocation": "Ward allocation", + "yes": "Yes" +} diff --git a/packages/esm-bed-management-app/translations/pt.json b/packages/esm-bed-management-app/translations/pt.json index 4faed92b3..92b55c06a 100644 --- a/packages/esm-bed-management-app/translations/pt.json +++ b/packages/esm-bed-management-app/translations/pt.json @@ -1,51 +1,67 @@ { "actions": "Ações", "addBed": "Adicionar cama", - "addBedTag": "Criar Etiqueta de Cama", - "addBedtype": "Adicionar Tipo de Cama", - "addBedType": "Criar tipo de cama", + "addBedTag": "Criar etiqueta da cama", + "addBedType": "Adicionar tipo de cama", "allocationStatus": "Alocada", + "bedDescription": "Descrição da cama", "bedId": "ID da Cama", - "bedIdPlaceholder": "exemplo: BMW-201", "bedLocation": "Local", - "bedManagement": "Gestão de Camas", + "bedManagement": "Gestão de Cama", + "bedManagementLeftPanel": "Gestão de Cama - Painel esquerdo", "bedName": "Nome da Cama", + "bedNumber": "Número da Cama", "beds": "Camas", - "bedTag": "Nome da Etiqueta de Cama", + "bedTagCreated": "Etiqueta de Cama Criada", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Adicionar Tipo de Cama", + "bedTags": "Etiquetas de Cama", + "bedTagUpdated": "Etiqueta de Cama Atualizada", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Tipo de cama criado", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Tipos de Cama", + "bedTypeUpdated": "Tipo de cama atualizado", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Cama atualizada", + "bedUpdatedSuccessfully": "", "checkFilters": "Verifique os filtros acima", "chooseBedtype": "Selecione o tipo de cama", "chooseOccupiedStatus": "Selecione o estado de ocupação ", + "createBedTag": "Criar etiqueta de cama", + "createBedType": "Criar tipo de cama", "createNewBed": "Criar nova cama", "description": "Descrição", - "displayName": "Nome de visualização", + "displayName": "Mostrar nome", "displayNamePlaceholder": "", - "editBed": "Editar Etiqueta", + "editBed": "Editar Cama", "editBedTag": "Editar Etiqueta da Cama", - "editBedType": "Editar Tipo de Cama", + "editBedType": "Editar tipo de Cama", + "editTag": "Editar etiqueta", + "enterBedDescription": "Escreva a descrição da cama", + "enterBedNumber": "ex.: ENF-201", "error": "Erro", + "errorCreatingBedTag": "Erro ao criar etiqueta da cama", "errorCreatingForm": "Erro ao criar cama", "errorFetchingbedInformation": "Erro ao buscar informações de cama", "filterByOccupancyStatus": "Filtrar por status de ocupação", - "formCreated": "Adicionar Tipo de Cama", - "formSaved": "Tipo de Cama", - "headerTitle": "", "ids": "Identificador", - "location": "Locais", - "manageBeds": "Gerir Camas", + "location": "Localização", + "manageBeds": "Gerir camas.", "name": "Nome", + "newBedCreated": "Novo cama criada.", "no": "Não", "No data": "Não há dados para visualizar", + "noDataToDisplay": "Sem dados para mostrar", "occupancyStatus": "Ocupado", "or": "ou", "pleaseFillField": "", "required": "Obrigatório", "save": "Salvar", - "saveSuccessMessage": "foi gravado com sucesso", "selectBedLocation": "Selecione a localização da cama", + "summary": "Summary", "viewBeds": "Visualizar camas", - "wardAllocation": "Enfermaria Alocada", + "wardAllocation": "Enfermaria alocada", "yes": "Sim" } diff --git a/packages/esm-bed-management-app/translations/qu.json b/packages/esm-bed-management-app/translations/qu.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/qu.json +++ b/packages/esm-bed-management-app/translations/qu.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/si.json b/packages/esm-bed-management-app/translations/si.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/si.json +++ b/packages/esm-bed-management-app/translations/si.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/sw.json b/packages/esm-bed-management-app/translations/sw.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/sw.json +++ b/packages/esm-bed-management-app/translations/sw.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/sw_KE.json b/packages/esm-bed-management-app/translations/sw_KE.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/sw_KE.json +++ b/packages/esm-bed-management-app/translations/sw_KE.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/tr.json b/packages/esm-bed-management-app/translations/tr.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/tr.json +++ b/packages/esm-bed-management-app/translations/tr.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/tr_TR.json b/packages/esm-bed-management-app/translations/tr_TR.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/tr_TR.json +++ b/packages/esm-bed-management-app/translations/tr_TR.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/uk.json b/packages/esm-bed-management-app/translations/uk.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/uk.json +++ b/packages/esm-bed-management-app/translations/uk.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/vi.json b/packages/esm-bed-management-app/translations/vi.json index 5fcdeda69..eca9a59d7 100644 --- a/packages/esm-bed-management-app/translations/vi.json +++ b/packages/esm-bed-management-app/translations/vi.json @@ -1,50 +1,66 @@ { "actions": "Hành động", "addBed": "Thêm giường", - "addBedTag": "Tạo thẻ giường", - "addBedtype": "Thêm loại giường", - "addBedType": "Tạo loại giường", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Đã phân bổ", + "bedDescription": "Bed description", "bedId": "ID", - "bedIdPlaceholder": "Ví dụ: BMW-201", "bedLocation": "Vị trí", - "bedManagement": "Quản lý giường bệnh", - "bedName": "Tên giường", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Giường", - "bedTag": "Tên thẻ giường", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Thêm loại thẻ giường", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Kiểm tra các bộ lọc ở trên", "chooseBedtype": "Chọn loại giường", "chooseOccupiedStatus": "Chọn trạng thái đã chiếm đóng", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Tạo một giường mới", "description": "Mô tả", - "displayName": "Tên hiển thị", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Sủa thẻ", + "editBed": "Edit bed", "editBedTag": "Sửa thẻ giường", - "editBedType": "Sửa loại giường", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Lỗi", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Lỗi khi tạo giường", "errorFetchingbedInformation": "Lỗi khi tìm kiếm thông tin giường", "filterByOccupancyStatus": "Lọc theo tình trạng cư trú", - "formCreated": "Thêm loại giường", - "formSaved": "Loại giường", - "headerTitle": "", "ids": "Id", - "location": "Vị trí", - "manageBeds": "Quản lý giường bệnh", + "location": "Location", + "manageBeds": "Manage beds", "name": "Tên", + "newBedCreated": "New bed created", "no": "Số", "No data": "Không có dữ liệu hiển thị", + "noDataToDisplay": "No data to display", "occupancyStatus": "Đã sử dụng", "or": "hoặc", "pleaseFillField": "", "required": "Yêu cầu", "save": "Lưu", - "saveSuccessMessage": "đã lưu thành công", "selectBedLocation": "Chọn một vị trí giường", + "summary": "Summary", "viewBeds": "Xem giường", "wardAllocation": "Phân bổ giường", "yes": "Có" diff --git a/packages/esm-bed-management-app/translations/zh.json b/packages/esm-bed-management-app/translations/zh.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/zh.json +++ b/packages/esm-bed-management-app/translations/zh.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-bed-management-app/translations/zh_CN.json b/packages/esm-bed-management-app/translations/zh_CN.json index dba1d2c0e..d19a3e425 100644 --- a/packages/esm-bed-management-app/translations/zh_CN.json +++ b/packages/esm-bed-management-app/translations/zh_CN.json @@ -1,51 +1,67 @@ { "actions": "Actions", "addBed": "Add bed", - "addBedTag": "Create Bed Tag", - "addBedtype": "Add Bed Type", - "addBedType": "Create Bed type", + "addBedTag": "Create bed tag", + "addBedType": "Add bed type", "allocationStatus": "Allocated", + "bedDescription": "Bed description", "bedId": "Bed ID", - "bedIdPlaceholder": "e.g. BMW-201", "bedLocation": "Location", - "bedManagement": "Bed Management", - "bedName": "Bed Name", + "bedManagement": "Bed management", + "bedManagementLeftPanel": "Bed management left panel", + "bedName": "Bed name", + "bedNumber": "Bed number", "beds": "Beds", - "bedTag": "Bed Tag Name", + "bedTagCreated": "Bed tag created", + "bedTagCreatedSuccessfully": "", "bedTagPlaceholder": "", - "bedType": "Add Bed Type", + "bedTags": "Bed tags", + "bedTagUpdated": "Bed tag updated", + "bedTagUpdatedSuccessfully": "", + "bedTypeCreated": "Bed type created", + "bedTypeCreatedSuccessfully": "", "bedTypePlaceholder": "", + "bedTypes": "Bed types", + "bedTypeUpdated": "Bed type updated", + "bedTypeUpdatedSuccessfully": "", + "bedUpdated": "Bed updated", + "bedUpdatedSuccessfully": "", "checkFilters": "Check the filters above", "chooseBedtype": "Choose a bed type", "chooseOccupiedStatus": "Choose occupied status", + "createBedTag": "Create bed tag", + "createBedType": "Create bed type", "createNewBed": "Create a new bed", "description": "Description", - "displayName": "Display Name", + "displayName": "Display name", "displayNamePlaceholder": "", - "editBed": "Edit Tag", + "editBed": "Edit bed", "editBedTag": "Edit Bed Tag", - "editBedType": "Edit Bed Type", + "editBedType": "Edit bed type", + "editTag": "Edit Tag", + "enterBedDescription": "Enter the bed description", + "enterBedNumber": "e.g. BMW-201", "error": "Error", + "errorCreatingBedTag": "Error creating bed tag", "errorCreatingForm": "Error creating bed", "errorFetchingbedInformation": "Error fetching bed information", "filterByOccupancyStatus": "Filter by occupancy status", - "formCreated": "Add bed Type", - "formSaved": "Bed Type", - "headerTitle": "", "ids": "Id", - "location": "Locations", - "manageBeds": "Manage Beds", + "location": "Location", + "manageBeds": "Manage beds", "name": "Name", + "newBedCreated": "New bed created", "no": "No", "No data": "No data to display", + "noDataToDisplay": "No data to display", "occupancyStatus": "Occupied", "or": "or", "pleaseFillField": "", "required": "Required", "save": "Save", - "saveSuccessMessage": "was saved successfully.", "selectBedLocation": "Select a bed location", + "summary": "Summary", "viewBeds": "View beds", - "wardAllocation": "Ward Allocation", + "wardAllocation": "Ward allocation", "yes": "Yes" } diff --git a/packages/esm-patient-list-management-app/package.json b/packages/esm-patient-list-management-app/package.json index 02cfa77bc..7fb7edcdd 100644 --- a/packages/esm-patient-list-management-app/package.json +++ b/packages/esm-patient-list-management-app/package.json @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "dexie": "^3.0.3", "fuzzy": "^0.1.3", "lodash-es": "^4.17.15" diff --git a/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx b/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx index 6c6b5b345..e3ec1d27d 100644 --- a/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx +++ b/packages/esm-patient-list-management-app/src/add-patient/add-patient.component.tsx @@ -39,7 +39,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { const key = `${restBaseUrl}/cohortm/cohortmember?patient=${patientUuid}&v=custom:(uuid,patient:ref,cohort:(uuid,name,startDate,endDate))`; return mutate((k) => typeof k === 'string' && k === key); - }, []); + }, [patientUuid]); const handleSubmit = useCallback(() => { Promise.all( @@ -67,7 +67,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { }); }), ).finally(closeModal); - }, [data, selected, closeModal, t, patientUuid]); + }, [selected, closeModal, data, mutateCohortMembers, t]); const searchResults = useMemo(() => { if (!data) { @@ -88,7 +88,7 @@ const AddPatient: React.FC = ({ closeModal, patientUuid }) => { if (currentPage !== 1) { goTo(1); } - }, [searchValue]); + }, [currentPage, goTo, searchValue]); return (
diff --git a/packages/esm-patient-list-management-app/src/header/header.component.tsx b/packages/esm-patient-list-management-app/src/header/header.component.tsx new file mode 100644 index 000000000..c585e96b0 --- /dev/null +++ b/packages/esm-patient-list-management-app/src/header/header.component.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Add } from '@carbon/react/icons'; +import { Button } from '@carbon/react'; +import { PageHeader, PageHeaderContent, PatientListsPictogram } from '@openmrs/esm-framework'; +import styles from './header.scss'; + +interface HeaderProps { + handleShowNewListOverlay: () => void; +} + +const Header: React.FC = ({ handleShowNewListOverlay }) => { + const { t } = useTranslation(); + return ( + + } /> + + + ); +}; + +export default Header; diff --git a/packages/esm-patient-list-management-app/src/header/header.scss b/packages/esm-patient-list-management-app/src/header/header.scss index 4de1f6efc..34b3a77a2 100644 --- a/packages/esm-patient-list-management-app/src/header/header.scss +++ b/packages/esm-patient-list-management-app/src/header/header.scss @@ -2,51 +2,13 @@ @use '@carbon/type'; @use '@openmrs/esm-styleguide/src/vars' as *; -.patientListHeader { - @include type.type-style('body-compact-02'); - color: $text-02; - height: layout.$spacing-12; - display: flex; - justify-content: space-between; -} - -.leftJustifiedItems { - display: flex; - flex-direction: row; - align-items: center; -} - -.rightJustifiedItems { - @include type.type-style('body-compact-02'); - display: flex; - flex-direction: column; - color: $text-02; - padding: layout.$spacing-05 0; - justify-content: space-between; -} - -.date { - display: flex; - justify-content: flex-end; - align-items: center; - margin-right: layout.$spacing-05; - - svg { - margin-right: layout.$spacing-03; - } -} - -.pageName { - @include type.type-style('heading-04'); -} - -.pageLabels { - p:first-of-type { - margin-bottom: layout.$spacing-02; - } +.header { + background-color: $ui-02; + border-bottom: 1px solid $ui-03; } .newListButton { - align-self: flex-end; width: fit-content; + height: fit-content; + margin: layout.$spacing-05 0; } diff --git a/packages/esm-patient-list-management-app/src/list-details/list-details.component.tsx b/packages/esm-patient-list-management-app/src/list-details/list-details.component.tsx index fdfd2bb02..b530ccc57 100644 --- a/packages/esm-patient-list-management-app/src/list-details/list-details.component.tsx +++ b/packages/esm-patient-list-management-app/src/list-details/list-details.component.tsx @@ -2,7 +2,7 @@ import React, { useState, useCallback, useMemo } from 'react'; import classNames from 'classnames'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { OverflowMenuItem, Modal } from '@carbon/react'; +import { Modal, OverflowMenuItem } from '@carbon/react'; import { OverflowMenuVertical } from '@carbon/react/icons'; import { navigate, formatDate, parseDate, showSnackbar, CustomOverflowMenu } from '@openmrs/esm-framework'; import { deletePatientList } from '../api/api-remote'; diff --git a/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.component.tsx b/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.component.tsx index 1b825c9a9..a5bf74d75 100755 --- a/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.component.tsx +++ b/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.component.tsx @@ -2,12 +2,11 @@ import React, { useMemo, useState } from 'react'; import classnames from 'classnames'; import { useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Tab, Tabs, TabList, Button } from '@carbon/react'; -import { Add } from '@carbon/react/icons'; -import { PageHeader, PageHeaderContent, PatientListsPictogram } from '@openmrs/esm-framework'; +import { Tab, Tabs, TabList } from '@carbon/react'; import { type PatientListFilter, PatientListType } from '../api/types'; import { useAllPatientLists } from '../api/hooks'; import CreateEditPatientList from '../create-edit-patient-list/create-edit-list.component'; +import Header from '../header/header.component'; import ListsTable from '../lists-table/lists-table.component'; import styles from './lists-dashboard.scss'; @@ -61,43 +60,33 @@ const ListsDashboard: React.FC = () => { return (
- - } /> - - - { - setSelectedTab(selectedIndex); - }} - selectedIndex={selectedTab} - tabContentClassName={styles.hiddenTabsContent}> - - {t('starredLists', 'Starred lists')} - {t('systemLists', 'System lists')} - {t('myLists', 'My lists')} - {t('allLists', 'All lists')} - - -
- +
+
+ { + setSelectedTab(selectedIndex); + }} + selectedIndex={selectedTab} + tabContentClassName={styles.hiddenTabsContent}> + + {t('starredLists', 'Starred lists')} + {t('systemLists', 'System lists')} + {t('myLists', 'My lists')} + {t('allLists', 'All lists')} + + +
+ +
diff --git a/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.scss b/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.scss index 1b5b66def..8c1d07a5b 100644 --- a/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.scss +++ b/packages/esm-patient-list-management-app/src/lists-dashboard/lists-dashboard.scss @@ -6,26 +6,23 @@ margin: layout.$spacing-05 0; } -.newListButton { - width: fit-content; - height: fit-content; - margin: 1rem 0; -} - .dashboardContainer { display: flex; background-color: $ui-02; } .dashboard { - height: 100%; width: 100%; background-color: $ui-02; height: calc(100vh - layout.$spacing-09); } +.tabsContainer { + margin: layout.$spacing-05 0; +} + .tabs { - grid-column: 'span 2'; + grid-column: span 2; } .tablist { diff --git a/packages/esm-patient-list-management-app/src/lists-table/lists-table.component.tsx b/packages/esm-patient-list-management-app/src/lists-table/lists-table.component.tsx index ad657e335..b8554bf01 100755 --- a/packages/esm-patient-list-management-app/src/lists-table/lists-table.component.tsx +++ b/packages/esm-patient-list-management-app/src/lists-table/lists-table.component.tsx @@ -269,19 +269,22 @@ function useStarredLists() { setStarredLists(starredPatientLists.split(',')); }, [currentUser?.userProperties?.starredPatientLists, setStarredLists]); - const updateUserProperties = (newStarredLists: Array) => { - const starredPatientLists = newStarredLists.join(','); - const userProperties = { ...(currentUser?.userProperties ?? {}), starredPatientLists }; + const updateUserProperties = useCallback( + (newStarredLists: Array) => { + const starredPatientLists = newStarredLists.join(','); + const userProperties = { ...(currentUser?.userProperties ?? {}), starredPatientLists }; - starPatientList(currentUser?.uuid, userProperties).catch(() => { - setInitialStarredLists(); - showSnackbar({ - subtitle: t('starringPatientListFailed', 'Marking patient lists starred / unstarred failed'), - kind: 'error', - title: 'Failed to update patient lists', + starPatientList(currentUser?.uuid, userProperties).catch(() => { + setInitialStarredLists(); + showSnackbar({ + subtitle: t('starringPatientListFailed', 'Marking patient lists starred / unstarred failed'), + kind: 'error', + title: 'Failed to update patient lists', + }); }); - }); - }; + }, + [currentUser?.userProperties, currentUser?.uuid, setInitialStarredLists, t], + ); /** * Handles toggling the starred list diff --git a/packages/esm-patient-list-management-app/translations/ar.json b/packages/esm-patient-list-management-app/translations/ar.json index 4f3a9c83e..88be9bc32 100644 --- a/packages/esm-patient-list-management-app/translations/ar.json +++ b/packages/esm-patient-list-management-app/translations/ar.json @@ -31,7 +31,7 @@ "errorUpdatingList": "Error updating list", "identifier": "المعرف", "items": "العناصر", - "itemsDisplayed": "{{numberOfItemsDisplayed}} items", + "itemsDisplayed": "{{numberOfItemsDisplayed}} العناصر", "listCreated": "List created successfully", "listDescriptionPlaceholder": "مثل: المرضى الذين تم تشخيصهم بالربو والذين قد يكونون على استعداد للمشاركة في دراسة جامعية", "listName": "اسم القائمة", diff --git a/packages/esm-patient-list-management-app/translations/he.json b/packages/esm-patient-list-management-app/translations/he.json index e0c87b8d5..ed5d7e7b8 100644 --- a/packages/esm-patient-list-management-app/translations/he.json +++ b/packages/esm-patient-list-management-app/translations/he.json @@ -47,7 +47,7 @@ "newPatientListNameLabel": "שם הרשימה", "nextPage": "עמוד הבא", "noMatchingLists": "אין רשימות תואמות להצגה", - "noMatchingListsFound": "No matching lists found", + "noMatchingListsFound": "לא נמצאו רשימות מתאימות", "noMatchingPatients": "אין מטופלים תואמים להצגה", "noOfPatients": "מספר מטופלים", "noPatientsInList": "אין מטופלים ברשימה זו", @@ -76,7 +76,7 @@ "successfullyAdded": "נוסף בהצלחה", "systemDefined": "הוגדר על ידי המערכת", "systemLists": "רשימות מערכת", - "trySearchingForADifferentList": "Try searching for a different list", + "trySearchingForADifferentList": "נא לנסות לחפש רשימה אחרת", "unstarList": "Unstar list", "updated": "עודכן", "userDefined": "הוגדר על ידי המשתמש" diff --git a/packages/esm-patient-list-management-app/translations/ne.json b/packages/esm-patient-list-management-app/translations/ne.json new file mode 100644 index 000000000..03432c1a8 --- /dev/null +++ b/packages/esm-patient-list-management-app/translations/ne.json @@ -0,0 +1,83 @@ +{ + "actions": "Actions", + "addPatientToList": "Add patient to list", + "addToList": "Add to list", + "allLists": "All lists", + "backToListsPage": "Back to lists page", + "cancel": "Cancel", + "checkFilters": "Check the filters above", + "configureList": "Configure your patient list using the fields below", + "confirmDeletePatientList": "Are you sure you want to delete this patient list?", + "created": "Created", + "createdOn": "Created on", + "createList": "Create list", + "createNewPatientList": "Create new patient list", + "createPatientList": "Create patient list", + "deleted": "Deleted", + "deletedPatientList": "Deleted patient list", + "deletePatientList": "Delete patient list", + "editList": "Edit list", + "editNameDescription": "Edit name or description", + "editPatientListHeader": "Edit patient list", + "emptyList": "This list has no patients", + "emptyStateIllustration": "Empty state illustration", + "emptyStateText": "There are no {{listType}} patient lists to display", + "error": "Error", + "errorAddPatientToList": "Patient not added to list", + "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.", + "errorCreatingList": "Error creating list", + "errorDeletingList": "Error deleting patient list", + "errorRemovingPatientFromList": "Failed to remove patient from list", + "errorUpdatingList": "Error updating list", + "identifier": "Identifier", + "items": "items", + "itemsDisplayed": "{{numberOfItemsDisplayed}} items", + "listCreated": "List created successfully", + "listDescriptionPlaceholder": "e.g. Patients with diagnosed asthma who may be willing to be a part of a university research study", + "listName": "List name", + "listNamePlaceholder": "e.g. Potential research participants", + "listType": "List type", + "listUpdated": "List updated successfully", + "listUpToDate": "The list is now up to date", + "myLists": "My lists", + "name": "Name", + "newList": "New list", + "newPatientListDescriptionLabel": "Describe the purpose of this list in a few words", + "newPatientListHeader": "New patient list", + "newPatientListNameLabel": "List name", + "nextPage": "Next page", + "noMatchingLists": "No matching lists to display", + "noMatchingListsFound": "No matching lists found", + "noMatchingPatients": "No matching patients to display", + "noOfPatients": "No. of patients", + "noPatientsInList": "There are no patients in this list", + "openPatientList": "Add to list", + "patientListMemberCount_one": "This list has {{count}} patient", + "patientListMemberCount_other": "This list has {{count}} patients", + "patientLists": "Patient lists", + "patientRemovedFromList": "Patient removed from list", + "patients": "patients", + "previousPage": "Previous page", + "problemCreatingList": "There was a problem creating the list", + "problemUpdatingList": "There was a problem updating the list", + "removeFromList": "Remove from list", + "removePatientFromListConfirmation": "Are you sure you want to remove {{patientName}} from this list?", + "searchForAListToAddThisPatientTo": "Search for a list to add this patient to.", + "searchForList": "Search for a list", + "searchThisList": "Search this list", + "sex": "Sex", + "starList": "Star list", + "starred": "starred", + "starredLists": "Starred lists", + "starringPatientListFailed": "Marking patient lists starred / unstarred failed", + "startDate": "Start Date", + "submitting": "Submitting", + "successAddPatientToList": "Patient added to list", + "successfullyAdded": "Successfully added", + "systemDefined": "system-defined", + "systemLists": "System lists", + "trySearchingForADifferentList": "Try searching for a different list", + "unstarList": "Unstar list", + "updated": "Updated", + "userDefined": "user-defined" +} diff --git a/packages/esm-patient-list-management-app/translations/pt.json b/packages/esm-patient-list-management-app/translations/pt.json index 2213f0d0b..d4b865cff 100644 --- a/packages/esm-patient-list-management-app/translations/pt.json +++ b/packages/esm-patient-list-management-app/translations/pt.json @@ -14,7 +14,7 @@ "createNewPatientList": "Criar nova lista de utentes", "createPatientList": "Criar lista de utente", "deleted": "Excluído", - "deletedPatientList": "Lista de utentes excluída", + "deletedPatientList": "Lista de utentes apagada", "deletePatientList": "Apagar lista de utente", "editList": "Editar a lista", "editNameDescription": "Editar nome ou descrição", @@ -42,7 +42,7 @@ "myLists": "Minhas listas", "name": "Nome", "newList": "Nova lista", - "newPatientListDescriptionLabel": "Descreva em poucas palavras o proposito desta lista", + "newPatientListDescriptionLabel": "Descreva em poucas palavras o propósito desta lista", "newPatientListHeader": "Nova lista de utente", "newPatientListNameLabel": "Nome da Lista", "nextPage": "Próxima página", @@ -62,12 +62,12 @@ "problemUpdatingList": "Ocorreu um problema ao actualizar a lista", "removeFromList": "Remover da lista", "removePatientFromListConfirmation": "Tem certeza de que deseja remover {{patientName}} desta fila?", - "searchForAListToAddThisPatientTo": "Procure uma lista à qual a adicionar este utente.", + "searchForAListToAddThisPatientTo": "Procure uma lista à qual adicionar este utente.", "searchForList": "Procure uma lista", "searchThisList": "Pesquisar esta lista", "sex": "Sexo", "starList": "Lista de estrelas", - "starred": "estrelado", + "starred": "com estrela", "starredLists": "Listas com estrela", "starringPatientListFailed": "Falha ao marcar listas de utentes com estrela/sem estrela", "startDate": "Data inicial", diff --git a/packages/esm-patient-list-management-app/translations/zh.json b/packages/esm-patient-list-management-app/translations/zh.json index e980be62d..695dfcddf 100644 --- a/packages/esm-patient-list-management-app/translations/zh.json +++ b/packages/esm-patient-list-management-app/translations/zh.json @@ -29,7 +29,7 @@ "errorDeletingList": "删除患者列表时出现错误", "errorRemovingPatientFromList": "未能从列表中移除患者", "errorUpdatingList": "更新列表时出现错误", - "identifier": "ID", + "identifier": "Identifier", "items": "项", "itemsDisplayed": "{{numberOfItemsDisplayed}} 项", "listCreated": "列表创建成功", diff --git a/packages/esm-patient-registration-app/package.json b/packages/esm-patient-registration-app/package.json index 5c0cbd36b..204e1ec42 100644 --- a/packages/esm-patient-registration-app/package.json +++ b/packages/esm-patient-registration-app/package.json @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "formik": "^2.1.5", "lodash-es": "^4.17.15", "uuid": "^8.3.2", diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx index d763dfd3d..8a04666a6 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/address/address-search.component.tsx @@ -14,8 +14,11 @@ const AddressSearchComponent: React.FC = ({ address const separator = ' > '; const searchBox = useRef(null); const wrapper = useRef(null); - const [searchString, setSearchString] = useState(''); - const { addresses, isLoading, error } = useAddressHierarchy(searchString, separator); + + const [searchString, setSearchString] = useState(''); + + const { addresses } = useAddressHierarchy(searchString, separator); + const addressOptions: Array = useMemo(() => { const options: Set = new Set(); addresses.forEach((address) => { diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx index 0a27740f4..95b04468c 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx @@ -35,7 +35,7 @@ function DeathDateField() { selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined, ); }, - [deathDate], + [setFieldValue], ); return ( diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx index 6599103ec..efbaa6b9b 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/dob/dob.component.tsx @@ -41,7 +41,7 @@ export const DobField: React.FC = () => { setFieldValue('monthsEstimated', ''); setFieldTouched('birthdateEstimated', true, false); }, - [setFieldValue], + [setFieldTouched, setFieldValue], ); const onDateChange = useCallback( diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx index 75fa4663a..b2964df44 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/gender/gender-field.component.tsx @@ -30,8 +30,12 @@ export const GenderField: React.FC = () => {

{t('sexFieldLabelText', 'Sex')}

-

{t('genderLabelText', 'Sex')}

- + {fieldConfigs.map((option) => ( = ({ clo const { identifierTypes } = useContext(ResourcesContext); const { isOffline, values, initialFormValues } = useContext(PatientRegistrationContext); const [unsavedIdentifierTypes, setUnsavedIdentifierTypes] = useState(values.identifiers); - const [searchString, setSearchString] = useState(''); + const [searchString, setSearchString] = useState(''); const { t } = useTranslation(); const { defaultPatientIdentifierTypes } = useConfig(); const defaultPatientIdentifierTypesMap = useMemo(() => { @@ -107,11 +107,11 @@ const PatientIdentifierOverlay: React.FC = ({ clo /> {patientIdentifier && identifierType?.identifierSources?.length > 0 && - /* + /* This check are for the cases when there's an initialValue identifier is assigned to the patient The corresponding flow is like: - 1. If there's no change to the actual initial identifier, then the source remains null, + 1. If there's no change to the actual initial identifier, then the source remains null, hence the list of the identifier sources shouldn't be displayed. 2. If user wants to edit the patient identifier's value, hence there will be an initialValue, along with a source assigned to itself(only if the identifierType has sources, else there's nothing to worry about), which by diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx index f2b4e2147..8de9fe809 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/name/name-field.component.tsx @@ -3,9 +3,9 @@ import { useTranslation } from 'react-i18next'; import { ContentSwitcher, Switch } from '@carbon/react'; import { useField } from 'formik'; import { ExtensionSlot, useConfig } from '@openmrs/esm-framework'; +import { type RegistrationConfig } from '../../../config-schema'; import { Input } from '../../input/basic-input/input/input.component'; import { PatientRegistrationContext } from '../../patient-registration-context'; -import { type RegistrationConfig } from '../../../config-schema'; import styles from '../field.scss'; export const unidentifiedPatientAttributeTypeUuid = '8b56eac7-5c76-4b9c-8c6f-1deab8d3fc47'; @@ -51,7 +51,7 @@ export const NameField = () => { setFieldTouched('photo', true, false); } }, - [setCapturePhotoProps], + [setCapturePhotoProps, setFieldTouched], ); const toggleNameKnown = (e) => { diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx index e5b06a050..d72e935cd 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx @@ -3,10 +3,10 @@ import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { Field } from 'formik'; import { Layer, Select, SelectItem } from '@carbon/react'; +import { reportError } from '@openmrs/esm-framework'; import { type PersonAttributeTypeResponse } from '../../patient-registration.types'; import { useConceptAnswers } from '../field.resource'; import styles from './../field.scss'; -import { reportError } from '@openmrs/esm-framework'; export interface CodedPersonAttributeFieldProps { id: string; @@ -44,7 +44,7 @@ export function CodedPersonAttributeField({ ); setError(true); } - }, [answerConceptSetUuid, customConceptAnswers]); + }, [answerConceptSetUuid, customConceptAnswers, id, t]); useEffect(() => { if (!isLoadingConceptAnswers && !customConceptAnswers.length) { @@ -72,7 +72,7 @@ export function CodedPersonAttributeField({ setError(true); } } - }, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers]); + }, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers, t, id, answerConceptSetUuid]); const answers = useMemo(() => { if (customConceptAnswers.length) { diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx index dda9aa8a0..fd6ec4526 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx @@ -4,19 +4,19 @@ import { render, screen } from '@testing-library/react'; import { useConceptAnswers } from '../field.resource'; import { CodedPersonAttributeField } from './coded-person-attribute-field.component'; -jest.mock('formik', () => ({ - ...jest.requireActual('formik'), -})); - -jest.mock('../field.resource'); - const mockUseConceptAnswers = jest.mocked(useConceptAnswers); +jest.mock('../field.resource', () => ({ + ...jest.requireActual('../field.resource'), + useConceptAnswers: jest.fn(), +})); + describe('CodedPersonAttributeField', () => { const conceptAnswers = [ { uuid: '1', display: 'Option 1' }, { uuid: '2', display: 'Option 2' }, ]; + const personAttributeType = { format: 'org.openmrs.Concept', display: 'Referred by', @@ -24,26 +24,35 @@ describe('CodedPersonAttributeField', () => { name: '', description: '', }; + const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c'; + let consoleSpy: jest.SpyInstance; beforeEach(() => { mockUseConceptAnswers.mockReturnValue({ data: conceptAnswers, isLoading: false, + error: null, }); + + consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); }); - it('shows error if there is no concept answer set provided', () => { + afterEach(() => { + consoleSpy.mockRestore(); + }); + + it('renders an error if there is no concept answer set provided', () => { expect(() => { render( {}}> @@ -52,11 +61,13 @@ describe('CodedPersonAttributeField', () => { }).toThrow(expect.stringMatching(/has been defined without an answer concept set UUID/i)); }); - it('shows error if the concept answer set does not have any concept answers', () => { + it('renders an error if the concept answer set does not have any concept answers', () => { mockUseConceptAnswers.mockReturnValue({ data: [], isLoading: false, + error: null, }); + expect(() => { render( {}}> diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx index ba1156a23..3fcf15b74 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx @@ -25,7 +25,7 @@ export function LocationPersonAttributeField({ const { t } = useTranslation(); const fieldName = `attributes.${personAttributeType.uuid}`; const [field, meta, { setValue }] = useField(`attributes.${personAttributeType.uuid}`); - const [searchQuery, setSearchQuery] = useState(''); + const [searchQuery, setSearchQuery] = useState(''); const { locations, isLoading, loadingNewData } = useLocations(locationTag || null, searchQuery); const prevLocationOptions = useRef([]); diff --git a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx index 115f94b36..371490026 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx @@ -1,13 +1,20 @@ import React from 'react'; import { Form, Formik } from 'formik'; import { render, screen } from '@testing-library/react'; +import { type FieldDefinition } from '../../../config-schema'; import { usePersonAttributeType } from './person-attributes.resource'; import { useConceptAnswers } from '../field.resource'; -import { type FieldDefinition } from '../../../config-schema'; import { PersonAttributeField } from './person-attribute-field.component'; -jest.mock('./person-attributes.resource'); -jest.mock('../field.resource'); +jest.mock('./person-attributes.resource', () => ({ + ...jest.requireActual('./person-attributes.resource'), + usePersonAttributeType: jest.fn(), +})); + +jest.mock('../field.resource', () => ({ + ...jest.requireActual('../field.resource'), + useConceptAnswers: jest.fn(), +})); const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType); const mockUseConceptAnswers = jest.mocked(useConceptAnswers); @@ -92,6 +99,7 @@ describe('PersonAttributeField', () => { { uuid: '1', display: 'Option 1' }, { uuid: '2', display: 'Option 2' }, ], + error: null, isLoading: false, }); @@ -180,6 +188,6 @@ describe('PersonAttributeField', () => { ); await screen.findByRole('heading', { name: /attribute/i }); - expect(screen.queryByLabelText(/Referred by/i)).not.toBeInTheDocument(); + expect(screen.queryByLabelText(/referred by/i)).not.toBeInTheDocument(); }); }); diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts index b54a3ee48..16c9b2667 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.test.ts @@ -2,7 +2,10 @@ import { FormManager } from './form-manager'; import { type FormValues } from './patient-registration.types'; import { generateIdentifier } from './patient-registration.resource'; -jest.mock('./patient-registration.resource'); +jest.mock('./patient-registration.resource', () => ({ + ...jest.requireActual('./patient-registration.resource'), + generateIdentifier: jest.fn(), +})); const mockGenerateIdentifier = generateIdentifier as jest.Mock; diff --git a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts index 531e8dc44..26b6b9304 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/form-manager.ts @@ -35,7 +35,6 @@ import { updateRelationship, } from './patient-registration.resource'; import { type RegistrationConfig } from '../config-schema'; -import dayjs from 'dayjs'; export type SavePatientForm = ( isNewPatient: boolean, diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts index ff50d4b20..b89aed9c6 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-hooks.ts @@ -1,14 +1,15 @@ +import { type Dispatch, useEffect, useMemo, useState } from 'react'; import { type FetchResponse, + type OpenmrsResource, getSynchronizationItems, openmrsFetch, - type OpenmrsResource, restBaseUrl, useConfig, usePatient, } from '@openmrs/esm-framework'; import camelCase from 'lodash-es/camelCase'; -import { type Dispatch, useEffect, useMemo, useState } from 'react'; +import dayjs from 'dayjs'; import useSWR from 'swr'; import { v4 } from 'uuid'; import { type RegistrationConfig } from '../config-schema'; @@ -29,7 +30,15 @@ import { latestFirstEncounter, } from './patient-registration-utils'; import { useInitialPatientRelationships } from './section/patient-relationships/relationships.resource'; -import dayjs from 'dayjs'; + +interface DeathInfoResults { + uuid: string; + display: string; + causeOfDeath: OpenmrsResource | null; + dead: boolean; + deathDate: string; + causeOfDeathNonCoded: string | null; +} export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch] { const { freeTextFieldConceptUuid } = useConfig(); @@ -99,7 +108,7 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch setInitialFormValues(registration._patientRegistrationData.formValues); } })(); - }, [isLoadingPatientToEdit, patientToEdit, patientUuid]); + }, [initialFormValues, isLoadingPatientToEdit, patientToEdit, patientUuid]); // Set initial patient death info useEffect(() => { @@ -118,7 +127,7 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch nonCodedCauseOfDeath: deathInfo.causeOfDeathNonCoded, })); } - }, [isLoadingDeathInfo, deathInfo, setInitialFormValues]); + }, [isLoadingDeathInfo, deathInfo, setInitialFormValues, freeTextFieldConceptUuid]); // Set initial patient relationships useEffect(() => { @@ -187,7 +196,7 @@ export function useInitialAddressFieldValues(patientUuid: string, fallback = {}) setInitialAddressFieldValues(registration?._patientRegistrationData.initialAddressFieldValues ?? fallback); } })(); - }, [isLoading, patient, patientUuid]); + }, [fallback, initialAddressFieldValues, isLoading, patient, patientUuid]); return [initialAddressFieldValues, setInitialAddressFieldValues]; } @@ -208,7 +217,7 @@ export function usePatientUuidMap( setPatientUuidMap(registration?._patientRegistrationData.initialAddressFieldValues ?? fallback), ); } - }, [isLoadingPatientToEdit, patientToEdit, patientUuid]); + }, [fallback, isLoadingPatientToEdit, patientToEdit, patientUuid, patientUuidMap]); useEffect(() => { if (attributes) { @@ -263,7 +272,7 @@ export function useInitialPatientIdentifiers(patientUuid: string): { data: identifiers, isLoading, }; - }, [data, error]); + }, [data?.data?.results, isLoading]); return result; } @@ -299,19 +308,10 @@ function useInitialPersonAttributes(personUuid: string) { data: data?.data?.results, isLoading, }; - }, [data, error]); + }, [data?.data?.results, isLoading]); return result; } -interface DeathInfoResults { - uuid: string; - display: string; - causeOfDeath: OpenmrsResource | null; - dead: boolean; - deathDate: string; - causeOfDeathNonCoded: string | null; -} - function useInitialPersonDeathInfo(personUuid: string) { const { data, error, isLoading } = useSWR, Error>( !!personUuid @@ -325,7 +325,7 @@ function useInitialPersonDeathInfo(personUuid: string) { data: data?.data, isLoading, }; - }, [data, error]); + }, [data?.data, isLoading]); return result; } diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.test.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.test.ts new file mode 100644 index 000000000..f5b636b88 --- /dev/null +++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.test.ts @@ -0,0 +1,33 @@ +import { filterOutUndefinedPatientIdentifiers } from './patient-registration-utils'; + +describe('filterOutUndefinedPatientIdentifiers', () => { + const getIdentifiers = (autoGeneration = true, manualEntryEnabled = false) => ({ + OpenMRSId: { + autoGeneration: autoGeneration, + identifierName: 'OpenMRS ID', + identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + identifierValue: undefined, + initialValue: '100GEJ', + preferred: true, + required: true, + selectedSource: { + uuid: '01af8526-cea4-4175-aa90-340acb411771', + name: 'Generator for OpenMRS ID', + autoGenerationOption: { + manualEntryEnabled: manualEntryEnabled, + automaticGenerationEnabled: autoGeneration, + }, + }, + }, + }); + + it('should fitler out undefined identifiers', () => { + const filteredIdentifiers = filterOutUndefinedPatientIdentifiers(getIdentifiers()); + expect(filteredIdentifiers.OpenMRSId).not.toBeDefined(); + }); + + it('should retain auto-generated identifiers with manual entry', () => { + const filteredIdentifiers = filterOutUndefinedPatientIdentifiers(getIdentifiers(true, true)); + expect(filteredIdentifiers.OpenMRSId).toBeDefined(); + }); +}); diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts index 3aa777a2c..971543b95 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts +++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration-utils.ts @@ -200,10 +200,13 @@ export function getPhonePersonAttributeValueFromFhirPatient(patient: fhir.Patien return result; } -export const filterOutUndefinedPatientIdentifiers = (patientIdentifiers) => +type IdentifierMap = { [identifierFieldName: string]: PatientIdentifierValue }; +export const filterOutUndefinedPatientIdentifiers = (patientIdentifiers: IdentifierMap): IdentifierMap => Object.fromEntries( - Object.entries(patientIdentifiers).filter( - ([key, value]) => value.identifierValue !== undefined, + Object.entries(patientIdentifiers).filter( + ([key, value]) => + (value.autoGeneration && value.selectedSource.autoGenerationOption.manualEntryEnabled) || + value.identifierValue !== undefined, ), ); diff --git a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx index f2c93fd80..caa603f0e 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/patient-registration.test.tsx @@ -11,21 +11,24 @@ import { useConfig, usePatient, } from '@openmrs/esm-framework'; +import type { AddressTemplate, Encounter, FormValues } from './patient-registration.types'; import { mockedAddressTemplate } from '__mocks__'; import { mockPatient } from 'tools'; import { saveEncounter, savePatient } from './patient-registration.resource'; import { esmPatientRegistrationSchema, type RegistrationConfig } from '../config-schema'; -import type { AddressTemplate, Encounter } from './patient-registration.types'; import { ResourcesContext } from '../offline.resources'; import { FormManager } from './form-manager'; import { PatientRegistration } from './patient-registration.component'; +import { useInitialFormValues } from './patient-registration-hooks'; +const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker); const mockSaveEncounter = jest.mocked(saveEncounter); const mockSavePatient = savePatient as jest.Mock; const mockShowSnackbar = jest.mocked(showSnackbar); const mockUseConfig = jest.mocked(useConfig); const mockUsePatient = jest.mocked(usePatient); -const mockOpenmrsDatePicker = jest.mocked(OpenmrsDatePicker); +const mockUseParams = useParams as jest.Mock; +const mockUseInitialFormValues = jest.mocked(useInitialFormValues); jest.mock('./field/field.resource', () => ({ useConcept: jest.fn().mockImplementation((uuid: string) => { @@ -85,7 +88,7 @@ jest.mock('./field/field.resource', () => ({ })); jest.mock('react-router-dom', () => ({ - ...(jest.requireActual('react-router-dom') as any), + ...jest.requireActual('react-router-dom'), useLocation: () => ({ pathname: 'openmrs/spa/patient-registration', }), @@ -99,6 +102,13 @@ jest.mock('./patient-registration.resource', () => ({ savePatient: jest.fn(), })); +jest.mock('./patient-registration-hooks', () => ({ + ...jest.requireActual('./patient-registration-hooks'), + useInitialFormValues: jest.fn().mockReturnValue([{}, jest.fn()]), + useInitialAddressFieldValues: jest.fn().mockReturnValue([{}, jest.fn()]), + usePatientUuidMap: jest.fn().mockReturnValue([{}, jest.fn()]), +})); + mockOpenmrsDatePicker.mockImplementation(({ id, labelText, value, onChange }) => { return ( <> @@ -126,7 +136,7 @@ const mockResourcesContextValue = { identifierTypes: [], }; -let mockOpenmrsConfig: RegistrationConfig = { +const mockOpenmrsConfig: RegistrationConfig = { sections: ['demographics', 'contact'], sectionDefinitions: [ { id: 'demographics', name: 'Demographics', fields: ['name', 'gender', 'dob'] }, @@ -159,6 +169,10 @@ let mockOpenmrsConfig: RegistrationConfig = { value: 'male', label: 'Male', }, + { + value: 'female', + label: 'Female', + }, ], address: { useAddressHierarchy: { @@ -180,11 +194,10 @@ let mockOpenmrsConfig: RegistrationConfig = { encounterProviderRoleUuid: 'asdf', registrationFormUuid: null, }, + freeTextFieldConceptUuid: '', }; - -const path = `/patient/:patientUuid/edit`; - const configWithObs = JSON.parse(JSON.stringify(mockOpenmrsConfig)); + configWithObs.fieldDefinitions = [ { id: 'weight', @@ -217,7 +230,6 @@ configWithObs.fieldDefinitions = [ customConceptAnswers: [], }, ]; - configWithObs.sectionDefinitions?.push({ id: 'custom', name: 'Custom', @@ -238,16 +250,14 @@ const fillRequiredFields = async () => { await user.type(familyNameInput, 'Gaihre'); await user.clear(dateOfBirthInput); await user.type(dateOfBirthInput, '02/08/1993'); - user.click(genderInput); + await user.click(genderInput); }; -function Wrapper({ children }) { - return ( - - {children} - - ); -} +const Wrapper = ({ children }) => ( + + {children} + +); describe('Registering a new patient', () => { beforeEach(() => { @@ -258,15 +268,30 @@ describe('Registering a new patient', () => { mockSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true }); }); - it('renders without crashing', () => { + it('should render all the required fields and sections', async () => { render(, { wrapper: Wrapper }); - }); - it('has the expected sections', async () => { - render(, { wrapper: Wrapper }); + await screen.findByRole('heading', { name: /create new patient/i }); + + const demographicSection = screen.getByRole('region', { name: /demographics section/i }); + const contactSection = screen.getByRole('region', { name: /contact info section/i }); + + expect(demographicSection).toBeInTheDocument(); + expect(contactSection).toBeInTheDocument(); + expect(screen.getByText(/jump to/i)).toBeInTheDocument(); + expect(within(demographicSection).getByLabelText(/first name/i)).toBeInTheDocument(); + expect(within(demographicSection).getByLabelText(/middle name \(optional\)/i)).toBeInTheDocument(); + expect(within(demographicSection).getByLabelText(/family name/i)).toBeInTheDocument(); + expect(within(demographicSection).getByLabelText(/date of birth/i)).toBeInTheDocument(); + expect(within(demographicSection).getByRole('radio', { name: /^male$/i })).toBeInTheDocument(); + expect(within(demographicSection).getByRole('radio', { name: /^female$/i })).toBeInTheDocument(); + expect(within(demographicSection).getByText(/date of birth known\?/i)).toBeInTheDocument(); + expect(within(demographicSection).getByLabelText(/date of birth/i)).toBeInTheDocument(); - expect(screen.getByRole('region', { name: /demographics section/i })).toBeInTheDocument(); - expect(screen.getByRole('region', { name: /contact info section/i })).toBeInTheDocument(); + expect(within(contactSection).getByRole('heading', { name: /address/i })).toBeInTheDocument(); + + expect(screen.getByRole('button', { name: /register patient/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); }); // TODO O3-3482: Fix this test case when OpenmrsDatePicker gets fixed on core @@ -300,14 +325,12 @@ describe('Registering a new patient', () => { it('should not save the patient if validation fails', async () => { const user = userEvent.setup(); - const mockSavePatientForm = jest.fn(); - render(, { wrapper: Wrapper }); - const givenNameInput = (await screen.findByLabelText('First Name')) as HTMLInputElement; + render(, { wrapper: Wrapper }); - await user.type(givenNameInput, '5'); - await user.click(screen.getByText(/Register Patient/i)); + await screen.findByRole('heading', { name: /create new patient/i }); + await user.click(screen.getByRole('button', { name: /register patient/i })); expect(mockSavePatientForm).not.toHaveBeenCalled(); }); @@ -386,76 +409,149 @@ describe('Registering a new patient', () => { describe('Updating an existing patient record', () => { beforeEach(() => { - mockUseConfig.mockReturnValue(mockOpenmrsConfig); + mockUseConfig.mockReturnValue({ + ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), + ...mockOpenmrsConfig, + }); + mockUsePatient.mockImplementation(() => { + return { + error: null, + isLoading: false, + patient: mockPatient, + patientUuid: mockPatient.id, + }; + }); mockSavePatient.mockReturnValue({ data: { uuid: 'new-pt-uuid' }, ok: true }); + mockUseParams.mockReturnValue({ patientUuid: mockPatient.id }); }); it('edits patient demographics', async () => { const user = userEvent.setup(); - mockSavePatient.mockResolvedValue({} as FetchResponse); + const mockSavePatientForm = jest.fn(); - const mockUseParams = useParams as jest.Mock; + mockUseInitialFormValues.mockReturnValue([ + { + additionalFamilyName: '', + additionalGivenName: '', + additionalMiddleName: '', + addNameInLocalLanguage: false, + address: {}, + birthdate: mockPatient.birthDate, + birthdateEstimated: false, + deathCause: '', + deathDate: undefined, + deathTime: undefined, + deathTimeFormat: 'AM', + familyName: mockPatient.name[0].family, + gender: mockPatient.gender, + givenName: mockPatient.name[0].given[0], + identifiers: { + openMrsId: { + autoGeneration: false, + identifierName: 'OpenMRS ID', + identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + identifierUuid: '1f0ad7a1-430f-4397-b571-59ea654a52db', + identifierValue: '100GEJ', + initialValue: '100GEJ', + preferred: true, + required: true, + selectedSource: null, + }, + idCard: { + autoGeneration: false, + identifierName: 'ID Card', + identifierTypeUuid: 'b4143563-16cd-4439-b288-f83d61670fc8', + identifierUuid: '346d09b1-8509-43c6-9697-3b4d1ce06ad6', + identifierValue: '1234567890', + initialValue: '1234567890', + preferred: false, + required: false, + selectedSource: null, + }, + }, + isDead: false, + middleName: '', + monthsEstimated: 0, + nonCodedCauseOfDeath: '', + patientUuid: mockPatient.id, + relationships: [], + telephoneNumber: '', + yearsEstimated: 0, + } as FormValues, + jest.fn(), + ]); - mockUseParams.mockReturnValue({ patientUuid: mockPatient.id }); + render(, { wrapper: Wrapper }); - mockUsePatient.mockReturnValue({ - isLoading: false, - patient: mockPatient, - patientUuid: mockPatient.id, - error: null, - }); + await screen.findByRole('heading', { name: /edit patient details/i }); - render(, { wrapper: Wrapper }); - - const givenNameInput: HTMLInputElement = screen.getByLabelText(/First Name/); - const familyNameInput: HTMLInputElement = screen.getByLabelText(/Family Name/); - const middleNameInput: HTMLInputElement = screen.getByLabelText(/Middle Name/); - const dateOfBirthInput: HTMLInputElement = screen.getByLabelText(/Date of Birth/i); - const genderInput: HTMLInputElement = screen.getByLabelText(/Male/); - - // assert initial values - expect(givenNameInput.value).toBe('John'); - expect(familyNameInput.value).toBe('Wilson'); - expect(middleNameInput.value).toBeFalsy(); - expect(dateOfBirthInput.value).toBe('04/04/1972'); - expect(genderInput.value).toBe('male'); - - // do some edits - await user.clear(givenNameInput); - await user.clear(middleNameInput); - await user.clear(familyNameInput); - await user.type(givenNameInput, 'Eric'); - await user.type(middleNameInput, 'Johnson'); - await user.type(familyNameInput, 'Smith'); - await user.click(screen.getByText(/Update patient/i)); + expect(screen.queryByRole('button', { name: /register patient/i })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: /update patient/i })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument(); - expect(mockSavePatient).toHaveBeenCalledWith( + expect(screen.getByLabelText(/first name/i)).toHaveValue(mockPatient.name[0].given[0]); + expect(screen.getByLabelText(/family name/i)).toHaveValue(mockPatient.name[0].family); + expect(screen.getByLabelText(/date of birth/i)).toHaveValue('04/04/1972'); + expect( + screen.getByRole('radio', { + name: /^male$/i, + }), + ).toBeChecked(); + expect( + screen.getByRole('radio', { + name: /^female$/i, + }), + ).not.toBeChecked(); + expect(screen.getAllByRole('tab', { name: /yes/i })).toHaveLength(2); + + await user.click(screen.getByRole('button', { name: /update patient/i })); + + expect(mockSavePatientForm).toHaveBeenCalledWith( false, { - '0': { - oldIdentificationNumber: '100732HE', - }, - '1': { - openMrsId: '100GEJ', - }, - addNameInLocalLanguage: undefined, + addNameInLocalLanguage: false, additionalFamilyName: '', additionalGivenName: '', additionalMiddleName: '', - address: {}, - birthdate: new Date('1972-04-04T00:00:00.000Z'), + address: { + country: 'កម្ពុជា (Cambodia)', + }, + birthdate: '1972-04-04', birthdateEstimated: false, deathCause: '', nonCodedCauseOfDeath: '', deathDate: undefined, deathTime: undefined, deathTimeFormat: 'AM', - familyName: 'Smith', - gender: expect.stringMatching(/male/i), - givenName: 'Eric', - identifiers: {}, + familyName: 'Wilson', + gender: 'male', + givenName: 'John', + identifiers: { + idCard: { + autoGeneration: false, + identifierName: 'ID Card', + identifierTypeUuid: 'b4143563-16cd-4439-b288-f83d61670fc8', + identifierUuid: '346d09b1-8509-43c6-9697-3b4d1ce06ad6', + identifierValue: '1234567890', + initialValue: '1234567890', + preferred: false, + required: false, + selectedSource: null, + }, + openMrsId: { + autoGeneration: false, + identifierName: 'OpenMRS ID', + identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', + identifierUuid: '1f0ad7a1-430f-4397-b571-59ea654a52db', + identifierValue: '100GEJ', + initialValue: '100GEJ', + preferred: true, + required: true, + selectedSource: null, + }, + }, isDead: false, - middleName: 'Johnson', + middleName: '', monthsEstimated: 0, patientUuid: '8673ee4f-e2ab-4077-ba55-4980f408773e', relationships: [], diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/demographics/demographics-section.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/section/demographics/demographics-section.component.tsx index 3ab1adb22..cf2e9074e 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/section/demographics/demographics-section.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/section/demographics/demographics-section.component.tsx @@ -1,8 +1,8 @@ import React, { useContext, useEffect } from 'react'; -import styles from './../section.scss'; import { useField } from 'formik'; -import { PatientRegistrationContext } from '../../patient-registration-context'; import { Field } from '../../field/field.component'; +import { PatientRegistrationContext } from '../../patient-registration-context'; +import styles from './../section.scss'; export interface DemographicsSectionProps { fields: Array; @@ -18,7 +18,7 @@ export const DemographicsSection: React.FC = ({ fields setFieldValue('additionalMiddleName', ''); setFieldValue('additionalFamilyName', ''); } - }, [field.value, meta.touched]); + }, [field.value, meta.touched, setFieldValue]); return (
diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships-section.component.tsx b/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships-section.component.tsx index 5076a31e6..9162f1f58 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships-section.component.tsx @@ -40,7 +40,7 @@ const RelationshipView: React.FC = ({ }) => { const { t } = useTranslation(); const { setFieldValue } = React.useContext(PatientRegistrationContext); - const [isInvalid, setIsInvalid] = useState(false); + const [isInvalid, setIsInvalid] = useState(false); const newRelationship = !relationship.uuid; const handleRelationshipTypeChange = useCallback( diff --git a/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships.resource.tsx b/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships.resource.tsx index 643639167..810f374ac 100644 --- a/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships.resource.tsx +++ b/packages/esm-patient-registration-app/src/patient-registration/section/patient-relationships/relationships.resource.tsx @@ -4,6 +4,33 @@ import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-fram import { type RelationshipValue } from '../../patient-registration.types'; import { personRelationshipRepresentation } from '../../../constants'; +export interface Relationship { + display: string; + uuid: string; + personA: { + age: number; + display: string; + birthdate: string; + uuid: string; + }; + personB: { + age: number; + display: string; + birthdate: string; + uuid: string; + }; + relationshipType: { + uuid: string; + display: string; + aIsToB: string; + bIsToA: string; + }; +} + +interface RelationshipsResponse { + results: Array; +} + export function useInitialPatientRelationships(patientUuid: string): { data: Array; isLoading: boolean; @@ -45,34 +72,7 @@ export function useInitialPatientRelationships(patientUuid: string): { error, isLoading, }; - }, [patientUuid, data, error]); + }, [data?.data?.results, error, isLoading, patientUuid]); return result; } - -export interface Relationship { - display: string; - uuid: string; - personA: { - age: number; - display: string; - birthdate: string; - uuid: string; - }; - personB: { - age: number; - display: string; - birthdate: string; - uuid: string; - }; - relationshipType: { - uuid: string; - display: string; - aIsToB: string; - bIsToA: string; - }; -} - -interface RelationshipsResponse { - results: Array; -} diff --git a/packages/esm-patient-registration-app/translations/ar.json b/packages/esm-patient-registration-app/translations/ar.json index 845f8d14f..f4f8cc29c 100644 --- a/packages/esm-patient-registration-app/translations/ar.json +++ b/packages/esm-patient-registration-app/translations/ar.json @@ -20,12 +20,12 @@ "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?", "contactSection": "تفاصيل الاتصال", "createNewPatient": "Create new patient", - "dateOfBirthLabelText": "Date of birth", + "dateOfBirthLabelText": "تاريخ الميلاد", "deathCauseRequired": "Cause of death is required", "deathDateInFuture": "Death date cannot be in future", - "deathDateInputLabel": "Date of death", + "deathDateInputLabel": "تاريخ الوفاة", "deathDateRequired": "Death date is required", - "deathdayInvalidDate": "Death date and time cannot be before the birthday", + "deathdayInvalidDate": "لا يمكن أن يكون تاريخ ووقت الوفاة قبل عيد الميلاد", "deathdayIsRequired": "Death date is required when the patient is marked as deceased.", "deathdayNotInTheFuture": "", "deathSection": "معلومات الوفاة", @@ -82,7 +82,7 @@ "patientNameKnown": "هل اسم المريض معروف؟", "patientRegistrationBreadcrumb": "Patient Registration", "refreshOrContactAdmin": "Try refreshing the page or contact your system administrator", - "registerPatient": "Register patient", + "registerPatient": "تسجيل المريض", "registerPatientSuccessSnackbarSubtitle": "The patient can now be found by searching for them using their name or ID number", "registerPatientSuccessSnackbarTitle": "New Patient Created", "registrationErrorSnackbarTitle": "Patient Registration Failed", @@ -99,7 +99,7 @@ "restoreRelationshipActionButton": "تراجع", "searchAddress": "ابحث عن العنوان", "searchIdentifierPlaceholder": "Search identifier", - "searchLocationPersonAttribute": "Search location", + "searchLocationPersonAttribute": "موقع البحث", "selectAnOption": "اختر خيارًا", "sexFieldLabelText": "الجنس", "source": "Source", diff --git a/packages/esm-patient-registration-app/translations/es.json b/packages/esm-patient-registration-app/translations/es.json index dda9f8b6a..7360c4b59 100644 --- a/packages/esm-patient-registration-app/translations/es.json +++ b/packages/esm-patient-registration-app/translations/es.json @@ -77,7 +77,7 @@ "numberInNameDubious": "Número en nombre es dudoso", "obsFieldUnknownDatatype": "El concepto para el campo de observación '{{fieldDefinitionId}}' tiene un tipo de datos desconocido '{{datatypeName}}'", "optional": "Opcional", - "optionalIdentifierLabel": "{{identifierName}} (optional)", + "optionalIdentifierLabel": "{{identifierName}} (opcional)", "other": "Otro", "patientNameKnown": "¿Se sabe el nombre del paciente?", "patientRegistrationBreadcrumb": "Registro de Pacientes", @@ -99,7 +99,7 @@ "restoreRelationshipActionButton": "Deshacer", "searchAddress": "Buscar dirección", "searchIdentifierPlaceholder": "Buscar identificador", - "searchLocationPersonAttribute": "Search location", + "searchLocationPersonAttribute": "Ubicación de búsqueda", "selectAnOption": "Seleccionar una opción", "sexFieldLabelText": "Sexo", "source": "Fuente", diff --git a/packages/esm-patient-registration-app/translations/he.json b/packages/esm-patient-registration-app/translations/he.json index 33073dbb0..b80fc5cbf 100644 --- a/packages/esm-patient-registration-app/translations/he.json +++ b/packages/esm-patient-registration-app/translations/he.json @@ -77,7 +77,7 @@ "numberInNameDubious": "מספר בשם חשוד", "obsFieldUnknownDatatype": "Concept for obs field '{{fieldDefinitionId}}' has unknown datatype '{{datatypeName}}'", "optional": "אופציונלי", - "optionalIdentifierLabel": "{{identifierName}} (optional)", + "optionalIdentifierLabel": "{{identifierName}} (רשות)", "other": "אחר", "patientNameKnown": "שם המטופל ידוע?", "patientRegistrationBreadcrumb": "רישום מטופל", @@ -99,7 +99,7 @@ "restoreRelationshipActionButton": "ביטול", "searchAddress": "חיפוש כתובת", "searchIdentifierPlaceholder": "חיפוש זיהוי", - "searchLocationPersonAttribute": "Search location", + "searchLocationPersonAttribute": "מקום חיפוש", "selectAnOption": "בחר אפשרות", "sexFieldLabelText": "מין", "source": "מקור", diff --git a/packages/esm-patient-registration-app/translations/ne.json b/packages/esm-patient-registration-app/translations/ne.json new file mode 100644 index 000000000..5c21d45fc --- /dev/null +++ b/packages/esm-patient-registration-app/translations/ne.json @@ -0,0 +1,118 @@ +{ + "addRelationshipButtonText": "Add Relationship", + "addressHeader": "Address", + "allFieldsRequiredText": "All fields are required unless marked optional", + "autoGeneratedPlaceholderText": "Auto-generated", + "birthdayNotInTheFuture": "Birthday cannot be in future", + "birthdayNotOver140YearsAgo": "Birthday cannot be more than 140 years ago", + "birthdayRequired": "Birthday is required", + "birthFieldLabelText": "Birth", + "cancel": "Cancel", + "causeOfDeathInputLabel": "Cause of death", + "closeOverlay": "Close overlay", + "codedPersonAttributeAnswerSetEmpty": "The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an answer concept set UUID '{{answerConceptSetUuid}}' that does not have any concept answers.", + "codedPersonAttributeAnswerSetInvalid": "The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an invalid answer concept set UUID '{{answerConceptSetUuid}}'.", + "codedPersonAttributeNoAnswerSet": "The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.", + "configure": "Configure", + "configureIdentifiers": "Configure identifiers", + "confirmDiscardChangesBody": "Your unsaved changes will be lost if you proceed to discard the form", + "confirmDiscardChangesTitle": "Are you sure you want to discard these changes?", + "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?", + "contactSection": "Contact Details", + "createNewPatient": "Create new patient", + "dateOfBirthLabelText": "Date of birth", + "deathCauseRequired": "Cause of death is required", + "deathDateInFuture": "Death date cannot be in future", + "deathDateInputLabel": "Date of death", + "deathDateRequired": "Death date is required", + "deathdayInvalidDate": "Death date and time cannot be before the birthday", + "deathdayIsRequired": "Death date is required when the patient is marked as deceased.", + "deathdayNotInTheFuture": "", + "deathSection": "Death Info", + "deathTimeFormatInvalid": "Time format is invalid", + "deathTimeFormatRequired": "Time format is required", + "deathTimeInvalid": "Time doesn't match the format 'hh:mm'", + "deathTimeRequired": "Death time is required", + "deleteIdentifierModalHeading": "Remove identifier?", + "deleteIdentifierModalText": " has a value of ", + "deleteIdentifierTooltip": "Delete", + "deleteRelationshipTooltipText": "Delete", + "demographicsSection": "Basic Info", + "discard": "Discard", + "dobToggleLabelText": "Date of Birth Known?", + "editIdentifierTooltip": "Edit", + "editPatientDetails": "Edit patient details", + "editPatientDetailsBreadcrumb": "Edit patient details", + "enterNonCodedCauseOfDeath": "Enter non-coded cause of death", + "error": "Error", + "errorFetchingCodedCausesOfDeath": "Error fetching coded causes of death", + "errorFetchingOrderedFields": "Error occured fetching ordered fields for address hierarchy", + "estimatedAgeInMonthsLabelText": "Estimated age in months", + "estimatedAgeInYearsLabelText": "Estimated age in years", + "familyNameLabelText": "Family Name", + "familyNameRequired": "Family name is required", + "female": "Female", + "fieldsWithErrors": "The following fields have errors: ", + "fullNameLabelText": "Full Name", + "genderLabelText": "Sex", + "genderRequired": "Gender is required", + "genderUnspecified": "Gender unspecified", + "givenNameLabelText": "First Name", + "givenNameRequired": "Given name is required", + "identifierValueRequired": "Identifier value is required", + "idFieldLabelText": "Identifiers", + "IDInstructions": "Select the identifiers you'd like to add for this patient:", + "invalidEmail": "Invalid email", + "invalidInput": "Invalid Input", + "isDeadInputLabel": "Is dead", + "jumpTo": "Jump to", + "male": "Male", + "middleNameLabelText": "Middle Name", + "negativeMonths": "Estimated months cannot be negative", + "negativeYears": "Estimated years cannot be negative", + "no": "No", + "nonCodedCauseOfDeath": "Non-coded cause of death", + "nonCodedCauseOfDeathRequired": "Cause of death is required", + "nonsensicalYears": "Estimated years cannot be more than 140", + "numberInNameDubious": "Number in name is dubious", + "obsFieldUnknownDatatype": "Concept for obs field '{{fieldDefinitionId}}' has unknown datatype '{{datatypeName}}'", + "optional": "optional", + "optionalIdentifierLabel": "{{identifierName}} (optional)", + "other": "Other", + "patientNameKnown": "Patient's Name is Known?", + "patientRegistrationBreadcrumb": "Patient Registration", + "refreshOrContactAdmin": "Try refreshing the page or contact your system administrator", + "registerPatient": "Register patient", + "registerPatientSuccessSnackbarSubtitle": "The patient can now be found by searching for them using their name or ID number", + "registerPatientSuccessSnackbarTitle": "New Patient Created", + "registrationErrorSnackbarTitle": "Patient Registration Failed", + "relationship": "Relationship", + "relationshipPersonMustExist": "Related person must be an existing person", + "relationshipPlaceholder": "Relationship", + "relationshipRemovedText": "Relationship removed", + "relationshipsSection": "Relationships", + "relationshipToPatient": "Relationship to patient", + "relativeFullNameLabelText": "Full name", + "relativeNamePlaceholder": "Firstname Familyname", + "removeIdentifierButton": "Remove identifier", + "resetIdentifierTooltip": "Reset", + "restoreRelationshipActionButton": "Undo", + "searchAddress": "Search address", + "searchIdentifierPlaceholder": "Search identifier", + "searchLocationPersonAttribute": "Search location", + "selectAnOption": "Select an option", + "sexFieldLabelText": "Sex", + "source": "Source", + "submitting": "Submitting", + "timeFormat": "Time Format", + "timeOfDeathInputLabel": "Time of death (hh:mm)", + "unableToFetch": "Unable to fetch person attribute type - {{personattributetype}}", + "unknown": "Unknown", + "unknownPatientAttributeType": "Patient attribute type has unknown format {{personAttributeTypeFormat}}", + "updatePatient": "Update patient", + "updatePatientErrorSnackbarTitle": "Patient Details Update Failed", + "updatePatientSuccessSnackbarSubtitle": "The patient's information has been successfully updated", + "updatePatientSuccessSnackbarTitle": "Patient Details Updated", + "yearsEstimateRequired": "Estimated years required", + "yes": "Yes" +} diff --git a/packages/esm-patient-registration-app/translations/pt.json b/packages/esm-patient-registration-app/translations/pt.json index 80afecc6c..4516cb9c4 100644 --- a/packages/esm-patient-registration-app/translations/pt.json +++ b/packages/esm-patient-registration-app/translations/pt.json @@ -45,7 +45,7 @@ "editPatientDetailsBreadcrumb": "Alterar detalhes do utente", "enterNonCodedCauseOfDeath": "Insira a causa de óbito não codificada", "error": "Erro", - "errorFetchingCodedCausesOfDeath": "Erro ao buscar causas de óbito", + "errorFetchingCodedCausesOfDeath": "Erro ao buscar causas de óbito codificadas.", "errorFetchingOrderedFields": "Ocorreu um erro ao buscar campos solicitados para hierarquia de endereços", "estimatedAgeInMonthsLabelText": "Idade estimada em meses", "estimatedAgeInYearsLabelText": "Idade estimada em anos", diff --git a/packages/esm-patient-registration-app/translations/vi.json b/packages/esm-patient-registration-app/translations/vi.json index 29113ebbb..c39daf1b7 100644 --- a/packages/esm-patient-registration-app/translations/vi.json +++ b/packages/esm-patient-registration-app/translations/vi.json @@ -14,35 +14,35 @@ "codedPersonAttributeAnswerSetInvalid": "Trường thuộc tính người được mã hóa '{{codedPersonAttributeFieldId}}' đã được định nghĩa với tập khái niệm câu trả lời không hợp lệ UUID '{{answerConceptSetUuid}}'.", "codedPersonAttributeNoAnswerSet": "Trường thuộc tính người '{{codedPersonAttributeFieldId}}' thuộc loại 'coded' nhưng đã được định nghĩa mà không có UUID tập hợp khái niệm câu trả lời. Khóa 'answerConceptSetUuid' là bắt buộc.", "configure": "Cấu hình", - "configureIdentifiers": "Configure identifiers", - "confirmDiscardChangesBody": "Your unsaved changes will be lost if you proceed to discard the form", - "confirmDiscardChangesTitle": "Are you sure you want to discard these changes?", - "confirmIdentifierDeletionText": "Are you sure you want to remove this identifier?", - "contactSection": "Contact Details", - "createNewPatient": "Create new patient", - "dateOfBirthLabelText": "Date of birth", + "configureIdentifiers": "Cấu hình định danh", + "confirmDiscardChangesBody": "Những thay đổi chưa lưu của bạn sẽ bị mất nếu bạn tiếp tục hủy biểu mẫu", + "confirmDiscardChangesTitle": "Bạn có chắc chắn muốn hủy bỏ thay đổi này?", + "confirmIdentifierDeletionText": "Bạn có chắc chắn muốn xóa bỏ id này?", + "contactSection": "Chi tiết liên hệ", + "createNewPatient": "Thêm mới bệnh nhân", + "dateOfBirthLabelText": "Ngày sinh", "deathCauseRequired": "Cause of death is required", "deathDateInFuture": "Death date cannot be in future", - "deathDateInputLabel": "Date of death", - "deathDateRequired": "Death date is required", + "deathDateInputLabel": "Ngày tử vong", + "deathDateRequired": "Ngày tử vong là bắt buộc", "deathdayInvalidDate": "Death date and time cannot be before the birthday", "deathdayIsRequired": "Death date is required when the patient is marked as deceased.", "deathdayNotInTheFuture": "", - "deathSection": "Death Info", + "deathSection": "Thông tin tử vong", "deathTimeFormatInvalid": "Time format is invalid", "deathTimeFormatRequired": "Time format is required", "deathTimeInvalid": "Time doesn't match the format 'hh:mm'", "deathTimeRequired": "Death time is required", "deleteIdentifierModalHeading": "Remove identifier?", - "deleteIdentifierModalText": " has a value of ", + "deleteIdentifierModalText": "có một giá trị của", "deleteIdentifierTooltip": "Xóa", "deleteRelationshipTooltipText": "Xóa", - "demographicsSection": "Basic Info", + "demographicsSection": "Thông tin cơ bản", "discard": "Hủy bỏ", "dobToggleLabelText": "Date of Birth Known?", "editIdentifierTooltip": "Chỉnh sửa", - "editPatientDetails": "Edit patient details", - "editPatientDetailsBreadcrumb": "Edit patient details", + "editPatientDetails": "Chỉnh sửa thông tin bệnh nhân", + "editPatientDetailsBreadcrumb": "Chỉnh sửa thông tin bệnh nhân", "enterNonCodedCauseOfDeath": "Enter non-coded cause of death", "error": "Lỗi", "errorFetchingCodedCausesOfDeath": "Error fetching coded causes of death", @@ -53,26 +53,26 @@ "familyNameRequired": "Family name is required", "female": "Nữ giới", "fieldsWithErrors": "The following fields have errors: ", - "fullNameLabelText": "Full Name", + "fullNameLabelText": "Tên đầy đủ", "genderLabelText": "Giới tính", - "genderRequired": "Gender is required", + "genderRequired": "Giới tính là bắt buộc", "genderUnspecified": "Gender unspecified", "givenNameLabelText": "First Name", "givenNameRequired": "Given name is required", "identifierValueRequired": "Identifier value is required", "idFieldLabelText": "Identifiers", "IDInstructions": "Select the identifiers you'd like to add for this patient:", - "invalidEmail": "Invalid email", + "invalidEmail": "Email không hợp lệ", "invalidInput": "Invalid Input", "isDeadInputLabel": "Is dead", "jumpTo": "Jump to", "male": "Nam giới", - "middleNameLabelText": "Middle Name", + "middleNameLabelText": "Tên Đệm", "negativeMonths": "Estimated months cannot be negative", "negativeYears": "Estimated years cannot be negative", "no": "Không", "nonCodedCauseOfDeath": "Non-coded cause of death", - "nonCodedCauseOfDeathRequired": "Cause of death is required", + "nonCodedCauseOfDeathRequired": "Lý do tử vong là bắt buộc", "nonsensicalYears": "Estimated years cannot be more than 140", "numberInNameDubious": "Number in name is dubious", "obsFieldUnknownDatatype": "Concept for obs field '{{fieldDefinitionId}}' has unknown datatype '{{datatypeName}}'", @@ -82,9 +82,9 @@ "patientNameKnown": "Patient's Name is Known?", "patientRegistrationBreadcrumb": "Patient Registration", "refreshOrContactAdmin": "Try refreshing the page or contact your system administrator", - "registerPatient": "Register patient", + "registerPatient": "Đăng ký bệnh nhân", "registerPatientSuccessSnackbarSubtitle": "The patient can now be found by searching for them using their name or ID number", - "registerPatientSuccessSnackbarTitle": "New Patient Created", + "registerPatientSuccessSnackbarTitle": "Bệnh nhân mới đã được tạo", "registrationErrorSnackbarTitle": "Patient Registration Failed", "relationship": "Mối quan hệ", "relationshipPersonMustExist": "Related person must be an existing person", @@ -92,19 +92,19 @@ "relationshipRemovedText": "Relationship removed", "relationshipsSection": "Mối quan hệ", "relationshipToPatient": "Relationship to patient", - "relativeFullNameLabelText": "Full name", + "relativeFullNameLabelText": "Tên đầy đủ", "relativeNamePlaceholder": "Firstname Familyname", "removeIdentifierButton": "Remove identifier", "resetIdentifierTooltip": "Cài lại", "restoreRelationshipActionButton": "Hoàn tác", - "searchAddress": "Search address", + "searchAddress": "Tìm kiếm địa chỉ", "searchIdentifierPlaceholder": "Search identifier", - "searchLocationPersonAttribute": "Search location", - "selectAnOption": "Select an option", + "searchLocationPersonAttribute": "Tìm kiếm vị trí", + "selectAnOption": "Chọn một tùy chọn", "sexFieldLabelText": "Giới tính", "source": "Nguồn", "submitting": "Đang nộp", - "timeFormat": "Time Format", + "timeFormat": "Định dạng thời gian", "timeOfDeathInputLabel": "Time of death (hh:mm)", "unableToFetch": "Unable to fetch person attribute type - {{personattributetype}}", "unknown": "Unknown", diff --git a/packages/esm-patient-registration-app/translations/zh.json b/packages/esm-patient-registration-app/translations/zh.json index 61cca5fe9..7e8aeff59 100644 --- a/packages/esm-patient-registration-app/translations/zh.json +++ b/packages/esm-patient-registration-app/translations/zh.json @@ -60,7 +60,7 @@ "givenNameLabelText": "名字", "givenNameRequired": "名字是必填项", "identifierValueRequired": "ID标识是必填项", - "idFieldLabelText": "ID", + "idFieldLabelText": "Identifiers", "IDInstructions": "选择您想为该患者添加的ID标识:", "invalidEmail": "Invalid email", "invalidInput": "输入无效", diff --git a/packages/esm-patient-search-app/package.json b/packages/esm-patient-search-app/package.json index e70177005..2f03061e9 100644 --- a/packages/esm-patient-search-app/package.json +++ b/packages/esm-patient-search-app/package.json @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "lodash-es": "^4.17.15" }, "peerDependencies": { diff --git a/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-banner.component.tsx b/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-banner.component.tsx index 7cd9e425b..c64b8f3b0 100644 --- a/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-banner.component.tsx +++ b/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-banner.component.tsx @@ -1,6 +1,6 @@ import React, { forwardRef, useContext, useMemo } from 'react'; -import classNames from 'classnames'; import { v4 as uuidv4 } from 'uuid'; +import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { Tag } from '@carbon/react'; import { @@ -173,7 +173,7 @@ const ClickablePatientContainer = ({ patient, children }: ClickablePatientContai const IdentifierTag: React.FC = ({ identifier }) => { return ( <> - + {identifier.identifierType.display} {identifier.identifier} diff --git a/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-search.component.tsx b/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-search.component.tsx index 7973a5bc9..8e74237d2 100644 --- a/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-search.component.tsx +++ b/packages/esm-patient-search-app/src/compact-patient-search/compact-patient-search.component.tsx @@ -96,7 +96,7 @@ const CompactPatientSearchComponent: React.FC = ({ }); } }, - [config.search.patientChartUrl, user, currentLocation], + [addViewedPatientAndCloseSearchResults, config.search.patientChartUrl], ); const focusedResult = useArrowNavigation( !recentPatients ? searchedPatients?.length ?? 0 : recentPatients?.length ?? 0, @@ -134,7 +134,7 @@ const CompactPatientSearchComponent: React.FC = ({ subtitle: errorFetchingUserProperties?.message, }); } - }, [fetchError, errorFetchingUserProperties]); + }, [fetchError, errorFetchingUserProperties, t]); const handleSubmit = useCallback( (debouncedSearchTerm) => { diff --git a/packages/esm-patient-search-app/src/compact-patient-search/recently-searched-patients.component.tsx b/packages/esm-patient-search-app/src/compact-patient-search/recently-searched-patients.component.tsx index aa5a18516..0b180219a 100644 --- a/packages/esm-patient-search-app/src/compact-patient-search/recently-searched-patients.component.tsx +++ b/packages/esm-patient-search-app/src/compact-patient-search/recently-searched-patients.component.tsx @@ -73,7 +73,7 @@ const RecentlySearchedPatients = React.forwardRef
-

+

{t('recentSearchResultsCount', '{{count}} recent search result', { count: searchResults.length, @@ -84,7 +84,7 @@ const RecentlySearchedPatients = React.forwardRef )} -

+
{hasMore && (
diff --git a/packages/esm-patient-search-app/src/config-schema.ts b/packages/esm-patient-search-app/src/config-schema.ts index 11d61ef71..f1c41ce7f 100644 --- a/packages/esm-patient-search-app/src/config-schema.ts +++ b/packages/esm-patient-search-app/src/config-schema.ts @@ -21,6 +21,111 @@ export const configSchema = { _description: 'Disable the default "keyup search" for instant patient search as typing concludes on tablet devices', }, + searchFilterFields: { + _type: Type.Object, + _description: 'Configuration for advanced search fields', + _default: { + gender: { + enabled: true, + }, + dateOfBirth: { + enabled: true, + }, + age: { + enabled: true, + min: 0, + }, + postcode: { + enabled: true, + }, + personAttributes: [ + { + attributeTypeUuid: '14d4f066-15f5-102d-96e4-000c29c2a5d7', + }, + ], + }, + gender: { + _type: Type.Object, + _description: 'Configuration for gender field', + enabled: { + _type: Type.Boolean, + _description: 'Optional. If true, determines whether to display the gender field or not. Defaults to true', + _default: true, + }, + }, + dateOfBirth: { + _type: Type.Object, + _description: 'Configuration for the date of birth field', + enabled: { + _type: Type.Boolean, + _description: + 'Optional. If true, determines whether to display the date of birth field or not. Defaults to true', + _default: true, + }, + }, + age: { + _type: Type.Object, + _description: 'Configuration for the age field', + enabled: { + _type: Type.Boolean, + _description: 'Optional. If true, determines whether to display the age field or not. Defaults to true', + _default: true, + }, + min: { + _type: Type.Number, + _description: 'The minimum value for the age field', + _default: 0, + }, + max: { + _type: Type.Number, + _description: 'The maximum value for the age field', + _default: 0, + }, + }, + postcode: { + _type: Type.Object, + _description: 'Configuration for the postcode field', + enabled: { + _type: Type.Boolean, + _description: 'Optional. If true, determines whether to display the postcode field or not. Defaults to true', + _default: true, + }, + }, + personAttributes: { + _type: Type.Array, + _description: 'Configuration for person attributes to display on advanced search', + _elements: { + _type: Type.Object, + placeholder: { + _type: Type.String, + _description: 'Placeholder text for the field', + _default: '', + }, + attributeTypeUuid: { + _type: Type.UUID, + _description: 'UUID of the person attribute type', + }, + answerConceptSetUuid: { + _type: Type.ConceptUuid, + _default: null, + _description: + 'For coded questions only. A concept which has the possible responses either as answers or as set members.', + }, + conceptAnswersUuids: { + _type: Type.Array, + _description: 'A list of UUIDs representing the possible answers for the associated concept question.', + _default: [], + }, + locationTag: { + _type: Type.String, + _default: null, + _description: + 'Only for fields with "person attribute" type `org.openmrs.Location`. This filters the list of location options in the dropdown based on their location tag.', + }, + }, + _default: [], + }, + }, }, includeDead: { _type: Type.Boolean, @@ -53,11 +158,34 @@ export const configSchema = { }, }; +export type BuiltInFieldType = 'gender' | 'dateOfBirth' | 'age' | 'postcode'; + +export interface PersonAttributeFieldConfig { + attributeTypeUuid: string; + placeholder?: string; + answerConceptSetUuid?: string; + conceptAnswersUuids?: Array; + locationTag?: string; +} + +export interface BuiltInFieldConfig { + enabled: boolean; + min?: number; + max?: number; +} + export type PatientSearchConfig = { search: { disableTabletSearchOnKeyUp: boolean; patientChartUrl: string; showRecentlySearchedPatients: boolean; + searchFilterFields: { + gender: BuiltInFieldConfig; + dateOfBirth: BuiltInFieldConfig; + age: BuiltInFieldConfig & { min?: number }; + postcode: BuiltInFieldConfig; + personAttributes: Array; + }; }; contactAttributeType: Array; defaultIdentifier: string; diff --git a/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.component.tsx b/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.component.tsx index b6ad648b4..92c979edc 100644 --- a/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.component.tsx +++ b/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.component.tsx @@ -13,10 +13,10 @@ interface PatientSearchBarProps { } const PatientSearchBar = React.forwardRef>( - ({ buttonProps, initialSearchTerm, onChange, onClear, onSubmit, isCompact }, ref) => { + ({ buttonProps, initialSearchTerm = '', onChange, onClear, onSubmit, isCompact }, ref) => { const { t } = useTranslation(); - const responsiveSize = isCompact ? 'sm' : 'lg'; const [searchTerm, setSearchTerm] = useState(initialSearchTerm); + const responsiveSize = isCompact ? 'sm' : 'lg'; const handleChange = useCallback( (value: string) => { diff --git a/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.test.tsx b/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.test.tsx index 6d4e854e3..64f32ed31 100644 --- a/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.test.tsx +++ b/packages/esm-patient-search-app/src/patient-search-bar/patient-search-bar.test.tsx @@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react'; import PatientSearchBar from './patient-search-bar.component'; describe('PatientSearchBar', () => { - it('should render', () => { + it('renders a search input', () => { render(); const searchInput = screen.getByPlaceholderText('Search for a patient by name or identifier number'); @@ -12,7 +12,7 @@ describe('PatientSearchBar', () => { expect(searchInput).toBeInTheDocument(); }); - it('displays initial search term', () => { + it('displays the initial search term', () => { const initialSearchTerm = 'John Doe'; render(); @@ -23,7 +23,7 @@ describe('PatientSearchBar', () => { expect(searchInput.value).toBe(initialSearchTerm); }); - it('calls onChange callback on input change', async () => { + it('calls the onChange callback on input change', async () => { const user = userEvent.setup(); const onChangeMock = jest.fn(); @@ -36,7 +36,7 @@ describe('PatientSearchBar', () => { expect(onChangeMock).toHaveBeenCalledWith('New Value'); }); - it('calls onClear callback on clear button click', async () => { + it('calls the onClear callback on clear button click', async () => { const user = userEvent.setup(); const onClearMock = jest.fn(); @@ -49,7 +49,7 @@ describe('PatientSearchBar', () => { expect(onClearMock).toHaveBeenCalled(); }); - it('calls onSubmit callback on form submission', async () => { + it('calls the onSubmit callback on form submission', async () => { const user = userEvent.setup(); const onSubmitMock = jest.fn(); diff --git a/packages/esm-patient-search-app/src/patient-search-button/patient-search-button.component.tsx b/packages/esm-patient-search-app/src/patient-search-button/patient-search-button.component.tsx index 5382abad5..23f63176a 100644 --- a/packages/esm-patient-search-app/src/patient-search-button/patient-search-button.component.tsx +++ b/packages/esm-patient-search-app/src/patient-search-button/patient-search-button.component.tsx @@ -1,7 +1,7 @@ +import React, { useCallback, useEffect } from 'react'; import { Button } from '@carbon/react'; import { Search } from '@carbon/react/icons'; import { launchWorkspace } from '@openmrs/esm-framework'; -import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { type PatientSearchWorkspaceProps } from '../patient-search-workspace/patient-search.workspace'; @@ -37,7 +37,7 @@ const PatientSearchButton: React.FC = ({ }) => { const { t } = useTranslation(); - const launchPatientSearchWorkspace = () => { + const launchPatientSearchWorkspace = useCallback(() => { const workspaceProps: PatientSearchWorkspaceProps = { handleSearchTermUpdated: searchQueryUpdatedAction, initialQuery: searchQuery, @@ -47,13 +47,13 @@ const PatientSearchButton: React.FC = ({ ...workspaceProps, workspaceTitle: overlayHeader, }); - }; + }, [overlayHeader, searchQuery, searchQueryUpdatedAction, selectPatientAction]); useEffect(() => { if (isOpen) { launchPatientSearchWorkspace(); } - }, [isOpen]); + }, [isOpen, launchPatientSearchWorkspace]); return ( -
- )} - -
- {showRefineSearchDialog && ( -
-
-
-

{t('refineSearchHeaderText', 'Add additional search criteria')}

- -
-
-
-
-
- -
- - - - - - - - - -
-
- - - -
-
-
-
-
- -
-
- -
-
-
- -
-
-
- - -
-
-
-
- )} - - ); - } - - return ( -
-

{t('refineSearch', 'Refine search')}

-
-
- -
- - - - - - - - - -
-
- - - - - - - - - - - -
-
- - - -
-
- - - -
-
- - - -
-
- - -
- ); -}; - -export default RefineSearch; diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attribute-field.component.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attribute-field.component.tsx new file mode 100644 index 000000000..eac7462b0 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attribute-field.component.tsx @@ -0,0 +1,233 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { ComboBox, InlineLoading, InlineNotification, TextInput, TextInputSkeleton } from '@carbon/react'; +import { useTranslation } from 'react-i18next'; +import { type Control, Controller } from 'react-hook-form'; +import { + useAttributeConceptAnswers, + useConfiguredAnswerConcepts, + useLocations, + usePersonAttributeType, +} from './person-attributes.resource'; +import { type AdvancedPatientSearchState, type SearchFieldConfig } from '../../types'; +import styles from './search-field.scss'; +import { type OpenmrsResource } from '@openmrs/esm-framework'; + +export interface PersonAttributeFieldProps { + field: SearchFieldConfig; + control: Control; + inTabletOrOverlay: boolean; + isTablet: boolean; +} + +export function PersonAttributeField({ field, control, isTablet }: PersonAttributeFieldProps) { + const { t } = useTranslation(); + const { data: personAttributeType, isLoading, error } = usePersonAttributeType(field.attributeTypeUuid || ''); + + const formatField = useMemo(() => { + if (!personAttributeType || isLoading) { + return ; + } + + switch (personAttributeType.format) { + case 'java.lang.String': + return ( + ( + onChange(e.target.value)} + placeholder={field.placeholder} + size={isTablet ? 'lg' : 'md'} + /> + )} + /> + ); + + case 'org.openmrs.Concept': + return ( + + ); + + case 'org.openmrs.Location': + return ( + + ); + + default: + return ( + + {t('unsupportedAttributeFormat', 'Unsupported attribute format: {{format}}', { + format: personAttributeType.format, + })} + + ); + } + }, [personAttributeType, isLoading, field, control, t, isTablet]); + + if (error) { + return ( + + {t('errorLoadingAttribute', 'Error loading attribute type {{attributeUuid}}', { + attributeUuid: field.attributeTypeUuid, + })} + + ); + } + + return formatField; +} + +interface ConceptAttributeFieldProps { + field: SearchFieldConfig; + control: Control; + isTablet: boolean; + attributeDisplay: string; +} + +const ConceptAttributeField: React.FC = ({ + field, + control, + isTablet, + attributeDisplay, +}) => { + const { t } = useTranslation(); + const { configuredConceptAnswers, isLoadingConfiguredAnswers } = useConfiguredAnswerConcepts( + field.conceptAnswersUuids ?? [], + ); + const { conceptAnswers, isLoadingConceptAnswers, errorFetchingConceptAnswers } = useAttributeConceptAnswers( + field.conceptAnswersUuids?.length ? '' : field.answerConceptSetUuid, + ); + + const items = useMemo(() => { + if (isLoadingConceptAnswers || isLoadingConfiguredAnswers) return []; + if (field.conceptAnswersUuids?.length) return configuredConceptAnswers || []; + return conceptAnswers || []; + }, [ + isLoadingConceptAnswers, + isLoadingConfiguredAnswers, + field.conceptAnswersUuids, + configuredConceptAnswers, + conceptAnswers, + ]); + + if (isLoadingConceptAnswers || isLoadingConfiguredAnswers) { + return ; + } + + if (errorFetchingConceptAnswers) { + return ( + + {t('errorLoadingConceptAttributeAnswers', 'Error loading concept attribute answers')} + + ); + } + + return ( + ( + item?.display} + selectedItem={items.sort((a, b) => a.display.localeCompare(b.display)).find((item) => item.uuid === value)} + onChange={({ selectedItem }) => onChange(selectedItem?.uuid)} + placeholder={t('selectOption', 'Select an option')} + size={isTablet ? 'lg' : 'md'} + /> + )} + /> + ); +}; + +interface LocationAttributeFieldProps { + field: SearchFieldConfig; + control: Control; + isTablet: boolean; + attributeDisplay: string; +} + +const LocationAttributeField: React.FC = ({ + field, + control, + isTablet, + attributeDisplay, +}) => { + const { t } = useTranslation(); + const [searchQuery, setSearchQuery] = useState(''); + const { locations, isLoading, loadingNewData, error } = useLocations(field.locationTag || null, searchQuery); + const prevLocationOptions = useRef([]); + + const locationOptions = useMemo(() => { + if (!(isLoading && loadingNewData)) { + const newOptions = locations.map(({ resource }) => ({ + value: resource.id, + label: resource.name, + })); + prevLocationOptions.current = newOptions; + return newOptions; + } + return prevLocationOptions.current; + }, [locations, isLoading, loadingNewData]); + + if (error) { + return ( + + {t('errorLoadingLocationsForAttribute', 'Error loading locations for person attribute {{attributeName}}', { + attributeName: attributeDisplay, + })} + + ); + } + + return ( +
+ ( + option.value === value)} + onChange={({ selectedItem }) => onChange(selectedItem?.value)} + onInputChange={(inputValue) => { + if (inputValue && !locationOptions.find(({ label }) => label === inputValue)) { + setSearchQuery(inputValue); + onChange(''); + } + }} + placeholder={t('searchLocationPersonAttribute', 'Search location')} + size={isTablet ? 'lg' : 'md'} + typeahead + /> + )} + /> + {loadingNewData && ( +
+ +
+ )} +
+ ); +}; diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attribute-field.test.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attribute-field.test.tsx new file mode 100644 index 000000000..843840a15 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attribute-field.test.tsx @@ -0,0 +1,273 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { PersonAttributeField, type PersonAttributeFieldProps } from './person-attribute-field.component'; +import { + useAttributeConceptAnswers, + useConfiguredAnswerConcepts, + useLocations, + usePersonAttributeType, +} from './person-attributes.resource'; +import { + type AdvancedPatientSearchState, + type LocationEntry, + type PersonAttributeTypeResponse, + type SearchFieldConfig, +} from '../../types'; +import { useForm } from 'react-hook-form'; + +jest.mock('react-hook-form', () => ({ + ...jest.requireActual('react-hook-form'), + useForm: jest.fn().mockReturnValue({ + handleSubmit: jest.fn(), + control: { + register: jest.fn(), + unregister: jest.fn(), + getFieldState: jest.fn(), + _names: { + array: new Set(['test']), + mount: new Set(['test']), + unMount: new Set(['test']), + watch: new Set(['test']), + focus: 'test', + watchAll: false, + }, + _subjects: { + watch: jest.fn(), + array: jest.fn(), + state: jest.fn(), + }, + _getWatch: jest.fn(), + _formValues: {}, + _defaultValues: {}, + }, + getValues: jest.fn((str) => (str === 'recurringPatternDaysOfWeek' ? [] : null)), + setValue: jest.fn(), + formState: { errors: {} }, + watch: jest.fn(), + }), + Controller: ({ render }) => + render({ + field: { + onChange: jest.fn(), + onBlur: jest.fn(), + value: '', + name: 'test', + ref: jest.fn(), + }, + formState: { + isSubmitted: false, + errors: {}, + }, + fieldState: { + isTouched: false, + error: undefined, + }, + }), +})); + +jest.mock('./person-attributes.resource', () => ({ + usePersonAttributeType: jest.fn(), + useAttributeConceptAnswers: jest.fn(), + useConfiguredAnswerConcepts: jest.fn(), + useLocations: jest.fn(), +})); + +const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType); +const mockUseAttributeConceptAnswers = jest.mocked(useAttributeConceptAnswers); +const mockUseConfiguredAnswerConcepts = jest.mocked(useConfiguredAnswerConcepts); +const mockUseLocations = jest.mocked(useLocations); + +describe('PersonAttributeField', () => { + const user = userEvent.setup(); + + const defaultProps: PersonAttributeFieldProps = { + field: { + name: 'testAttribute', + type: 'personAttribute', + label: 'Test Attribute', + attributeTypeUuid: 'test-uuid', + } as SearchFieldConfig, + inTabletOrOverlay: false, + isTablet: false, + control: useForm().control, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('String Attribute Type', () => { + beforeEach(() => { + mockUsePersonAttributeType.mockReturnValue({ + data: { format: 'java.lang.String', display: 'Test String Attribute' } as PersonAttributeTypeResponse, + isLoading: false, + error: null, + }); + }); + + it('renders text input for string attribute type', () => { + render(); + expect(screen.getByLabelText('Test String Attribute')).toBeInTheDocument(); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); + }); + + describe('Concept Attribute Type', () => { + beforeEach(() => { + mockUsePersonAttributeType.mockReturnValue({ + data: { format: 'org.openmrs.Concept', display: 'Test Concept Attribute' } as PersonAttributeTypeResponse, + isLoading: false, + error: null, + }); + + mockUseConfiguredAnswerConcepts.mockReturnValue({ + configuredConceptAnswers: [], + isLoadingConfiguredAnswers: false, + }); + + mockUseAttributeConceptAnswers.mockReturnValue({ + conceptAnswers: [ + { uuid: 'concept-answer-uuid-1', display: 'concept-answer-1' }, + { uuid: 'concept-answer-uuid-2', display: 'concept-answer-2' }, + ], + isLoadingConceptAnswers: false, + errorFetchingConceptAnswers: null, + }); + }); + + it('renders a combobox for concept attribute type', async () => { + render(); + const combobox = screen.getByRole('combobox'); + + expect(combobox).toBeInTheDocument(); + expect(screen.getByText('Test Concept Attribute')).toBeInTheDocument(); + await user.click(combobox); + expect(screen.getByText('concept-answer-1')).toBeInTheDocument(); + expect(screen.getByText('concept-answer-2')).toBeInTheDocument(); + }); + + it('handles custom concept answers', async () => { + mockUsePersonAttributeType.mockReturnValue({ + data: { + format: 'org.openmrs.Concept', + display: 'Test Concept Attribute', + } as PersonAttributeTypeResponse, + isLoading: false, + error: null, + }); + + mockUseConfiguredAnswerConcepts.mockReturnValue({ + configuredConceptAnswers: [ + { uuid: 'concept-answer-1-uuid', display: 'concept-answer-1' }, + { uuid: 'concept-answer-2-uuid', display: 'concept-answer-2' }, + ], + isLoadingConfiguredAnswers: false, + }); + + const propsWithCustomConcepts: PersonAttributeFieldProps = { + ...defaultProps, + field: { + ...defaultProps.field, + answerConceptSetUuid: 'test-concept-set-uuid', + conceptAnswersUuids: ['concept-answer-1-uuid', 'concept-answer-2-uuid'], + }, + }; + + render(); + const combobox = screen.getByRole('combobox'); + await user.click(combobox); + expect(screen.getByText('concept-answer-1')).toBeInTheDocument(); + expect(screen.getByText('concept-answer-2')).toBeInTheDocument(); + }); + + it('handles concept selection', async () => { + mockUsePersonAttributeType.mockReturnValue({ + data: { + format: 'org.openmrs.Concept', + display: 'Test Concept Attribute', + } as PersonAttributeTypeResponse, + isLoading: false, + error: null, + }); + mockUseConfiguredAnswerConcepts.mockReturnValue({ + configuredConceptAnswers: [ + { uuid: 'concept-answer-1-uuid', display: 'concept-answer-1' }, + { uuid: 'concept-answer-2-uuid', display: 'concept-answer-2' }, + ], + isLoadingConfiguredAnswers: false, + }); + const propsWithAnswerConceptUuidAndCustomAnswers: PersonAttributeFieldProps = { + ...defaultProps, + field: { + ...defaultProps.field, + answerConceptSetUuid: 'test-concept-set-uuid', + conceptAnswersUuids: ['concept-answer-1-uuid', 'concept-answer-2-uuid'], + }, + }; + + render(); + + const combobox = screen.getByRole('combobox'); + await user.click(combobox); + expect(screen.getByText('concept-answer-1')).toBeInTheDocument(); + expect(screen.getByText('concept-answer-2')).toBeInTheDocument(); + }); + }); + + describe('Location Attribute Type', () => { + beforeEach(() => { + mockUsePersonAttributeType.mockReturnValue({ + data: { format: 'org.openmrs.Location', display: 'Test Location Attribute' } as PersonAttributeTypeResponse, + isLoading: false, + error: null, + }); + mockUseLocations.mockReturnValue({ + locations: [ + { resource: { id: 'location-1-uuid', name: 'Location 1' } }, + { resource: { id: 'location-2-uuid', name: 'Location 2' } }, + ] as LocationEntry[], + isLoading: false, + loadingNewData: false, + error: undefined, + }); + }); + + it('renders location combobox', () => { + render(); + expect(screen.getByRole('combobox')).toBeInTheDocument(); + expect(screen.getByText('Test Location Attribute')).toBeInTheDocument(); + }); + + it('handles location search input', async () => { + render(); + const combobox = screen.getByRole('combobox'); + await user.type(combobox, 'Loc'); + expect(mockUseLocations).toHaveBeenCalledWith(null, 'Loc'); + }); + }); + + describe('Error Handling', () => { + it('shows an error notification when loading attribute type fails', () => { + mockUsePersonAttributeType.mockReturnValue({ + data: null, + isLoading: false, + error: new Error('Failed to load attribute type'), + }); + render(); + expect(screen.getByText('Error loading attribute type test-uuid')).toBeInTheDocument(); + }); + }); + + describe('Unsupported Attribute Format', () => { + it('shows an error for unsupported formats', () => { + mockUsePersonAttributeType.mockReturnValue({ + data: { format: 'unsupported.format', display: 'Unsupported Attribute' } as PersonAttributeTypeResponse, + isLoading: false, + error: null, + }); + render(); + expect(screen.getByText('Unsupported attribute format: unsupported.format')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attributes.resource.ts b/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attributes.resource.ts new file mode 100644 index 000000000..c11279d72 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/person-attributes.resource.ts @@ -0,0 +1,144 @@ +import useSWRImmutable from 'swr/immutable'; +import { + type FetchResponse, + fhirBaseUrl, + openmrsFetch, + type OpenmrsResource, + restBaseUrl, + useDebounce, +} from '@openmrs/esm-framework'; +import { useEffect, useMemo } from 'react'; +import useSWR from 'swr'; +import { + type ConceptResponse, + type LocationEntry, + type LocationResponse, + type PersonAttributeTypeResponse, +} from '../../types'; + +export function useAttributeConceptAnswers(conceptUuid: string | undefined): { + conceptAnswers: Array; + isLoadingConceptAnswers: boolean; + errorFetchingConceptAnswers: Error | undefined; +} { + const shouldFetch = Boolean(conceptUuid); + + const { data, error, isLoading } = useSWRImmutable, Error>( + shouldFetch ? `${restBaseUrl}/concept/${conceptUuid}` : null, + openmrsFetch, + ); + + useEffect(() => { + if (error) { + console.error(`Error loading concept answers for conceptUuid: ${conceptUuid}`, error); + } + }, [conceptUuid, error]); + + const conceptAnswers = useMemo(() => { + if (!data?.data) { + return []; + } + return data.data.answers?.length > 0 ? data.data.answers : data.data.setMembers ?? []; + }, [data]); + + return { + conceptAnswers, + isLoadingConceptAnswers: isLoading, + errorFetchingConceptAnswers: error, + }; +} + +export function useLocations( + locationTag: string | null, + searchQuery: string = '', +): { + locations: Array; + isLoading: boolean; + loadingNewData: boolean; + error: any; +} { + const debouncedQuery = useDebounce(searchQuery); + + const constructUrl = useMemo(() => { + let url = `${fhirBaseUrl}/Location?`; + let urlSearchParameters = new URLSearchParams(); + urlSearchParameters.append('_summary', 'data'); + + if (!debouncedQuery) { + urlSearchParameters.append('_count', '10'); + } + + if (locationTag) { + urlSearchParameters.append('_tag', locationTag); + } + + if (typeof debouncedQuery === 'string' && debouncedQuery != '') { + urlSearchParameters.append('name:contains', debouncedQuery); + } + + return url + urlSearchParameters.toString(); + }, [locationTag, debouncedQuery]); + + const { data, error, isLoading, isValidating } = useSWR, Error>( + constructUrl, + openmrsFetch, + ); + + return useMemo( + () => ({ + locations: data?.data?.entry || [], + isLoading, + loadingNewData: isValidating, + error, + }), + [data?.data?.entry, error, isLoading, isValidating], + ); +} + +export function usePersonAttributeType(personAttributeTypeUuid: string): { + data: PersonAttributeTypeResponse | undefined; + isLoading: boolean; + error: any; +} { + const { data, error, isLoading } = useSWRImmutable>( + `${restBaseUrl}/personattributetype/${personAttributeTypeUuid}`, + openmrsFetch, + ); + + return useMemo( + () => ({ + data: data?.data, + isLoading, + error, + }), + [data, isLoading, error], + ); +} + +export function useConfiguredAnswerConcepts(uuids: Array): { + configuredConceptAnswers: Array; + isLoadingConfiguredAnswers: boolean; +} { + const fetchConcept = async (uuid: string): Promise => { + try { + const response = await openmrsFetch(`${restBaseUrl}/concept/${uuid}?v=custom:(uuid,display)`); + return response?.data; + } catch (error) { + console.error(`Error fetching concept for UUID: ${uuid}`, error); + return null; + } + }; + + const { data, isLoading } = useSWR(uuids.length > 0 ? ['answer-concepts', uuids] : null, async () => { + const results = await Promise.all(uuids.map(fetchConcept)); + return results.filter((concept) => concept !== null); + }); + + return useMemo( + () => ({ + configuredConceptAnswers: data ?? [], + isLoadingConfiguredAnswers: isLoading, + }), + [data, isLoading], + ); +} diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search-tablet.component.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search-tablet.component.tsx new file mode 100644 index 000000000..243372c67 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search-tablet.component.tsx @@ -0,0 +1,161 @@ +import React, { useMemo } from 'react'; +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@carbon/react'; +import { ChevronDownIcon, ChevronUpIcon } from '@openmrs/esm-framework'; +import { type Control } from 'react-hook-form'; +import { type AdvancedPatientSearchState, type SearchFieldConfig, type SearchFieldType } from '../../types'; +import { + type BuiltInFieldConfig, + type PatientSearchConfig, + type PersonAttributeFieldConfig, +} from '../../config-schema'; +import { SearchField } from './search-field.component'; +import styles from './refine-search-tablet.scss'; + +interface RefineSearchTabletProps { + showRefineSearchDialog: boolean; + filtersApplied: number; + control: Control; + config: PatientSearchConfig; + isTablet: boolean; + onResetFields: () => void; + onToggleDialog: () => void; + onSubmit: (evt: React.FormEvent) => void; +} + +export const RefineSearchTablet: React.FC = ({ + showRefineSearchDialog, + filtersApplied, + control, + config, + isTablet, + onResetFields, + onToggleDialog, + onSubmit, +}) => { + const { t } = useTranslation(); + + const renderSearchFields = useMemo(() => { + const fields: Array = []; + + Object.entries(config.search.searchFilterFields).forEach(([fieldName, fieldConfig]) => { + if (fieldName !== 'personAttributes' && (fieldConfig as BuiltInFieldConfig).enabled) { + fields.push({ + name: fieldName, + type: fieldName as SearchFieldType, + }); + } + }); + + config.search.searchFilterFields.personAttributes?.forEach((attribute: PersonAttributeFieldConfig) => { + fields.push({ + name: attribute.attributeTypeUuid, + type: 'personAttribute', + ...attribute, + }); + }); + + const genderField = fields.find((field) => field.type === 'gender'); + const dobField = fields.find((field) => field.type === 'dateOfBirth'); + const otherFields = fields.filter((field) => !['gender', 'dateOfBirth'].includes(field.type)); + + const otherFieldsRows = otherFields + .reduce((rows, field, index) => { + const rowIndex = Math.floor(index / 3); + rows[rowIndex] = rows[rowIndex] || []; + rows[rowIndex].push(field); + return rows; + }, [] as SearchFieldConfig[][]) + .map((row, index) => ( +
+ {row.map((field) => ( +
+ +
+ ))} +
+ )); + + return ( + <> + {Boolean(genderField || dobField) && ( +
+ {genderField && ( +
+ +
+ )} + {dobField && ( +
+ +
+ )} +
+ )} + {otherFields.length > 0 && ( +
{otherFieldsRows}
+ )} + + ); + }, [config, control, isTablet]); + + return ( + <> +
+ {!filtersApplied ? ( +

+ {t('refineSearchTabletBannerText', "Can't find who you're looking for?")} +

+ ) : ( +
+ {filtersApplied}{' '} +

{t('filtersAppliedText', 'search queries added')}

+ +
+ )} + +
+ {showRefineSearchDialog && ( +
+
+
+

{t('refineSearchHeaderText', 'Add additional search criteria')}

+ +
+
+ {renderSearchFields} +
+ + +
+
+
+
+ )} + + ); +}; diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search.scss b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search-tablet.scss similarity index 63% rename from packages/esm-patient-search-app/src/patient-search-page/refine-search.scss rename to packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search-tablet.scss index cb18a8b5e..a92393428 100644 --- a/packages/esm-patient-search-app/src/patient-search-page/refine-search.scss +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search-tablet.scss @@ -1,43 +1,6 @@ @use '@carbon/layout'; @use '@carbon/type'; -@use '@openmrs/esm-styleguide/src/vars' as *; - -.refineSearchContainer { - h2 { - margin: layout.$spacing-03 0; - } -} - -.field { - margin-bottom: layout.$spacing-05; - - input { - min-width: unset !important; - } -} - -.fieldTabletOrOverlay { - margin-bottom: layout.$spacing-07; - - input { - min-width: unset !important; - } -} - -.dobFields { - display: grid; - grid-template-columns: 1fr 1fr 1.25fr; - grid-gap: layout.$spacing-03; - input { - min-width: unset !important; - padding-right: layout.$spacing-02 !important; - } -} - -.button { - width: 100%; - max-width: unset; -} +@use '~@openmrs/esm-styleguide/src/vars' as *; .refineSearchBanner { padding: layout.$spacing-03 layout.$spacing-05; @@ -51,12 +14,15 @@ } } -.padded { - padding: 0 layout.$spacing-05; -} +.refineSearchBannerFilterInfo { + display: flex; + justify-items: center; + align-items: center; + color: $ui-02; -.refineSearchDialogOpener { - color: $inverse-link; + p { + margin-left: layout.$spacing-03; + } } .refineSearchDialogContainer { @@ -85,6 +51,10 @@ margin-bottom: layout.$spacing-05; } +.padded { + padding: 0 layout.$spacing-05; +} + .refineSearchDialogGenderSexRow { display: grid; grid-template-columns: 1.25fr 1fr; @@ -92,21 +62,28 @@ padding: 0 layout.$spacing-05; } -.labelText { - margin-bottom: layout.$spacing-03; +.otherFieldsContainer { + display: flex; + flex-direction: column; + gap: layout.$spacing-05; } -.phoneLastVisitRow { +.otherFieldsRow { display: grid; - grid-template-columns: 1fr auto; - grid-gap: layout.$spacing-11; - padding: 0 layout.$spacing-05; -} + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + gap: layout.$spacing-05; -.phonePostcode { - display: grid; - grid-template-columns: 1.5fr 1fr; - grid-gap: layout.$spacing-05; + &:has(:only-child) { + grid-template-columns: 1fr; + } + + &:has(:nth-child(2)):not(:has(:nth-child(3))) { + grid-template-columns: repeat(2, 1fr); + } + + &:has(:nth-child(3)) { + grid-template-columns: repeat(3, 1fr); + } } .buttonSet { @@ -119,31 +96,21 @@ margin: layout.$spacing-07 layout.$spacing-05; } -.filtersAppliedCount { - padding: 0 layout.$spacing-03; - background-color: $color-blue-60-2; - color: $ui-01; -} - -.refineSearchBannerFilterInfo { - display: flex; - justify-items: center; - align-items: center; - color: $ui-02; - - p { - margin-left: layout.$spacing-03; - } +.button { + width: 100%; + max-width: unset; } .bodyShort01 { @include type.type-style('body-compact-01'); } -.label01 { - @include type.type-style('label-01'); +.filtersAppliedCount { + padding: 0 layout.$spacing-03; + background-color: $color-blue-60-2; + color: $ui-01; } -.productiveHeading02 { - @include type.type-style('heading-compact-02'); +.refineSearchDialogOpener { + color: $inverse-link; } diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.component.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.component.tsx new file mode 100644 index 000000000..5a35f1328 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.component.tsx @@ -0,0 +1,153 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { Button, Layer } from '@carbon/react'; +import { useForm } from 'react-hook-form'; +import { type AdvancedPatientSearchState, type SearchFieldConfig, type SearchFieldType } from '../../types'; +import { useConfig, useLayoutType } from '@openmrs/esm-framework'; +import { + type BuiltInFieldConfig, + type PatientSearchConfig, + type PersonAttributeFieldConfig, +} from '../../config-schema'; +import { RefineSearchTablet } from './refine-search-tablet.component'; +import styles from './refine-search.scss'; +import { SearchField } from './search-field.component'; + +export const initialFilters: AdvancedPatientSearchState = { + gender: 'any', + dateOfBirth: 0, + monthOfBirth: 0, + yearOfBirth: 0, + postcode: '', + age: 0, + attributes: {}, +}; + +interface RefineSearchProps { + inTabletOrOverlay: boolean; + setFilters: React.Dispatch>; + filtersApplied: number; +} + +const RefineSearch: React.FC = ({ setFilters, inTabletOrOverlay, filtersApplied }) => { + const [showRefineSearchDialog, setShowRefineSearchDialog] = useState(false); + const { t } = useTranslation(); + const isTablet = useLayoutType() === 'tablet'; + const config = useConfig(); + + const { control, handleSubmit, reset, formState } = useForm({ + defaultValues: { + gender: 'any', + dateOfBirth: 0, + monthOfBirth: 0, + yearOfBirth: 0, + age: 0, + postcode: '', + attributes: {}, + }, + }); + + const onSubmit = useCallback( + (data: AdvancedPatientSearchState) => { + const cleanedAttributes = Object.entries(data.attributes || {}).reduce( + (acc, [key, value]) => { + if (value) { + acc[key] = value; + } + return acc; + }, + {} as Record, + ); + + const cleanedData = { + ...data, + attributes: cleanedAttributes, + }; + + setFilters(cleanedData); + setShowRefineSearchDialog(false); + }, + [setFilters], + ); + const handleResetFields = useCallback(() => { + reset({ ...initialFilters, attributes: {} }); + setFilters(initialFilters); + setShowRefineSearchDialog(false); + }, [reset, setFilters]); + + const toggleShowRefineSearchDialog = useCallback(() => { + setShowRefineSearchDialog((prevState) => !prevState); + }, []); + + const renderSearchFields = useMemo(() => { + const fields: Array = []; + + Object.entries(config.search.searchFilterFields).forEach(([fieldName, fieldConfig]) => { + if (fieldName !== 'personAttributes' && (fieldConfig as BuiltInFieldConfig).enabled) { + fields.push({ + name: fieldName, + type: fieldName as SearchFieldType, + }); + } + }); + + config.search.searchFilterFields.personAttributes?.forEach((attribute: PersonAttributeFieldConfig) => { + fields.push({ + name: attribute.attributeTypeUuid, + type: 'personAttribute', + ...attribute, + }); + }); + + return fields.map((field) => ( + +
+ +
+
+ )); + }, [config, inTabletOrOverlay, isTablet, control]); + + if (inTabletOrOverlay) { + return ( + + ); + } + + return ( +
+

{t('refineSearch', 'Refine search')}

+ {renderSearchFields} +
+ + +
+ ); +}; + +export default RefineSearch; diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.scss b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.scss new file mode 100644 index 000000000..067244755 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.scss @@ -0,0 +1,25 @@ +@use '@carbon/layout'; +@use '@carbon/type'; + +.refineSearchContainer { + h2 { + margin: layout.$spacing-03 0; + } +} + +.field { + margin-bottom: layout.$spacing-05; + + input { + min-width: unset !important; + } +} + +.button { + width: 100%; + max-width: unset; +} + +.productiveHeading02 { + @include type.type-style('heading-compact-02'); +} diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.test.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.test.tsx new file mode 100644 index 000000000..17d7660a0 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/refine-search.test.tsx @@ -0,0 +1,191 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useConfig, useLayoutType } from '@openmrs/esm-framework'; +import RefineSearch from './refine-search.component'; +import { type PatientSearchConfig } from '../../config-schema'; +import { usePersonAttributeType } from './person-attributes.resource'; + +const mockUseConfig = jest.mocked(useConfig); +const mockUseLayoutType = jest.mocked(useLayoutType); +const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType); + +jest.mock('./person-attributes.resource', () => ({ + usePersonAttributeType: jest.fn(), +})); + +describe('RefineSearch', () => { + const user = userEvent.setup(); + + const mockSetFilters = jest.fn(); + const mockConfig = { + search: { + searchFilterFields: { + gender: { + enabled: true, + }, + dateOfBirth: { + enabled: true, + }, + age: { + enabled: true, + min: 0, + }, + postcode: { + enabled: true, + }, + personAttributes: [ + { + attributeTypeUuid: '14d4f066-15f5-102d-96e4-000c29c2a5d7', + }, + ], + }, + }, + } as PatientSearchConfig; + + beforeEach(() => { + mockUseConfig.mockReturnValue(mockConfig); + mockUseLayoutType.mockReturnValue('tablet'); + mockUsePersonAttributeType.mockReturnValue({ + isLoading: false, + error: null, + data: { + format: 'java.lang.String', + uuid: '14d4f066-15f5-102d-96e4-000c29c2a5d7', + display: 'Telephone Number', + }, + }); + }); + + const renderComponent = (props = {}) => { + return render(); + }; + + it('renders all enabled search fields', () => { + renderComponent(); + + expect(screen.getByText('Sex')).toBeInTheDocument(); + expect(screen.getByText('Day of Birth')).toBeInTheDocument(); + expect(screen.getByText('Month of Birth')).toBeInTheDocument(); + expect(screen.getByText('Year of Birth')).toBeInTheDocument(); + expect(screen.getByLabelText('Age')).toBeInTheDocument(); + expect(screen.getByLabelText('Postcode')).toBeInTheDocument(); + }); + + it('shows number of filters applied in Apply button when filters are active', () => { + renderComponent({ filtersApplied: 2 }); + + const applyButton = screen.getByRole('button', { name: /apply/i }); + expect(applyButton).toHaveTextContent('Apply (2 filters applied)'); + }); + + it('calls setFilters with initial state when Reset Fields is clicked', async () => { + renderComponent(); + + await user.click(screen.getByRole('button', { name: /reset fields/i })); + + expect(mockSetFilters).toHaveBeenCalledWith({ + gender: 'any', + dateOfBirth: 0, + monthOfBirth: 0, + yearOfBirth: 0, + postcode: '', + age: 0, + attributes: {}, + }); + }); + + it('submits form with current state when Apply is clicked', async () => { + renderComponent(); + + const ageInput = screen.getByRole('spinbutton', { name: /age/i }); + await user.type(ageInput, '30'); + await user.click(screen.getByRole('button', { name: /apply/i })); + + expect(mockSetFilters).toHaveBeenCalledWith( + expect.objectContaining({ + gender: 'any', + dateOfBirth: 0, + monthOfBirth: 0, + yearOfBirth: 0, + postcode: '', + attributes: {}, + age: 30, + }), + ); + }); + + describe('Layout rendering', () => { + it('renders desktop layout by default', () => { + renderComponent(); + + expect(screen.getByRole('refine-search')).toHaveClass('refineSearchContainer'); + expect(screen.queryByRole('button', { name: /refine search/i })).not.toBeInTheDocument(); + }); + + it('renders tablet layout when inTabletOrOverlay is true', () => { + mockUseLayoutType.mockReturnValue('tablet'); + renderComponent({ inTabletOrOverlay: true }); + + expect(screen.queryByRole('form')).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: /refine search/i })).toBeInTheDocument(); + }); + + it('updates filter count in tablet mode', async () => { + mockUseLayoutType.mockReturnValue('tablet'); + renderComponent({ inTabletOrOverlay: true, filtersApplied: 2 }); + + expect(screen.getByText('2')).toBeInTheDocument(); + expect(screen.getByText(/search queries added/i)).toBeInTheDocument(); + }); + }); + + describe('Input handling', () => { + it('handles gender selection correctly', async () => { + renderComponent(); + + await user.click(screen.getByRole('tab', { name: 'Male' })); + await user.click(screen.getByRole('button', { name: /apply/i })); + + expect(mockSetFilters).toHaveBeenCalledWith( + expect.objectContaining({ + gender: 'male', + }), + ); + }); + + it('handles date of birth inputs correctly', async () => { + renderComponent(); + + const dayInput = screen.getByRole('spinbutton', { name: /day of birth/i }); + const monthInput = screen.getByRole('spinbutton', { name: /month of birth/i }); + const yearInput = screen.getByRole('spinbutton', { name: /year of birth/i }); + + await user.type(dayInput, '15'); + await user.type(monthInput, '03'); + await user.type(yearInput, '1990'); + await user.click(screen.getByRole('button', { name: /apply/i })); + + expect(mockSetFilters).toHaveBeenCalledWith( + expect.objectContaining({ + dateOfBirth: 15, + monthOfBirth: 3, + yearOfBirth: 1990, + }), + ); + }); + + it('validates date of birth inputs', async () => { + renderComponent(); + + const dayInput = screen.getByRole('spinbutton', { name: /day of birth/i }); + const monthInput = screen.getByRole('spinbutton', { name: /month of birth/i }); + + await user.type(dayInput, '32'); + expect(dayInput).toHaveAttribute('max', '31'); + + await user.type(monthInput, '13'); + expect(monthInput).toHaveAttribute('max', '12'); + }); + }); +}); diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.component.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.component.tsx new file mode 100644 index 000000000..f677a214e --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.component.tsx @@ -0,0 +1,180 @@ +import React from 'react'; +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { ContentSwitcher, NumberInput, Switch, TextInput } from '@carbon/react'; +import { type Control, Controller } from 'react-hook-form'; +import styles from './search-field.scss'; +import { PersonAttributeField } from './person-attribute-field.component'; +import { type AdvancedPatientSearchState, type SearchFieldConfig } from '../../types'; + +interface SearchFieldProps { + field: SearchFieldConfig; + control: Control; + inTabletOrOverlay: boolean; + isTablet: boolean; +} + +export const SearchField: React.FC = ({ field, control, inTabletOrOverlay, isTablet }) => { + const { t } = useTranslation(); + + switch (field.type) { + case 'gender': + return ( +
+
+ +
+ ( + <> + onChange(name)} + selectedIndex={['any', 'male', 'female'].indexOf(value)}> + + + + + onChange(name)} + selectedIndex={['other', 'unknown'].indexOf(value)}> + + + + + )} + /> +
+ ); + + case 'dateOfBirth': + return ( +
+ ( + onChange(parseInt(e.target.value) || 0)} + className={styles.dobField} + type="number" + label={t('dayOfBirth', 'Day of Birth')} + min={1} + max={31} + allowEmpty + hideSteppers + size={isTablet ? 'lg' : 'md'} + /> + )} + /> + ( + onChange(parseInt(e.target.value) || 0)} + className={styles.dobField} + type="number" + label={t('monthOfBirth', 'Month of Birth')} + min={1} + max={12} + allowEmpty + hideSteppers + size={isTablet ? 'lg' : 'md'} + /> + )} + /> + ( + onChange(parseInt(e.target.value) || 0)} + className={styles.dobField} + type="number" + label={t('yearOfBirth', 'Year of Birth')} + allowEmpty + hideSteppers + min={1800} + max={new Date().getFullYear()} + size={isTablet ? 'lg' : 'md'} + /> + )} + /> +
+ ); + + case 'age': + return ( +
+ ( + onChange(parseInt(e.target.value) || 0)} + type="number" + label={t('age', 'Age')} + min={field.min} + max={field.max} + allowEmpty + hideSteppers + size={isTablet ? 'lg' : 'md'} + placeholder={field.placeholder} + /> + )} + /> +
+ ); + + case 'postcode': + return ( +
+ ( + onChange(e.target.value)} + value={value} + size={isTablet ? 'lg' : 'md'} + placeholder={field.placeholder} + /> + )} + /> +
+ ); + + case 'personAttribute': + return ( +
+ +
+ ); + } +}; diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.scss b/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.scss new file mode 100644 index 000000000..44605fe3d --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.scss @@ -0,0 +1,41 @@ +@use '@carbon/layout'; +@use '@carbon/type'; +@use '@carbon/colors'; + +.fieldTabletOrOverlay { + margin-bottom: layout.$spacing-07; + + input { + min-width: unset !important; + } +} + +.dobFields { + display: grid; + grid-template-columns: 1fr 1fr 1.25fr; + grid-gap: layout.$spacing-03; + + input { + min-width: unset !important; + padding-right: layout.$spacing-02 !important; + } +} + +.labelText { + margin-bottom: layout.$spacing-03; +} + +.label01 { + @include type.type-style('label-01'); +} + +.locationAttributeFieldContainer { + position: relative; + + .loadingContainer { + background-color: colors.$white; + position: absolute; + right: layout.$spacing-07; + bottom: layout.$spacing-02; + } +} diff --git a/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.test.tsx b/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.test.tsx new file mode 100644 index 000000000..cfe6578c5 --- /dev/null +++ b/packages/esm-patient-search-app/src/patient-search-page/refine-search/search-field.test.tsx @@ -0,0 +1,197 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { SearchField } from './search-field.component'; +import { usePersonAttributeType } from './person-attributes.resource'; +import { type AdvancedPatientSearchState, type SearchFieldConfig } from '../../types'; +import { useForm } from 'react-hook-form'; +import { renderWithSwr } from 'tools'; + +jest.mock('./person-attributes.resource', () => ({ + usePersonAttributeType: jest.fn(), +})); + +const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType); + +jest.mock('react-hook-form', () => ({ + ...jest.requireActual('react-hook-form'), + useForm: jest.fn().mockReturnValue({ + control: { + register: jest.fn(), + unregister: jest.fn(), + getFieldState: jest.fn(), + _names: { + array: new Set(['test']), + mount: new Set(['test']), + unMount: new Set(['test']), + watch: new Set(['test']), + focus: 'test', + watchAll: false, + }, + _subjects: { + watch: jest.fn(), + array: jest.fn(), + state: jest.fn(), + }, + _getWatch: jest.fn(), + _formValues: {}, + _defaultValues: {}, + }, + getValues: jest.fn(), + setValue: jest.fn(), + formState: { errors: {} }, + }), + Controller: ({ render, name, control }) => + render({ + field: { + onChange: jest.fn(), + onBlur: jest.fn(), + value: '', + name, + ref: jest.fn(), + }, + formState: { errors: {} }, + fieldState: { error: undefined }, + }), +})); +it; + +describe('SearchField', () => { + const defaultProps = { + control: useForm().control, + inTabletOrOverlay: false, + isTablet: false, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Gender field', () => { + const genderField: SearchFieldConfig = { + name: 'gender', + type: 'gender', + }; + + it('renders all gender options', () => { + render(); + + expect(screen.getByText('Any')).toBeInTheDocument(); + expect(screen.getByText('Male')).toBeInTheDocument(); + expect(screen.getByText('Female')).toBeInTheDocument(); + expect(screen.getByText('Other')).toBeInTheDocument(); + expect(screen.getByText('Unknown')).toBeInTheDocument(); + }); + + it('groups gender options into two content switchers', () => { + render(); + const switchers = screen.getAllByRole('tablist'); + expect(switchers).toHaveLength(2); + }); + }); + + describe('Date of Birth field', () => { + const dobField: SearchFieldConfig = { + name: 'dateOfBirth', + type: 'dateOfBirth', + }; + + it('renders three number inputs for day, month, and year', () => { + render(); + + expect(screen.getByLabelText('Day of Birth')).toBeInTheDocument(); + expect(screen.getByLabelText('Month of Birth')).toBeInTheDocument(); + expect(screen.getByLabelText('Year of Birth')).toBeInTheDocument(); + }); + + it('applies correct validation constraints to date inputs', () => { + render(); + + const dayInput = screen.getByLabelText('Day of Birth'); + const monthInput = screen.getByLabelText('Month of Birth'); + const yearInput = screen.getByLabelText('Year of Birth'); + + expect(dayInput).toHaveAttribute('min', '1'); + expect(dayInput).toHaveAttribute('max', '31'); + expect(monthInput).toHaveAttribute('min', '1'); + expect(monthInput).toHaveAttribute('max', '12'); + expect(yearInput).toHaveAttribute('min', '1800'); + expect(yearInput).toHaveAttribute('max', new Date().getFullYear().toString()); + }); + }); + + describe('Age field', () => { + const ageField: SearchFieldConfig = { + name: 'age', + type: 'age', + min: 0, + max: 120, + }; + + it('renders number input with correct constraints', () => { + render(); + + const ageInput = screen.getByLabelText('Age'); + expect(ageInput).toBeInTheDocument(); + expect(ageInput).toHaveAttribute('type', 'number'); + expect(ageInput).toHaveAttribute('min', '0'); + expect(ageInput).toHaveAttribute('max', '120'); + }); + }); + + describe('Postcode field', () => { + const postcodeField: SearchFieldConfig = { + name: 'postcode', + type: 'postcode', + placeholder: 'Enter postcode', + }; + + it('renders text input with correct attributes', () => { + render(); + const input = screen.getByLabelText('Postcode'); + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute('type', 'text'); + }); + }); + + describe('Person Attribute field', () => { + const personAttributeField: SearchFieldConfig = { + name: 'test-uuid', + type: 'personAttribute', + attributeTypeUuid: 'test-uuid', + }; + + beforeEach(() => { + mockUsePersonAttributeType.mockReturnValue({ + data: { + format: 'java.lang.String', + display: 'Phone Number', + uuid: 'test-uuid', + }, + isLoading: false, + error: null, + }); + }); + + it('renders person attribute field with correct props', () => { + renderWithSwr(); + expect(screen.getByText('Phone Number')).toBeInTheDocument(); + }); + }); + + describe('Responsive behavior', () => { + const ageField: SearchFieldConfig = { + name: 'age', + type: 'age', + }; + + it('applies tablet styles when in tablet mode', () => { + render(); + expect(screen.getByLabelText('Age')).toBeInTheDocument(); + }); + + it('applies overlay styles when in overlay mode', () => { + render(); + expect(screen.getByLabelText('Age')).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/esm-patient-search-app/src/patient-search-workspace/patient-search.workspace.tsx b/packages/esm-patient-search-app/src/patient-search-workspace/patient-search.workspace.tsx index c8419cfa2..4ec629ede 100644 --- a/packages/esm-patient-search-app/src/patient-search-workspace/patient-search.workspace.tsx +++ b/packages/esm-patient-search-app/src/patient-search-workspace/patient-search.workspace.tsx @@ -1,8 +1,8 @@ -import { useConfig, useDebounce } from '@openmrs/esm-framework'; import React, { useCallback, useState } from 'react'; +import { useConfig, useDebounce } from '@openmrs/esm-framework'; import { type PatientSearchConfig } from '../config-schema'; -import PatientSearchBar from '../patient-search-bar/patient-search-bar.component'; import { PatientSearchContext, type PatientSearchContextProps } from '../patient-search-context'; +import PatientSearchBar from '../patient-search-bar/patient-search-bar.component'; import AdvancedPatientSearchComponent from '../patient-search-page/advanced-patient-search.component'; export interface PatientSearchWorkspaceProps extends PatientSearchContextProps { @@ -28,10 +28,13 @@ const PatientSearchWorkspace: React.FC = ({ const handleClearSearchTerm = useCallback(() => setSearchTerm(''), [setSearchTerm]); - const onSearchTermChange = useCallback((value: string) => { - setSearchTerm(value); - handleSearchTermUpdated && handleSearchTermUpdated(value); - }, []); + const onSearchTermChange = useCallback( + (value: string) => { + setSearchTerm(value); + handleSearchTermUpdated && handleSearchTermUpdated(value); + }, + [handleSearchTermUpdated], + ); return ( diff --git a/packages/esm-patient-search-app/src/patient-search.resource.tsx b/packages/esm-patient-search-app/src/patient-search.resource.tsx index bda4ccc82..e7ceb67bd 100644 --- a/packages/esm-patient-search-app/src/patient-search.resource.tsx +++ b/packages/esm-patient-search-app/src/patient-search.resource.tsx @@ -92,7 +92,7 @@ export function useInfinitePatientSearch( currentPage: size, totalResults: data?.[0]?.data?.totalCount ?? 0, }), - [data, isLoading, isValidating, error, setSize, size], + [mappedData, isLoading, error, data, isValidating, setSize, size], ); } @@ -120,7 +120,10 @@ export function useRecentlyViewedPatients(showRecentlySearchedPatients: boolean ); const userProperties = data?.data?.userProperties; - const patientsVisited = userProperties?.patientsVisited?.split(',').filter(Boolean) ?? []; + const patientsVisited = useMemo( + () => userProperties?.patientsVisited?.split(',').filter(Boolean) ?? [], + [userProperties], + ); const updateRecentlyViewedPatients = useCallback( (patientUuid: string) => { @@ -138,7 +141,7 @@ export function useRecentlyViewedPatients(showRecentlySearchedPatients: boolean }, }); }, - [patientsVisited, userProperties], + [patientsVisited, url, userProperties], ); return useMemo( @@ -149,7 +152,7 @@ export function useRecentlyViewedPatients(showRecentlySearchedPatients: boolean updateRecentlyViewedPatients, mutateUserProperties: mutate, }), - [data, error, mutate], + [error, isLoading, mutate, patientsVisited, updateRecentlyViewedPatients], ); } @@ -213,6 +216,6 @@ export function useRestPatients( currentPage: size, totalResults: patientUuids?.length ?? 0, }), - [data, isLoading, isValidating, error, setSize, size, patientUuids], + [mappedData, isLoading, error, patientUuids, size, isValidating, setSize], ); } diff --git a/packages/esm-patient-search-app/src/types/index.ts b/packages/esm-patient-search-app/src/types/index.ts index ad7507831..eb32cdf4f 100644 --- a/packages/esm-patient-search-app/src/types/index.ts +++ b/packages/esm-patient-search-app/src/types/index.ts @@ -17,7 +17,10 @@ export interface SearchedPatient { middleName: string; }; }; - attributes: Array<{ value: string; attributeType: { uuid: string; display: string } }>; + attributes: Array<{ + value: OpenmrsResource | string; + attributeType: { uuid: string; display: string }; + }>; } export interface Identifier { @@ -97,31 +100,11 @@ export interface AdvancedPatientSearchState { dateOfBirth: number; monthOfBirth: number; yearOfBirth: number; - phoneNumber: number; postcode: string; age: number; -} - -export enum AdvancedPatientSearchActionTypes { - SET_GENDER, - SET_DATE_OF_BIRTH, - SET_MONTH_OF_BIRTH, - SET_YEAR_OF_BIRTH, - SET_PHONE_NUMBER, - SET_POSTCODE, - SET_AGE, - RESET_FIELDS, -} - -export interface AdvancedPatientSearchAction { - type: AdvancedPatientSearchActionTypes; - gender?: 'any' | 'male' | 'female' | 'other' | 'unknown'; - dateOfBirth?: number; - monthOfBirth?: number; - yearOfBirth?: number; - phoneNumber?: number; - postcode?: string; - age?: number; + attributes: { + [key: string]: string; + }; } export interface User { @@ -132,3 +115,67 @@ export interface User { defaultLocation: string; }; } + +export interface PersonAttributeTypeResponse { + uuid: string; + display: string; + name?: string; + description?: string; + format: string; +} + +export interface ConceptResponse { + uuid: string; + display: string; + datatype: { + uuid: string; + display: string; + }; + answers: Array; + setMembers: Array; +} + +export interface LocationEntry { + resource: { + id: string; + name: string; + resourceType: string; + status: 'active' | 'inactive'; + meta?: { + tag?: Array<{ + code: string; + display: string; + system: string; + }>; + }; + }; +} + +export interface LocationResponse { + type: string; + total: number; + resourceType: string; + meta: { + lastUpdated: string; + }; + link: Array<{ + relation: string; + url: string; + }>; + id: string; + entry: Array; +} + +export type SearchFieldType = 'age' | 'dateOfBirth' | 'gender' | 'personAttribute' | 'postcode'; + +export interface SearchFieldConfig { + name: string; + type: SearchFieldType; + placeholder?: string; + answerConceptSetUuid?: string; + conceptAnswersUuids?: Array; + locationTag?: string; + attributeTypeUuid?: string; + min?: number; + max?: number; +} diff --git a/packages/esm-patient-search-app/translations/am.json b/packages/esm-patient-search-app/translations/am.json index 434eee3ba..8d0248173 100644 --- a/packages/esm-patient-search-app/translations/am.json +++ b/packages/esm-patient-search-app/translations/am.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/ar.json b/packages/esm-patient-search-app/translations/ar.json index a624bf2ef..037f52262 100644 --- a/packages/esm-patient-search-app/translations/ar.json +++ b/packages/esm-patient-search-app/translations/ar.json @@ -5,12 +5,16 @@ "clear": "مسح", "clearSearch": "مسح البحث", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "تم تطبيق الفلاتر", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "يوم الميلاد", "error": "خطأ", "errorCopy": "عذرًا، حدث خطأ. يمكنك محاولة إعادة تحميل هذه الصفحة، أو الاتصال بمسؤول الموقع وذكر رمز الخطأ أعلاه.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "أنثى", "filtersAppliedText": "تمت إضافة استعلامات البحث", @@ -19,7 +23,6 @@ "nextPage": "الصفحة التالية", "noPatientChartsFoundMessage": "عذرًا، لم يتم العثور على سجلات المرضى", "other": "آخر", - "phoneNumber": "رقم الهاتف", "postcode": "الرمز البريدي", "previousPage": "الصفحة السابقة", "recentSearchResultsCount_one": "{{count}} نتيجة بحث حديثة", @@ -31,12 +34,15 @@ "search": "بحث", "searchForPatient": "ابحث عن مريض بالاسم أو رقم التعريف", "searchingText": "جاري البحث...", - "searchPatient": "Search patient", - "searchResults": "Search results", + "searchLocationPersonAttribute": "Search location", + "searchPatient": "Search Patient", + "searchResults": "نتائج البحث", "searchResultsCount_one": "{{count}} نتيجة بحث", "searchResultsCount_other": "{{count}} نتيجة بحث", + "selectOption": "Select an option", "sex": "الجنس", "trySearchWithPatientUniqueID": "حاول البحث مرة أخرى باستخدام رقم التعريف الفريد للمريض", "unknown": "غير معروف", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "سنة الميلاد" } diff --git a/packages/esm-patient-search-app/translations/de.json b/packages/esm-patient-search-app/translations/de.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/de.json +++ b/packages/esm-patient-search-app/translations/de.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/en.json b/packages/esm-patient-search-app/translations/en.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/en.json +++ b/packages/esm-patient-search-app/translations/en.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/es.json b/packages/esm-patient-search-app/translations/es.json index 7e73e7391..968c5d352 100644 --- a/packages/esm-patient-search-app/translations/es.json +++ b/packages/esm-patient-search-app/translations/es.json @@ -1,42 +1,48 @@ { "age": "Edad", - "any": "Cualquier/a", + "any": "Cualquier", "apply": "Aplicar", "clear": "Borrar", "clearSearch": "Borrar", - "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filtros utilizados", + "closeSearch": "Cerrar Panel de Búsqueda", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Día de Nacimiento", "error": "Error", - "errorCopy": "Lo sentimos, se ha producido un error. Puede intentar recargar esta página o ponerse en contacto con el administrador del sitio y especificar el código de error.", - "errorFetchingPatients": "Error fetching patients", - "errorFetchingUserProperties": "Error fetching user properties", - "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", + "errorCopy": "Lo sentimos, hubo un error. Puede intentar recargar esta página o contactar al administrador del sitio y hacer referencia al código de error que aparece arriba.", + "errorFetchingPatients": "Error al obtener pacientes", + "errorFetchingUserProperties": "Error al obtener propiedades del usuario", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", + "errorUpdatingRecentlyViewedPatients": "Error al actualizar pacientes recientemente vistos", "female": "Femenino", - "filtersAppliedText": "búsquedas añadidas", + "filtersAppliedText": "búsquedas agregadas", "male": "Masculino", "monthOfBirth": "Mes de Nacimiento", - "nextPage": "Página siguiente", - "noPatientChartsFoundMessage": "Lo sentimos, no se han encontrado historiales de pacientes", + "nextPage": "Siguiente página", + "noPatientChartsFoundMessage": "Lo sentimos, no se han encontrado historias clínicas de pacientes", "other": "Otro", - "phoneNumber": "Número de teléfono", "postcode": "Código postal", "previousPage": "Página anterior", "recentSearchResultsCount_one": "{{count}} resultado de búsqueda reciente", "recentSearchResultsCount_other": "{{count}} resultados de búsqueda reciente", - "refineSearch": "Afinar búsqueda", - "refineSearchHeaderText": "Añadir criterios de búsqueda adicionales", - "refineSearchTabletBannerText": "¿No encuentra lo que está buscando?", + "refineSearch": "Refinar búsqueda", + "refineSearchHeaderText": "Agregar criterios de búsqueda adicionales", + "refineSearchTabletBannerText": "¿No puede encontrar a la persona que está buscando?", "resetFields": "Restablecer campos", "search": "Buscar", "searchForPatient": "Buscar un paciente por nombre o número de identificación", "searchingText": "Buscando...", - "searchPatient": "Search patient", - "searchResults": "Search results", + "searchLocationPersonAttribute": "Search location", + "searchPatient": "Buscar paciente", + "searchResults": "Buscar resultados", "searchResultsCount_one": "{{count}} resultado de búsqueda", "searchResultsCount_other": "{{count}} resultados de búsqueda", + "selectOption": "Select an option", "sex": "Sexo", - "trySearchWithPatientUniqueID": "Intente buscar con el número de identificación único del paciente", + "trySearchWithPatientUniqueID": "Intente buscar nuevamente usando el número de identificación único del paciente", "unknown": "Desconocido", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Año de Nacimiento" } diff --git a/packages/esm-patient-search-app/translations/fr.json b/packages/esm-patient-search-app/translations/fr.json index 758a5d7c9..6b7bffac5 100644 --- a/packages/esm-patient-search-app/translations/fr.json +++ b/packages/esm-patient-search-app/translations/fr.json @@ -1,17 +1,21 @@ { "age": "Âge", - "any": "N'importe quel", + "any": "N'importe lequel", "apply": "Appliquer", "clear": "Effacer", "clearSearch": "Effacer", "closeSearch": "Fermer le Panneau de Recherche", - "countOfFiltersApplied": "filtres appliqués", + "countOfFiltersApplied_one": "{{count}} filtre(s) appliqué(s)", + "countOfFiltersApplied_other": "{{count}} filtre(s) appliqué(s)", "dayOfBirth": "Jour de Naissance", "error": "Erreur", "errorCopy": "Désolé, une erreur s'est produite. Vous pouvez tenter de rafraîchir la page, ou bien contacter l'administrateur du site et transmettre le message d'erreur ci-dessus.", - "errorFetchingPatients": "Error fetching patients", - "errorFetchingUserProperties": "Error fetching user properties", - "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", + "errorFetchingPatients": "Erreur lors de la récupération des données", + "errorFetchingUserProperties": "Erreur lors de la récupération des propriétés d'utilisateur", + "errorLoadingAttribute": "Erreur lors du chargement du type d'attribut : {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Erreur lors du chargement des réponses aux attributs de concept", + "errorLoadingLocationsForAttribute": "Erreur lors du chargement des emplacements pour l'attribut de personne {{attributeName}}", + "errorUpdatingRecentlyViewedPatients": "Erreur lors de la mise à jour des patients reçus récemment", "female": "Féminin", "filtersAppliedText": "Rechercher les requêtes ajoutées", "male": "Masculin", @@ -19,7 +23,6 @@ "nextPage": "Page suivante", "noPatientChartsFoundMessage": "Désolé, aucun dossier de patient n'a été trouvé", "other": "Autre", - "phoneNumber": "Numéro de téléphone", "postcode": "Code postal", "previousPage": "Page précédente", "recentSearchResultsCount_one": "{{count}} résultat de recherche récent", @@ -31,12 +34,15 @@ "search": "Recherche", "searchForPatient": "Trouver un patient par nom ou identifiant", "searchingText": "Recherche", - "searchPatient": "Search patient", - "searchResults": "Search results", + "searchLocationPersonAttribute": "Search location", + "searchPatient": "Rechercher Patient", + "searchResults": "Rechercher les résultats", "searchResultsCount_one": "{{count}} résultat de recherche", "searchResultsCount_other": "{{count}} résultats de recherche", + "selectOption": "Choisir une option", "sex": "Sexe", "trySearchWithPatientUniqueID": "Essayez de chercher par l'identifiant unique du patient", "unknown": "Inconnu", + "unsupportedAttributeFormat": "Format d'attribut non supporté: {{format}}", "yearOfBirth": "Année de Naissance" } diff --git a/packages/esm-patient-search-app/translations/he.json b/packages/esm-patient-search-app/translations/he.json index 84565f62d..869acaabc 100644 --- a/packages/esm-patient-search-app/translations/he.json +++ b/packages/esm-patient-search-app/translations/he.json @@ -5,13 +5,17 @@ "clear": "נקה", "clearSearch": "נקה", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "מספר מסננים שהוחלו", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "יום הלידה", "error": "שגיאה", "errorCopy": "מצטערים, אירעה שגיאה. ניתן לנסות לטעון מחדש את הדף או ליצור קשר עם מנהל האתר ולציין את קוד השגיאה שמופיע למעלה.", - "errorFetchingPatients": "Error fetching patients", - "errorFetchingUserProperties": "Error fetching user properties", - "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", + "errorFetchingPatients": "שגיאה במשיכת מטופלים", + "errorFetchingUserProperties": "שגיאה במשיכת מאפייני משתמש", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", + "errorUpdatingRecentlyViewedPatients": "שגיאה בעדכון מטופלים שנצפו לאחרונה", "female": "נקבה", "filtersAppliedText": "תוספו שאילתות חיפוש", "male": "זכר", @@ -19,7 +23,6 @@ "nextPage": "הדף הבא", "noPatientChartsFoundMessage": "מצטערים, לא נמצאו כרטיסי מטופלים", "other": "אחר", - "phoneNumber": "מספר טלפון", "postcode": "מיקוד", "previousPage": "הדף הקודם", "recentSearchResultsCount_one": "{{count}} תוצאת חיפוש אחרונה", @@ -31,12 +34,15 @@ "search": "חיפוש", "searchForPatient": "חפש מטופל לפי שם או מספר זיהוי", "searchingText": "...מחפש", - "searchPatient": "Search patient", - "searchResults": "Search results", + "searchLocationPersonAttribute": "Search location", + "searchPatient": "חיפוש מטופל/", + "searchResults": "תוצאות חיפוש", "searchResultsCount_one": "{{count}} תוצאת חיפוש", "searchResultsCount_other": "{{count}} תוצאות חיפוש", + "selectOption": "Select an option", "sex": "מין", "trySearchWithPatientUniqueID": "נסה לחפש עם מספר הזיהוי הייחודי של המטופל", "unknown": "לא ידוע", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "שנת הלידה" } diff --git a/packages/esm-patient-search-app/translations/hi.json b/packages/esm-patient-search-app/translations/hi.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/hi.json +++ b/packages/esm-patient-search-app/translations/hi.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/hi_IN.json b/packages/esm-patient-search-app/translations/hi_IN.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/hi_IN.json +++ b/packages/esm-patient-search-app/translations/hi_IN.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/id.json b/packages/esm-patient-search-app/translations/id.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/id.json +++ b/packages/esm-patient-search-app/translations/id.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/it.json b/packages/esm-patient-search-app/translations/it.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/it.json +++ b/packages/esm-patient-search-app/translations/it.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/km.json b/packages/esm-patient-search-app/translations/km.json index 1da2d634e..8d6a9a90e 100644 --- a/packages/esm-patient-search-app/translations/km.json +++ b/packages/esm-patient-search-app/translations/km.json @@ -5,12 +5,16 @@ "clear": "ជម្រះ លុបចោល", "clearSearch": "ជម្រះ លុបចោល", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "បានអនុវត្តតម្រង ( ចុចតម្រង)", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "កំហុស", "errorCopy": "សូមអភ័យទោស មានកំហុសមួយ។ អ្នកអាចព្យាយាមផ្ទុកទំព័រនេះឡើងវិញ ឬទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័រ ហើយដកស្រង់កូដកំហុសខាងលើ។", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "ស្រី", "filtersAppliedText": "សំណួរស្វែងរកបានបន្ថែម", @@ -19,7 +23,6 @@ "nextPage": "ទំbពbរ័bបន្ទាប់", "noPatientChartsFoundMessage": "សូមអភ័យទោស រកមិនឃើញតារាងអ្នកជំងឺទេ។", "other": "ផ្សេងទៀត", - "phoneNumber": "លេខទូរស័ព្ទ", "postcode": "លេខកូដប្រៃសណីយ៍", "previousPage": "ទំព័រbមុន", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "ស្វែងរក", "searchForPatient": "ស្វែងរកអ្នកជំងឺតាមឈ្មោះ ឬអត្តលេខអ្នកជំងឺ", "searchingText": "កំពុងស្វែងរក...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "ភេទ", "trySearchWithPatientUniqueID": "ព្យាយាមស្វែងរកដោយប្រើអត្តលេខតែមួយគត់របស់អ្នកជំងឺ", "unknown": "មិនដឹង", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/ne.json b/packages/esm-patient-search-app/translations/ne.json new file mode 100644 index 000000000..72761ca8a --- /dev/null +++ b/packages/esm-patient-search-app/translations/ne.json @@ -0,0 +1,48 @@ +{ + "age": "Age", + "any": "Any", + "apply": "Apply", + "clear": "Clear", + "clearSearch": "Clear", + "closeSearch": "Close Search Panel", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", + "dayOfBirth": "Day of Birth", + "error": "Error", + "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", + "errorFetchingPatients": "Error fetching patients", + "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", + "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", + "female": "Female", + "filtersAppliedText": "search queries added", + "male": "Male", + "monthOfBirth": "Month of Birth", + "nextPage": "Next page", + "noPatientChartsFoundMessage": "Sorry, no patient charts were found", + "other": "Other", + "postcode": "Postcode", + "previousPage": "Previous page", + "recentSearchResultsCount_one": "{{count}} recent search result", + "recentSearchResultsCount_other": "{{count}} recent search results", + "refineSearch": "Refine search", + "refineSearchHeaderText": "Add additional search criteria", + "refineSearchTabletBannerText": "Can't find who you're looking for?", + "resetFields": "Reset fields", + "search": "Search", + "searchForPatient": "Search for a patient by name or identifier number", + "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", + "searchPatient": "Search patient", + "searchResults": "Search results", + "searchResultsCount_one": "{{count}} search result", + "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", + "sex": "Sex", + "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", + "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", + "yearOfBirth": "Year of Birth" +} diff --git a/packages/esm-patient-search-app/translations/pt.json b/packages/esm-patient-search-app/translations/pt.json index 4c26db793..5b4811508 100644 --- a/packages/esm-patient-search-app/translations/pt.json +++ b/packages/esm-patient-search-app/translations/pt.json @@ -5,12 +5,16 @@ "clear": "Limpar", "clearSearch": "Limpar", "closeSearch": "Fechar painel de pesquisa", - "countOfFiltersApplied": "filtros aplicados", + "countOfFiltersApplied_one": "{{count}} filtros aplicados", + "countOfFiltersApplied_other": "{{count}} filtros aplicados", "dayOfBirth": "Dia de Nascimento", "error": "Erro", "errorCopy": "Desculpe, ocorreu um erro. Você pode tentar recarregar esta página ou entrar em contato com o administrador do sistema providenciando o código de erro acima", "errorFetchingPatients": "Erro ao buscar utentes", "errorFetchingUserProperties": "Erro ao buscar propriedades dos utilizadores", + "errorLoadingAttribute": "Erro ao carregar tipo de atributo {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Erro ao carregar respostas de atributos de conceito", + "errorLoadingLocationsForAttribute": "Erro ao carregar localizações para o atributo pessoa {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Erro ao atualizar utentes visualizados recentemente", "female": "Feminino", "filtersAppliedText": "pesquisas adicionadas", @@ -19,24 +23,26 @@ "nextPage": "Próxima página", "noPatientChartsFoundMessage": "Desculpe, nenhum prontuário de utente foi encontrado", "other": "Outro", - "phoneNumber": "Número de Telefone", "postcode": "Caixa Postal", "previousPage": "Página anterior", "recentSearchResultsCount_one": "{{count}} resultado de pesquisa recente", "recentSearchResultsCount_other": "{{count}} resultados de pesquisa recente", "refineSearch": "Refinar a pesquisa", "refineSearchHeaderText": "Adicionar mais critérios de pesqusa", - "refineSearchTabletBannerText": "Não consegue encontrar a quem você está a procurar?", + "refineSearchTabletBannerText": "Não consegue encontrar a quem você procura?", "resetFields": "Redefinir campos", "search": "Pesquisar", "searchForPatient": "Pesquise um utente por nome ou identificador", "searchingText": "Pesquisando...", + "searchLocationPersonAttribute": "Procurar localização", "searchPatient": "Pesquisar Utente", "searchResults": "Resultados da pesquisa", "searchResultsCount_one": "{{count}} resultado da pesquisa", "searchResultsCount_other": "{{count}} resultados da pesquisa", + "selectOption": "Selecione uma opção", "sex": "Sexo", "trySearchWithPatientUniqueID": "Tente pesquisar novamente usando o número único de identificação do utente", "unknown": "Desconhecido", + "unsupportedAttributeFormat": "Formato de atributo não compatível: {{format}}", "yearOfBirth": "Ano de Nascimento" } diff --git a/packages/esm-patient-search-app/translations/qu.json b/packages/esm-patient-search-app/translations/qu.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/qu.json +++ b/packages/esm-patient-search-app/translations/qu.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/si.json b/packages/esm-patient-search-app/translations/si.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/si.json +++ b/packages/esm-patient-search-app/translations/si.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/sw.json b/packages/esm-patient-search-app/translations/sw.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/sw.json +++ b/packages/esm-patient-search-app/translations/sw.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/sw_KE.json b/packages/esm-patient-search-app/translations/sw_KE.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/sw_KE.json +++ b/packages/esm-patient-search-app/translations/sw_KE.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/tr.json b/packages/esm-patient-search-app/translations/tr.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/tr.json +++ b/packages/esm-patient-search-app/translations/tr.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/tr_TR.json b/packages/esm-patient-search-app/translations/tr_TR.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/tr_TR.json +++ b/packages/esm-patient-search-app/translations/tr_TR.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/uk.json b/packages/esm-patient-search-app/translations/uk.json index 6ab1b0c7b..72761ca8a 100644 --- a/packages/esm-patient-search-app/translations/uk.json +++ b/packages/esm-patient-search-app/translations/uk.json @@ -5,12 +5,16 @@ "clear": "Clear", "clearSearch": "Clear", "closeSearch": "Close Search Panel", - "countOfFiltersApplied": "filters applied", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Day of Birth", "error": "Error", "errorCopy": "Sorry, there was a an error. You can try to reload this page, or contact the site administrator and quote the error code above.", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "Female", "filtersAppliedText": "search queries added", @@ -19,7 +23,6 @@ "nextPage": "Next page", "noPatientChartsFoundMessage": "Sorry, no patient charts were found", "other": "Other", - "phoneNumber": "Phone number", "postcode": "Postcode", "previousPage": "Previous page", "recentSearchResultsCount_one": "{{count}} recent search result", @@ -31,12 +34,15 @@ "search": "Search", "searchForPatient": "Search for a patient by name or identifier number", "searchingText": "Searching...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}} search result", "searchResultsCount_other": "{{count}} search results", + "selectOption": "Select an option", "sex": "Sex", "trySearchWithPatientUniqueID": "Try to search again using the patient's unique ID number", "unknown": "Unknown", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Year of Birth" } diff --git a/packages/esm-patient-search-app/translations/vi.json b/packages/esm-patient-search-app/translations/vi.json index 2a7d57777..211157c1d 100644 --- a/packages/esm-patient-search-app/translations/vi.json +++ b/packages/esm-patient-search-app/translations/vi.json @@ -5,12 +5,16 @@ "clear": "Xóa", "clearSearch": "Xóa", "closeSearch": "Đóng bảng điều khiển", - "countOfFiltersApplied": "bộ lọc được áp dụng", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "Ngày sinh", "error": "Lỗi", "errorCopy": "Xin lỗi, đã xảy ra lỗi. Bạn có thể thử tải lại trang này hoặc liên hệ với quản trị viên trang web và trích dẫn mã lỗi ở trên.", "errorFetchingPatients": "Lỗi khi lấy danh sách bệnh nhân", "errorFetchingUserProperties": "Lỗi khi tìm nạp thuộc tính người dùng", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Lỗi khi cập nhật bệnh nhân đã xem gần đây", "female": "Nữ", "filtersAppliedText": "truy vấn tìm kiếm được thêm vào", @@ -19,7 +23,6 @@ "nextPage": "Trang tiếp theo", "noPatientChartsFoundMessage": "Xin lỗi, không tìm thấy biểu đồ bệnh nhân", "other": "Khác", - "phoneNumber": "Số điện thoại", "postcode": "Mã bưu chính", "previousPage": "Trang trước", "recentSearchResultsCount_one": "{{count}} kết quả tìm kiếm gần đây", @@ -31,12 +34,15 @@ "search": "Tìm kiếm", "searchForPatient": "Tìm kiếm bệnh nhân theo tên hoặc số định danh", "searchingText": "Đang tìm kiếm", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Tìm kiếm bệnh nhân", "searchResults": "Kết quả tìm kiếm", "searchResultsCount_one": "{{count}} kết quả tìm kiếm", "searchResultsCount_other": "{{count}} kết quả tìm kiếm", + "selectOption": "Select an option", "sex": "Giới tính", "trySearchWithPatientUniqueID": "Hãy thử tìm kiếm lại bằng cách sử dụng số ID duy nhất của bệnh nhân", "unknown": "Không rõ", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "Năm sinh" } diff --git a/packages/esm-patient-search-app/translations/zh.json b/packages/esm-patient-search-app/translations/zh.json index f10dc1371..6ffb7dd50 100644 --- a/packages/esm-patient-search-app/translations/zh.json +++ b/packages/esm-patient-search-app/translations/zh.json @@ -5,12 +5,16 @@ "clear": "清除", "clearSearch": "清除", "closeSearch": "关闭搜索面板", - "countOfFiltersApplied": "已应用筛选器", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "出生日期", "error": "错误", "errorCopy": "抱歉,发生了一个错误。您可以尝试重新加载此页面,或联系网站管理员并引用上面的错误代码。", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "女性", "filtersAppliedText": "搜索查询已添加", @@ -19,7 +23,6 @@ "nextPage": "下一页", "noPatientChartsFoundMessage": "抱歉,未找到患者记录。", "other": "其他", - "phoneNumber": "电话号码", "postcode": "邮政编码", "previousPage": "上一页", "recentSearchResultsCount_one": "{{count}}最近的搜索结果", @@ -31,12 +34,15 @@ "search": "搜索", "searchForPatient": "按照姓名或ID标识搜索患者", "searchingText": "搜索中...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}}搜索结果", "searchResultsCount_other": "{{count}}搜索结果", + "selectOption": "Select an option", "sex": "性别", "trySearchWithPatientUniqueID": "尝试使用患者的唯一ID再次进行搜索", "unknown": "未知", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "出生年份" } diff --git a/packages/esm-patient-search-app/translations/zh_CN.json b/packages/esm-patient-search-app/translations/zh_CN.json index f10dc1371..6ffb7dd50 100644 --- a/packages/esm-patient-search-app/translations/zh_CN.json +++ b/packages/esm-patient-search-app/translations/zh_CN.json @@ -5,12 +5,16 @@ "clear": "清除", "clearSearch": "清除", "closeSearch": "关闭搜索面板", - "countOfFiltersApplied": "已应用筛选器", + "countOfFiltersApplied_one": "{{count}} filters applied", + "countOfFiltersApplied_other": "{{count}} filters applied", "dayOfBirth": "出生日期", "error": "错误", "errorCopy": "抱歉,发生了一个错误。您可以尝试重新加载此页面,或联系网站管理员并引用上面的错误代码。", "errorFetchingPatients": "Error fetching patients", "errorFetchingUserProperties": "Error fetching user properties", + "errorLoadingAttribute": "Error loading attribute type {{attributeUuid}}", + "errorLoadingConceptAttributeAnswers": "Error loading concept attribute answers", + "errorLoadingLocationsForAttribute": "Error loading locations for person attribute {{attributeName}}", "errorUpdatingRecentlyViewedPatients": "Error updating recently viewed patients", "female": "女性", "filtersAppliedText": "搜索查询已添加", @@ -19,7 +23,6 @@ "nextPage": "下一页", "noPatientChartsFoundMessage": "抱歉,未找到患者记录。", "other": "其他", - "phoneNumber": "电话号码", "postcode": "邮政编码", "previousPage": "上一页", "recentSearchResultsCount_one": "{{count}}最近的搜索结果", @@ -31,12 +34,15 @@ "search": "搜索", "searchForPatient": "按照姓名或ID标识搜索患者", "searchingText": "搜索中...", + "searchLocationPersonAttribute": "Search location", "searchPatient": "Search patient", "searchResults": "Search results", "searchResultsCount_one": "{{count}}搜索结果", "searchResultsCount_other": "{{count}}搜索结果", + "selectOption": "Select an option", "sex": "性别", "trySearchWithPatientUniqueID": "尝试使用患者的唯一ID再次进行搜索", "unknown": "未知", + "unsupportedAttributeFormat": "Unsupported attribute format: {{format}}", "yearOfBirth": "出生年份" } diff --git a/packages/esm-service-queues-app/package.json b/packages/esm-service-queues-app/package.json index 949e986e4..6650bad2e 100644 --- a/packages/esm-service-queues-app/package.json +++ b/packages/esm-service-queues-app/package.json @@ -37,7 +37,7 @@ "url": "https://github.com/openmrs/openmrs-esm-patient-management/issues" }, "dependencies": { - "@carbon/react": "~1.37.0", + "@carbon/react": "^1.71.0", "lodash-es": "^4.17.15" }, "peerDependencies": { diff --git a/packages/esm-service-queues-app/src/active-visits/active-visits-table.resource.ts b/packages/esm-service-queues-app/src/active-visits/active-visits-table.resource.ts index 587096787..d87634b57 100644 --- a/packages/esm-service-queues-app/src/active-visits/active-visits-table.resource.ts +++ b/packages/esm-service-queues-app/src/active-visits/active-visits-table.resource.ts @@ -193,68 +193,6 @@ export function useServiceQueueEntries(service: string, locationUuid: string) { }; } -export async function postQueueEntry( - visitUuid: string, - queueUuid: string, - patientUuid: string, - priority: string, - status: string, - sortWeight: number, - locationUuid: string, - visitQueueNumberAttributeUuid: string, -) { - const abortController = new AbortController(); - - await Promise.all([generateVisitQueueNumber(locationUuid, visitUuid, queueUuid, visitQueueNumberAttributeUuid)]); - - return openmrsFetch(`${restBaseUrl}/visit-queue-entry`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - signal: abortController.signal, - body: { - visit: { uuid: visitUuid }, - queueEntry: { - status: { - uuid: status, - }, - priority: { - uuid: priority, - }, - queue: { - uuid: queueUuid, - }, - patient: { - uuid: patientUuid, - }, - startedAt: new Date(), - sortWeight: sortWeight, - }, - }, - }); -} - -export async function generateVisitQueueNumber( - location: string, - visitUuid: string, - queueUuid: string, - visitQueueNumberAttributeUuid: string, -) { - const abortController = new AbortController(); - - await openmrsFetch( - `${restBaseUrl}/queue-entry-number?location=${location}&queue=${queueUuid}&visit=${visitUuid}&visitAttributeType=${visitQueueNumberAttributeUuid}`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - signal: abortController.signal, - }, - ); -} - export function serveQueueEntry(servicePointName: string, ticketNumber: string, status: string) { const abortController = new AbortController(); diff --git a/packages/esm-service-queues-app/src/active-visits/change-status-dialog.component.tsx b/packages/esm-service-queues-app/src/active-visits/change-status-dialog.component.tsx index e9d9bad2a..6477d75ed 100644 --- a/packages/esm-service-queues-app/src/active-visits/change-status-dialog.component.tsx +++ b/packages/esm-service-queues-app/src/active-visits/change-status-dialog.component.tsx @@ -25,7 +25,7 @@ import { type ConfigObject } from '../config-schema'; import { useQueues } from '../hooks/useQueues'; import { updateQueueEntry } from './active-visits-table.resource'; import { useMutateQueueEntries } from '../hooks/useQueueEntries'; -import { useQueueLocations } from '../patient-search/hooks/useQueueLocations'; +import { useQueueLocations } from '../create-queue-entry/hooks/useQueueLocations'; import styles from './change-status-dialog.scss'; interface ChangeStatusDialogProps { @@ -46,7 +46,7 @@ const ChangeStatus: React.FC = ({ queueEntry, closeModa service: z.string({ required_error: t('serviceIsRequired', 'Service is required') }), status: z.string({ required_error: t('statusIsRequired', 'Status is required') }), }), - [], + [t], ); type ChangeStatusForm = z.infer; diff --git a/packages/esm-service-queues-app/src/active-visits/change-status-dialog.test.tsx b/packages/esm-service-queues-app/src/active-visits/change-status-dialog.test.tsx index 917411b60..4f0063a93 100644 --- a/packages/esm-service-queues-app/src/active-visits/change-status-dialog.test.tsx +++ b/packages/esm-service-queues-app/src/active-visits/change-status-dialog.test.tsx @@ -14,18 +14,18 @@ import { configSchema, type ConfigObject } from '../config-schema'; import { updateQueueEntry } from './active-visits-table.resource'; import ChangeStatus from './change-status-dialog.component'; -const mockUseConfig = jest.mocked(useConfig); const mockShowSnackbar = jest.mocked(showSnackbar); const mockUpdateQueueEntry = jest.mocked(updateQueueEntry); -const mockUseSession = jest.mocked(useSession); +const mockUseConfig = jest.mocked(useConfig); const mockUseLocations = jest.mocked(useLocations); +const mockUseSession = jest.mocked(useSession); jest.mock('./active-visits-table.resource', () => ({ ...jest.requireActual('./active-visits-table.resource'), updateQueueEntry: jest.fn(), })); -jest.mock('../patient-search/hooks/useQueueLocations', () => { +jest.mock('../create-queue-entry/hooks/useQueueLocations', () => { return { useQueueLocations: jest.fn().mockReturnValue({ queueLocations: mockLocations.data?.results.map((location) => ({ ...location, id: location.uuid })), @@ -40,6 +40,8 @@ jest.mock('../hooks/useQueues', () => { }); describe('ChangeStatusDialog', () => { + let consoleSpy: jest.SpyInstance; + beforeEach(() => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(configSchema), @@ -47,6 +49,12 @@ describe('ChangeStatusDialog', () => { } as ConfigObject); mockUseLocations.mockReturnValue(mockLocations.data.results); mockUseSession.mockReturnValue(mockSession.data); + + consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleSpy.mockRestore(); }); it('should update a queue entry and display toast message', async () => { diff --git a/packages/esm-service-queues-app/src/add-patient-toqueue/add-patient-toqueue-dialog.component.tsx b/packages/esm-service-queues-app/src/add-patient-toqueue/add-patient-toqueue-dialog.component.tsx deleted file mode 100644 index 9aa866a0f..000000000 --- a/packages/esm-service-queues-app/src/add-patient-toqueue/add-patient-toqueue-dialog.component.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - Button, - Form, - InlineNotification, - ModalBody, - ModalFooter, - ModalHeader, - Select, - SelectItem, - RadioButtonGroup, - RadioButton, - RadioButtonSkeleton, - SelectSkeleton, -} from '@carbon/react'; -import { showSnackbar, useConfig } from '@openmrs/esm-framework'; -import { postQueueEntry } from '../active-visits/active-visits-table.resource'; -import { type ActiveVisit } from '../visits-missing-inqueue/visits-missing-inqueue.resource'; -import { useQueueLocations } from '../patient-search/hooks/useQueueLocations'; -import { useQueues } from '../hooks/useQueues'; -import { useMutateQueueEntries } from '../hooks/useQueueEntries'; -import { type ConfigObject } from '../config-schema'; -import styles from './add-patient-toqueue-dialog.scss'; - -interface AddVisitToQueueDialogProps { - visitDetails: ActiveVisit; - closeModal: () => void; -} - -const AddVisitToQueue: React.FC = ({ visitDetails, closeModal }) => { - const { t } = useTranslation(); - - const visitUuid = visitDetails?.visitUuid; - const [queueUuid, setQueueUuid] = useState(''); - const patientUuid = visitDetails?.patientUuid; - const patientName = visitDetails?.name; - const patientAge = visitDetails?.age; - const patientSex = visitDetails?.gender; - const [selectedQueueLocation, setSelectedQueueLocation] = useState(''); - const { queues, isLoading: isLoadingQueues } = useQueues(selectedQueueLocation); - const { queueLocations, isLoading: isLoadingQueueLocations } = useQueueLocations(); - const [isMissingPriority, setIsMissingPriority] = useState(false); - const [isMissingService, setIsMissingService] = useState(false); - const config = useConfig(); - const { mutateQueueEntries } = useMutateQueueEntries(); - const [priority, setPriority] = useState(config.concepts.defaultPriorityConceptUuid); - const priorities = queues.find((q) => q.uuid === queueUuid)?.allowedPriorities ?? []; - - const addVisitToQueue = useCallback(() => { - if (!queueUuid) { - setIsMissingService(true); - return; - } - setIsMissingService(false); - - if (!priority) { - setIsMissingPriority(true); - return; - } - setIsMissingPriority(false); - const emergencyPriorityConceptUuid = config.concepts.emergencyPriorityConceptUuid; - const sortWeight = priority === emergencyPriorityConceptUuid ? 1.0 : 0.0; - const status = config.concepts.defaultStatusConceptUuid; - const visitQueueNumberAttributeUuid = config.visitQueueNumberAttributeUuid; - - postQueueEntry( - visitUuid, - queueUuid, - patientUuid, - priority, - status, - sortWeight, - selectedQueueLocation, - visitQueueNumberAttributeUuid, - ).then( - ({ status }) => { - if (status === 201) { - showSnackbar({ - isLowContrast: true, - title: t('addEntry', 'Add entry'), - kind: 'success', - subtitle: t('queueEntryAddedSuccessfully', 'Queue entry added successfully'), - }); - closeModal(); - mutateQueueEntries(); - } - }, - (error) => { - showSnackbar({ - title: t('queueEntryAddFailed', 'Error adding queue entry status'), - kind: 'error', - isLowContrast: false, - subtitle: error?.message, - }); - }, - ); - }, [ - queueUuid, - priority, - config.concepts.emergencyPriorityConceptUuid, - config.concepts.defaultStatusConceptUuid, - config.visitQueueNumberAttributeUuid, - visitUuid, - patientUuid, - selectedQueueLocation, - t, - closeModal, - mutateQueueEntries, - ]); - - return ( -
- - -
-
-
- {patientName}   ·  {patientSex}   ·  {patientAge} {t('years', 'Years')} -
-
-
- {isLoadingQueueLocations ? ( - - ) : ( - - )} -
- -
-
{t('queueService', 'Queue service')}
- {isLoadingQueues ? ( - - ) : ( - - )} -
- {isMissingService && ( -
- -
- )} - -
-
{t('queueStatus', 'Queue status')}
- {isLoadingQueues ? ( - - - - - - ) : !priorities?.length ? ( - - ) : ( - { - setPriority(uuid); - }}> - {priorities?.length > 0 && - priorities.map(({ uuid, display }) => )} - - )} -
- {isMissingPriority && ( -
- -
- )} -
-
- - - - -
- ); -}; - -export default AddVisitToQueue; diff --git a/packages/esm-service-queues-app/src/add-patient-toqueue/add-patient-toqueue-dialog.scss b/packages/esm-service-queues-app/src/add-patient-toqueue/add-patient-toqueue-dialog.scss deleted file mode 100644 index 467a0557e..000000000 --- a/packages/esm-service-queues-app/src/add-patient-toqueue/add-patient-toqueue-dialog.scss +++ /dev/null @@ -1,32 +0,0 @@ -@use '@carbon/layout'; -@use '@carbon/type'; -@use '@openmrs/esm-styleguide/src/vars' as *; - -.radioButtonGroup { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-top: layout.$spacing-03; - min-height: layout.$spacing-10; - width: 100%; - @include type.type-style('body-compact-01'); -} - -.radioButton { - padding: layout.$spacing-02 layout.$spacing-02; - margin: layout.$spacing-03 0; -} - -section { - margin: layout.$spacing-03; -} - -.sectionTitle { - @include type.type-style('heading-compact-02'); - color: $text-02; - margin-bottom: layout.$spacing-04; -} - -.modalBody { - padding-bottom: layout.$spacing-05; -} diff --git a/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.component.tsx b/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.component.tsx index 8496ab5ba..8b3bd0698 100644 --- a/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.component.tsx +++ b/packages/esm-service-queues-app/src/add-provider-queue-room/add-provider-queue-room.component.tsx @@ -13,7 +13,7 @@ import { SelectItem, } from '@carbon/react'; import { showSnackbar } from '@openmrs/esm-framework'; -import { useQueueLocations } from '../patient-search/hooks/useQueueLocations'; +import { useQueueLocations } from '../create-queue-entry/hooks/useQueueLocations'; import { addProviderToQueueRoom, updateProviderToQueueRoom, @@ -35,20 +35,21 @@ import useQueueServices from '../hooks/useQueueService'; import styles from './add-provider-queue-room.scss'; interface AddProviderQueueRoomProps { - providerUuid: string; closeModal: () => void; + providerUuid: string; } -const AddProviderQueueRoom: React.FC = ({ providerUuid, closeModal }) => { +const AddProviderQueueRoom: React.FC = ({ closeModal, providerUuid }) => { const { t } = useTranslation(); - + const { providerRoom } = useProvidersQueueRoom(providerUuid); + const currentIsPermanentProviderQueueRoom = useIsPermanentProviderQueueRoom() ?? false; const currentLocationName = useSelectedQueueLocationName(); const currentLocationUuid = useSelectedQueueLocationUuid(); const currentService = useSelectedService(); - const currentIsPermanentProviderQueueRoom = useIsPermanentProviderQueueRoom(); - const { providerRoom } = useProvidersQueueRoom(providerUuid); - const [queueRoomUuid, setQueueRoomUuid] = useState(''); + + const [isMissingQueueRoom, setIsMissingQueueRoom] = useState(false); const [queueProviderMapUuid, setQueueProviderMapUuid] = useState(''); + const [queueRoomUuid, setQueueRoomUuid] = useState(''); useEffect(() => { if (providerRoom?.length > 0) { @@ -58,10 +59,9 @@ const AddProviderQueueRoom: React.FC = ({ providerUui }, [providerRoom]); const { mutate } = useProvidersQueueRoom(providerUuid); - const { services } = useQueueServices(); - const { rooms } = useQueueRooms(currentLocationUuid, currentService?.serviceUuid); const { queueLocations } = useQueueLocations(); - const [isMissingQueueRoom, setIsMissingQueueRoom] = useState(false); + const { rooms } = useQueueRooms(currentLocationUuid, currentService?.serviceUuid); + const { services } = useQueueServices(); const handleServiceChange = ({ selectedItem }) => { localStorage.setItem('queueServiceName', selectedItem.name); @@ -90,19 +90,17 @@ const AddProviderQueueRoom: React.FC = ({ providerUui if (providerRoom?.length > 0) { updateProviderToQueueRoom(queueProviderMapUuid, queueRoomUuid, providerUuid).then( - ({ status }) => { - if (status === 200) { - showSnackbar({ - isLowContrast: true, - title: t('updateRoom', 'Update room'), - kind: 'success', - subtitle: t('queueRoomUpdatedSuccessfully', 'Queue room updated successfully'), - }); - closeModal(); - localStorage.setItem('lastUpdatedQueueRoomTimestamp', new Date().toString()); - updatedSelectedQueueRoomTimestamp(new Date()); - mutate(); - } + () => { + showSnackbar({ + isLowContrast: true, + title: t('updateRoom', 'Update room'), + kind: 'success', + subtitle: t('queueRoomUpdatedSuccessfully', 'Queue room updated successfully'), + }); + closeModal(); + localStorage.setItem('lastUpdatedQueueRoomTimestamp', new Date().toString()); + updatedSelectedQueueRoomTimestamp(new Date()); + mutate(); }, (error) => { showSnackbar({ @@ -116,19 +114,17 @@ const AddProviderQueueRoom: React.FC = ({ providerUui ); } else { addProviderToQueueRoom(queueRoomUuid, providerUuid).then( - ({ status }) => { - if (status === 201) { - showSnackbar({ - isLowContrast: true, - title: t('addRoom', 'Add room'), - kind: 'success', - subtitle: t('queueRoomAddedSuccessfully', 'Queue room added successfully'), - }); - closeModal(); - localStorage.setItem('lastUpdatedQueueRoomTimestamp', new Date().toString()); - updatedSelectedQueueRoomTimestamp(new Date()); - mutate(); - } + () => { + showSnackbar({ + isLowContrast: true, + title: t('addRoom', 'Add room'), + kind: 'success', + subtitle: t('queueRoomAddedSuccessfully', 'Queue room added successfully'), + }); + closeModal(); + localStorage.setItem('lastUpdatedQueueRoomTimestamp', new Date().toString()); + updatedSelectedQueueRoomTimestamp(new Date()); + mutate(); }, (error) => { showSnackbar({ @@ -149,31 +145,31 @@ const AddProviderQueueRoom: React.FC = ({ providerUui
(item ? item.name : '')} + label="" onChange={handleQueueLocationChange} size="md" - initialSelectedItem={{ uuid: currentLocationUuid, name: currentLocationName }} + titleText={t('queueLocation', 'Queue location')} + type="default" />
(item ? item.display : '')} + label="" onChange={handleServiceChange} size="md" - initialSelectedItem={{ uuid: currentService?.serviceUuid, display: currentService?.serviceDisplay }} + titleText={t('queueService', 'Queue service')} + type="default" />
@@ -181,15 +177,14 @@ const AddProviderQueueRoom: React.FC = ({ providerUui
{t('queueRoom', 'Queue room')}
setSelectedLocation(event.target.value)}> - {!selectedLocation ? : null} - {!isEmpty(defaultFacility) ? ( - - {defaultFacility?.display} - - ) : locations?.length > 0 ? ( - locations.map((location) => ( - - {location.display} - - )) - ) : null} - -
- - {config.showRecommendedVisitTypeTab && ( -
-
{t('program', 'Program')}
- - - setEnrollment(activePatientEnrollment.find(({ program }) => program.uuid === uuid)) - } - name="program-type-radio-group" - valueSelected="default-selected"> - {activePatientEnrollment.map(({ uuid, display, program }) => ( - - ))} - - -
- )} -
-
{t('visitType', 'Visit Type')}
- {config.showRecommendedVisitTypeTab && ( - setContentSwitcherIndex(index)}> - - - - )} - {config.showRecommendedVisitTypeTab && contentSwitcherIndex === 0 && ( - { - setVisitType(visitType); - setIsMissingVisitType(false); - }} - patientUuid={patientUuid} - patientProgram={enrollment} - locationUuid={selectedLocation} - /> - )} - {(!config.showRecommendedVisitTypeTab || contentSwitcherIndex === 1) && ( - { - setVisitType(visitType); - setIsMissingVisitType(false); - }} - /> - )} -
- {isMissingVisitType && ( -
- -
- )} - - - -
- - - - - - ); -}; - -export default VisitForm; diff --git a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx deleted file mode 100644 index 9e7ef26cf..000000000 --- a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.component.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import React, { useState, useMemo, useEffect } from 'react'; -import classNames from 'classnames'; -import isEmpty from 'lodash-es/isEmpty'; -import { useTranslation } from 'react-i18next'; -import { - InlineNotification, - Layer, - RadioButton, - RadioButtonGroup, - Search, - StructuredListSkeleton, - Tile, -} from '@carbon/react'; -import { ResponsiveWrapper, useDebounce, useLayoutType, useVisitTypes, type VisitType } from '@openmrs/esm-framework'; -import EmptyDataIllustration from '../empty-data-illustration.component'; -import styles from './visit-type-selector.scss'; -import { useRecommendedVisitTypes } from '../hooks/useRecommendedVisitTypes'; -import { type PatientProgram } from '../../types'; - -export interface VisitTypeSelectorProps { - onChange: (event) => void; -} - -export const VisitTypeSelector: React.FC = ({ onChange }) => { - const allVisitTypes = useVisitTypes(); - - return ( -
- {allVisitTypes.length == 0 ? ( - - ) : ( - - )} -
- ); -}; - -export interface RecommendedVisitTypeSelectorProps { - onChange: (event) => void; - patientUuid: string; - patientProgram: PatientProgram; - locationUuid: string; -} - -/** Recommended visits are specfic to a patient, patient program, and location. */ -export const RecommendedVisitTypeSelector: React.FC = ({ - onChange, - patientUuid, - patientProgram, - locationUuid, -}) => { - const { t } = useTranslation(); - const { recommendedVisitTypes, error, isLoading } = useRecommendedVisitTypes( - patientUuid, - patientProgram?.uuid, - patientProgram?.program?.uuid, - locationUuid, - ); - - return ( -
- {isLoading ? ( - - ) : ( - - )} - {error && ( - - )} -
- ); -}; - -interface VisitTypeSelectorPresentationProps { - onChange: (event) => void; - visitTypes: VisitType[]; -} - -const MAX_RESULTS = 5; - -const VisitTypeSelectorPresentation: React.FC = ({ visitTypes, onChange }) => { - const { t } = useTranslation(); - const isTablet = useLayoutType() === 'tablet'; - const [searchTerm, setSearchTerm] = useState(''); - const debouncedSearchTerm = useDebounce(searchTerm); - const [selectedVisitType, setSelectedVisitType] = useState(); - - const results = useMemo(() => { - if (!isEmpty(debouncedSearchTerm)) { - return visitTypes.filter( - (visitType) => visitType.display.toLowerCase().search(debouncedSearchTerm.toLowerCase()) !== -1, - ); - } else { - return visitTypes; - } - }, [debouncedSearchTerm, visitTypes]); - - const truncatedResults = results.slice(0, MAX_RESULTS); - - useEffect(() => { - if (results.length > 0) { - onChange(results[0].uuid); - setSelectedVisitType(results[0].uuid); - } - }, [results]); - - return ( -
- {truncatedResults.length < visitTypes.length ? ( - - setSearchTerm(event.target.value)} - placeholder={t('searchForAVisitType', 'Search for a visit type')} - labelText="" - /> - - ) : null} - {truncatedResults.length ? ( - { - setSelectedVisitType(visitType); - onChange(visitType); - }} - name="radio-button-group" - valueSelected={selectedVisitType}> - {truncatedResults.map(({ uuid, display, name }) => ( - - ))} - {/* TODO: This is supposed to paginate. Right now it just shows the user a truncated list - with no indication that the list is truncated. */} - - ) : ( - - - -

- {t('noVisitTypesMatchingSearch', 'There are no visit types matching this search text')} -

-
-
- )} -
- ); -}; diff --git a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.scss b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.scss deleted file mode 100644 index f93b62baf..000000000 --- a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.scss +++ /dev/null @@ -1,100 +0,0 @@ -@use '@carbon/layout'; -@use '@carbon/type'; -@use '@openmrs/esm-styleguide/src/vars' as *; - -.visitTypeOverviewWrapper { - margin: layout.$spacing-05 0; - border: 0.0625rem solid $grey-2; -} - -.tablet { - background-color: $ui-02; -} - -.desktop { - background-color: $ui-01; - - .paginationContainer div { - background-color: $ui-01; - } -} - -.visitTypeOverviewWrapper div:nth-child(3) > div:nth-child(2) { - position: relative; -} - -.visitTypeOverviewWrapper div:nth-child(3) span * { - display: none; -} - -.radioButtonGroup { - display: flex; - flex-direction: column; - align-items: flex-start; - margin-top: layout.$spacing-03; - min-height: layout.$spacing-10; - width: 100%; - @include type.type-style('body-compact-01'); -} - -.radioButton { - padding: layout.$spacing-02 layout.$spacing-05; - margin: layout.$spacing-03 0; -} - -.content { - @include type.type-style('heading-compact-01'); - color: $text-02; - margin-top: layout.$spacing-05; - margin-bottom: layout.$spacing-03; -} - -.desktopHeading { - h4 { - @include type.type-style('heading-compact-02'); - color: $text-02; - } -} - -.tabletHeading { - h4 { - @include type.type-style('heading-03'); - color: $text-02; - } -} - -.desktopHeading, -.tabletHeading { - text-align: left; - text-transform: capitalize; - margin-bottom: layout.$spacing-05; - - h4:after { - content: ''; - display: block; - width: layout.$spacing-07; - padding-top: 0.188rem; - border-bottom: 0.375rem solid var(--brand-03); - } -} - -.heading:after { - content: ''; - display: block; - width: layout.$spacing-07; - padding-top: 0.188rem; - border-bottom: 0.375rem solid var(--brand-03); -} - -.tile { - text-align: center; - border: 1px solid $ui-03; -} - -// Overriding styles for RTL support -html[dir='rtl'] { - .desktopHeading, - .tabletHeading { - text-align: right; - } -} diff --git a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx b/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx deleted file mode 100644 index f94105bce..000000000 --- a/packages/esm-service-queues-app/src/patient-search/visit-form/visit-type-selector.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-disable testing-library/no-node-access */ -import React from 'react'; -import userEvent from '@testing-library/user-event'; -import { render, screen } from '@testing-library/react'; -import { mockVisitTypes } from '__mocks__'; -import { useVisitTypes } from '@openmrs/esm-framework'; -import { VisitTypeSelector } from './visit-type-selector.component'; - -const mockUseVisitTypes = jest.mocked(useVisitTypes); - -describe('VisitTypeSelector', () => { - beforeEach(() => { - mockUseVisitTypes.mockReturnValue(mockVisitTypes); - }); - - it('renders visit types with no search bar if there are 5 or fewer', () => { - const fewVisitTypes = mockVisitTypes.slice(0, 3); - mockUseVisitTypes.mockReturnValue(fewVisitTypes); - render( {}} />); - - expect(screen.queryByRole('searchbox')).not.toBeInTheDocument(); - - fewVisitTypes.forEach((visitType) => { - const radioButton = screen.getByLabelText(visitType.display); - expect(radioButton).toBeInTheDocument(); - }); - }); - - it('renders the first 5 visit types with a search bar if there are more than 5', () => { - render( {}} />); - - expect(screen.getByRole('searchbox')).toBeInTheDocument(); - - mockVisitTypes.slice(0, 5).forEach((visitType) => { - const radioButton = screen.getByLabelText(visitType.display); - expect(radioButton).toBeInTheDocument(); - }); - }); - - it('filters by search input and calls onChange', async () => { - const user = userEvent.setup(); - - const mockOnChange = jest.fn(); - render(); - - const searchInput = screen.getByRole('searchbox').closest('input'); - await user.type(searchInput, 'hiv'); - - expect(searchInput.value).toBe('hiv'); - expect(screen.getByLabelText('HIV Return Visit')).toBeInTheDocument(); - expect(screen.getByLabelText('HIV Initial Visit')).toBeInTheDocument(); - expect(screen.queryByLabelText('Outpatient Visit')).not.toBeInTheDocument(); - expect(screen.queryByLabelText('Diabetes Clinic Visit')).not.toBeInTheDocument(); - - expect(mockOnChange).toHaveBeenLastCalledWith( - mockVisitTypes.filter((vt) => vt.display == 'HIV Return Visit')[0].uuid, - ); - }); - - it('calls onChange when a visit type is selected', async () => { - const user = userEvent.setup(); - - const mockOnChange = jest.fn(); - render(); - - const radioButton = screen.getByLabelText(mockVisitTypes[1].display).closest('input'); - await user.click(radioButton); - expect(radioButton).toBeChecked(); - expect(mockOnChange).toHaveBeenLastCalledWith(mockVisitTypes[1].uuid); - }); - - it('allows changing the search input if no results are returned from a search', async () => { - const user = userEvent.setup(); - - render( {}} />); - - const searchInput: HTMLInputElement = screen.getByRole('searchbox'); - await user.type(searchInput, 'asdfasdf'); - - const searchInputAfter: HTMLInputElement = screen.getByRole('searchbox'); - expect(searchInputAfter).toBeInTheDocument(); - expect(screen.getByText(/no visit types/i)).toBeInTheDocument(); - }); -}); diff --git a/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.component.tsx b/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.component.tsx index e5b1fed52..31de60b15 100644 --- a/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.component.tsx +++ b/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.component.tsx @@ -13,23 +13,28 @@ interface QueuePriorityProps { const QueuePriority: React.FC = ({ priority, priorityComment, priorityConfigs }) => { const priorityConfig = priorityConfigs.find((c) => c.conceptUuid === priority.uuid); + + const tag = ( + + {priority.display} + + ); + return ( <> {priorityComment ? ( - - {priority.display} - + {tag} ) : ( - - {priority.display} - + tag )} ); diff --git a/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.scss b/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.scss index 575843c7d..e3c5a4585 100644 --- a/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.scss +++ b/packages/esm-service-queues-app/src/queue-entry-table-components/queue-priority.scss @@ -1,5 +1,6 @@ @use '@carbon/layout'; @use '@openmrs/esm-styleguide/src/vars' as *; +@use '@carbon/colors'; .tag { margin: layout.$spacing-02 0; @@ -9,3 +10,7 @@ .bold { font-weight: bold; } + +.orange { + background-color: colors.$orange-20; +} diff --git a/packages/esm-service-queues-app/src/queue-entry-table-components/transition-entry.component.tsx b/packages/esm-service-queues-app/src/queue-entry-table-components/transition-entry.component.tsx index 81b65ee21..a1cfbc069 100644 --- a/packages/esm-service-queues-app/src/queue-entry-table-components/transition-entry.component.tsx +++ b/packages/esm-service-queues-app/src/queue-entry-table-components/transition-entry.component.tsx @@ -35,7 +35,7 @@ const TransitionMenu: React.FC = ({ queueEntry }) => { closeModal: () => dispose(), queueEntry, }); - }, [queueEntry]); + }, [queueEntry, t]); return (