@cc-openmrs/cc-esm-active-prescriptions 1.0.71 → 1.0.73

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.
@@ -0,0 +1,224 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useTranslation } from 'react-i18next';
4
+ import {
5
+ Button,
6
+ Checkbox,
7
+ InlineLoading,
8
+ InlineNotification,
9
+ Tab,
10
+ TabList,
11
+ TabPanel,
12
+ TabPanels,
13
+ Tabs,
14
+ TextArea,
15
+ } from '@carbon/react';
16
+ import { useSession, useVisit } from '@openmrs/esm-framework';
17
+ import { PrescriptionService } from '../prescriptions.service';
18
+ import { usePrescriptionStore } from '../prescription.store';
19
+ import styles from '../root.scss';
20
+
21
+ interface PrescriptionFormProps {
22
+ patientUuid?: string;
23
+ patient?: {
24
+ name?: Array<{ given?: string[]; family?: string }>;
25
+ birthDate?: string;
26
+ gender?: string;
27
+ };
28
+ }
29
+
30
+ const PrescriptionForm: React.FC<PrescriptionFormProps> = ({ patientUuid, patient }) => {
31
+ const { t } = useTranslation();
32
+ const navigate = useNavigate();
33
+ const { state, dispatch } = usePrescriptionStore();
34
+
35
+ const session = useSession();
36
+ const { activeVisit } = useVisit(patientUuid ?? '');
37
+
38
+ // Location: prefer the active visit's location, fall back to session location
39
+ const location = activeVisit?.location ?? session?.sessionLocation ?? null;
40
+ const locationUuid = location?.uuid ?? '';
41
+ const locationName = location?.display ?? '';
42
+
43
+ // Provider from session
44
+ const providerUuid = session?.currentProvider?.uuid ?? '';
45
+ const providerName = session?.user?.display ?? '';
46
+
47
+ const [activeTab, setActiveTab] = useState(0);
48
+ const [isSubmitting, setIsSubmitting] = useState(false);
49
+ const [submitError, setSubmitError] = useState<string | null>(null);
50
+
51
+ const canSubmit =
52
+ Boolean(patientUuid) && Boolean(locationUuid) && Boolean(providerUuid) && state.orders.length > 0 && !isSubmitting;
53
+
54
+ const handleSubmit = async () => {
55
+ if (!canSubmit) return;
56
+ setIsSubmitting(true);
57
+ setSubmitError(null);
58
+ try {
59
+ const payload = {
60
+ patientUuid: patientUuid!,
61
+ patientName: patient?.name?.[0]
62
+ ? `${patient.name[0].given?.join(' ')} ${patient.name[0].family}`.trim()
63
+ : '',
64
+ patientBirthDate: patient?.birthDate ?? '',
65
+ patientGender: patient?.gender ?? '',
66
+ providerUuid,
67
+ providerName,
68
+ locationUuid,
69
+ locationName,
70
+ digitallySigned: state.digitallySigned,
71
+ printRequested: state.printRequested,
72
+ observations: state.observations,
73
+ orders: state.orders.map((o) => ({
74
+ drugUuid: o.drug.uuid,
75
+ drugDisplay: o.drug.display,
76
+ dosage: o.dosage,
77
+ unit: o.unit,
78
+ route: o.route,
79
+ frequency: o.frequency,
80
+ patientInstructions: o.patientInstructions,
81
+ asNeeded: o.asNeeded,
82
+ asNeededCondition: o.asNeededCondition,
83
+ indication: o.indication,
84
+ })),
85
+ };
86
+ await PrescriptionService.createPrescription(payload);
87
+ dispatch({ type: 'RESET' });
88
+ } catch (e: any) {
89
+ setSubmitError(e?.message ?? t('submitError', 'Erro ao salvar a prescrição.'));
90
+ } finally {
91
+ setIsSubmitting(false);
92
+ }
93
+ };
94
+
95
+ return (
96
+ <div className={styles.workspace}>
97
+ <div className={styles.header}>
98
+ <h2>{t('activePrescriptionsWorkspaceTitle', 'Prescrições ativas')}</h2>
99
+ <p className={styles.subtitle}>
100
+ {t('activePrescriptionsHelper', 'Adicione os medicamentos e salve a prescrição.')}
101
+ </p>
102
+ </div>
103
+
104
+ <section className={styles.section}>
105
+ <Tabs selectedIndex={activeTab} onChange={({ selectedIndex }) => setActiveTab(selectedIndex)}>
106
+ <TabList aria-label={t('visitTimingLabel', 'A visita é')}>
107
+ <Tab>{t('visitTimingNew', 'Nova')}</Tab>
108
+ <Tab>{t('visitTimingPast', 'No passado')}</Tab>
109
+ </TabList>
110
+ <TabPanels>
111
+ <TabPanel className={styles.tabContent}>
112
+ <div className={styles.fieldGroup}>
113
+ <Checkbox
114
+ id="sign-prescription-checkbox"
115
+ labelText={t('signPrescriptionLabel', 'Assinar digitalmente a prescrição')}
116
+ checked={state.digitallySigned}
117
+ onChange={(_: any, { checked }: any) =>
118
+ dispatch({ type: 'SET_DIGITALLY_SIGNED', payload: checked })
119
+ }
120
+ />
121
+ <Checkbox
122
+ id="print-prescription-checkbox"
123
+ labelText={t('printPrescriptionLabel', 'Imprimir a prescrição')}
124
+ checked={state.printRequested}
125
+ onChange={(_: any, { checked }: any) =>
126
+ dispatch({ type: 'SET_PRINT_REQUESTED', payload: checked })
127
+ }
128
+ />
129
+ </div>
130
+
131
+ <div className={styles.fieldGroup}>
132
+ <TextArea
133
+ id="observations"
134
+ labelText={t('observations', 'Observações')}
135
+ placeholder={t('observationsPlaceholder', 'Observações da prescrição...')}
136
+ value={state.observations}
137
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
138
+ dispatch({ type: 'SET_OBSERVATIONS', payload: e.target.value })
139
+ }
140
+ rows={3}
141
+ />
142
+ </div>
143
+
144
+ <div className={styles.fieldGroup}>
145
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
146
+ <h3 style={{ margin: 0 }}>{t('medicationsList', 'Medicamentos')}</h3>
147
+ <Button
148
+ size="sm"
149
+ hasIconOnly
150
+ kind="ghost"
151
+ renderIcon={() => (
152
+ <svg width="20" height="20" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
153
+ <path d="M16 6V26" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
154
+ <path d="M6 16H26" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
155
+ </svg>
156
+ )}
157
+ iconDescription={t('addMedication', 'Adicionar medicamento')}
158
+ onClick={() => navigate('/medications/new')}
159
+ />
160
+ </div>
161
+
162
+ {state.orders.length === 0 ? (
163
+ <p>{t('noMedicationsAdded', 'Nenhum medicamento adicionado ainda.')}</p>
164
+ ) : (
165
+ <ul className={styles.medicationsList}>
166
+ {state.orders.map((order, idx) => (
167
+ <li key={idx} className={styles.medicationItem}>
168
+ <span>
169
+ <b>{order.drug.display}</b> — {order.dosage} {order.unit}
170
+ {order.route ? ` · ${order.route}` : ''} · {order.frequency}
171
+ </span>
172
+ <Button size="sm" kind="ghost" onClick={() => navigate(`/medications/${idx}/edit`)}>
173
+ {t('edit', 'Editar')}
174
+ </Button>
175
+ <Button
176
+ size="sm"
177
+ kind="danger--ghost"
178
+ onClick={() => dispatch({ type: 'REMOVE_ORDER', index: idx })}
179
+ >
180
+ {t('remove', 'Remover')}
181
+ </Button>
182
+ </li>
183
+ ))}
184
+ </ul>
185
+ )}
186
+ </div>
187
+
188
+ {submitError && (
189
+ <InlineNotification
190
+ lowContrast
191
+ kind="error"
192
+ title={t('submitError', 'Erro ao salvar a prescrição.')}
193
+ subtitle={submitError}
194
+ onCloseButtonClick={() => setSubmitError(null)}
195
+ />
196
+ )}
197
+
198
+ <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
199
+ <Button kind="primary" disabled={!canSubmit} onClick={handleSubmit}>
200
+ {isSubmitting ? (
201
+ <InlineLoading description={t('saving', 'Salvando...')} />
202
+ ) : (
203
+ t('createPrescriptionButtonLabel', 'Salvar Prescrição')
204
+ )}
205
+ </Button>
206
+ </div>
207
+ </TabPanel>
208
+
209
+ <TabPanel className={styles.tabContent}>
210
+ <InlineNotification
211
+ lowContrast
212
+ kind="info"
213
+ title={t('inPastTabPlaceholderTitle', 'Receitas históricas em breve')}
214
+ subtitle={t('inPastTabPlaceholderSubtitle', 'Volte para Nova para gerenciar medicamentos atuais.')}
215
+ />
216
+ </TabPanel>
217
+ </TabPanels>
218
+ </Tabs>
219
+ </section>
220
+ </div>
221
+ );
222
+ };
223
+
224
+ export default PrescriptionForm;
@@ -0,0 +1,70 @@
1
+ import React, { createContext, useContext, useReducer } from 'react';
2
+ import type { PrescriptionDrugItem } from './prescriptions-actions/add-drug-prescription/add-drug-prescription.component';
3
+
4
+ // ─── State ───────────────────────────────────────────────────────────────────
5
+
6
+ export interface PrescriptionState {
7
+ orders: PrescriptionDrugItem[];
8
+ digitallySigned: boolean;
9
+ printRequested: boolean;
10
+ observations: string;
11
+ }
12
+
13
+ const initialState: PrescriptionState = {
14
+ orders: [],
15
+ digitallySigned: false,
16
+ printRequested: false,
17
+ observations: '',
18
+ };
19
+
20
+ // ─── Actions ─────────────────────────────────────────────────────────────────
21
+
22
+ export type PrescriptionAction =
23
+ | { type: 'ADD_ORDER'; payload: PrescriptionDrugItem }
24
+ | { type: 'UPDATE_ORDER'; index: number; payload: PrescriptionDrugItem }
25
+ | { type: 'REMOVE_ORDER'; index: number }
26
+ | { type: 'SET_DIGITALLY_SIGNED'; payload: boolean }
27
+ | { type: 'SET_PRINT_REQUESTED'; payload: boolean }
28
+ | { type: 'SET_OBSERVATIONS'; payload: string }
29
+ | { type: 'RESET' };
30
+
31
+ // ─── Reducer ─────────────────────────────────────────────────────────────────
32
+
33
+ function prescriptionReducer(state: PrescriptionState, action: PrescriptionAction): PrescriptionState {
34
+ switch (action.type) {
35
+ case 'ADD_ORDER':
36
+ return { ...state, orders: [...state.orders, action.payload] };
37
+ case 'UPDATE_ORDER':
38
+ return { ...state, orders: state.orders.map((o, i) => (i === action.index ? action.payload : o)) };
39
+ case 'REMOVE_ORDER':
40
+ return { ...state, orders: state.orders.filter((_, i) => i !== action.index) };
41
+ case 'SET_DIGITALLY_SIGNED':
42
+ return { ...state, digitallySigned: action.payload };
43
+ case 'SET_PRINT_REQUESTED':
44
+ return { ...state, printRequested: action.payload };
45
+ case 'SET_OBSERVATIONS':
46
+ return { ...state, observations: action.payload };
47
+ case 'RESET':
48
+ return initialState;
49
+ default:
50
+ return state;
51
+ }
52
+ }
53
+
54
+ // ─── Context ─────────────────────────────────────────────────────────────────
55
+
56
+ const PrescriptionContext = createContext<{
57
+ state: PrescriptionState;
58
+ dispatch: React.Dispatch<PrescriptionAction>;
59
+ } | null>(null);
60
+
61
+ export function PrescriptionProvider({ children }: { children: React.ReactNode }) {
62
+ const [state, dispatch] = useReducer(prescriptionReducer, initialState);
63
+ return <PrescriptionContext.Provider value={{ state, dispatch }}>{children}</PrescriptionContext.Provider>;
64
+ }
65
+
66
+ export function usePrescriptionStore() {
67
+ const ctx = useContext(PrescriptionContext);
68
+ if (!ctx) throw new Error('usePrescriptionStore must be used within PrescriptionProvider');
69
+ return ctx;
70
+ }
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import { useNavigate, useParams } from 'react-router-dom';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Button } from '@carbon/react';
5
+ import { usePrescriptionStore } from '../../prescription.store';
6
+ import AddDrugPrescription, {
7
+ type PrescriptionDrugItem,
8
+ } from './add-drug-prescription.component';
9
+ import styles from '../../root.scss';
10
+
11
+ const AddDrugPrescriptionPage: React.FC = () => {
12
+ const { index } = useParams<{ index: string }>();
13
+ const navigate = useNavigate();
14
+ const { t } = useTranslation();
15
+ const { state, dispatch } = usePrescriptionStore();
16
+
17
+ const editIndex = index !== undefined ? parseInt(index, 10) : null;
18
+ const initialData = editIndex !== null ? state.orders[editIndex] : undefined;
19
+
20
+ const handleSave = (item: PrescriptionDrugItem) => {
21
+ if (editIndex !== null) {
22
+ dispatch({ type: 'UPDATE_ORDER', index: editIndex, payload: item });
23
+ } else {
24
+ dispatch({ type: 'ADD_ORDER', payload: item });
25
+ }
26
+ navigate('/');
27
+ };
28
+
29
+ return (
30
+ <div className={styles.workspace}>
31
+ <div className={styles.header}>
32
+ <Button
33
+ size="sm"
34
+ kind="ghost"
35
+ onClick={() => navigate('/')}
36
+ renderIcon={() => (
37
+ <svg width="16" height="16" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
38
+ <path
39
+ d="M20 6L10 16L20 26"
40
+ stroke="currentColor"
41
+ strokeWidth="2"
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ />
45
+ </svg>
46
+ )}
47
+ >
48
+ {t('backToPrescription', 'Voltar à prescrição')}
49
+ </Button>
50
+ <h2 style={{ margin: 0 }}>
51
+ {editIndex !== null
52
+ ? t('editMedication', 'Editar medicamento')
53
+ : t('addMedication', 'Adicionar medicamento')}
54
+ </h2>
55
+ </div>
56
+ <AddDrugPrescription
57
+ initialData={initialData}
58
+ onSave={handleSave}
59
+ onCancel={() => navigate('/')}
60
+ />
61
+ </div>
62
+ );
63
+ };
64
+
65
+ export default AddDrugPrescriptionPage;
@@ -1,31 +1,37 @@
1
1
  // prescription.service.ts
