@pih/esm-audit-app 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/.turbo/turbo-lint.log +14 -0
  2. package/.turbo/turbo-test.log +12735 -0
  3. package/.turbo/turbo-typescript.log +1 -0
  4. package/README.md +116 -0
  5. package/dist/115.js +1 -0
  6. package/dist/115.js.map +1 -0
  7. package/dist/117.js +1 -0
  8. package/dist/117.js.map +1 -0
  9. package/dist/240.js +1 -0
  10. package/dist/240.js.map +1 -0
  11. package/dist/349.js +1 -0
  12. package/dist/349.js.map +1 -0
  13. package/dist/446.js +1 -0
  14. package/dist/446.js.map +1 -0
  15. package/dist/455.js +1 -0
  16. package/dist/455.js.map +1 -0
  17. package/dist/466.js +1 -0
  18. package/dist/466.js.map +1 -0
  19. package/dist/508.js +1 -0
  20. package/dist/508.js.map +1 -0
  21. package/dist/61.js +1 -0
  22. package/dist/61.js.map +1 -0
  23. package/dist/689.js +1 -0
  24. package/dist/689.js.map +1 -0
  25. package/dist/711.js +1 -0
  26. package/dist/711.js.map +1 -0
  27. package/dist/712.js +1 -0
  28. package/dist/712.js.map +1 -0
  29. package/dist/771.js +1 -0
  30. package/dist/771.js.map +1 -0
  31. package/dist/859.js +1 -0
  32. package/dist/859.js.map +1 -0
  33. package/dist/912.js +1 -0
  34. package/dist/912.js.map +1 -0
  35. package/dist/913.js +1 -0
  36. package/dist/913.js.map +1 -0
  37. package/dist/94.js +49 -0
  38. package/dist/94.js.map +1 -0
  39. package/dist/989.js +1 -0
  40. package/dist/989.js.map +1 -0
  41. package/dist/main.js +3 -0
  42. package/dist/main.js.map +1 -0
  43. package/dist/pih-esm-audit-app.js +3 -0
  44. package/dist/pih-esm-audit-app.js.buildmanifest.json +628 -0
  45. package/dist/pih-esm-audit-app.js.map +1 -0
  46. package/dist/routes.json +1 -0
  47. package/jest.config.js +3 -0
  48. package/package.json +53 -0
  49. package/rspack.config.js +1 -0
  50. package/src/audit/audit-format.ts +24 -0
  51. package/src/audit/audit.component.tsx +69 -0
  52. package/src/audit/audit.resource.test.tsx +174 -0
  53. package/src/audit/audit.resource.ts +409 -0
  54. package/src/audit/audit.scss +171 -0
  55. package/src/audit/encounter-audit.component.test.tsx +242 -0
  56. package/src/audit/encounter-audit.component.tsx +204 -0
  57. package/src/audit/encounter-filters.component.tsx +75 -0
  58. package/src/audit/encounter-filters.test.ts +119 -0
  59. package/src/audit/encounter-filters.ts +105 -0
  60. package/src/audit/obs-audit-table.component.tsx +181 -0
  61. package/src/audit/obs-audit.test.ts +170 -0
  62. package/src/audit/obs-audit.ts +156 -0
  63. package/src/audit/patient-activity.component.test.tsx +191 -0
  64. package/src/audit/patient-activity.component.tsx +203 -0
  65. package/src/audit/patient-activity.test.ts +155 -0
  66. package/src/audit/patient-activity.ts +156 -0
  67. package/src/audit/patient-encounters.component.tsx +134 -0
  68. package/src/audit/patient-record.component.test.tsx +193 -0
  69. package/src/audit/patient-record.component.tsx +115 -0
  70. package/src/audit/patient-search.component.test.tsx +73 -0
  71. package/src/audit/patient-search.component.tsx +120 -0
  72. package/src/config-schema.ts +36 -0
  73. package/src/dashboard-link.component.tsx +38 -0
  74. package/src/dashboard.meta.ts +11 -0
  75. package/src/declarations.d.tsx +3 -0
  76. package/src/index.ts +33 -0
  77. package/src/root.component.test.tsx +58 -0
  78. package/src/root.component.tsx +16 -0
  79. package/src/routes.json +20 -0
  80. package/src/types.ts +122 -0
  81. package/translations/en.json +89 -0
  82. package/translations/es.json +89 -0
  83. package/translations/fr.json +89 -0
  84. package/tsconfig.json +5 -0
