@cc-openmrs/cc-esm-active-prescriptions 1.0.64 → 1.0.67

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.
package/package.json CHANGED
@@ -1,86 +1,85 @@
1
1
  {
2
2
  "name": "@cc-openmrs/cc-esm-active-prescriptions",
3
- "version": "1.0.64",
3
+ "version": "1.0.67",
4
4
  "license": "MPL-2.0",
5
- "description": "An OpenMRS seed application for building microfrontends",
6
- "browser": "dist/openmrs-esm-template-app.js",
5
+ "description": "An OpenMRS frontend module for managing patient lists in the Patient Chart",
6
+ "browser": "dist/openmrs-esm-patient-lists-app.js",
7
7
  "main": "src/index.ts",
8
8
  "source": true,
9
9
  "scripts": {
10
10
  "start": "openmrs develop",
11
11
  "serve": "webpack serve --mode=development",
12
- "build": "webpack --mode production",
12
+ "build": "webpack --mode production --color",
13
13
  "analyze": "webpack --mode=production --env analyze=true",
14
- "lint": "eslint src --ext js,jsx,ts,tsx --max-warnings 0",
15
- "prettier": "prettier --write \"src/**/*.{ts,tsx}\" --list-different",
14
+ "lint": "cross-env eslint src --ext js,jsx,ts,tsx --fix --max-warnings=0",
15
+ "prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
16
16
  "typescript": "tsc",
17
- "test": "jest --config jest.config.js --passWithNoTests",
18
- "verify": "turbo lint typescript coverage",
17
+ "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests --color",
18
+ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
19
19
  "coverage": "yarn test --coverage",
20
- "prepare": "husky install",
21
- "extract-translations": "i18next 'src/**/*.component.tsx' --config ./tools/i18next-parser.config.js",
22
- "test-e2e": "playwright test"
20
+ "extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.modal.tsx' 'src/**/*.extension.tsx' 'src/**/*.workspace.tsx' 'src/**/*.hook.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
23
21
  },
24
22
  "browserslist": [
25
23
  "extends browserslist-config-openmrs"
26
24
  ],
27
25
  "keywords": [
28
- "openmrs",
29
- "microfrontends"
26
+ "openmrs"
30
27
  ],
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/openmrs/openmrs-esm-template-app.git"
34
- },
35
- "homepage": "https://github.com/openmrs/openmrs-esm-template-app#readme",
28
+ "homepage": "https://github.com/openmrs/openmrs-esm-patient-chart#readme",
36
29
  "publishConfig": {
37
30
  "access": "public"
38
31
  },
39
- "bugs": {
40
- "url": "https://github.com/openmrs/openmrs-esm-template-app/issues"
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/openmrs/openmrs-esm-patient-chart.git"
41
35
  },