2
2
  // Serviço para manipulação de prescrições e integração com backend
3
3
 
4
+ export interface PrescriptionOrderItem {
5
+ drugUuid: string;
6
+ drugDisplay: string;
7
+ dosage: string;
8
+ unit: string;
9
+ route: string;
10
+ frequency: string;
11
+ patientInstructions?: string;
12
+ asNeeded?: boolean;
13
+ asNeededCondition?: string;
14
+ indication?: string;
15
+ }
16
+
4
17
  export interface PrescriptionPayload {
5
- patient: {
6
- uuid: string;
7
- name?: string;
8
- birthDate?: string;
9
- gender?: string;
10
- };
11
- provider: {
12
- uuid: string;
13
- name?: string;
14
- };
15
- location: {
16
- uuid: string;
17
- name?: string;
18
- };
19
- policyNumber?: string;
20
- orderUuids: string[];
18
+ patientUuid: string;
19
+ patientName: string;
20
+ patientBirthDate: string;
21
+ patientGender: string;
22
+ providerUuid: string;
23
+ providerName: string;
24
+ locationUuid: string;
25
+ locationName: string;
21
26
  digitallySigned: boolean;
22
27
  printRequested: boolean;
28
+ observations: string;
29
+ orders: PrescriptionOrderItem[];
23
30
  }
24
31
 
25
32
  export class PrescriptionService {
26
33
  static async createPrescription(payload: PrescriptionPayload): Promise<Response> {
27
- // Troque a URL abaixo pelo endpoint real do backend
28
- const url = '/api/prescriptions';
34
+ const url = '/openmrs/ws/rest/v1/prescriptions';
29
35
  return fetch(url, {
30
36
  method: 'POST',
31
37
  headers: {
@@ -35,3 +41,4 @@ export class PrescriptionService {
35
41
  });
36
42
  }
37
43
  }
44
+