diff --git a/packages/esm-audit-app/locales/en/translation.json b/packages/esm-audit-app/locales/en/translation.json new file mode 100644 index 0000000..0ff8d12 --- /dev/null +++ b/packages/esm-audit-app/locales/en/translation.json @@ -0,0 +1,118 @@ +{ + "searchBy": "Search by", + "patients": "Patients", + "users": "Users", + "providers": "Providers", + "auditTrail": "Audit trail", + "encounterAudit": "Encounter audit", + "encounterNotFound": "That encounter could not be found.", + "patient": "Patient", + "identifier": "Identifier", + "encounterDate": "Encounter date", + "location": "Location", + "visit": "Visit", + "encounterType": "Encounter type", + "form": "Form", + "createdBy": "Created by", + "changedBy": "Changed by", + "deletedBy": "Deleted by", + "deletionReason": "Reason for deletion", + "encounter": "Encounter", + "deleted": "Deleted", + "encounterDeletedSubtitle": "This encounter has been deleted. Its observations are shown as they were left.", + "encounterDeleted": "Deleted encounter", + "encounterSummary": "Encounter summary", + "noProviders": "No providers are recorded for this encounter.", + "role": "Role", + "providerName": "Provider name", + "backToEncounters": "Back to encounters", + "allEncounterTypes": "All encounter types", + "encounterDateRange": "Encounter date range", + "clearFilters": "Clear filters", + "edited": "Edited", + "editedLegend": "the value was changed after it was first recorded", + "addedLater": "Added later", + "addedLaterLegend": "recorded after the encounter itself was created", + "deletedLegend": "removed from the encounter", + "previousValue": "Previous value", + "previousValueLegend": "the value that an edit replaced", + "observations": "Observations", + "showDeletedObs": "Show deleted observations", + "showConceptDescriptions": "Show concept descriptions", + "noObservations": "This encounter has no observations.", + "questionConcept": "Question concept", + "value": "Value", + "change": "Change", + "recordedBy": "Recorded by", + "wasPreviously": "was {{value}}", + "changedByUser": "Changed by {{user}}", + "encounterCreatedAction": "Created encounter", + "encounterChangedAction": "Changed encounter", + "encounterDeletedAction": "Deleted encounter", + "obsRecordedAction": "Recorded observation", + "obsEditedAction": "Edited observation", + "obsDeletedAction": "Deleted observation", + "recordActivity": "Patient activity", + "readingObservations": "Reading observations from {{read}} of {{total}} encounters…", + "noActivity": "There is no recorded activity to show.", + "whoTouchedThisRecord": "Who modified this patient record", + "user": "User", + "encountersCreated": "Encounters created", + "obsRecordedCount": "Observations recorded", + "obsEditedCount": "Observations edited", + "obsDeletedCount": "Observations deleted", + "firstActivity": "First activity", + "lastActivity": "Last activity", + "showOnlyThisUser": "Show only this user's activity", + "unknownUser": "Unknown user", + "activityLog": "Activity log", + "showEveryone": "Show everyone", + "when": "When", + "action": "Action", + "detail": "Detail", + "encounters": "Encounters", + "noEncountersMatchFilters": "No encounters match these filters.", + "noEncountersFound": "This patient has no encounters to audit.", + "provider": "Provider", + "status": "Status", + "backToPatientSearch": "Back to patient search", + "includeDeletedEncounters": "Include deleted encounters", + "cannotIncludeDeletedEncounters": "Deleted encounters can only be listed for a patient who has an identifier.", + "deletedEncountersUnavailable": "Deleted encounters unavailable", + "auditViews": "Audit views", + "searchForPatient": "Search for a patient by name or identifier", + "searchToBegin": "Enter a patient name or identifier to begin auditing their records.", + "patientSearchResults": "Patient search results", + "noPatientsFound": "No patients match \"{{searchTerm}}\".", + "patientName": "Patient name", + "gender": "Gender", + "age": "Age", + "birthdate": "Date of birth", + "backToProviderSearch": "Back to provider search", + "providerEncountersScope": "These are the encounters this provider is recorded on, which is not the same as the ones they entered or changed — the last column names who did that, and the Users tab searches by it.", + "recordedAsProvider": "Recorded as provider", + "noProviderEncountersMatch": "No encounters for this provider match these filters.", + "noProviderEncounters": "This provider is not recorded on any encounters.", + "searchForProvider": "Search for a provider by name or identifier", + "searchProviderToBegin": "Enter a provider name or identifier to see the encounters they are recorded on.", + "providerSearchResults": "Provider search results", + "noProvidersFound": "No providers match \"{{searchTerm}}\".", + "backToUserSearch": "Back to user search", + "changedBetween": "Changed between", + "readingUserObservations": "Read {{obsRead}} observations so far…", + "noUserEncountersMatch": "This user did not change any observations matching these filters.", + "noUserEncounters": "This user has not recorded or deleted any observations.", + "userScanStoppedEarly": "Stopped after reading {{obsRead}} observation changes without filling the page. Narrow the date range to search further back.", + "partialUserScan": "Part of the trail", + "lastChanged": "Last changed", + "lookingForMoreEncounters": "Looking for more encounters…", + "encounterRange": "Encounters {{from}}–{{to}}", + "searchForUser": "Search for a user by name or username", + "searchUserToBegin": "Enter a name or username to see the encounters that user has modified.", + "userSearchResults": "User search results", + "noUsersFound": "No users match \"{{searchTerm}}\".", + "name": "Name", + "username": "Username", + "retired": "Retired", + "audit": "" +} diff --git a/packages/esm-audit-app/locales/fr/translation.json b/packages/esm-audit-app/locales/fr/translation.json new file mode 100644 index 0000000..0ff8d12 --- /dev/null +++ b/packages/esm-audit-app/locales/fr/translation.json @@ -0,0 +1,118 @@ +{ + "searchBy": "Search by", + "patients": "Patients", + "users": "Users", + "providers": "Providers", + "auditTrail": "Audit trail", + "encounterAudit": "Encounter audit", + "encounterNotFound": "That encounter could not be found.", + "patient": "Patient", + "identifier": "Identifier", + "encounterDate": "Encounter date", + "location": "Location", + "visit": "Visit", + "encounterType": "Encounter type", + "form": "Form", + "createdBy": "Created by", + "changedBy": "Changed by", + "deletedBy": "Deleted by", + "deletionReason": "Reason for deletion", + "encounter": "Encounter", + "deleted": "Deleted", + "encounterDeletedSubtitle": "This encounter has been deleted. Its observations are shown as they were left.", + "encounterDeleted": "Deleted encounter", + "encounterSummary": "Encounter summary", + "noProviders": "No providers are recorded for this encounter.", + "role": "Role", + "providerName": "Provider name", + "backToEncounters": "Back to encounters", + "allEncounterTypes": "All encounter types", + "encounterDateRange": "Encounter date range", + "clearFilters": "Clear filters", + "edited": "Edited", + "editedLegend": "the value was changed after it was first recorded", + "addedLater": "Added later", + "addedLaterLegend": "recorded after the encounter itself was created", + "deletedLegend": "removed from the encounter", + "previousValue": "Previous value", + "previousValueLegend": "the value that an edit replaced", + "observations": "Observations", + "showDeletedObs": "Show deleted observations", + "showConceptDescriptions": "Show concept descriptions", + "noObservations": "This encounter has no observations.", + "questionConcept": "Question concept", + "value": "Value", + "change": "Change", + "recordedBy": "Recorded by", + "wasPreviously": "was {{value}}", + "changedByUser": "Changed by {{user}}", + "encounterCreatedAction": "Created encounter", + "encounterChangedAction": "Changed encounter", + "encounterDeletedAction": "Deleted encounter", + "obsRecordedAction": "Recorded observation", + "obsEditedAction": "Edited observation", + "obsDeletedAction": "Deleted observation", + "recordActivity": "Patient activity", + "readingObservations": "Reading observations from {{read}} of {{total}} encounters…", + "noActivity": "There is no recorded activity to show.", + "whoTouchedThisRecord": "Who modified this patient record", + "user": "User", + "encountersCreated": "Encounters created", + "obsRecordedCount": "Observations recorded", + "obsEditedCount": "Observations edited", + "obsDeletedCount": "Observations deleted", + "firstActivity": "First activity", + "lastActivity": "Last activity", + "showOnlyThisUser": "Show only this user's activity", + "unknownUser": "Unknown user", + "activityLog": "Activity log", + "showEveryone": "Show everyone", + "when": "When", + "action": "Action", + "detail": "Detail", + "encounters": "Encounters", + "noEncountersMatchFilters": "No encounters match these filters.", + "noEncountersFound": "This patient has no encounters to audit.", + "provider": "Provider", + "status": "Status", + "backToPatientSearch": "Back to patient search", + "includeDeletedEncounters": "Include deleted encounters", + "cannotIncludeDeletedEncounters": "Deleted encounters can only be listed for a patient who has an identifier.", + "deletedEncountersUnavailable": "Deleted encounters unavailable", + "auditViews": "Audit views", + "searchForPatient": "Search for a patient by name or identifier", + "searchToBegin": "Enter a patient name or identifier to begin auditing their records.", + "patientSearchResults": "Patient search results", + "noPatientsFound": "No patients match \"{{searchTerm}}\".", + "patientName": "Patient name", + "gender": "Gender", + "age": "Age", + "birthdate": "Date of birth", + "backToProviderSearch": "Back to provider search", + "providerEncountersScope": "These are the encounters this provider is recorded on, which is not the same as the ones they entered or changed — the last column names who did that, and the Users tab searches by it.", + "recordedAsProvider": "Recorded as provider", + "noProviderEncountersMatch": "No encounters for this provider match these filters.", + "noProviderEncounters": "This provider is not recorded on any encounters.", + "searchForProvider": "Search for a provider by name or identifier", + "searchProviderToBegin": "Enter a provider name or identifier to see the encounters they are recorded on.", + "providerSearchResults": "Provider search results", + "noProvidersFound": "No providers match \"{{searchTerm}}\".", + "backToUserSearch": "Back to user search", + "changedBetween": "Changed between", + "readingUserObservations": "Read {{obsRead}} observations so far…", + "noUserEncountersMatch": "This user did not change any observations matching these filters.", + "noUserEncounters": "This user has not recorded or deleted any observations.", + "userScanStoppedEarly": "Stopped after reading {{obsRead}} observation changes without filling the page. Narrow the date range to search further back.", + "partialUserScan": "Part of the trail", + "lastChanged": "Last changed", + "lookingForMoreEncounters": "Looking for more encounters…", + "encounterRange": "Encounters {{from}}–{{to}}", + "searchForUser": "Search for a user by name or username", + "searchUserToBegin": "Enter a name or username to see the encounters that user has modified.", + "userSearchResults": "User search results", + "noUsersFound": "No users match \"{{searchTerm}}\".", + "name": "Name", + "username": "Username", + "retired": "Retired", + "audit": "" +} diff --git a/packages/esm-audit-app/src/audit/audit.resource.ts b/packages/esm-audit-app/src/audit/audit.resource.ts index 9128fab..8ff7d97 100644 --- a/packages/esm-audit-app/src/audit/audit.resource.ts +++ b/packages/esm-audit-app/src/audit/audit.resource.ts @@ -302,19 +302,35 @@ export function useAllPatientEncounters( }; } +/** One entry of the pihapps encounter type count endpoint. */ +interface EncounterTypeCount { + encounterType: OpenmrsResourceRef; + count: number; +} + /** - * Every encounter type in the system, for a filter that cannot be narrowed to what a particular - * search contains. The user drill-down needs this because it discovers encounters lazily and so - * does not know which types a trail holds until it has read all of it. Retired types are left out. + * The encounter types this implementation actually records against, for a filter that cannot be + * narrowed to what a particular search contains. The user and provider drill-downs need this + * because they discover encounters lazily or page them on the server, and so do not know which + * types a trail holds until all of it is read. + * + * Every type in the system would be mostly noise, since which ones are in use varies a great deal + * by country, so the types come from pihapps' count endpoint, which is asked to leave out those + * with no encounters. Deleted encounters count, as they do everywhere else in an audit: a type whose + * encounters have all been deleted still has a trail to look at. Retired types are kept for the + * same reason, since encounters recorded before a type was retired can still be audited. */ -export function useAllEncounterTypes() { - const { data, error, isLoading } = useOpenmrsFetchAll( - `${restBaseUrl}/encountertype?v=custom:(uuid,display)&limit=${bulkPageSize}`, - restFetchOptions, +export function useUsedEncounterTypes() { + const { data, error, isLoading } = useSWR }>>( + `${restBaseUrl}/pihapps/encounterTypeCount?includeVoided=true&onlyUsed=true&v=custom:(uuid,display)`, + openmrsFetch, ); const encounterTypes = useMemo( - () => (data ?? []).slice().sort((a, b) => (a.display ?? '').localeCompare(b.display ?? '')), + () => + (data?.data?.results ?? []) + .map((entry) => entry.encounterType) + .sort((a, b) => (a.display ?? '').localeCompare(b.display ?? '')), [data], ); diff --git a/packages/esm-audit-app/src/audit/audit.scss b/packages/esm-audit-app/src/audit/audit.scss index 70d77d6..96edcd6 100644 --- a/packages/esm-audit-app/src/audit/audit.scss +++ b/packages/esm-audit-app/src/audit/audit.scss @@ -170,23 +170,8 @@ background-color: $color-blue-10; } -/* Paging a list whose length is not known: a range and two buttons, no page count. */ -.lazyPagination { - display: flex; - align-items: center; - gap: layout.$spacing-03; - padding: layout.$spacing-03 0; - border-top: 1px solid $ui-03; -} - -.paginationRange { - @include type.type-style('label-01'); - color: $text-02; - margin-right: auto; -} - -/* Sits inline with the paging controls, so the row does not jump as it appears and goes. */ +/* Sits above the pager while the trail is still being read, so the list does not look finished. */ .discovering { min-height: auto; - margin-right: layout.$spacing-03; + margin-bottom: layout.$spacing-03; } diff --git a/packages/esm-audit-app/src/audit/provider-encounters.component.test.tsx b/packages/esm-audit-app/src/audit/provider-encounters.component.test.tsx index 0d28071..ec72f6d 100644 --- a/packages/esm-audit-app/src/audit/provider-encounters.component.test.tsx +++ b/packages/esm-audit-app/src/audit/provider-encounters.component.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SWRConfig } from 'swr'; import { openmrsFetch, useConfig } from '@openmrs/esm-framework'; @@ -41,15 +41,20 @@ const encounters: Array = [ }, ]; -const encounterTypes = [ - { uuid: 'type-2', display: 'Inscription' }, - { uuid: 'type-1', display: 'Oncology Consultation' }, +const encounterTypeCounts = [ + { encounterType: { uuid: 'type-2', display: 'Inscription' }, count: 12 }, + { encounterType: { uuid: 'type-3', display: 'Never Recorded' }, count: 0 }, + { encounterType: { uuid: 'type-1', display: 'Oncology Consultation' }, count: 30 }, ]; function mockRestApi({ results = encounters } = {}) { mockOpenmrsFetch.mockImplementation((url: string) => { - if (url.includes('/encountertype')) { - return Promise.resolve({ data: { results: encounterTypes } }) as ReturnType; + if (url.includes('/pihapps/encounterTypeCount')) { + // the endpoint leaves out unused types only when asked, so the mock does too + const results = url.includes('onlyUsed=true') + ? encounterTypeCounts.filter((entry) => entry.count > 0) + : encounterTypeCounts; + return Promise.resolve({ data: { results } }) as ReturnType; } if (url.includes('/pihapps/encounter?')) { return Promise.resolve({ data: { results, totalCount: results.length } }) as ReturnType; @@ -153,6 +158,28 @@ describe('', () => { expect(await screen.findByText(/not the same as the ones they entered or changed/i)).toBeInTheDocument(); }); + it('offers only the encounter types something has been recorded against', async () => { + renderProviderEncounters(); + await screen.findByRole('cell', { name: 'Oncology Consultation' }); + + await userEvent.click(screen.getByRole('combobox', { name: /encounter type/i })); + + const options = within(screen.getByRole('listbox')).getAllByRole('option'); + expect(options.map((option) => option.textContent)).toEqual(['Inscription', 'Oncology Consultation']); + }); + + it('counts deleted encounters towards their type, as the rest of the audit does', async () => { + renderProviderEncounters(); + await screen.findByRole('cell', { name: 'Oncology Consultation' }); + + const countUrls = mockOpenmrsFetch.mock.calls + .map(([url]) => String(url)) + .filter((url) => url.includes('/pihapps/encounterTypeCount')); + expect(countUrls.length).toBeGreaterThan(0); + expect(countUrls.every((url) => url.includes('includeVoided=true'))).toBe(true); + expect(countUrls.every((url) => url.includes('onlyUsed=true'))).toBe(true); + }); + it('narrows the search on the server by encounter type', async () => { renderProviderEncounters(); await screen.findByRole('cell', { name: 'Oncology Consultation' }); diff --git a/packages/esm-audit-app/src/audit/provider-encounters.component.tsx b/packages/esm-audit-app/src/audit/provider-encounters.component.tsx index 33832f4..5ab29cc 100644 --- a/packages/esm-audit-app/src/audit/provider-encounters.component.tsx +++ b/packages/esm-audit-app/src/audit/provider-encounters.component.tsx @@ -17,7 +17,7 @@ import { import { ArrowLeftIcon, ErrorState, useConfig } from '@openmrs/esm-framework'; import { type Config } from '../config-schema'; import { formatAuditDatetime, formatUserAndDate } from './audit-format'; -import { useAllEncounterTypes, useAuditProvider, useProviderEncounters } from './audit.resource'; +import { useAuditProvider, useProviderEncounters, useUsedEncounterTypes } from './audit.resource'; import { type EncounterFilters, hasActiveFilters } from './encounter-filters'; import EncounterFiltersBar from './encounter-filters.component'; import styles from './audit.scss'; @@ -47,7 +47,7 @@ export default function ProviderEncounters({ const [filters, setFilters] = useState({}); const { provider, error: providerError, isLoading: isLoadingProvider } = useAuditProvider(providerUuid); - const { encounterTypes, isLoading: isLoadingTypes } = useAllEncounterTypes(); + const { encounterTypes, isLoading: isLoadingTypes } = useUsedEncounterTypes(); const { encounters, totalCount, currentPage, goTo, error, isLoading } = useProviderEncounters( providerUuid, pageSize, @@ -66,7 +66,7 @@ export default function ProviderEncounters({ {/* Both filters narrow on the server, so the page count and totals stay right — which is why - the encounter types come from the system-wide list rather than from what is on screen. + the encounter types come from those in use system-wide rather than from what is on screen. */} ; + if (url.includes('/pihapps/encounterTypeCount')) { + // deliberately unsorted, and including a type the trail does not use and one nothing uses + const counts = [ + { encounterType: vitalsType, count: 40 }, + { encounterType: neverRecordedType, count: 0 }, + { encounterType: unusedType, count: 3 }, + { encounterType: consultationType, count: 40 }, + ]; + // the endpoint leaves out unused types only when asked, so the mock does too + const results = url.includes('onlyUsed=true') ? counts.filter((entry) => entry.count > 0) : counts; + return Promise.resolve({ data: { results } }) as ReturnType; } if (url.includes('/obs?encounter=')) { return Promise.resolve({ @@ -187,7 +196,7 @@ describe('', () => { await screen.findByText('Encounters 1–10'); const beforePaging = auditRequests().length; - await userEvent.click(screen.getByRole('button', { name: 'Next' })); + await userEvent.click(screen.getByRole('button', { name: 'Next page' })); expect(await screen.findByText('Encounters 11–20')).toBeInTheDocument(); expect(auditRequests().length).toBeGreaterThan(beforePaging); @@ -198,11 +207,11 @@ describe('', () => { mockRestApi({ totalCreated: 5000, obsPerEncounter: 20 }); renderUserEncounters(); await screen.findByText('Encounters 1–10'); - await userEvent.click(screen.getByRole('button', { name: 'Next' })); + await userEvent.click(screen.getByRole('button', { name: 'Next page' })); await screen.findByText('Encounters 11–20'); const afterPaging = auditRequests().length; - await userEvent.click(screen.getByRole('button', { name: 'Previous' })); + await userEvent.click(screen.getByRole('button', { name: 'Previous page' })); expect(await screen.findByText('Encounters 1–10')).toBeInTheDocument(); expect(auditRequests()).toHaveLength(afterPaging); @@ -214,10 +223,10 @@ describe('', () => { renderUserEncounters(); await screen.findByText('Encounters 1–10'); - await userEvent.click(screen.getByRole('button', { name: 'Next' })); + await userEvent.click(screen.getByRole('button', { name: 'Next page' })); expect(await screen.findByText('Encounters 11–12')).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Next' })).toBeDisabled(); + expect(screen.getByRole('button', { name: 'Next page' })).toBeDisabled(); }); it('counts each row’s observations exactly, from the encounter itself', async () => { @@ -270,7 +279,7 @@ describe('', () => { mockRestApi({ totalCreated: 5000, obsPerEncounter: 20 }); renderUserEncounters(); await screen.findByText('Encounters 1–10'); - await userEvent.click(screen.getByRole('button', { name: 'Next' })); + await userEvent.click(screen.getByRole('button', { name: 'Next page' })); await screen.findByText('Encounters 11–20'); await userEvent.clear(screen.getByLabelText('Changed between')); @@ -300,7 +309,7 @@ describe('', () => { expect(screen.queryByRole('button', { name: /clear filters/i })).not.toBeInTheDocument(); }); - it('offers every encounter type in the system, not just the ones in the trail', async () => { + it('offers every encounter type in use in the system, not just the ones in the trail', async () => { renderUserEncounters(); await screen.findByText('Encounters 1–10'); diff --git a/packages/esm-audit-app/src/audit/user-encounters.component.tsx b/packages/esm-audit-app/src/audit/user-encounters.component.tsx index 39cce80..8cb7887 100644 --- a/packages/esm-audit-app/src/audit/user-encounters.component.tsx +++ b/packages/esm-audit-app/src/audit/user-encounters.component.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, @@ -6,6 +6,7 @@ import { DataTableSkeleton, InlineLoading, InlineNotification, + Pagination, Table, TableBody, TableCell, @@ -20,7 +21,7 @@ import { type Config } from '../config-schema'; import { type OpenmrsResourceRef } from '../types'; import { formatAuditDatetime } from './audit-format'; import { type DateRange, fromPickerRange, hasDateRange, toPickerDefault } from './date-range'; -import { useAllEncounterTypes, useAuditUser, useUserEncounters } from './audit.resource'; +import { useAuditUser, useUsedEncounterTypes, useUserEncounters } from './audit.resource'; import styles from './audit.scss'; interface UserEncountersProps { @@ -37,7 +38,8 @@ interface UserEncountersProps { export default function UserEncounters({ userUuid, onSelectEncounter, onBackToSearch }: UserEncountersProps) { const { t } = useTranslation(); const config = useConfig(); - const pageSize = config.encountersPageSize ?? 10; + const [pageSize, setPageSize] = useState(config.encountersPageSize ?? 10); + const pageSizes = useMemo(() => Array.from(new Set([pageSize, 10, 20, 50])).sort((a, b) => a - b), [pageSize]); const [dateRange, setDateRange] = useState({}); // bumped to clear the range picker, which has to be left to hold its own value const [datePickerKey, setDatePickerKey] = useState(0); @@ -45,7 +47,7 @@ export default function UserEncounters({ userUuid, onSelectEncounter, onBackToSe const today = new Date(); const { user, error: userError, isLoading: isLoadingUser } = useAuditUser(userUuid); - const { encounterTypes, isLoading: isLoadingTypes } = useAllEncounterTypes(); + const { encounterTypes, isLoading: isLoadingTypes } = useUsedEncounterTypes(); const { activity, currentPage, @@ -78,7 +80,7 @@ export default function UserEncounters({ userUuid, onSelectEncounter, onBackToSe */}
{/* - Every type in the system rather than only those this account has touched: encounters are + Every type in use rather than only those this account has touched: encounters are discovered lazily, so which types the trail holds is not known until all of it is read. */} + {/* + A filter is applied after the trail has been read, so matching rows arrive as the scan + goes on. Without this the list looks finished while it is still filling. + */} + {isDiscovering ? ( + + ) : null} {/* How many encounters this account has touched cannot be known without reading its whole - trail, which is the very thing being avoided, so there is no total to page against. - A range and a next button say exactly what is known. + trail, which is the very thing being avoided, so there is no total to page against: the + pager runs with its page count unknown and says only the range on screen. */} -
- - {t('encounterRange', 'Encounters {{from}}–{{to}}', { + + t('encounterRange', 'Encounters {{from}}–{{to}}', { from: firstRowOnPage + 1, to: firstRowOnPage + activity.length, - })} - - {/* - A filter is applied after the trail has been read, so matching rows arrive as the - scan goes on. Without this the list looks finished while it is still filling. - */} - {isDiscovering ? ( - - ) : null} - - -
+ }) + } + onChange={({ page: nextPage, pageSize: nextPageSize }) => { + setPageSize(nextPageSize); + goTo(nextPage); + }} + page={currentPage} + pageSize={pageSize} + pageSizes={pageSizes} + pagesUnknown + size="sm" + /> )}
diff --git a/packages/esm-audit-app/translations/en.json b/packages/esm-audit-app/translations/en.json index 80d65db..22fa810 100644 --- a/packages/esm-audit-app/translations/en.json +++ b/packages/esm-audit-app/translations/en.json @@ -53,7 +53,6 @@ "location": "Location", "lookingForMoreEncounters": "Looking for more encounters…", "name": "Name", - "nextPage": "Next", "noActivity": "There is no recorded activity to show.", "noEncountersFound": "This patient has no encounters to audit.", "noEncountersMatchFilters": "No encounters match these filters.", @@ -78,7 +77,6 @@ "patientName": "Patient name", "patients": "Patients", "patientSearchResults": "Patient search results", - "previousPage": "Previous", "previousValue": "Previous value", "previousValueLegend": "the value that an edit replaced", "provider": "Provider", diff --git a/packages/esm-audit-app/translations/es.json b/packages/esm-audit-app/translations/es.json index 749c8d8..bedecf9 100644 --- a/packages/esm-audit-app/translations/es.json +++ b/packages/esm-audit-app/translations/es.json @@ -53,7 +53,6 @@ "location": "Localidad", "lookingForMoreEncounters": "Buscando más encuentros…", "name": "Nombre", - "nextPage": "Siguiente", "noActivity": "No hay actividad registrada que mostrar.", "noEncountersFound": "Este paciente no tiene encuentros que auditar.", "noEncountersMatchFilters": "Ningún encuentro coincide con estos filtros.", @@ -78,7 +77,6 @@ "patientName": "Nombre del paciente", "patients": "Pacientes", "patientSearchResults": "Resultados de la búsqueda de pacientes", - "previousPage": "Anterior", "previousValue": "Valor anterior", "previousValueLegend": "el valor sustituido por una edición", "provider": "Proveedor", diff --git a/packages/esm-audit-app/translations/fr.json b/packages/esm-audit-app/translations/fr.json index eabfd7d..3db2196 100644 --- a/packages/esm-audit-app/translations/fr.json +++ b/packages/esm-audit-app/translations/fr.json @@ -53,7 +53,6 @@ "location": "Lieu", "lookingForMoreEncounters": "Recherche d'autres rencontres…", "name": "Nom", - "nextPage": "Suivant", "noActivity": "Aucune activité enregistrée à afficher.", "noEncountersFound": "Ce patient n'a aucune rencontre à auditer.", "noEncountersMatchFilters": "Aucune rencontre ne correspond à ces filtres.", @@ -78,7 +77,6 @@ "patientName": "Nom du patient", "patients": "Patients", "patientSearchResults": "Résultats de la recherche de patients", - "previousPage": "Précédent", "previousValue": "Valeur précédente", "previousValueLegend": "la valeur remplacée par une modification", "provider": "Prestataire de service",