42
- "openmrs": {
43
- "frontendModuleName": "@cc-openmrs/cc-esm-active-prescriptions",
44
- "routes": "src/routes.json"
36
+ "bugs": {
37
+ "url": "https://github.com/openmrs/openmrs-esm-patient-chart/issues"
45
38
  },
46
39
  "dependencies": {
47
- "@carbon/react": "^1.83.0",
48
- "lodash-es": "^4.17.21"
40
+ "@carbon/react": "^1.83.0"
49
41
  },
50
42
  "peerDependencies": {
51
43
  "@openmrs/esm-framework": "9.x",
44
+ "@openmrs/esm-patient-common-lib": "12.x",
52
45
  "dayjs": "1.x",
53
46
  "react": "18.x",
54
47
  "react-i18next": "16.x",
55
48
  "react-router-dom": "6.x",
56
- "rxjs": "6.x"
49
+ "rxjs": "6.x",
50
+ "swr": "2.x"
57
51
  },
58
52
  "devDependencies": {
59
53
  "@openmrs/esm-framework": "next",
60
- "@openmrs/esm-styleguide": "next",
61
- "@playwright/test": "^1.52.0",
62
- "@swc/cli": "^0.3.12",
63
- "@swc/core": "^1.3.68",
64
- "@swc/jest": "^0.2.36",
54
+ "@playwright/test": "^1.51.1",
55
+ "@swc/cli": "^0.1.62",
56
+ "@swc/core": "^1.3.89",
57
+ "@swc/jest": "^0.2.29",
65
58
  "@testing-library/dom": "^10.1.0",
66
59
  "@testing-library/jest-dom": "^6.4.5",
67
- "@testing-library/react": "^15.0.6",
60
+ "@testing-library/react": "^15.0.7",
68
61
  "@testing-library/user-event": "^14.5.2",
69
- "@types/jest": "^29.5.12",
70
- "@types/react": "^18.3.21",
62
+ "@types/fhir": "^0.0.31",
63
+ "@types/jest": "^29.5.11",
64
+ "@types/lodash-es": "^4.17.9",
65
+ "@types/react": "^18.3.2",
71
66
  "@types/react-dom": "^18.3.0",
72
- "@types/react-router": "^5.1.20",
73
- "@types/react-router-dom": "^5.3.3",
74
- "@types/webpack-env": "^1.18.1",
75
- "@typescript-eslint/eslint-plugin": "^7.8.0",
76
- "@typescript-eslint/parser": "^7.8.0",
77
- "dayjs": "^1.11.13",
78
- "dotenv": "^16.0.3",
79
- "eslint": "^8.50.0",
80
- "eslint-config-prettier": "^8.8.0",
67
+ "@types/webpack-env": "^1.18.8",
68
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
69
+ "@typescript-eslint/parser": "^8.0.0",
70
+ "babel-preset-minify": "^0.5.2",
71
+ "concurrently": "^8.2.1",
72
+ "cross-env": "^7.0.3",
73
+ "css-loader": "^6.6.0",
74
+ "d3-selection": "^3.0.0",
75
+ "dayjs": "^1.11.10",
76
+ "dotenv": "^16.3.1",
77
+ "eslint": "^8.57.0",
78
+ "eslint-plugin-import": "^2.31.0",
81
79
  "eslint-plugin-jest-dom": "^5.4.0",
82
- "eslint-plugin-prettier": "^5.1.3",
83
- "eslint-plugin-react-hooks": "^4.6.2",
80
+ "eslint-plugin-playwright": "^0.16.0",
81
+ "eslint-plugin-react-hooks": "^4.6.0",
82
+ "eslint-plugin-testing-library": "^6.2.2",
84
83
  "husky": "^8.0.3",
85
84
  "i18next": "^25.0.0",
86
85
  "i18next-parser": "^9.3.0",
@@ -88,20 +87,19 @@
88
87
  "jest": "^29.7.0",
89
88
  "jest-cli": "^29.7.0",
90
89
  "jest-environment-jsdom": "^29.7.0",
91
- "lint-staged": "^15.2.2",
90
+ "lint-staged": "^14.0.1",
91
+ "lodash": "^4.17.23",
92
92
  "openmrs": "next",
93
- "prettier": "^3.3.3",
93
+ "prettier": "^3.0.3",
94
94
  "react": "^18.3.1",
95
95
  "react-dom": "^18.3.1",
96
96
  "react-i18next": "^16.0.0",
97
- "react-router-dom": "^6.14.1",
97
+ "react-router-dom": "^6.16.0",
98
98
  "rxjs": "^6.6.7",
99
+ "sass": "^1.54.3",
100
+ "swc-loader": "^0.2.3",
101
+ "swr": "2.2.5",
99
102
  "turbo": "^2.5.2",
100
103
  "typescript": "^5.0.0"
101
- },
102
- "lint-staged": {
103
- "packages/**/src/**/*.{ts,tsx}": "eslint --cache --fix --max-warnings 0",
104
- "*.{css,scss,ts,tsx}": "prettier --write --list-different"
105
- },
106
- "packageManager": "yarn@4.10.3"
104
+ }
107
105
  }
@@ -1,5 +1,4 @@
1
- declare module '*.css';
2
- declare module '*.scss';
3
- declare module '*.png';
4
-
5
- declare type SideNavProps = object;
1
+ declare module '*.scss' {
2
+ const content: { [className: string]: string };
3
+ export default content;
4
+ }
@@ -1,22 +1,18 @@
1
- import React, { useCallback } from 'react';
2
- import { launchWorkspace } from '@openmrs/esm-framework';
3
- import { Events } from '@carbon/react/icons';
4
- import { ActionMenuButton } from '@openmrs/esm-styleguide';
1
+ import React, { type ComponentProps } from 'react';
2
+ import { ActionMenuButton2, EventsIcon } from '@openmrs/esm-framework';
5
3
  import { useTranslation } from 'react-i18next';
6
4
 
