@kenyaemr/esm-pcs-app 5.4.5-pre.1092

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 (286) hide show
  1. package/.turbo/turbo-build.log +6 -0
  2. package/dist/1072.js +1 -0
  3. package/dist/1072.js.map +1 -0
  4. package/dist/1102.js +1 -0
  5. package/dist/1102.js.map +1 -0
  6. package/dist/1105.js +1 -0
  7. package/dist/1105.js.map +1 -0
  8. package/dist/1148.js +1 -0
  9. package/dist/1148.js.map +1 -0
  10. package/dist/1278.js +1 -0
  11. package/dist/1278.js.map +1 -0
  12. package/dist/1442.js +1 -0
  13. package/dist/1442.js.map +1 -0
  14. package/dist/15.js +1 -0
  15. package/dist/15.js.map +1 -0
  16. package/dist/1620.js +1 -0
  17. package/dist/1620.js.map +1 -0
  18. package/dist/1789.js +1 -0
  19. package/dist/1789.js.map +1 -0
  20. package/dist/1818.js +1 -0
  21. package/dist/1818.js.map +1 -0
  22. package/dist/1831.js +1 -0
  23. package/dist/1831.js.map +1 -0
  24. package/dist/1844.js +1 -0
  25. package/dist/1844.js.map +1 -0
  26. package/dist/1854.js +1 -0
  27. package/dist/1854.js.map +1 -0
  28. package/dist/1946.js +1 -0
  29. package/dist/1946.js.map +1 -0
  30. package/dist/2007.js +1 -0
  31. package/dist/2007.js.map +1 -0
  32. package/dist/2115.js +1 -0
  33. package/dist/2115.js.map +1 -0
  34. package/dist/2152.js +1 -0
  35. package/dist/2152.js.map +1 -0
  36. package/dist/2202.js +1 -0
  37. package/dist/2202.js.map +1 -0
  38. package/dist/2394.js +1 -0
  39. package/dist/2394.js.map +1 -0
  40. package/dist/2530.js +1 -0
  41. package/dist/2530.js.map +1 -0
  42. package/dist/2534.js +1 -0
  43. package/dist/2534.js.map +1 -0
  44. package/dist/2537.js +1 -0
  45. package/dist/2537.js.map +1 -0
  46. package/dist/2556.js +1 -0
  47. package/dist/2556.js.map +1 -0
  48. package/dist/2648.js +1 -0
  49. package/dist/2648.js.map +1 -0
  50. package/dist/2795.js +1 -0
  51. package/dist/2795.js.map +1 -0
  52. package/dist/2827.js +1 -0
  53. package/dist/2827.js.map +1 -0
  54. package/dist/2916.js +1 -0
  55. package/dist/2916.js.map +1 -0
  56. package/dist/2924.js +1 -0
  57. package/dist/2924.js.map +1 -0
  58. package/dist/2947.js +1 -0
  59. package/dist/2947.js.map +1 -0
  60. package/dist/3022.js +1 -0
  61. package/dist/3022.js.map +1 -0
  62. package/dist/3285.js +1 -0
  63. package/dist/3285.js.map +1 -0
  64. package/dist/3335.js +1 -0
  65. package/dist/3335.js.map +1 -0
  66. package/dist/3378.js +1 -0
  67. package/dist/3378.js.map +1 -0
  68. package/dist/3381.js +1 -0
  69. package/dist/3381.js.map +1 -0
  70. package/dist/3504.js +1 -0
  71. package/dist/3504.js.map +1 -0
  72. package/dist/3530.js +1 -0
  73. package/dist/3530.js.map +1 -0
  74. package/dist/356.js +1 -0
  75. package/dist/356.js.map +1 -0
  76. package/dist/3569.js +1 -0
  77. package/dist/3569.js.map +1 -0
  78. package/dist/3723.js +1 -0
  79. package/dist/3723.js.map +1 -0
  80. package/dist/3745.js +1 -0
  81. package/dist/3745.js.map +1 -0
  82. package/dist/3813.js +1 -0
  83. package/dist/3813.js.map +1 -0
  84. package/dist/393.js +6 -0
  85. package/dist/393.js.map +1 -0
  86. package/dist/3953.js +1 -0
  87. package/dist/3953.js.map +1 -0
  88. package/dist/3989.js +1 -0
  89. package/dist/3989.js.map +1 -0
  90. package/dist/412.js +1 -0
  91. package/dist/412.js.map +1 -0
  92. package/dist/4178.js +1 -0
  93. package/dist/4178.js.map +1 -0
  94. package/dist/4413.js +1 -0
  95. package/dist/4413.js.map +1 -0
  96. package/dist/4487.js +11 -0
  97. package/dist/4487.js.map +1 -0
  98. package/dist/4537.js +1 -0
  99. package/dist/4537.js.map +1 -0
  100. package/dist/4588.js +1 -0
  101. package/dist/4588.js.map +1 -0
  102. package/dist/4637.js +1 -0
  103. package/dist/4637.js.map +1 -0
  104. package/dist/4650.js +1 -0
  105. package/dist/4650.js.map +1 -0
  106. package/dist/466.js +1 -0
  107. package/dist/466.js.map +1 -0
  108. package/dist/4817.js +1 -0
  109. package/dist/4817.js.map +1 -0
  110. package/dist/4818.js +1 -0
  111. package/dist/4818.js.map +1 -0
  112. package/dist/500.js +1 -0
  113. package/dist/500.js.map +1 -0
  114. package/dist/5086.js +1 -0
  115. package/dist/5086.js.map +1 -0
  116. package/dist/5117.js +1 -0
  117. package/dist/5117.js.map +1 -0
  118. package/dist/5279.js +1 -0
  119. package/dist/5279.js.map +1 -0
  120. package/dist/5366.js +1 -0
  121. package/dist/5366.js.map +1 -0
  122. package/dist/5598.js +1 -0
  123. package/dist/5598.js.map +1 -0
  124. package/dist/5676.js +1 -0
  125. package/dist/5676.js.map +1 -0
  126. package/dist/5731.js +5 -0
  127. package/dist/5731.js.map +1 -0
  128. package/dist/5792.js +1 -0
  129. package/dist/5792.js.map +1 -0
  130. package/dist/5825.js +1 -0
  131. package/dist/5825.js.map +1 -0
  132. package/dist/5932.js +1 -0
  133. package/dist/5932.js.map +1 -0
  134. package/dist/5941.js +1 -0
  135. package/dist/5941.js.map +1 -0
  136. package/dist/5967.js +1 -0
  137. package/dist/5967.js.map +1 -0
  138. package/dist/6104.js +1 -0
  139. package/dist/6104.js.map +1 -0
  140. package/dist/624.js +1 -0
  141. package/dist/624.js.map +1 -0
  142. package/dist/6259.js +1 -0
  143. package/dist/6259.js.map +1 -0
  144. package/dist/6273.js +1 -0
  145. package/dist/6273.js.map +1 -0
  146. package/dist/6337.js +1 -0
  147. package/dist/6337.js.map +1 -0
  148. package/dist/6427.js +1 -0
  149. package/dist/6427.js.map +1 -0
  150. package/dist/6478.js +7 -0
  151. package/dist/6478.js.map +1 -0
  152. package/dist/6508.js +1 -0
  153. package/dist/6508.js.map +1 -0
  154. package/dist/6561.js +1 -0
  155. package/dist/6561.js.map +1 -0
  156. package/dist/6566.js +1 -0
  157. package/dist/6566.js.map +1 -0
  158. package/dist/6567.js +1 -0
  159. package/dist/6567.js.map +1 -0
  160. package/dist/6634.js +1 -0
  161. package/dist/6634.js.map +1 -0
  162. package/dist/7022.js +1 -0
  163. package/dist/7022.js.map +1 -0
  164. package/dist/7085.js +1 -0
  165. package/dist/7085.js.map +1 -0
  166. package/dist/7209.js +1 -0
  167. package/dist/7209.js.map +1 -0
  168. package/dist/7291.js +1 -0
  169. package/dist/7291.js.map +1 -0
  170. package/dist/7360.js +1 -0
  171. package/dist/7360.js.map +1 -0
  172. package/dist/7453.js +1 -0
  173. package/dist/7453.js.map +1 -0
  174. package/dist/7454.js +1 -0
  175. package/dist/7454.js.map +1 -0
  176. package/dist/7478.js +1 -0
  177. package/dist/7478.js.map +1 -0
  178. package/dist/7778.js +1 -0
  179. package/dist/7778.js.map +1 -0
  180. package/dist/7782.js +1 -0
  181. package/dist/7782.js.map +1 -0
  182. package/dist/8028.js +1 -0
  183. package/dist/8028.js.map +1 -0
  184. package/dist/8031.js +1 -0
  185. package/dist/8031.js.map +1 -0
  186. package/dist/8196.js +1 -0
  187. package/dist/8196.js.map +1 -0
  188. package/dist/820.js +1 -0
  189. package/dist/820.js.map +1 -0
  190. package/dist/8240.js +1 -0
  191. package/dist/8240.js.map +1 -0
  192. package/dist/8472.js +1 -0
  193. package/dist/8472.js.map +1 -0
  194. package/dist/8607.js +1 -0
  195. package/dist/8607.js.map +1 -0
  196. package/dist/8793.js +1 -0
  197. package/dist/8793.js.map +1 -0
  198. package/dist/8924.js +1 -0
  199. package/dist/8924.js.map +1 -0
  200. package/dist/8990.js +1 -0
  201. package/dist/8990.js.map +1 -0
  202. package/dist/9060.js +1 -0
  203. package/dist/9060.js.map +1 -0
  204. package/dist/9061.js +1 -0
  205. package/dist/9061.js.map +1 -0
  206. package/dist/9087.js +1 -0
  207. package/dist/9087.js.map +1 -0
  208. package/dist/9165.js +1 -0
  209. package/dist/9165.js.map +1 -0
  210. package/dist/9274.js +1 -0
  211. package/dist/9274.js.map +1 -0
  212. package/dist/9425.js +1 -0
  213. package/dist/9520.js +1 -0
  214. package/dist/9520.js.map +1 -0
  215. package/dist/9547.js +1 -0
  216. package/dist/9547.js.map +1 -0
  217. package/dist/9612.js +1 -0
  218. package/dist/9612.js.map +1 -0
  219. package/dist/9771.js +1 -0
  220. package/dist/9771.js.map +1 -0
  221. package/dist/9774.js +1 -0
  222. package/dist/9774.js.map +1 -0
  223. package/dist/9806.js +1 -0
  224. package/dist/9806.js.map +1 -0
  225. package/dist/9885.js +1 -0
  226. package/dist/9885.js.map +1 -0
  227. package/dist/992.js +1 -0
  228. package/dist/992.js.map +1 -0
  229. package/dist/9941.js +1 -0
  230. package/dist/9941.js.map +1 -0
  231. package/dist/997.js +1 -0
  232. package/dist/997.js.map +1 -0
  233. package/dist/kenyaemr-esm-pcs-app.js +3 -0
  234. package/dist/kenyaemr-esm-pcs-app.js.buildmanifest.json +3173 -0
  235. package/dist/kenyaemr-esm-pcs-app.js.map +1 -0
  236. package/dist/main.js +3 -0
  237. package/dist/main.js.map +1 -0
  238. package/dist/routes.json +1 -0
  239. package/package.json +50 -0
  240. package/rspack.config.js +1 -0
  241. package/src/add-dependant/add-dependant.scss +29 -0
  242. package/src/add-dependant/add-dependant.workspace.test.ts +65 -0
  243. package/src/add-dependant/add-dependant.workspace.tsx +249 -0
  244. package/src/config-schema.ts +67 -0
  245. package/src/declarations.d.ts +2 -0
  246. package/src/empty-svg.component.tsx +565 -0
  247. package/src/index.ts +34 -0
  248. package/src/link-hie-dependant/link-hie-dependant.scss +55 -0
  249. package/src/link-hie-dependant/link-hie-dependant.workspace.tsx +207 -0
  250. package/src/linked/dependant-row.component.test.tsx +134 -0
  251. package/src/linked/dependant-row.component.tsx +122 -0
  252. package/src/linked/dependants-list.component.tsx +60 -0
  253. package/src/linked/linked-participant-body.component.tsx +82 -0
  254. package/src/linked/linked-participant.component.test.tsx +134 -0
  255. package/src/linked/linked-participant.component.tsx +142 -0
  256. package/src/linked/participant-details.component.tsx +89 -0
  257. package/src/linked/use-study-sync-snackbars.ts +41 -0
  258. package/src/modals/delink-participant.modal.tsx +96 -0
  259. package/src/modals/link-dependant.modal.tsx +244 -0
  260. package/src/modals/link-participant.modal.tsx +143 -0
  261. package/src/modals/link-participant.scss +48 -0
  262. package/src/pcs.scss +241 -0
  263. package/src/resources/hie-contacts.ts +61 -0
  264. package/src/resources/identifiers.ts +52 -0
  265. package/src/resources/link-dependant.resource.test.ts +445 -0
  266. package/src/resources/link-dependant.resource.ts +268 -0
  267. package/src/resources/link-participant.resource.test.ts +336 -0
  268. package/src/resources/link-participant.resource.ts +452 -0
  269. package/src/resources/patient-create.resource.ts +296 -0
  270. package/src/resources/patient-search.resource.ts +106 -0
  271. package/src/resources/pcs.resource.test.ts +36 -0
  272. package/src/resources/pcs.resource.ts +182 -0
  273. package/src/resources/use-identifier-type-uuids.ts +38 -0
  274. package/src/routes.json +54 -0
  275. package/src/search/participant-results.component.tsx +96 -0
  276. package/src/search/participant-tile.component.tsx +133 -0
  277. package/src/search/pcs-empty-state.component.tsx +31 -0
  278. package/src/search/pcs-results-skeleton.component.tsx +21 -0
  279. package/src/search/pcs-search.component.test.tsx +169 -0
  280. package/src/search/pcs-search.component.tsx +228 -0
  281. package/src/types/index.ts +140 -0
  282. package/translations/am.json +120 -0
  283. package/translations/en.json +120 -0
  284. package/translations/fr.json +122 -0
  285. package/translations/sw.json +120 -0
  286. package/tsconfig.json +5 -0
