@kenyaemr/esm-etims-app 5.4.5-pre.1109

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 (321) hide show
  1. package/.turbo/turbo-build.log +6 -0
  2. package/README.md +3 -0
  3. package/dist/1067.js +1 -0
  4. package/dist/1067.js.map +1 -0
  5. package/dist/1072.js +1 -0
  6. package/dist/1072.js.map +1 -0
  7. package/dist/1148.js +1 -0
  8. package/dist/1148.js.map +1 -0
  9. package/dist/1278.js +1 -0
  10. package/dist/1278.js.map +1 -0
  11. package/dist/1442.js +1 -0
  12. package/dist/1442.js.map +1 -0
  13. package/dist/15.js +1 -0
  14. package/dist/15.js.map +1 -0
  15. package/dist/1620.js +1 -0
  16. package/dist/1620.js.map +1 -0
  17. package/dist/1638.js +1 -0
  18. package/dist/1638.js.map +1 -0
  19. package/dist/1789.js +1 -0
  20. package/dist/1789.js.map +1 -0
  21. package/dist/1831.js +1 -0
  22. package/dist/1831.js.map +1 -0
  23. package/dist/1844.js +1 -0
  24. package/dist/1844.js.map +1 -0
  25. package/dist/1946.js +1 -0
  26. package/dist/1946.js.map +1 -0
  27. package/dist/2007.js +1 -0
  28. package/dist/2007.js.map +1 -0
  29. package/dist/2115.js +1 -0
  30. package/dist/2115.js.map +1 -0
  31. package/dist/2131.js +1 -0
  32. package/dist/2131.js.map +1 -0
  33. package/dist/2152.js +1 -0
  34. package/dist/2152.js.map +1 -0
  35. package/dist/2202.js +1 -0
  36. package/dist/2202.js.map +1 -0
  37. package/dist/2394.js +1 -0
  38. package/dist/2394.js.map +1 -0
  39. package/dist/2534.js +1 -0
  40. package/dist/2534.js.map +1 -0
  41. package/dist/2537.js +1 -0
  42. package/dist/2537.js.map +1 -0
  43. package/dist/2556.js +1 -0
  44. package/dist/2556.js.map +1 -0
  45. package/dist/2621.js +1 -0
  46. package/dist/2621.js.map +1 -0
  47. package/dist/2648.js +1 -0
  48. package/dist/2648.js.map +1 -0
  49. package/dist/2795.js +1 -0
  50. package/dist/2795.js.map +1 -0
  51. package/dist/2842.js +1 -0
  52. package/dist/2842.js.map +1 -0
  53. package/dist/2916.js +1 -0
  54. package/dist/2916.js.map +1 -0
  55. package/dist/2924.js +1 -0
  56. package/dist/2924.js.map +1 -0
  57. package/dist/2929.js +1 -0
  58. package/dist/2929.js.map +1 -0
  59. package/dist/2947.js +1 -0
  60. package/dist/2947.js.map +1 -0
  61. package/dist/2971.js +1 -0
  62. package/dist/2971.js.map +1 -0
  63. package/dist/3284.js +1 -0
  64. package/dist/3284.js.map +1 -0
  65. package/dist/3285.js +1 -0
  66. package/dist/3285.js.map +1 -0
  67. package/dist/3335.js +1 -0
  68. package/dist/3335.js.map +1 -0
  69. package/dist/335.js +1 -0
  70. package/dist/335.js.map +1 -0
  71. package/dist/3378.js +1 -0
  72. package/dist/3378.js.map +1 -0
  73. package/dist/338.js +1 -0
  74. package/dist/338.js.map +1 -0
  75. package/dist/3504.js +1 -0
  76. package/dist/3504.js.map +1 -0
  77. package/dist/3544.js +1 -0
  78. package/dist/3544.js.map +1 -0
  79. package/dist/356.js +1 -0
  80. package/dist/356.js.map +1 -0
  81. package/dist/3569.js +1 -0
  82. package/dist/3569.js.map +1 -0
  83. package/dist/3723.js +1 -0
  84. package/dist/3723.js.map +1 -0
  85. package/dist/3745.js +1 -0
  86. package/dist/3745.js.map +1 -0
  87. package/dist/3813.js +1 -0
  88. package/dist/3813.js.map +1 -0
  89. package/dist/393.js +6 -0
  90. package/dist/393.js.map +1 -0
  91. package/dist/3949.js +1 -0
  92. package/dist/3949.js.map +1 -0
  93. package/dist/3953.js +1 -0
  94. package/dist/3953.js.map +1 -0
  95. package/dist/3989.js +1 -0
  96. package/dist/3989.js.map +1 -0
  97. package/dist/4178.js +1 -0
  98. package/dist/4178.js.map +1 -0
  99. package/dist/4303.js +1 -0
  100. package/dist/4303.js.map +1 -0
  101. package/dist/4308.js +1 -0
  102. package/dist/4308.js.map +1 -0
  103. package/dist/4487.js +11 -0
  104. package/dist/4487.js.map +1 -0
  105. package/dist/4588.js +1 -0
  106. package/dist/4588.js.map +1 -0
  107. package/dist/4637.js +1 -0
  108. package/dist/4637.js.map +1 -0
  109. package/dist/466.js +1 -0
  110. package/dist/466.js.map +1 -0
  111. package/dist/4817.js +1 -0
  112. package/dist/4817.js.map +1 -0
  113. package/dist/4818.js +1 -0
  114. package/dist/4818.js.map +1 -0
  115. package/dist/500.js +1 -0
  116. package/dist/500.js.map +1 -0
  117. package/dist/5086.js +1 -0
  118. package/dist/5086.js.map +1 -0
  119. package/dist/5117.js +1 -0
  120. package/dist/5117.js.map +1 -0
  121. package/dist/5159.js +1 -0
  122. package/dist/5159.js.map +1 -0
  123. package/dist/5285.js +1 -0
  124. package/dist/5285.js.map +1 -0
  125. package/dist/5361.js +1 -0
  126. package/dist/5361.js.map +1 -0
  127. package/dist/5366.js +1 -0
  128. package/dist/5366.js.map +1 -0
  129. package/dist/5484.js +1 -0
  130. package/dist/5484.js.map +1 -0
  131. package/dist/5598.js +1 -0
  132. package/dist/5598.js.map +1 -0
  133. package/dist/5676.js +1 -0
  134. package/dist/5676.js.map +1 -0
  135. package/dist/5694.js +1 -0
  136. package/dist/5694.js.map +1 -0
  137. package/dist/5731.js +5 -0
  138. package/dist/5731.js.map +1 -0
  139. package/dist/5743.js +1 -0
  140. package/dist/5743.js.map +1 -0
  141. package/dist/5825.js +1 -0
  142. package/dist/5825.js.map +1 -0
  143. package/dist/5932.js +1 -0
  144. package/dist/5932.js.map +1 -0
  145. package/dist/5941.js +1 -0
  146. package/dist/5941.js.map +1 -0
  147. package/dist/5967.js +1 -0
  148. package/dist/5967.js.map +1 -0
  149. package/dist/6104.js +1 -0
  150. package/dist/6104.js.map +1 -0
  151. package/dist/624.js +1 -0
  152. package/dist/624.js.map +1 -0
  153. package/dist/6259.js +1 -0
  154. package/dist/6259.js.map +1 -0
  155. package/dist/6273.js +1 -0
  156. package/dist/6273.js.map +1 -0
  157. package/dist/6337.js +1 -0
  158. package/dist/6337.js.map +1 -0
  159. package/dist/6427.js +1 -0
  160. package/dist/6427.js.map +1 -0
  161. package/dist/647.js +1 -0
  162. package/dist/647.js.map +1 -0
  163. package/dist/6478.js +7 -0
  164. package/dist/6478.js.map +1 -0
  165. package/dist/6508.js +1 -0
  166. package/dist/6508.js.map +1 -0
  167. package/dist/6567.js +1 -0
  168. package/dist/6567.js.map +1 -0
  169. package/dist/6634.js +1 -0
  170. package/dist/6634.js.map +1 -0
  171. package/dist/6868.js +1 -0
  172. package/dist/6868.js.map +1 -0
  173. package/dist/689.js +1 -0
  174. package/dist/689.js.map +1 -0
  175. package/dist/7022.js +1 -0
  176. package/dist/7022.js.map +1 -0
  177. package/dist/7101.js +1 -0
  178. package/dist/7101.js.map +1 -0
  179. package/dist/7209.js +1 -0
  180. package/dist/7209.js.map +1 -0
  181. package/dist/7293.js +1 -0
  182. package/dist/7293.js.map +1 -0
  183. package/dist/7360.js +1 -0
  184. package/dist/7360.js.map +1 -0
  185. package/dist/7453.js +1 -0
  186. package/dist/7453.js.map +1 -0
  187. package/dist/7478.js +1 -0
  188. package/dist/7478.js.map +1 -0
  189. package/dist/7782.js +1 -0
  190. package/dist/7782.js.map +1 -0
  191. package/dist/7808.js +1 -0
  192. package/dist/7808.js.map +1 -0
  193. package/dist/7876.js +1 -0
  194. package/dist/7876.js.map +1 -0
  195. package/dist/8028.js +1 -0
  196. package/dist/8028.js.map +1 -0
  197. package/dist/8196.js +1 -0
  198. package/dist/8196.js.map +1 -0
  199. package/dist/820.js +1 -0
  200. package/dist/820.js.map +1 -0
  201. package/dist/8240.js +1 -0
  202. package/dist/8240.js.map +1 -0
  203. package/dist/8458.js +1 -0
  204. package/dist/8458.js.map +1 -0
  205. package/dist/8472.js +1 -0
  206. package/dist/8472.js.map +1 -0
  207. package/dist/8602.js +1 -0
  208. package/dist/8602.js.map +1 -0
  209. package/dist/8607.js +1 -0
  210. package/dist/8607.js.map +1 -0
  211. package/dist/8640.js +1 -0
  212. package/dist/8640.js.map +1 -0
  213. package/dist/8762.js +1 -0
  214. package/dist/8762.js.map +1 -0
  215. package/dist/9060.js +1 -0
  216. package/dist/9060.js.map +1 -0
  217. package/dist/9061.js +1 -0
  218. package/dist/9061.js.map +1 -0
  219. package/dist/9087.js +1 -0
  220. package/dist/9087.js.map +1 -0
  221. package/dist/9168.js +1 -0
  222. package/dist/9168.js.map +1 -0
  223. package/dist/9213.js +1 -0
  224. package/dist/9213.js.map +1 -0
  225. package/dist/9286.js +1 -0
  226. package/dist/9286.js.map +1 -0
  227. package/dist/9425.js +1 -0
  228. package/dist/9520.js +1 -0
  229. package/dist/9520.js.map +1 -0
  230. package/dist/9547.js +1 -0
  231. package/dist/9547.js.map +1 -0
  232. package/dist/9612.js +1 -0
  233. package/dist/9612.js.map +1 -0
  234. package/dist/9632.js +1 -0
  235. package/dist/9632.js.map +1 -0
  236. package/dist/9771.js +1 -0
  237. package/dist/9771.js.map +1 -0
  238. package/dist/9774.js +1 -0
  239. package/dist/9774.js.map +1 -0
  240. package/dist/9806.js +1 -0
  241. package/dist/9806.js.map +1 -0
  242. package/dist/9885.js +1 -0
  243. package/dist/9885.js.map +1 -0
  244. package/dist/992.js +1 -0
  245. package/dist/992.js.map +1 -0
  246. package/dist/kenyaemr-esm-etims-app.js +3 -0
  247. package/dist/kenyaemr-esm-etims-app.js.buildmanifest.json +3354 -0
  248. package/dist/kenyaemr-esm-etims-app.js.map +1 -0
  249. package/dist/main.js +3 -0
  250. package/dist/main.js.map +1 -0
  251. package/dist/routes.json +1 -0
  252. package/package.json +48 -0
  253. package/rspack.config.js +1 -0
  254. package/src/components/header/header.component.tsx +22 -0
  255. package/src/components/header/header.scss +6 -0
  256. package/src/components/side-menu/left-panel.component.tsx +22 -0
  257. package/src/components/side-menu/left-panel.scss +34 -0
  258. package/src/components/user-menu-link/etims-nav-link.scss +29 -0
  259. package/src/components/user-menu-link/user-menu-link.component.tsx +25 -0
  260. package/src/config-schema.ts +1 -0
  261. package/src/credit-note/etims-credit-note-dashboard.component.tsx +23 -0
  262. package/src/credit-note/etims-credit-note-dashboard.scss +8 -0
  263. package/src/credit-note/etims-credit-note-table.component.tsx +284 -0
  264. package/src/credit-note/etims-credit-note-table.scss +16 -0
  265. package/src/credit-note/etims-credit-note.resource.ts +96 -0
  266. package/src/credit-note/etims-credit-note.types.ts +56 -0
  267. package/src/credit-note/etims-credit-note.workspace.scss +76 -0
  268. package/src/credit-note/etims-credit-note.workspace.tsx +267 -0
  269. package/src/declarations.d.ts +2 -0
  270. package/src/global-properties/etims-global-properties-dashboard.component.tsx +141 -0
  271. package/src/global-properties/etims-global-properties-dashboard.scss +62 -0
  272. package/src/global-properties/etims-global-properties-form.workspace.scss +74 -0
  273. package/src/global-properties/etims-global-properties-form.workspace.tsx +244 -0
  274. package/src/global-properties/etims-global-properties.resource.ts +60 -0
  275. package/src/index.ts +100 -0
  276. package/src/item-mapping/etims-item-mapping-dashboard.component.tsx +29 -0
  277. package/src/item-mapping/etims-item-mapping-dashboard.scss +42 -0
  278. package/src/item-mapping/etims-item-mapping-form.fields.tsx +136 -0
  279. package/src/item-mapping/etims-item-mapping-form.schema.ts +35 -0
  280. package/src/item-mapping/etims-item-mapping-form.workspace.scss +54 -0
  281. package/src/item-mapping/etims-item-mapping-form.workspace.tsx +555 -0
  282. package/src/item-mapping/etims-item-mapping-table.component.tsx +398 -0
  283. package/src/item-mapping/etims-item-mapping-table.scss +20 -0
  284. package/src/item-mapping/etims-item-mapping.resource.ts +318 -0
  285. package/src/item-mapping/etims-item-mapping.types.ts +92 -0
  286. package/src/item-mapping/register-item-mapping.modal.tsx +113 -0
  287. package/src/item-mapping/void-item-mapping.modal.tsx +80 -0
  288. package/src/left-panel-link.component.tsx +39 -0
  289. package/src/left-panel-link.scss +12 -0
  290. package/src/reference-data/etims-catalog-table.component.tsx +178 -0
  291. package/src/reference-data/etims-catalog-table.scss +16 -0
  292. package/src/reference-data/etims-code-table.component.tsx +87 -0
  293. package/src/reference-data/etims-item-class-table.component.tsx +86 -0
  294. package/src/reference-data/etims-reference-data-dashboard.component.tsx +38 -0
  295. package/src/reference-data/etims-reference-data-dashboard.scss +11 -0
  296. package/src/reference-data/etims-reference-data.resource.ts +40 -0
  297. package/src/reference-data/etims-reference-data.types.ts +38 -0
  298. package/src/reference-data/etims-sync-meta.modal.tsx +62 -0
  299. package/src/root.component.tsx +38 -0
  300. package/src/root.scss +11 -0
  301. package/src/routes.json +117 -0
  302. package/src/status/etims-status-dashboard.component.tsx +212 -0
  303. package/src/status/etims-status-dashboard.scss +116 -0
  304. package/src/status/etims-status.resource.ts +20 -0
  305. package/src/status/etims-status.types.ts +30 -0
  306. package/src/transaction/confirm-transaction-action.modal.tsx +113 -0
  307. package/src/transaction/etims-transaction-dashboard.component.tsx +23 -0
  308. package/src/transaction/etims-transaction-dashboard.scss +8 -0
  309. package/src/transaction/etims-transaction-details.workspace.scss +76 -0
  310. package/src/transaction/etims-transaction-details.workspace.tsx +201 -0
  311. package/src/transaction/etims-transaction-table.component.tsx +450 -0
  312. package/src/transaction/etims-transaction-table.scss +20 -0
  313. package/src/transaction/etims-transaction.resource.ts +180 -0
  314. package/src/transaction/etims-transaction.types.ts +78 -0
  315. package/src/transaction/override-reconciliation.modal.scss +7 -0
  316. package/src/transaction/override-reconciliation.modal.tsx +203 -0
  317. package/translations/am.json +129 -0
  318. package/translations/en.json +129 -0
  319. package/translations/fr.json +130 -0
  320. package/translations/sw.json +129 -0
  321. package/tsconfig.json +5 -0