7
- const handleClick = () => launchWorkspace('active-prescriptions');
8
-
9
5
  const PrescriptionsActionButton: React.FC = () => {
10
6
  const { t } = useTranslation();
11
7
  const label = t('activePrescriptions', 'Active prescriptions');
12
8
 
13
9
  return (
14
- <ActionMenuButton
15
- getIcon={(props) => <Events {...props} />}
10
+ <ActionMenuButton2
11
+ icon={(props: ComponentProps<typeof EventsIcon>) => <EventsIcon {...props} />}
16
12
  label={label}
17
- iconDescription={label}
18
- handler={handleClick}
19
- type="active-prescriptions"
13
+ workspaceToLaunch={{
14
+ workspaceName: 'active-prescriptions-workspace',
15
+ }}
20
16
  />
21
17
  );
22
18
  };
@@ -11,7 +11,7 @@
11
11
  import React, { useMemo, useState } from 'react';
12
12
  import { PrescriptionService, PrescriptionPayload } from './prescriptions.service';
13
13
  import useSWR from 'swr';
14
- import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
14
+ import { openmrsFetch, restBaseUrl, Workspace2 } from '@openmrs/esm-framework';
15
15
  import {
16
16
  Checkbox,
17
17
  ComboBox,
@@ -240,127 +240,127 @@ const Root: React.FC<RootProps> = ({ patientUuid, patient }) => {
240
240
  };
241
241
 
242
242
  return (
243
- <div className={styles.workspace}>
244
- <div className={styles.header}>
245
- <h2>{t('activePrescriptionsWorkspaceTitle', 'Prescrições ativas')}</h2>
246
- <p className={styles.subtitle}>
247
- {t(
248
- 'activePrescriptionsHelper',
249
- 'Revise o contexto do paciente, escolha os detalhes da visita e adicione os medicamentos à prescrição.',
250
- )}
251
- </p>
252
- </div>
243
+ <Workspace2 title={t('activePrescriptionsWorkspaceTitle', 'Prescrições ativas')} hasUnsavedChanges={false}>
244
+ <div className={styles.workspace}>
245
+ <div className={styles.header}>
246
+ <h2>{t('activePrescriptionsWorkspaceTitle', 'Prescrições ativas')}</h2>
247
+ <p className={styles.subtitle}>
248
+ {t(
249
+ 'activePrescriptionsHelper',
250
+ 'Revise o contexto do paciente, escolha os detalhes da visita e adicione os medicamentos à prescrição.',
251
+ )}
252
+ </p>
253
+ </div>
253
254
 
254
- <section className={styles.section}>
255
- <p className={styles.fieldLabel}>{t('visitTimingLabel', 'A visita é')}</p>
256
- <Tabs
257
- selectedIndex={activeTab === 'new' ? 0 : 1}
258
- onChange={({ selectedIndex }) => setActiveTab(selectedIndex === 0 ? 'new' : 'past')}
259
- >
260
- <TabList aria-label={t('visitTimingLabel', 'A visita é')}>
261
- <Tab>{t('visitTimingNew', 'Nova')}</Tab>
262
- <Tab>{t('visitTimingPast', 'No passado')}</Tab>
263
- </TabList>
264
- <TabPanels>
265
- <TabPanel className={styles.tabContent}>
266
- <div className={styles.fieldGroup}>
267
- <ComboBox
268
- id="visit-location"
269
- items={locationItems}
270
- itemToString={(item) => item?.display ?? ''}
271
- selectedItem={selectedLocation}
272
- onChange={({ selectedItem }) => setSelectedLocation((selectedItem as Location) ?? null)}
273
- placeholder={t('visitLocationPlaceholder', 'Selecione o local')}
274
- titleText={t('visitLocationHeading', 'Local da visita')}
275
- helperText={t('visitLocationHelper', 'Selecione onde a visita ocorrerá.')}
276
- />
277
- </div>
255
+ <section className={styles.section}>
256
+ <p className={styles.fieldLabel}>{t('visitTimingLabel', 'A visita é')}</p>
257
+ <Tabs
258
+ selectedIndex={activeTab === 'new' ? 0 : 1}
259
+ onChange={({ selectedIndex }) => setActiveTab(selectedIndex === 0 ? 'new' : 'past')}
260
+ >
261
+ <TabList aria-label={t('visitTimingLabel', 'A visita é')}>
262
+ <Tab>{t('visitTimingNew', 'Nova')}</Tab>
263
+ <Tab>{t('visitTimingPast', 'No passado')}</Tab>
264
+ </TabList>
265
+ <TabPanels>
266
+ <TabPanel className={styles.tabContent}>
267
+ <div className={styles.fieldGroup}>
268
+ <ComboBox
269
+ id="visit-location"
270
+ items={locationItems}
271
+ itemToString={(item) => item?.display ?? ''}
272
+ selectedItem={selectedLocation}
273
+ onChange={({ selectedItem }) => setSelectedLocation((selectedItem as Location) ?? null)}
274
+ placeholder={t('visitLocationPlaceholder', 'Selecione o local')}
275
+ titleText={t('visitLocationHeading', 'Local da visita')}
276
+ helperText={t('visitLocationHelper', 'Selecione onde a visita ocorrerá.')}
277
+ />
278
+ </div>
278
279
 
279
- <div className={styles.fieldGroup}>
280
- <TextInput
281
- id="insurance-policy"
282
- labelText={t('insurancePolicyNumberLabel', 'Número da apólice (opcional)')}
283
- placeholder={t('insurancePolicyNumberPlaceholder', 'Digite o número da apólice')}
284
- value={policyNumber}
285
- onChange={(event) => setPolicyNumber(event.target.value)}
286
- />
287
- </div>
288
- <div className={styles.fieldGroup}>
289
- <Checkbox
290
- id="sign-prescription-checkbox"
291
- labelText={t('signPrescriptionLabel', 'Assinar digitalmente a prescrição')}
292
- checked={digitallySigned}
293
- onChange={() => setDigitallySigned((prev) => !prev)}
294
- />
295
- <Checkbox
296
- id="print-prescription-checkbox"
297
- labelText={t('printPrescriptionLabel', 'Imprimir a prescrição')}
298
- checked={printRequested}
299
- onChange={() => setPrintRequested((prev) => !prev)}
300
- />
301
- </div>
280
+ <div className={styles.fieldGroup}>
281
+ <TextInput
282
+ id="insurance-policy"
283
+ labelText={t('insurancePolicyNumberLabel', 'Número da apólice (opcional)')}
284
+ placeholder={t('insurancePolicyNumberPlaceholder', 'Digite o número da apólice')}
285
+ value={policyNumber}
286
+ onChange={(event) => setPolicyNumber(event.target.value)}
287
+ />
288
+ </div>
289
+ <div className={styles.fieldGroup}>
290
+ <Checkbox
291
+ id="sign-prescription-checkbox"
292
+ labelText={t('signPrescriptionLabel', 'Assinar digitalmente a prescrição')}
293
+ checked={digitallySigned}
294
+ onChange={() => setDigitallySigned((prev) => !prev)}
295
+ />
296
+ <Checkbox
297
+ id="print-prescription-checkbox"
298
+ labelText={t('printPrescriptionLabel', 'Imprimir a prescrição')}
299
+ checked={printRequested}
300
+ onChange={() => setPrintRequested((prev) => !prev)}
301
+ />
302
+ </div>
302
303
 
303
- {/* Lista de medicamentos adicionados */}
304
- <div className={styles.fieldGroup}>
305
- <h3>{t('medicationsList', 'Medicamentos adicionados')}</h3>
306
- {medications.length === 0 ? (
307
- <p>{t('noMedicationsAdded', 'Nenhum medicamento adicionado ainda.')}</p>
308
- ) : (
309
- <ul className={styles.medicationsList}>
310
- {medications.map((med, idx) => (
311
- <li key={idx} className={styles.medicationItem}>
312
- <span>
313
- <b>{med.drugName}</b> - {med.dosage} {med.unit} - {med.frequency}
314
- </span>
315
- <button type="button" onClick={() => openEditMedication(idx)}>
316
- {t('edit', 'Editar')}
317
- </button>
318
- <button type="button" onClick={() => handleRemoveMedication(idx)}>
319
- {t('remove', 'Remover')}
320
- </button>
321
- </li>
322
- ))}
323
- </ul>
324
- )}
325
- <button type="button" onClick={openAddMedication} className={styles.addMedicationBtn}>
326
- {t('addMedication', '+ Adicionar medicamento')}
327
- </button>
328
- </div>
304
+ <div className={styles.fieldGroup}>
305
+ <h3>{t('medicationsList', 'Medicamentos adicionados')}</h3>
306
+ {medications.length === 0 ? (
307
+ <p>{t('noMedicationsAdded', 'Nenhum medicamento adicionado ainda.')}</p>
308
+ ) : (
309
+ <ul className={styles.medicationsList}>
310
+ {medications.map((med, idx) => (
311
+ <li key={idx} className={styles.medicationItem}>
312
+ <span>
313
+ <b>{med.drugName}</b> - {med.dosage} {med.unit} - {med.frequency}
314
+ </span>
315
+ <button type="button" onClick={() => openEditMedication(idx)}>
316
+ {t('edit', 'Editar')}
317
+ </button>
318
+ <button type="button" onClick={() => handleRemoveMedication(idx)}>
319
+ {t('remove', 'Remover')}
320
+ </button>
321
+ </li>
322
+ ))}
323
+ </ul>
324
+ )}
325
+ <button type="button" onClick={openAddMedication} className={styles.addMedicationBtn}>
326
+ {t('addMedication', '+ Adicionar medicamento')}
327
+ </button>
328
+ </div>
329
329
 
330
- {/* Tela sobreposta para adicionar/editar medicamento */}
331
- {showAddMedication && (
332
- <div className={styles.overlayForm}>
333
- <div className={styles.overlayContent}>
334
- <h3>
335
- {editIndex === null
336
- ? t('addMedication', 'Adicionar medicamento')
337
- : t('editMedication', 'Editar medicamento')}
338
- </h3>
339
- <MedicationForm
340
- initialData={editIndex !== null ? medications[editIndex] : undefined}
341
- onSubmit={editIndex === null ? handleAddMedication : handleEditMedication}
342
- onCancel={() => {
343
- setShowAddMedication(false);
344
- setEditIndex(null);
345
- }}
346
- t={t}
347
- />
330
+ {showAddMedication && (
331
+ <div className={styles.overlayForm}>
332
+ <div className={styles.overlayContent}>
333
+ <h3>
334
+ {editIndex === null
335
+ ? t('addMedication', 'Adicionar medicamento')
336
+ : t('editMedication', 'Editar medicamento')}
337
+ </h3>
338
+ <MedicationForm
339
+ initialData={editIndex !== null ? medications[editIndex] : undefined}
340
+ onSubmit={editIndex === null ? handleAddMedication : handleEditMedication}
341
+ onCancel={() => {
342
+ setShowAddMedication(false);
343
+ setEditIndex(null);
344
+ }}
345
+ t={t}
346
+ />
347
+ </div>
348
348
  </div>
349
- </div>
350
- )}
351
- </TabPanel>
352
- <TabPanel className={styles.tabContent}>
353
- <InlineNotification
354
- lowContrast
355
- kind="info"
356
- title={t('inPastTabPlaceholderTitle', 'Receitas históricas em breve')}
357
- subtitle={t('inPastTabPlaceholderSubtitle', 'Volte para Nova para gerenciar medicamentos atuais.')}
358
- />
359
- </TabPanel>
360
- </TabPanels>
361
- </Tabs>
362
- </section>
363
- </div>
349
+ )}
350
+ </TabPanel>
351
+ <TabPanel className={styles.tabContent}>
352
+ <InlineNotification
353
+ lowContrast
354
+ kind="info"
355
+ title={t('inPastTabPlaceholderTitle', 'Receitas históricas em breve')}
356
+ subtitle={t('inPastTabPlaceholderSubtitle', 'Volte para Nova para gerenciar medicamentos atuais.')}
357
+ />
358
+ </TabPanel>
359
+ </TabPanels>
360
+ </Tabs>
361
+ </section>
362
+ </div>
363
+ </Workspace2>
364
364
  );
365
365
  };
366
366
 
package/src/routes.json CHANGED
@@ -19,21 +19,22 @@
19
19
  "name": "Brand box",
20
20
  "component": "blueBox",
21
21
  "slot": "Boxes"
22
- },
23
- {
24
- "name": "prescriptions-action-menu",
25
- "component": "prescriptionsActionButton",
26
- "slot": "action-menu-patient-chart-items-slot"
27
22
  }
28
23
  ],
29
- "workspaces": [
24
+ "workspaces2": [
30
25
  {
31
- "name": "active-prescriptions",
32
- "title": "activePrescriptionsWorkspaceTitle",
26
+ "name": "active-prescriptions-workspace",
33
27
  "component": "root",
34
- "type": "active-prescriptions",
35
- "canHide": false,
36
- "width": "wider"
28
+ "window": "active-prescriptions-workspace"
29
+ }
30
+ ],
31
+ "workspaceWindows2": [
32
+ {
33
+ "name": "active-prescriptions-workspace",
34
+ "group": "patient-chart",
35
+ "icon": "prescriptionsActionButton",
36
+ "width": "wider",
37
+ "order": 4
37
38
  }
38
39
  ]
39
40
  }