@@ -0,0 +1,228 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Button, DismissibleTag, IconButton, Search, SkeletonText, TextInput } from '@carbon/react';
3
+ import { FilterEdit, Reset, Search as SearchIcon } from '@carbon/react/icons';
4
+ import { useConfig } from '@openmrs/esm-framework';
5
+ import { useTranslation } from 'react-i18next';
6
+ import styles from '../pcs.scss';
7
+ import { type PcsConfig } from '../config-schema';
8
+ import LinkedParticipant from '../linked/linked-participant.component';
9
+ import { usePatientStudyLink } from '../resources/link-participant.resource';
10
+ import ParticipantResults from './participant-results.component';
11
+ import { hasAnyFilter, toPcsParticipantFilters, usePcsParticipantSearch } from '../resources/pcs.resource';
12
+ import { type PcsParticipantFilters, type PcsSearchSubject } from '../types';
13
+
14
+ interface PCSSearchResultsProps {
15
+ /** The authorized patient the registry is being cross-checked against. */
16
+ subject: PcsSearchSubject;
17
+ }
18
+
19
+ const PCSSearchResults: React.FC<PCSSearchResultsProps> = ({ subject }) => {
20
+ const { t } = useTranslation();
21
+
22
+ // `draft` is what the form shows; `filters` is what the registry has actually been asked.
23
+ // Committing on submit rather than per keystroke keeps these server round-trips deliberate.
24
+ const [draft, setDraft] = useState<PcsParticipantFilters>(() => toPcsParticipantFilters(subject));
25
+ const [filters, setFilters] = useState<PcsParticipantFilters>(() => toPcsParticipantFilters(subject));
26
+ const [isEditingFilters, setIsEditingFilters] = useState(false);
27
+ const { pcsIdentifiers } = useConfig<PcsConfig>();
28
+ const {
29
+ studyParticipantId,
30
+ localPatient,
31
+ isLoading: isCheckingLink,
32
+ mutate: mutateStudyLink,
33
+ } = usePatientStudyLink(subject, pcsIdentifiers.studyParticipantID);
34
+
35
+ const { participants, totalCount, isLoading, error } = usePcsParticipantSearch(filters);
36
+
37
+ // Authorizing a patient searches on their own demographics straight away.
38
+ useEffect(() => {
39
+ const defaults = toPcsParticipantFilters(subject);
40
+ setDraft(defaults);
41
+ setFilters(defaults);
42
+ setIsEditingFilters(false);
43
+ }, [subject]);
44
+
45
+ const canSearch = hasAnyFilter(draft);
46
+ const hasCommittedFilters = hasAnyFilter(filters);
47
+
48
+ // Dismissing the last tag while the name is empty leaves nothing to search; open the
49
+ // panel so there are fields to type into rather than stranding the pane.
50
+ useEffect(() => {
51
+ if (!hasCommittedFilters) {
52
+ setIsEditingFilters(true);
53
+ }
54
+ }, [hasCommittedFilters]);
55
+
56
+ const updateFilter = (filter: keyof PcsParticipantFilters) => (event: React.ChangeEvent<HTMLInputElement>) =>
57
+ setDraft((previous) => ({ ...previous, [filter]: event.target.value }));
58
+
59
+ const submit = (event: React.SyntheticEvent) => {
60
+ event.preventDefault();
61
+ if (canSearch) {
62
+ setFilters(draft);
63
+ }
64
+ };
65
+
66
+ const resetToPatient = () => {
67
+ const defaults = toPcsParticipantFilters(subject);
68
+ setDraft(defaults);
69
+ setFilters(defaults);
70
+ };
71
+
72
+ const toggleEditingFilters = () => {
73
+ if (isEditingFilters) {
74
+ // Collapsing discards uncommitted edits, so the tags can never advertise a query
75
+ // that was never run.
76
+ setDraft(filters);
77
+ }
78
+ setIsEditingFilters(!isEditingFilters);
79
+ };
80
+
81
+ /** Dismissing a tag commits immediately — it is an edit and a search in one gesture. */
82
+ const clearFilter = (filter: keyof PcsParticipantFilters) => {
83
+ setDraft((previous) => ({ ...previous, [filter]: '' }));
84
+ setFilters((previous) => ({ ...previous, [filter]: '' }));
85
+ };
86
+
87
+ // Built from the committed filters, so they always describe the results on screen.
88
+ const activeTags = (
89
+ [
90
+ ['name', t('pcsNameTag', 'Name: {{value}}', { value: filters.name })],
91
+ ['phone', t('pcsPhoneTag', 'Phone: {{value}}', { value: filters.phone })],
92
+ ] as const
93
+ ).filter(([filter]) => filters[filter].trim());
94
+
95
+ // A patient already carrying a study participant ID gives us a unique key into PCS, so the
96
+ // pane reports on that participant instead of searching for candidates.
97
+ const isLinked = Boolean(studyParticipantId);
98
+
99
+ if (isCheckingLink) {
100
+ return (
101
+ <div className={styles.pcsColumn}>
102
+ <div className={styles.pcsHeader}>
103
+ <span className={styles.pcsTitle}>{t('pcsRegistry', 'PCS registry')}</span>
104
+ </div>
105
+ <div className={styles.pcsSkeletonTile}>
106
+ <SkeletonText heading width="60%" />
107
+ <SkeletonText paragraph lineCount={3} />
108
+ </div>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ if (isLinked) {
114
+ return (
115
+ <div className={styles.pcsColumn}>
116
+ <div className={styles.pcsHeader}>
117
+ <span className={styles.pcsTitle}>{t('pcsRegistry', 'PCS registry')}</span>
118
+ </div>
119
+ <LinkedParticipant
120
+ subject={subject}
121
+ studyParticipantId={studyParticipantId!}
122
+ localPatient={localPatient}
123
+ onDelinked={() => mutateStudyLink()}
124
+ />
125
+ </div>
126
+ );
127
+ }
128
+
129
+ return (
130
+ <div className={styles.pcsColumn}>
131
+ <div className={styles.pcsHeader}>
132
+ <span className={styles.pcsTitle}>{t('pcsRegistry', 'PCS registry')}</span>
133
+ </div>
134
+
135
+ <form className={styles.pcsFilters} onSubmit={submit}>
136
+ <div className={styles.pcsFilterRow}>
137
+ <Search
138
+ id="pcsFilterVillage"
139
+ size="sm"
140
+ labelText={t('village', 'Village')}
141
+ placeholder={t('pcsVillagePlaceholder', 'Village name')}
142
+ value={draft.village}
143
+ onChange={updateFilter('village')}
144
+ onClear={() => setDraft((previous) => ({ ...previous, village: '' }))}
145
+ // Implicit form submission would work here, but only by way of a subtle rule
146
+ // about lone text fields; being explicit is cheaper than depending on it.
147
+ onKeyDown={(event) => {
148
+ if (event.key === 'Enter') {
149
+ submit(event);
150
+ }
151
+ }}
152
+ />
153
+ <IconButton
154
+ kind="tertiary"
155
+ size="sm"
156
+ align="left"
157
+ label={isEditingFilters ? t('hideFilters', 'Hide filters') : t('editFilters', 'Edit filters')}
158
+ onClick={toggleEditingFilters}>
159
+ <FilterEdit />
160
+ </IconButton>
161
+ </div>
162
+
163
+ {!isEditingFilters && activeTags.length > 0 && (
164
+ <div className={styles.pcsFilterTags}>
165
+ {activeTags.map(([filter, label]) => (
166
+ <DismissibleTag
167
+ key={filter}
168
+ type="blue"
169
+ text={label}
170
+ // Without this every tag's dismiss button is announced as just "Dismiss".
171
+ title={t('removeFilter', 'Remove {{label}}', { label })}
172
+ onClose={() => clearFilter(filter)}
173
+ />
174
+ ))}
175
+ </div>
176
+ )}
177
+
178
+ {isEditingFilters && (
179
+ <>
180
+ <TextInput
181
+ id="pcsFilterName"
182
+ size="sm"
183
+ labelText={t('name', 'Name')}
184
+ helperText={t('pcsNameHelper', 'Matches the participant, their mother or their compound head')}
185
+ value={draft.name}
186
+ onChange={updateFilter('name')}
187
+ />
188
+ <TextInput
189
+ id="pcsFilterPhone"
190
+ size="sm"
191
+ labelText={t('phone', 'Phone')}
192
+ value={draft.phone}
193
+ onChange={updateFilter('phone')}
194
+ />
195
+
196
+ {!canSearch && (
197
+ <span className={styles.pcsFilterHint}>
198
+ {t('pcsFilterRequired', 'Enter at least one of name, village or phone')}
199
+ </span>
200
+ )}
201
+
202
+ <div className={styles.pcsFilterActions}>
203
+ <Button kind="ghost" size="sm" renderIcon={Reset} onClick={resetToPatient}>
204
+ {t('resetToPatient', 'Reset to patient details')}
205
+ </Button>
206
+ <Button kind="primary" size="sm" type="submit" renderIcon={SearchIcon} disabled={!canSearch}>
207
+ {t('search', 'Search')}
208
+ </Button>
209
+ </div>
210
+ </>
211
+ )}
212
+ </form>
213
+
214
+ <ParticipantResults
215
+ subject={subject}
216
+ participants={participants}
217
+ totalCount={totalCount}
218
+ hasCommittedFilters={hasCommittedFilters}
219
+ isLoading={isLoading}
220
+ error={error}
221
+ onReset={resetToPatient}
222
+ onLinked={() => mutateStudyLink()}
223
+ />
224
+ </div>
225
+ );
226
+ };
227
+
228
+ export default PCSSearchResults;
@@ -0,0 +1,140 @@
1
+ /** How strongly a `name` term matched, in the order the API ranks them. */
2
+ export type PcsMatchType = 'EXACT' | 'CONTAINS' | 'SOUNDEX';
3
+
4
+ /** Which name field the term hit. A participant's own name wins over their compound head's. */
5
+ export type PcsMatchedOn = 'name' | 'motherName' | 'compoundName';
6
+
7
+ export interface PcsMother {
8
+ individualId: string;
9
+ firstName: string;
10
+ middleName?: string;
11
+ lastName: string;
12
+ }
13
+
14
+ export interface PcsCompound {
15
+ compoundId: string;
16
+ headIndividualId: string;
17
+ headFirstName: string;
18
+ headMiddleName?: string;
19
+ headLastName: string;
20
+ }
21
+
22
+ export interface PcsVillage {
23
+ code: string;
24
+ name: string;
25
+ }
26
+
27
+ export interface PcsContact {
28
+ phone?: string;
29
+ email?: string;
30
+ nationalId?: string;
31
+ lastUpdated?: string;
32
+ }
33
+
34
+ export interface PcsParticipant {
35
+ individualId: string;
36
+ firstName: string;
37
+ middleName?: string;
38
+ lastName: string;
39
+ sex: 'M' | 'F';
40
+ dateOfBirth?: string;
41
+ pbidsEnrolled: boolean;
42
+ cardse: boolean;
43
+ mother: PcsMother | null;
44
+ compound: PcsCompound;
45
+ village: PcsVillage;
46
+ contacts: Array<PcsContact>;
47
+ matchedOn: PcsMatchedOn | null;
48
+ matchType: PcsMatchType | null;
49
+ }
50
+
51
+ export interface PcsParticipantSearchResponse {
52
+ totalCount: number;
53
+ startIndex: number;
54
+ results: Array<PcsParticipant>;
55
+ }
56
+
57
+ export interface PcsApiError {
58
+ timestamp: string;
59
+ status: number;
60
+ error: string;
61
+ message: string;
62
+ path: string;
63
+ }
64
+
65
+ export interface PcsParticipantFilters {
66
+ name: string;
67
+ village: string;
68
+ phone: string;
69
+ /**
70
+ * Returns the participants whose mother is this individual. Not part of the editable
71
+ * filter bar — it backs the dependants list on a linked participant.
72
+ */
73
+ motherId?: string;
74
+ }
75
+
76
+ /**
77
+ * Normalized demographics of the patient selected on the left-hand side of the
78
+ * registration screen. This is the EMR-side input the filters are derived from.
79
+ */
80
+ export interface PcsSearchSubject {
81
+ /** `fhir.Patient.id` — the local patient uuid, or the HIE resource id. */
82
+ id: string;
83
+ source: 'local' | 'hie';
84
+ /**
85
+ * The record the flattened fields below were derived from. Kept whole because linking an
86
+ * HIE patient has to be able to create them locally, which needs more than demographics.
87
+ */
88
+ patient: fhir.Patient;
89
+ /**
90
+ * The mother's HIE record when one was matched. Its `contact` array is where her dependants
91
+ * come from; a locally-converted patient has none, hence optional.
92
+ */
93
+ hiePatient?: fhir.Patient;
94
+ name: string;
95
+ gender?: string;
96
+ birthDate?: string;
97
+ nationalId?: string | null;
98
+ phoneNumber?: string | null;
99
+ }
100
+
101
+ /** The identifier types a dependant's HIE identifiers are written against. */
102
+ export interface IdentifierTypeUuids {
103
+ nationalIdUUID: string;
104
+ shaNumberUUID: string;
105
+ passportUUID: string;
106
+ birthCertificateUUID: string;
107
+ /** The CR number, which the HIE calls `sha-id-number`. */
108
+ crIdentificationNumberUUID: string;
109
+ phoneAttributeTypeUUID: string;
110
+ }
111
+
112
+ /** One row of `getDependentsFromContacts` — an HIE contact, flattened. */
113
+ export interface PcsHieDependant {
114
+ id: string;
115
+ name: string;
116
+ relationship: string;
117
+ phoneNumber?: string;
118
+ email?: string;
119
+ gender: string;
120
+ /** ISO date, or the string 'Unknown' when the HIE record carries none. */
121
+ birthDate?: string;
122
+ shaNumber?: string;
123
+ shaIdNumber?: string;
124
+ nationalId?: string;
125
+ birthCertificate?: string;
126
+ /** The raw HIE contact, which patient lookup reads identifiers out of. */
127
+ contactData: any;
128
+ }
129
+
130
+ /** Plain demographics for a patient with no HIE record behind them. */
131
+ export interface PcsDependantDemographics {
132
+ firstName: string;
133
+ middleName?: string;
134
+ lastName: string;
135
+ sex: 'M' | 'F';
136
+ /** ISO date, `yyyy-MM-dd`. */
137
+ dateOfBirth?: string;
138
+ nationalId?: string;
139
+ phone?: string;
140
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "addDependant": "Add dependant",
3
+ "addingDependant": "Adding...",
4
+ "alreadyLinkedTo": "already linked to {{individualId}}",
5
+ "cancel": "Cancel",
6
+ "cardse": "CARDSE",
7
+ "cardseEnrollment": "CARDSE enrollment",
8
+ "checkingPcsLinks": "Checking which dependants are already in PCS...",
9
+ "chooseDependantToAdd": "Choose the dependant to add to PCS. One at a time.",
10
+ "compound": "Compound",
11
+ "createAndLinkRecords": "Create and link records",
12
+ "dateOfBirth": "Date of birth",
13
+ "dateOfBirthInFuture": "Date of birth cannot be in the future",
14
+ "dateOfBirthRequired": "Date of birth is required",
15
+ "dependantAdded": "Dependant added",
16
+ "dependantAddedSubtitle": "PCS issued {{individualId}} for this dependant.",
17
+ "dependantAddFailed": "The dependant could not be added.",
18
+ "dependantAddFailedTitle": "Could not add dependant",
19
+ "dependantInHieNotPcs": "Dependant in HIE and not PCS?",
20
+ "dependantLinkedToPcs": "Dependant added to PCS",
21
+ "dependantLinkToPcsFailed": "The dependant could not be added to PCS.",
22
+ "dependantLinkToPcsFailedTitle": "Could not add dependant to PCS",
23
+ "dependantMustBeUnderOne": "This form is for infants — age cannot exceed one year",
24
+ "dependantNotInHieAndPcs": "Dependant not in HIE and PCS?",
25
+ "dependants": "Dependants",
26
+ "dependantsWithCount_one": "Dependants ({{count}})",
27
+ "dependantsWithCount_other": "Dependants ({{count}})",
28
+ "details": "Details",
29
+ "editFilters": "Edit filters",
30
+ "email": "Email",
31
+ "emrPatient": "EMR patient",
32
+ "errorLoadingDependants": "Error loading dependants",
33
+ "errorLoadingPcsRecords": "Error loading PCS records",
34
+ "familyName": "Family name",
35
+ "familyNameRequired": "Family name is required",
36
+ "female": "Female",
37
+ "givenName": "Given name",
38
+ "givenNameRequired": "Given name is required",
39
+ "hideFilters": "Hide filters",
40
+ "individualId": "Individual ID",
41
+ "linkDependant": "Link dependant",
42
+ "linkDependantToPcs": "Link dependant to PCS",
43
+ "linked": "Linked",
44
+ "linkedAs": "{{patientName}} is linked as {{individualId}}",
45
+ "linkedToPcs": "Linked to PCS",
46
+ "linkingRecords": "Linking records...",
47
+ "linkRecords": "Link records",
48
+ "linkToPcsParticipant": "Link to PCS participant",
49
+ "male": "Male",
50
+ "middleNameOptional": "Middle name (optional)",
51
+ "mother": "Mother",
52
+ "mothersDependantsInHie": "Mother's dependants in the HIE",
53
+ "name": "Name",
54
+ "nationalId": "National ID",
55
+ "no": "No",
56
+ "noDependants": "No dependants",
57
+ "noDependantsSubtitle": "PCS has no participants recorded with this mother.",
58
+ "noFilters": "No filters",
59
+ "noHieDependants": "The HIE record lists no dependants for this mother.",
60
+ "noHieDependantsSubtitle": "Use \"Dependant not in HIE and PCS?\" to register the child from their own details.",
61
+ "noPcsRecordsForFilters": "No participant matched these filters",
62
+ "noPcsRecordsFound": "No PCS records found",
63
+ "notInHie": "Not in the HIE — create from PCS details",
64
+ "participantLinked": "Records linked",
65
+ "participantLinkedSubtitle": "The patient is now linked to PCS participant {{individualId}}.",
66
+ "participantLinkFailed": "Could not link records",
67
+ "participantLinkFailedSubtitle": "The patient record could not be updated.",
68
+ "participantUnlinked": "Records unlinked",
69
+ "participantUnlinkedSubtitle": "The study participant ID and study attributes were removed.",
70
+ "participantUnlinkFailed": "Could not unlink records",
71
+ "participantUnlinkFailedSubtitle": "The patient record could not be updated.",
72
+ "patientWillBeCreated": "This patient is not registered here yet and will be created first.",
73
+ "pbidsEnrolled": "PBIDS enrolled",
74
+ "pbidsEnrollment": "PBIDS enrollment",
75
+ "pcsAddFilterToSearch": "Add a filter to search the PCS registry",
76
+ "pcsFieldCompoundName": "compound head",
77
+ "pcsFieldMotherName": "mother's name",
78
+ "pcsFieldName": "name",
79
+ "pcsFilterRequired": "Enter at least one of name, village or phone",
80
+ "pcsHousehold": "{{village}} village | Head: {{compoundHead}}",
81
+ "pcsMatchContains": "Partial match on {{field}}",
82
+ "pcsMatchExact": "Exact match on {{field}}",
83
+ "pcsMatchSoundex": "Sounds like {{field}}",
84
+ "pcsNameHelper": "Matches the participant, their mother or their compound head",
85
+ "pcsNameTag": "Name: {{value}}",
86
+ "pcsParticipant": "PCS participant",
87
+ "pcsParticipantTabs": "PCS participant",
88
+ "pcsParticipantUnavailable": "Participant could not be loaded",
89
+ "pcsParticipantUnavailableSubtitle": "PCS did not return a record for {{individualId}}.",
90
+ "pcsPhoneTag": "Phone: {{value}}",
91
+ "pcsRecordsFound_one": "PCS record(s) found ({{count}})",
92
+ "pcsRecordsFound_other": "PCS record(s) found ({{count}})",
93
+ "pcsRegistry": "PCS registry",
94
+ "pcsRegistryUnavailable": "PCS registry unavailable",
95
+ "pcsShowingOf": "Showing {{shown}} of {{total}} PCS records",
96
+ "pcsVillagePlaceholder": "Village name",
97
+ "phone": "Phone",
98
+ "refresh": "Refresh",
99
+ "refreshing": "Refreshing...",
100
+ "removeFilter": "Remove {{label}}",
101
+ "resetToPatient": "Reset to patient details",
102
+ "save": "Save",
103
+ "search": "Search",
104
+ "sex": "Sex",
105
+ "sexRequired": "Sex is required",
106
+ "studyAttributesSyncFailed": "Could not sync study attributes",
107
+ "studyAttributesUpdated": "Study attributes updated",
108
+ "studyAttributesUpdatedSubtitle": "Updated {{name}} from PCS: {{fields}}",
109
+ "studyParticipantId": "Study participant ID",
110
+ "unknownDateOfBirth": "Unknown date of birth",
111
+ "unlink": "Unlink",
112
+ "unlinkConfirmation": "Remove the link between this patient and PCS participant {{individualId}}?",
113
+ "unlinkFromPcs": "Unlink from PCS",
114
+ "unlinking": "Unlinking...",
115
+ "unlinkWhatIsRemoved": "The study participant ID and both enrollment status attributes will be removed from the patient record.",
116
+ "village": "Village",
117
+ "willBeWritten": "The following will be written to the patient record",
118
+ "willCreateFromPcsDetails": "A patient will be created as {{name}} · {{sex}}{{dateOfBirth}}{{contact}}.",
119
+ "yes": "Yes"
120
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "addDependant": "Add dependant",
3
+ "addingDependant": "Adding...",
4
+ "alreadyLinkedTo": "already linked to {{individualId}}",
5
+ "cancel": "Cancel",
6
+ "cardse": "CARDSE",
7
+ "cardseEnrollment": "CARDSE enrollment",
8
+ "checkingPcsLinks": "Checking which dependants are already in PCS...",
9
+ "chooseDependantToAdd": "Choose the dependant to add to PCS. One at a time.",
10
+ "compound": "Compound",
11
+ "createAndLinkRecords": "Create and link records",
12
+ "dateOfBirth": "Date of birth",
13
+ "dateOfBirthInFuture": "Date of birth cannot be in the future",
14
+ "dateOfBirthRequired": "Date of birth is required",
15
+ "dependantAdded": "Dependant added",
16
+ "dependantAddedSubtitle": "PCS issued {{individualId}} for this dependant.",
17
+ "dependantAddFailed": "The dependant could not be added.",
18
+ "dependantAddFailedTitle": "Could not add dependant",
19
+ "dependantInHieNotPcs": "Dependant in HIE and not PCS?",
20
+ "dependantLinkedToPcs": "Dependant added to PCS",
21
+ "dependantLinkToPcsFailed": "The dependant could not be added to PCS.",
22
+ "dependantLinkToPcsFailedTitle": "Could not add dependant to PCS",
23
+ "dependantMustBeUnderOne": "This form is for infants — age cannot exceed one year",
24
+ "dependantNotInHieAndPcs": "Dependant not in HIE and PCS?",
25
+ "dependants": "Dependants",
26
+ "dependantsWithCount_one": "Dependants ({{count}})",
27
+ "dependantsWithCount_other": "Dependants ({{count}})",
28
+ "details": "Details",
29
+ "editFilters": "Edit filters",
30
+ "email": "Email",
31
+ "emrPatient": "EMR patient",
32
+ "errorLoadingDependants": "Error loading dependants",
33
+ "errorLoadingPcsRecords": "Error loading PCS records",
34
+ "familyName": "Family name",
35
+ "familyNameRequired": "Family name is required",
36
+ "female": "Female",
37
+ "givenName": "Given name",
38
+ "givenNameRequired": "Given name is required",
39
+ "hideFilters": "Hide filters",
40
+ "individualId": "Individual ID",
41
+ "linkDependant": "Link dependant",
42
+ "linkDependantToPcs": "Link dependant to PCS",
43
+ "linked": "Linked",
44
+ "linkedAs": "{{patientName}} is linked as {{individualId}}",
45
+ "linkedToPcs": "Linked to PCS",
46
+ "linkingRecords": "Linking records...",
47
+ "linkRecords": "Link records",
48
+ "linkToPcsParticipant": "Link to PCS participant",
49
+ "male": "Male",
50
+ "middleNameOptional": "Middle name (optional)",
51
+ "mother": "Mother",
52
+ "mothersDependantsInHie": "Mother's dependants in the HIE",
53
+ "name": "Name",
54
+ "nationalId": "National ID",
55
+ "no": "No",
56
+ "noDependants": "No dependants",
57
+ "noDependantsSubtitle": "PCS has no participants recorded with this mother.",
58
+ "noFilters": "No filters",
59
+ "noHieDependants": "The HIE record lists no dependants for this mother.",
60
+ "noHieDependantsSubtitle": "Use \"Dependant not in HIE and PCS?\" to register the child from their own details.",
61
+ "noPcsRecordsForFilters": "No participant matched these filters",
62
+ "noPcsRecordsFound": "No PCS records found",
63
+ "notInHie": "Not in the HIE — create from PCS details",
64
+ "participantLinked": "Records linked",
65
+ "participantLinkedSubtitle": "The patient is now linked to PCS participant {{individualId}}.",
66
+ "participantLinkFailed": "Could not link records",
67
+ "participantLinkFailedSubtitle": "The patient record could not be updated.",
68
+ "participantUnlinked": "Records unlinked",
69
+ "participantUnlinkedSubtitle": "The study participant ID and study attributes were removed.",
70
+ "participantUnlinkFailed": "Could not unlink records",
71
+ "participantUnlinkFailedSubtitle": "The patient record could not be updated.",
72
+ "patientWillBeCreated": "This patient is not registered here yet and will be created first.",
73
+ "pbidsEnrolled": "PBIDS enrolled",
74
+ "pbidsEnrollment": "PBIDS enrollment",
75
+ "pcsAddFilterToSearch": "Add a filter to search the PCS registry",
76
+ "pcsFieldCompoundName": "compound head",
77
+ "pcsFieldMotherName": "mother's name",
78
+ "pcsFieldName": "name",
79
+ "pcsFilterRequired": "Enter at least one of name, village or phone",
80
+ "pcsHousehold": "{{village}} village | Head: {{compoundHead}}",
81
+ "pcsMatchContains": "Partial match on {{field}}",
82
+ "pcsMatchExact": "Exact match on {{field}}",
83
+ "pcsMatchSoundex": "Sounds like {{field}}",
84
+ "pcsNameHelper": "Matches the participant, their mother or their compound head",
85
+ "pcsNameTag": "Name: {{value}}",
86
+ "pcsParticipant": "PCS participant",
87
+ "pcsParticipantTabs": "PCS participant",
88
+ "pcsParticipantUnavailable": "Participant could not be loaded",
89
+ "pcsParticipantUnavailableSubtitle": "PCS did not return a record for {{individualId}}.",
90
+ "pcsPhoneTag": "Phone: {{value}}",
91
+ "pcsRecordsFound_one": "PCS record(s) found ({{count}})",
92
+ "pcsRecordsFound_other": "PCS record(s) found ({{count}})",
93
+ "pcsRegistry": "PCS registry",
94
+ "pcsRegistryUnavailable": "PCS registry unavailable",
95
+ "pcsShowingOf": "Showing {{shown}} of {{total}} PCS records",
96
+ "pcsVillagePlaceholder": "Village name",
97
+ "phone": "Phone",
98
+ "refresh": "Refresh",
99
+ "refreshing": "Refreshing...",
100
+ "removeFilter": "Remove {{label}}",
101
+ "resetToPatient": "Reset to patient details",
102
+ "save": "Save",
103
+ "search": "Search",
104
+ "sex": "Sex",
105
+ "sexRequired": "Sex is required",
106
+ "studyAttributesSyncFailed": "Could not sync study attributes",
107
+ "studyAttributesUpdated": "Study attributes updated",
108
+ "studyAttributesUpdatedSubtitle": "Updated {{name}} from PCS: {{fields}}",
109
+ "studyParticipantId": "Study participant ID",
110
+ "unknownDateOfBirth": "Unknown date of birth",
111
+ "unlink": "Unlink",
112
+ "unlinkConfirmation": "Remove the link between this patient and PCS participant {{individualId}}?",
113
+ "unlinkFromPcs": "Unlink from PCS",
114
+ "unlinking": "Unlinking...",
115
+ "unlinkWhatIsRemoved": "The study participant ID and both enrollment status attributes will be removed from the patient record.",
116
+ "village": "Village",
117
+ "willBeWritten": "The following will be written to the patient record",
118
+ "willCreateFromPcsDetails": "A patient will be created as {{name}} · {{sex}}{{dateOfBirth}}{{contact}}.",
119
+ "yes": "Yes"
120
+ }