@@ -0,0 +1,120 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ DataTableSkeleton,
5
+ Pagination,
6
+ Search,
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableContainer,
11
+ TableHead,
12
+ TableHeader,
13
+ TableRow,
14
+ } from '@carbon/react';
15
+ import { ErrorState, useConfig, useDebounce } from '@openmrs/esm-framework';
16
+ import { type Config } from '../config-schema';
17
+ import { formatAuditDate } from './audit-format';
18
+ import { getPreferredIdentifier, usePatientSearch } from './audit.resource';
19
+ import styles from './audit.scss';
20
+
21
+ interface PatientSearchProps {
22
+ onSelectPatient(patientUuid: string): void;
23
+ }
24
+
25
+ /**
26
+ * Step one of the audit trail: find the patient whose records are being audited, by name or by
27
+ * identifier. This mirrors the legacy admin page, which searched encounters by patient name or
28
+ * identifier, except that the patient is chosen explicitly before their encounters are listed.
29
+ */
30
+ export default function PatientSearch({ onSelectPatient }: PatientSearchProps) {
31
+ const { t } = useTranslation();
32
+ const config = useConfig<Config>();
33
+ const [searchTerm, setSearchTerm] = useState('');
34
+ const [page, setPage] = useState(1);
35
+ const [pageSize, setPageSize] = useState(config.patientSearchPageSize ?? 10);
36
+ const debouncedSearchTerm = useDebounce(searchTerm.trim(), 300);
37
+ const pageSizes = useMemo(() => Array.from(new Set([pageSize, 10, 20, 50])).sort((a, b) => a - b), [pageSize]);
38
+ const { patients, totalCount, error, isLoading } = usePatientSearch(debouncedSearchTerm, page, pageSize);
39
+
40
+ useEffect(() => {
41
+ setPage(1);
42
+ }, [debouncedSearchTerm]);
43
+
44
+ return (
45
+ <div className={styles.section}>
46
+ <Search
47
+ className={styles.searchInput}
48
+ labelText={t('searchForPatient', 'Search for a patient by name or identifier')}
49
+ placeholder={t('searchForPatient', 'Search for a patient by name or identifier')}
50
+ onChange={(event) => setSearchTerm(event.target.value)}
51
+ size="lg"
52
+ value={searchTerm}
53
+ />
54
+ {!debouncedSearchTerm ? (
55
+ <p className={styles.emptyState}>
56
+ {t('searchToBegin', 'Enter a patient name or identifier to begin auditing their records.')}
57
+ </p>
58
+ ) : error ? (
59
+ <ErrorState error={error} headerTitle={t('patientSearchResults', 'Patient search results')} />
60
+ ) : isLoading ? (
61
+ <DataTableSkeleton columnCount={5} compact role="progressbar" showHeader={false} showToolbar={false} />
62
+ ) : patients.length === 0 ? (
63
+ <p className={styles.emptyState}>
64
+ {t('noPatientsFound', 'No patients match "{{searchTerm}}".', { searchTerm: debouncedSearchTerm })}
65
+ </p>
66
+ ) : (
67
+ <>
68
+ <TableContainer className={styles.tableContainer}>
69
+ <Table size="sm" useZebraStyles>
70
+ <TableHead>
71
+ <TableRow>
72
+ <TableHeader>{t('patientName', 'Patient name')}</TableHeader>
73
+ <TableHeader>{t('identifier', 'Identifier')}</TableHeader>
74
+ <TableHeader>{t('gender', 'Gender')}</TableHeader>
75
+ <TableHeader>{t('age', 'Age')}</TableHeader>
76
+ <TableHeader>{t('birthdate', 'Date of birth')}</TableHeader>
77
+ </TableRow>
78
+ </TableHead>
79
+ <TableBody>
80
+ {patients.map((patient) => (
81
+ <TableRow
82
+ className={styles.clickableRow}
83
+ key={patient.uuid}
84
+ onClick={() => onSelectPatient(patient.uuid)}>
85
+ <TableCell>
86
+ <button
87
+ className={styles.linkButton}
88
+ onClick={(event) => {
89
+ event.stopPropagation();
90
+ onSelectPatient(patient.uuid);
91
+ }}
92
+ type="button">
93
+ {patient.person?.display ?? patient.display}
94
+ </button>
95
+ </TableCell>
96
+ <TableCell>{getPreferredIdentifier(patient)}</TableCell>
97
+ <TableCell>{patient.person?.gender}</TableCell>
98
+ <TableCell>{patient.person?.age}</TableCell>
99
+ <TableCell>{formatAuditDate(patient.person?.birthdate)}</TableCell>
100
+ </TableRow>
101
+ ))}
102
+ </TableBody>
103
+ </Table>
104
+ </TableContainer>
105
+ <Pagination
106
+ onChange={({ page: nextPage, pageSize: nextPageSize }) => {
107
+ setPage(nextPage);
108
+ setPageSize(nextPageSize);
109
+ }}
110
+ page={page}
111
+ pageSize={pageSize}
112
+ pageSizes={pageSizes}
113
+ size="sm"
114
+ totalItems={totalCount}
115
+ />
116
+ </>
117
+ )}
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * This is the config schema. In OpenMRS Microfrontends, all config parameters
3
+ * are optional, so every element must have a reasonable default. See the
4
+ * configuration system documentation:
5
+ * https://openmrs.github.io/openmrs-esm-core/#/main/config
6
+ */
7
+ import { Type, validators } from '@openmrs/esm-framework';
8
+
9
+ export const configSchema = {
10
+ patientSearchPageSize: {
11
+ _type: Type.Number,
12
+ _description: 'How many patients to show per page of patient search results.',
13
+ _default: 10,
14
+ _validators: [validators.inRange(1, 100)],
15
+ },
16
+ encountersPageSize: {
17
+ _type: Type.Number,
18
+ _description: "How many encounters to show per page of a patient's encounter list.",
19
+ _default: 10,
20
+ _validators: [validators.inRange(1, 100)],
21
+ },
22
+ patientChartUrl: {
23
+ _type: Type.String,
24
+ _description:
25
+ 'The URL of the patient chart to link to from the audit trail. May contain the ' +
26
+ 'template variables ${openmrsSpaBase}, ${openmrsBase} and ${patientUuid}.',
27
+ _default: '${openmrsSpaBase}/patient/${patientUuid}/chart',
28
+ _validators: [validators.isUrlWithTemplateParameters(['patientUuid'])],
29
+ },
30
+ };
31
+
32
+ export interface Config {
33
+ patientSearchPageSize: number;
34
+ encountersPageSize: number;
35
+ patientChartUrl: string;
36
+ }
@@ -0,0 +1,38 @@
1
+ import React, { useMemo } from 'react';
2
+ import { BrowserRouter, useLocation } from 'react-router-dom';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { ConfigurableLink } from '@openmrs/esm-framework';
5
+ import { dashboardMeta } from './dashboard.meta';
6
+
7
+ function AuditLink() {
8
+ const { t } = useTranslation();
9
+ const { pathname } = useLocation();
10
+ const isActive = useMemo(
11
+ () =>
12
+ pathname
13
+ .split('/')
14
+ .map((segment) => decodeURIComponent(segment))
15
+ .includes(dashboardMeta.name),
16
+ [pathname],
17
+ );
18
+
19
+ return (
20
+ <ConfigurableLink
21
+ to={`${window.spaBase}/home/${dashboardMeta.name}`}
22
+ className={`cds--side-nav__link ${isActive ? 'active-left-nav-link' : ''}`}>
23
+ {t('audit', dashboardMeta.title)}
24
+ </ConfigurableLink>
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Extensions are mounted as their own React root, so they do not inherit the
30
+ * home app's router context and need their own to read the current location.
31
+ */
32
+ export default function DashboardLink() {
33
+ return (
34
+ <BrowserRouter>
35
+ <AuditLink />
36
+ </BrowserRouter>
37
+ );
38
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The home app renders one dashboard per extension assigned to the
3
+ * `homepage-dashboard-slot`, keyed off the `name` in the extension's meta.
4
+ * That name is also the last segment of the dashboard's URL, so this dashboard
5
+ * lives at `/openmrs/spa/home/audit`.
6
+ */
7
+ export const dashboardMeta = {
8
+ name: 'audit',
9
+ slot: 'audit-dashboard-slot',
10
+ title: 'Audit',
11
+ };
@@ -0,0 +1,3 @@
1
+ declare module '@carbon/react';
2
+ declare module '*.css';
3
+ declare module '*.scss';
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * This is the entrypoint file of the application. It communicates the
3
+ * important features of this microfrontend to the app shell. It
4
+ * connects the app shell to the React application(s) that make up this
5
+ * microfrontend.
6
+ */
7
+
8
+ import { defineConfigSchema, getAsyncLifecycle, getSyncLifecycle, registerBreadcrumbs } from '@openmrs/esm-framework';
9
+ import { configSchema } from './config-schema';
10
+ import { dashboardMeta } from './dashboard.meta';
11
+ import DashboardLink from './dashboard-link.component';
12
+
13
+ const moduleName = '@pih/esm-audit-app';
14
+ const options = {
15
+ featureName: 'audit',
16
+ moduleName,
17
+ };
18
+
19
+ export const importTranslation = require.context('../translations', false, /.json$/, 'lazy');
20
+
21
+ export const root = getAsyncLifecycle(() => import('./root.component'), options);
22
+
23
+ export const auditDashboardLink = getSyncLifecycle(DashboardLink, options);
24
+
25
+ export function startupApp() {
26
+ defineConfigSchema(moduleName, configSchema);
27
+ registerBreadcrumbs([
28
+ {
29
+ path: `${window.spaBase}/home/${dashboardMeta.name}`,
30
+ title: dashboardMeta.title,
31
+ },
32
+ ]);
33
+ }
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { SWRConfig } from 'swr';
4
+ import { openmrsFetch, useConfig } from '@openmrs/esm-framework';
5
+ import Root from './root.component';
6
+
7
+ const mockOpenmrsFetch = jest.mocked(openmrsFetch);
8
+ const mockUseConfig = jest.mocked(useConfig);
9
+
10
+ function renderRoot(url = '/') {
11
+ window.history.pushState({}, '', url);
12
+ render(
13
+ <SWRConfig value={{ dedupingInterval: 0, provider: () => new Map() }}>
14
+ <Root />
15
+ </SWRConfig>,
16
+ );
17
+ }
18
+
19
+ describe('<Root />', () => {
20
+ beforeEach(() => {
21
+ mockUseConfig.mockReturnValue({ patientChartUrl: '${openmrsSpaBase}/patient/${patientUuid}/chart' });
22
+ mockOpenmrsFetch.mockImplementation((url: string) => {
23
+ if (url.includes('/patient/patient-1')) {
24
+ return Promise.resolve({
25
+ data: { uuid: 'patient-1', display: 'Y2AHXV - Dave TestPatient', person: { display: 'Dave TestPatient' } },
26
+ }) as ReturnType<typeof openmrsFetch>;
27
+ }
28
+ if (url.includes('/encounter/enc-1')) {
29
+ return Promise.resolve({
30
+ data: { uuid: 'enc-1', encounterType: { uuid: 'type-1', display: 'Oncology Consultation' } },
31
+ }) as ReturnType<typeof openmrsFetch>;
32
+ }
33
+ return Promise.resolve({ data: { results: [] } }) as ReturnType<typeof openmrsFetch>;
34
+ });
35
+ });
36
+
37
+ it('starts at the patient search', () => {
38
+ renderRoot();
39
+
40
+ expect(screen.getByText('Audit trail')).toBeInTheDocument();
41
+ expect(screen.getByRole('searchbox')).toBeInTheDocument();
42
+ expect(screen.getByText(/enter a patient name or identifier/i)).toBeInTheDocument();
43
+ });
44
+
45
+ it("shows a patient's encounters when the url names a patient", async () => {
46
+ renderRoot('/?patient=patient-1');
47
+
48
+ expect(await screen.findByText('Dave TestPatient')).toBeInTheDocument();
49
+ expect(screen.getByRole('checkbox', { name: /include deleted encounters/i })).toBeInTheDocument();
50
+ });
51
+
52
+ it('shows the encounter audit trail when the url names an encounter', async () => {
53
+ renderRoot('/?patient=patient-1&encounter=enc-1');
54
+
55
+ expect(await screen.findByText('Encounter summary')).toBeInTheDocument();
56
+ expect(screen.getByText('Observations')).toBeInTheDocument();
57
+ });
58
+ });
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { BrowserRouter } from 'react-router-dom';
3
+ import Audit from './audit/audit.component';
4
+
5
+ /**
6
+ * Extensions are mounted as their own React root, so this dashboard does not inherit the home
7
+ * app's router context and needs its own in order to keep the audit trail's position — the
8
+ * selected patient and encounter — in the URL's query string.
9
+ */
10
+ export default function Root() {
11
+ return (
12
+ <BrowserRouter>
13
+ <Audit />
14
+ </BrowserRouter>
15
+ );
16
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json.openmrs.org/routes.schema.json",
3
+ "extensions": [
4
+ {
5
+ "name": "audit-dashboard-link",
6
+ "slot": "homepage-dashboard-slot",
7
+ "component": "auditDashboardLink",
8
+ "meta": {
9
+ "name": "audit",
10
+ "slot": "audit-dashboard-slot",
11
+ "title": "Audit"
12
+ }
13
+ },
14
+ {
15
+ "name": "audit-dashboard",
16
+ "slot": "audit-dashboard-slot",
17
+ "component": "root"
18
+ }
19
+ ]
20
+ }
package/src/types.ts ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Types for the encounter audit trail. These mirror the shapes returned by the
3
+ * OpenMRS REST API for the custom representations requested in
4
+ * `audit/audit.resource.ts` — keep the two in sync.
5
+ */
6
+
7
+ /** The `{uuid, display}` shape the REST API uses for a reference to another resource. */
8
+ export interface OpenmrsResourceRef {
9
+ uuid: string;
10
+ display?: string;
11
+ }
12
+
13
+ /**
14
+ * The REST API's `auditInfo` property. `changedBy`/`dateChanged` are only set once a
15
+ * row has been edited in place, and the `voided*` fields are only present on voided rows.
16
+ */
17
+ export interface AuditInfo {
18
+ creator?: OpenmrsResourceRef;
19
+ dateCreated?: string;
20
+ changedBy?: OpenmrsResourceRef;
21
+ dateChanged?: string;
22
+ voidedBy?: OpenmrsResourceRef;
23
+ dateVoided?: string;
24
+ voidReason?: string;
25
+ }
26
+
27
+ export interface PagedResponse<T> {
28
+ results: Array<T>;
29
+ links?: Array<{ rel: 'prev' | 'next'; uri: string }>;
30
+ totalCount?: number;
31
+ }
32
+
33
+ export interface PatientIdentifier {
34
+ identifier: string;
35
+ preferred?: boolean;
36
+ identifierType?: OpenmrsResourceRef;
37
+ }
38
+
39
+ export interface AuditPatient {
40
+ uuid: string;
41
+ display: string;
42
+ identifiers?: Array<PatientIdentifier>;
43
+ person?: {
44
+ display?: string;
45
+ gender?: string;
46
+ age?: number;
47
+ birthdate?: string;
48
+ dead?: boolean;
49
+ };
50
+ }
51
+
52
+ export interface EncounterProvider {
53
+ uuid: string;
54
+ voided?: boolean;
55
+ provider?: OpenmrsResourceRef & { identifier?: string };
56
+ encounterRole?: OpenmrsResourceRef;
57
+ }
58
+
59
+ export interface AuditEncounter {
60
+ uuid: string;
61
+ display?: string;
62
+ encounterDatetime?: string;
63
+ voided?: boolean;
64
+ patient?: AuditPatient;
65
+ location?: OpenmrsResourceRef;
66
+ form?: OpenmrsResourceRef & { version?: string };
67
+ encounterType?: OpenmrsResourceRef;
68
+ visit?: OpenmrsResourceRef & { startDatetime?: string; visitType?: OpenmrsResourceRef };
69
+ encounterProviders?: Array<EncounterProvider>;
70
+ auditInfo?: AuditInfo;
71
+ }
72
+
73
+ export interface ConceptDescription {
74
+ description?: string;
75
+ locale?: string;
76
+ }
77
+
78
+ /**
79
+ * An obs value is a plain string or number for free-text, numeric and datetime obs, and a
80
+ * reference for coded obs (and for the drug/location values OpenMRS stores as coded-ish obs).
81
+ */
82
+ export type ObsValue = string | number | boolean | OpenmrsResourceRef | null;
83
+
84
+ export interface AuditObs {
85
+ uuid: string;
86
+ display?: string;
87
+ obsDatetime?: string;
88
+ voided?: boolean;
89
+ value?: ObsValue;
90
+ comment?: string;
91
+ formFieldPath?: string;
92
+ concept?: OpenmrsResourceRef & { descriptions?: Array<ConceptDescription> };
93
+ /** Set when this obs is a member of an obs group. */
94
+ obsGroup?: OpenmrsResourceRef;
95
+ /** Set when this obs replaced an earlier one, i.e. when the value was edited. */
96
+ previousVersion?: OpenmrsResourceRef;
97
+ auditInfo?: AuditInfo;
98
+ }
99
+
100
+ /**
101
+ * How an obs came to look the way it does, relative to the encounter it belongs to. Derived
102
+ * rather than stored — see `getObsChangeStatus`.
103
+ */
104
+ export type ObsChangeStatus =
105
+ /** Entered when the encounter was first created and never touched since. */
106
+ | 'unchanged'
107
+ /** Replaces an earlier obs: someone changed the value after the fact. */
108
+ | 'edited'
109
+ /** Added to the encounter after the encounter itself was created. */
110
+ | 'addedAfterEncounter'
111
+ /** Voided outright — the value was removed from the encounter. */
112
+ | 'deleted'
113
+ /** Voided because a newer obs replaced it, so it holds a value that used to be current. */
114
+ | 'supersededValue';
115
+
116
+ /** An obs plus the group nesting and change status the audit table renders. */
117
+ export interface ObsTreeNode {
118
+ obs: AuditObs;
119
+ status: ObsChangeStatus;
120
+ depth: number;
121
+ children: Array<ObsTreeNode>;
122
+ }
@@ -0,0 +1,89 @@
1
+ {
2
+ "action": "Action",
3
+ "activityLog": "Activity log",
4
+ "addedLater": "Added later",
5
+ "addedLaterLegend": "recorded after the encounter itself was created",
6
+ "age": "Age",
7
+ "allEncounterTypes": "All encounter types",
8
+ "audit": "Audit",
9
+ "auditTrail": "Audit trail",
10
+ "auditViews": "Audit views",
11
+ "backToEncounters": "Back to encounters",
12
+ "backToPatientSearch": "Back to patient search",
13
+ "birthdate": "Date of birth",
14
+ "cannotIncludeDeletedEncounters": "Deleted encounters can only be listed for a patient who has an identifier.",
15
+ "change": "Change",
16
+ "changedBy": "Changed by",
17
+ "changedByUser": "Changed by {{user}}",
18
+ "clearFilters": "Clear filters",
19
+ "createdBy": "Created by",
20
+ "deleted": "Deleted",
21
+ "deletedBy": "Deleted by",
22
+ "deletedEncountersUnavailable": "Deleted encounters unavailable",
23
+ "deletedLegend": "removed from the encounter",
24
+ "deletionReason": "Reason for deletion",
25
+ "detail": "Detail",
26
+ "edited": "Edited",
27
+ "editedLegend": "the value was changed after it was first recorded",
28
+ "encounter": "Encounter",
29
+ "encounterAudit": "Encounter audit",
30
+ "encounterChangedAction": "Changed encounter",
31
+ "encounterCreatedAction": "Created encounter",
32
+ "encounterDate": "Encounter date",
33
+ "encounterDateRange": "Encounter date range",
34
+ "encounterDeleted": "Deleted encounter",
35
+ "encounterDeletedAction": "Deleted encounter",
36
+ "encounterDeletedSubtitle": "This encounter has been deleted. Its observations are shown as they were left.",
37
+ "encounterNotFound": "That encounter could not be found.",
38
+ "encounters": "Encounters",
39
+ "encountersCreated": "Encounters created",
40
+ "encounterSummary": "Encounter summary",
41
+ "encounterType": "Encounter type",
42
+ "firstActivity": "First activity",
43
+ "form": "Form",
44
+ "gender": "Gender",
45
+ "identifier": "Identifier",
46
+ "includeDeletedEncounters": "Include deleted encounters",
47
+ "lastActivity": "Last activity",
48
+ "location": "Location",
49
+ "noActivity": "There is no recorded activity to show.",
50
+ "noEncountersFound": "This patient has no encounters to audit.",
51
+ "noEncountersMatchFilters": "No encounters match these filters.",
52
+ "noObservations": "This encounter has no observations.",
53
+ "noPatientsFound": "No patients match \"{{searchTerm}}\".",
54
+ "noProviders": "No providers are recorded for this encounter.",
55
+ "obsDeletedAction": "Deleted observation",
56
+ "obsDeletedCount": "Observations deleted",
57
+ "obsEditedAction": "Edited observation",
58
+ "obsEditedCount": "Observations edited",
59
+ "observations": "Observations",
60
+ "obsRecordedAction": "Recorded observation",
61
+ "obsRecordedCount": "Observations recorded",
62
+ "patient": "Patient",
63
+ "patientName": "Patient name",
64
+ "patientSearchResults": "Patient search results",
65
+ "previousValue": "Previous value",
66
+ "previousValueLegend": "the value that an edit replaced",
67
+ "provider": "Provider",
68
+ "providerName": "Provider name",
69
+ "providers": "Providers",
70
+ "questionConcept": "Question concept",
71
+ "readingObservations": "Reading observations from {{read}} of {{total}} encounters…",
72
+ "recordActivity": "Patient activity",
73
+ "recordedBy": "Recorded by",
74
+ "role": "Role",
75
+ "searchForPatient": "Search for a patient by name or identifier",
76
+ "searchToBegin": "Enter a patient name or identifier to begin auditing their records.",
77
+ "showConceptDescriptions": "Show concept descriptions",
78
+ "showDeletedObs": "Show deleted observations",
79
+ "showEveryone": "Show everyone",
80
+ "showOnlyThisUser": "Show only this user's activity",
81
+ "status": "Status",
82
+ "unknownUser": "Unknown user",
83
+ "user": "User",
84
+ "value": "Value",
85
+ "visit": "Visit",
86
+ "wasPreviously": "was {{value}}",
87
+ "when": "When",
88
+ "whoTouchedThisRecord": "Who modified this patient record"
89
+ }
@@ -0,0 +1,89 @@
1
+ {
2
+ "action": "Acción",
3
+ "activityLog": "Registro de actividad",
4
+ "addedLater": "Añadido posteriormente",
5
+ "addedLaterLegend": "registrado después de crearse el encuentro",
6
+ "age": "Edad",
7
+ "allEncounterTypes": "Todos los tipos de encuentro",
8
+ "audit": "Auditoría",
9
+ "auditTrail": "Pista de auditoría",
10
+ "auditViews": "Vistas de auditoría",
11
+ "backToEncounters": "Volver a los encuentros",
12
+ "backToPatientSearch": "Volver a la búsqueda de pacientes",
13
+ "birthdate": "Fecha de nacimiento",
14
+ "cannotIncludeDeletedEncounters": "Los encuentros eliminados solo pueden listarse para un paciente que tenga un identificador.",
15
+ "change": "Cambio",
16
+ "changedBy": "Modificado por",
17
+ "changedByUser": "Modificado por {{user}}",
18
+ "clearFilters": "Borrar los filtros",
19
+ "createdBy": "Creado por",
20
+ "deleted": "Eliminado",
21
+ "deletedBy": "Eliminado por",
22
+ "deletedEncountersUnavailable": "Encuentros eliminados no disponibles",
23
+ "deletedLegend": "retirado del encuentro",
24
+ "deletionReason": "Motivo de la eliminación",
25
+ "detail": "Detalle",
26
+ "edited": "Editado",
27
+ "editedLegend": "el valor se cambió después de registrarse por primera vez",
28
+ "encounter": "Encuentro",
29
+ "encounterAudit": "Auditoría del encuentro",
30
+ "encounterChangedAction": "Encuentro modificado",
31
+ "encounterCreatedAction": "Encuentro creado",
32
+ "encounterDate": "Fecha del encuentro",
33
+ "encounterDateRange": "Rango de fechas de los encuentros",
34
+ "encounterDeleted": "Encuentro eliminado",
35
+ "encounterDeletedAction": "Encuentro eliminado",
36
+ "encounterDeletedSubtitle": "Este encuentro ha sido eliminado. Sus observaciones se muestran tal como quedaron.",
37
+ "encounterNotFound": "No se ha encontrado ese encuentro.",
38
+ "encounters": "Encuentros",
39
+ "encountersCreated": "Encuentros creados",
40
+ "encounterSummary": "Resumen del encuentro",
41
+ "encounterType": "Tipo de encuentro",
42
+ "firstActivity": "Primera actividad",
43
+ "form": "Formulario",
44
+ "gender": "Género",
45
+ "identifier": "Identificador",
46
+ "includeDeletedEncounters": "Incluir encuentros eliminados",
47
+ "lastActivity": "Última actividad",
48
+ "location": "Localidad",
49
+ "noActivity": "No hay actividad registrada que mostrar.",
50
+ "noEncountersFound": "Este paciente no tiene encuentros que auditar.",
51
+ "noEncountersMatchFilters": "Ningún encuentro coincide con estos filtros.",
52
+ "noObservations": "Este encuentro no tiene observaciones.",
53
+ "noPatientsFound": "Ningún paciente coincide con «{{searchTerm}}».",
54
+ "noProviders": "No hay proveedores registrados para este encuentro.",
55
+ "obsDeletedAction": "Observación eliminada",
56
+ "obsDeletedCount": "Observaciones eliminadas",
57
+ "obsEditedAction": "Observación editada",
58
+ "obsEditedCount": "Observaciones editadas",
59
+ "observations": "Observaciones",
60
+ "obsRecordedAction": "Observación registrada",
61
+ "obsRecordedCount": "Observaciones registradas",
62
+ "patient": "Paciente",
63
+ "patientName": "Nombre del paciente",
64
+ "patientSearchResults": "Resultados de la búsqueda de pacientes",
65
+ "previousValue": "Valor anterior",
66
+ "previousValueLegend": "el valor sustituido por una edición",
67
+ "provider": "Proveedor",
68
+ "providerName": "Nombre del proveedor",
69
+ "providers": "Proveedores",
70
+ "questionConcept": "Concepto de la pregunta",
71
+ "readingObservations": "Leyendo las observaciones de {{read}} de {{total}} encuentros…",
72
+ "recordActivity": "Actividad del paciente",
73
+ "recordedBy": "Registrado por",
74
+ "role": "Rol",
75
+ "searchForPatient": "Buscar un paciente por nombre o identificador",
76
+ "searchToBegin": "Introduzca el nombre o el identificador de un paciente para empezar a auditar su historial.",
77
+ "showConceptDescriptions": "Mostrar las descripciones de los conceptos",
78
+ "showDeletedObs": "Mostrar las observaciones eliminadas",
79
+ "showEveryone": "Mostrar a todos",
80
+ "showOnlyThisUser": "Mostrar solo la actividad de este usuario",
81
+ "status": "Estado",
82
+ "unknownUser": "Usuario desconocido",
83
+ "user": "Usuario",
84
+ "value": "Valor",
85
+ "visit": "Visita",
86
+ "wasPreviously": "era {{value}}",
87
+ "when": "Cuándo",
88
+ "whoTouchedThisRecord": "Quién ha modificado este historial del paciente"
89
+ }