@@ -0,0 +1,398 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ Button,
4
+ DataTable,
5
+ DataTableSkeleton,
6
+ Dropdown,
7
+ OverflowMenu,
8
+ OverflowMenuItem,
9
+ Pagination,
10
+ Table,
11
+ TableBatchAction,
12
+ TableBatchActions,
13
+ TableBody,
14
+ TableCell,
15
+ TableContainer,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow,
19
+ TableSelectAll,
20
+ TableSelectRow,
21
+ TableToolbar,
22
+ TableToolbarContent,
23
+ TableToolbarSearch,
24
+ Tag,
25
+ type DataTableHeader,
26
+ } from '@carbon/react';
27
+ import { Add, Send } from '@carbon/react/icons';
28
+ import {
29
+ ErrorCard,
30
+ formatDatetime,
31
+ launchWorkspace2,
32
+ parseDate,
33
+ showModal,
34
+ useDebounce,
35
+ usePaginationInfo,
36
+ UserHasAccess,
37
+ } from '@openmrs/esm-framework';
38
+ import { useTranslation } from 'react-i18next';
39
+ import { useEtimsItemMappings } from './etims-item-mapping.resource';
40
+ import type { EtimsItemMapping, EtimsRegistrationStatus } from './etims-item-mapping.types';
41
+ import styles from './etims-item-mapping-table.scss';
42
+
43
+ const DEFAULT_PAGE_SIZE = 10;
44
+ const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
45
+
46
+ type StatusFilter = 'ALL' | EtimsRegistrationStatus;
47
+
48
+ type MappingRow = {
49
+ id: string;
50
+ itemName: string;
51
+ itemCode: string;
52
+ source: string;
53
+ itemClassCode: string;
54
+ taxType: string;
55
+ itemType: string;
56
+ registrationStatus: string;
57
+ resultMessage: string;
58
+ registeredAt: string;
59
+ };
60
+
61
+ const displayValue = (value?: string | number | boolean | null) => {
62
+ if (value === null || value === undefined || value === '') {
63
+ return '--';
64
+ }
65
+
66
+ return String(value);
67
+ };
68
+
69
+ const formatNullableDatetime = (value?: string | null) => {
70
+ if (!value) {
71
+ return '--';
72
+ }
73
+
74
+ try {
75
+ return formatDatetime(parseDate(value), { mode: 'standard', noToday: true });
76
+ } catch {
77
+ return value;
78
+ }
79
+ };
80
+
81
+ const statusTagType = (status?: string | null): 'green' | 'red' | 'blue' | 'gray' => {
82
+ if (status === 'REGISTERED') {
83
+ return 'green';
84
+ }
85
+ if (status === 'FAILED') {
86
+ return 'red';
87
+ }
88
+ if (status === 'PENDING') {
89
+ return 'blue';
90
+ }
91
+ return 'gray';
92
+ };
93
+
94
+ const sourceLabel = (mapping: EtimsItemMapping, t: ReturnType<typeof useTranslation>['t']) => {
95
+ if (mapping.stockItemId) {
96
+ return t('stockItemSource', 'Stock #{{id}}', { id: mapping.stockItemId });
97
+ }
98
+ if (mapping.billableServiceId) {
99
+ return t('billableServiceSource', 'Service #{{id}}', { id: mapping.billableServiceId });
100
+ }
101
+ return '--';
102
+ };
103
+
104
+ const EtimsItemMappingTable: React.FC = () => {
105
+ const { t } = useTranslation();
106
+ const [page, setPage] = useState(1);
107
+ const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
108
+ const [searchTerm, setSearchTerm] = useState('');
109
+ const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');
110
+ const debouncedSearchTerm = useDebounce(searchTerm, 300);
111
+ const { mappings, error, isLoading, mutate } = useEtimsItemMappings();
112
+
113
+ const statusItems = useMemo(
114
+ () => [
115
+ { id: 'ALL', label: t('allStatuses', 'All statuses') },
116
+ { id: 'PENDING', label: t('pending', 'Pending') },
117
+ { id: 'REGISTERED', label: t('registered', 'Registered') },
118
+ { id: 'FAILED', label: t('failed', 'Failed') },
119
+ ],
120
+ [t],
121
+ );
122
+
123
+ const filteredMappings = useMemo(() => {
124
+ const query = debouncedSearchTerm.trim().toLowerCase();
125
+
126
+ return mappings.filter((mapping) => {
127
+ if (statusFilter !== 'ALL' && mapping.registrationStatus !== statusFilter) {
128
+ return false;
129
+ }
130
+
131
+ if (!query) {
132
+ return true;
133
+ }
134
+
135
+ return [
136
+ mapping.itemName,
137
+ mapping.itemCode,
138
+ mapping.itemClassCode,
139
+ mapping.taxType,
140
+ mapping.itemType,
141
+ mapping.registrationResultMessage,
142
+ mapping.billableServiceId,
143
+ mapping.stockItemId,
144
+ ].some((value) => displayValue(value).toLowerCase().includes(query));
145
+ });
146
+ }, [debouncedSearchTerm, mappings, statusFilter]);
147
+
148
+ const pagedMappings = useMemo(() => {
149
+ const start = (page - 1) * pageSize;
150
+ return filteredMappings.slice(start, start + pageSize);
151
+ }, [filteredMappings, page, pageSize]);
152
+
153
+ const { pageSizes } = usePaginationInfo(DEFAULT_PAGE_SIZE, filteredMappings.length, page, pagedMappings.length);
154
+
155
+ const headers: Array<DataTableHeader> = useMemo(
156
+ () => [
157
+ { key: 'itemName', header: t('itemName', 'Item name') },
158
+ { key: 'itemCode', header: t('itemCode', 'Item code') },
159
+ { key: 'source', header: t('source', 'Source') },
160
+ { key: 'itemClassCode', header: t('itemClassCode', 'Item class') },
161
+ { key: 'taxType', header: t('taxType', 'Tax type') },
162
+ { key: 'itemType', header: t('itemType', 'Item type') },
163
+ { key: 'registrationStatus', header: t('registrationStatus', 'Registration') },
164
+ { key: 'resultMessage', header: t('resultMessage', 'Last result') },
165
+ { key: 'registeredAt', header: t('registeredAt', 'Registered at') },
166
+ ],
167
+ [t],
168
+ );
169
+
170
+ const tableRows: Array<MappingRow> = useMemo(
171
+ () =>
172
+ pagedMappings.map((mapping, index) => ({
173
+ id: mapping.uuid ?? `mapping-${index}`,
174
+ itemName: displayValue(mapping.itemName),
175
+ itemCode: displayValue(mapping.itemCode),
176
+ source: sourceLabel(mapping, t),
177
+ itemClassCode: displayValue(mapping.itemClassCode),
178
+ taxType: displayValue(mapping.taxType),
179
+ itemType: displayValue(mapping.itemType),
180
+ registrationStatus: displayValue(mapping.registrationStatus),
181
+ resultMessage: displayValue(mapping.registrationResultMessage),
182
+ registeredAt: formatNullableDatetime(mapping.registeredAt),
183
+ })),
184
+ [pagedMappings, t],
185
+ );
186
+
187
+ const openForm = useCallback(
188
+ (mapping?: EtimsItemMapping) => {
189
+ launchWorkspace2('etims-item-mapping-form', {
190
+ mapping,
191
+ mutateMappings: mutate,
192
+ });
193
+ },
194
+ [mutate],
195
+ );
196
+
197
+ const openRegisterModal = useCallback(
198
+ (scope: 'one' | 'selected' | 'all', options?: { uuid?: string; mappingUuids?: string[]; itemName?: string }) => {
199
+ const dispose = showModal('etims-register-item-mapping-modal', {
200
+ close: () => dispose(),
201
+ scope,
202
+ uuid: options?.uuid,
203
+ mappingUuids: options?.mappingUuids,
204
+ itemName: options?.itemName,
205
+ onRegistered: () => mutate(),
206
+ });
207
+ },
208
+ [mutate],
209
+ );
210
+
211
+ const openVoidModal = useCallback(
212
+ (mapping: EtimsItemMapping) => {
213
+ const dispose = showModal('etims-void-item-mapping-modal', {
214
+ close: () => dispose(),
215
+ uuid: mapping.uuid,
216
+ itemName: mapping.itemName ?? mapping.itemCode ?? mapping.uuid,
217
+ onVoided: () => mutate(),
218
+ });
219
+ },
220
+ [mutate],
221
+ );
222
+
223
+ const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement> | '', value?: string) => {
224
+ setSearchTerm(typeof event === 'string' ? value ?? '' : event.target.value);
225
+ setPage(1);
226
+ }, []);
227
+
228
+ const handlePaginationChange = useCallback(
229
+ ({ page: nextPage, pageSize: nextSize }: { page: number; pageSize: number }) => {
230
+ if (nextSize !== pageSize) {
231
+ setPageSize(nextSize);
232
+ }
233
+ setPage(nextPage);
234
+ },
235
+ [pageSize],
236
+ );
237
+
238
+ if (isLoading) {
239
+ return (
240
+ <div className={styles.container}>
241
+ <DataTableSkeleton headers={headers} rowCount={5} showHeader showToolbar />
242
+ </div>
243
+ );
244
+ }
245
+
246
+ if (error) {
247
+ return (
248
+ <div className={styles.dataTableContainer}>
249
+ <ErrorCard error={error} headerTitle={t('itemMappingsError', 'Item mappings')} />
250
+ </div>
251
+ );
252
+ }
253
+
254
+ return (
255
+ <div className={styles.container}>
256
+ <DataTable rows={tableRows} headers={headers} size="sm" useZebraStyles isSortable>
257
+ {({
258
+ rows,
259
+ headers: tableHeaders,
260
+ getHeaderProps,
261
+ getRowProps,
262
+ getSelectionProps,
263
+ getToolbarProps,
264
+ getBatchActionProps,
265
+ getTableProps,
266
+ getTableContainerProps,
267
+ selectedRows,
268
+ }) => {
269
+ const batchActionProps = getBatchActionProps();
270
+
271
+ return (
272
+ <TableContainer
273
+ title={t('itemMappings', 'Item mappings')}
274
+ description={t(
275
+ 'itemMappingsDescription',
276
+ 'Map cashier billable services and stock items to KRA item codes, then sync them to eTIMS.',
277
+ )}
278
+ {...getTableContainerProps()}>
279
+ <TableToolbar {...getToolbarProps()}>
280
+ <TableBatchActions {...batchActionProps}>
281
+ <UserHasAccess privilege="Register eTIMS Items">
282
+ <TableBatchAction
283
+ renderIcon={Send}
284
+ onClick={() =>
285
+ openRegisterModal('selected', { mappingUuids: selectedRows.map((row) => row.id) })
286
+ }>
287
+ {t('syncSelectedToKra', 'Sync selected to KRA')}
288
+ </TableBatchAction>
289
+ </UserHasAccess>
290
+ </TableBatchActions>
291
+ <TableToolbarContent aria-hidden={batchActionProps.shouldShowBatchActions}>
292
+ <TableToolbarSearch
293
+ persistent
294
+ onChange={handleSearchChange}
295
+ placeholder={t('searchItemMappings', 'Search mappings')}
296
+ />
297
+ <Dropdown
298
+ size="sm"
299
+ id="registration-status-filter"
300
+ className={styles.statusFilter}
301
+ label={t('registrationStatus', 'Registration')}
302
+ titleText=""
303
+ items={statusItems}
304
+ itemToString={(item) => item?.label ?? ''}
305
+ selectedItem={statusItems.find((item) => item.id === statusFilter)}
306
+ onChange={({ selectedItem }) => {
307
+ setStatusFilter((selectedItem?.id as StatusFilter) ?? 'ALL');
308
+ setPage(1);
309
+ }}
310
+ />
311
+ <UserHasAccess privilege="Register eTIMS Items">
312
+ <Button kind="tertiary" size="sm" renderIcon={Send} onClick={() => openRegisterModal('all')}>
313
+ {t('syncAllToKra', 'Sync all to KRA')}
314
+ </Button>
315
+ </UserHasAccess>
316
+ <Button kind="primary" size="sm" renderIcon={Add} onClick={() => openForm()}>
317
+ {t('addItemMapping', 'Add item mapping')}
318
+ </Button>
319
+ </TableToolbarContent>
320
+ </TableToolbar>
321
+
322
+ <Table {...getTableProps()} aria-label={t('itemMappings', 'Item mappings')}>
323
+ <TableHead>
324
+ <TableRow>
325
+ <TableSelectAll {...getSelectionProps()} />
326
+ {tableHeaders.map((header) => (
327
+ <TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
328
+ ))}
329
+ <TableHeader aria-label={t('rowActions', 'Row actions')} />
330
+ </TableRow>
331
+ </TableHead>
332
+ <TableBody>
333
+ {rows.map((row, index) => {
334
+ const mapping = pagedMappings[index];
335
+
336
+ return (
337
+ <TableRow {...getRowProps({ row })}>
338
+ <TableSelectRow {...getSelectionProps({ row })} />
339
+ {row.cells.map((cell) => {
340
+ if (cell.info.header === 'registrationStatus') {
341
+ return (
342
+ <TableCell key={cell.id}>
343
+ <Tag type={statusTagType(cell.value)} size="sm">
344
+ {cell.value}
345
+ </Tag>
346
+ </TableCell>
347
+ );
348
+ }
349
+
350
+ return <TableCell key={cell.id}>{cell.value}</TableCell>;
351
+ })}
352
+ <TableCell className="cds--table-column-menu">
353
+ <OverflowMenu flipped size="sm" aria-label={t('rowActions', 'Row actions')}>
354
+ <OverflowMenuItem itemText={t('edit', 'Edit')} onClick={() => openForm(mapping)} />
355
+ <UserHasAccess privilege="Register eTIMS Items">
356
+ <OverflowMenuItem
357
+ itemText={t('syncToKra', 'Sync to KRA')}
358
+ onClick={() =>
359
+ openRegisterModal('one', {
360
+ uuid: mapping.uuid,
361
+ itemName: mapping.itemName ?? mapping.itemCode ?? mapping.uuid,
362
+ })
363
+ }
364
+ />
365
+ </UserHasAccess>
366
+ <OverflowMenuItem
367
+ isDelete
368
+ itemText={t('void', 'Void')}
369
+ onClick={() => openVoidModal(mapping)}
370
+ />
371
+ </OverflowMenu>
372
+ </TableCell>
373
+ </TableRow>
374
+ );
375
+ })}
376
+ </TableBody>
377
+ </Table>
378
+ </TableContainer>
379
+ );
380
+ }}
381
+ </DataTable>
382
+ <Pagination
383
+ itemsPerPageText={t('itemsPerPage', 'Items per page:')}
384
+ forwardText={t('nextPage', 'Next page')}
385
+ backwardText={t('previousPage', 'Previous page')}
386
+ itemRangeText={(min, max, total) => t('minMaxItems', '{{min}}-{{max}} of {{total}} items', { min, max, total })}
387
+ pageRangeText={(_current, total) => t('pageRangeText', 'of {{count}} pages', { count: total })}
388
+ page={page}
389
+ pageSize={pageSize}
390
+ pageSizes={pageSizes?.length > 0 ? pageSizes : PAGE_SIZE_OPTIONS}
391
+ totalItems={filteredMappings.length}
392
+ onChange={handlePaginationChange}
393
+ />
394
+ </div>
395
+ );
396
+ };
397
+
398
+ export default EtimsItemMappingTable;
@@ -0,0 +1,20 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/colors';
3
+
4
+ .dataTableContainer {
5
+ height: 100%;
6
+ margin: layout.$layout-01;
7
+ }
8
+
9
+ .container {
10
+ margin: 0;
11
+ border: 1px solid colors.$gray-20;
12
+
13
+ :global(.cds--table-toolbar) {
14
+ position: relative !important;
15
+ }
16
+ }
17
+
18
+ .statusFilter {
19
+ min-width: 12rem;
20
+ }