@cc-openmrs/cc-esm-active-prescriptions 1.0.67 → 1.0.69
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/dist/{966.js → 389.js} +1 -1
- package/dist/416.js +1 -0
- package/dist/478.js +1 -2
- package/dist/{537.js → 488.js} +1 -1
- package/dist/597.js +2 -0
- package/dist/{779.js.LICENSE.txt → 597.js.LICENSE.txt} +10 -0
- package/dist/{845.js → 747.js} +2 -1
- package/dist/97.js +2 -0
- package/dist/{765.js.LICENSE.txt → 97.js.LICENSE.txt} +0 -10
- package/dist/{319.js → 970.js} +1 -1
- package/dist/main.js +1 -1
- package/dist/openmrs-esm-patient-lists-app.js +1 -1
- package/dist/openmrs-esm-patient-lists-app.js.buildmanifest.json +128 -155
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/prescriptions-actions/add-drug-prescription/add-drug-prescription.component.tsx +186 -0
- package/src/prescriptions-actions/add-drug-prescription/add-drug-prescription.scss +1 -0
- package/src/prescriptions-actions/add-drug-prescription/index.ts +2 -0
- package/src/root.component.tsx +135 -207
- package/dist/116.js +0 -1
- package/dist/499.js +0 -1
- package/dist/765.js +0 -2
- package/dist/779.js +0 -2
- /package/dist/{478.js.LICENSE.txt → 747.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { Button, Checkbox, ComboBox, TextInput } from '@carbon/react';
|
|
4
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
5
|
+
import useSWR from 'swr';
|
|
6
|
+
|
|
7
|
+
export interface PrescriptionDrugItem {
|
|
8
|
+
drug: { uuid: string; name: string; display: string };
|
|
9
|
+
dosage: string;
|
|
10
|
+
unit: string;
|
|
11
|
+
route: string;
|
|
12
|
+
frequency: string;
|
|
13
|
+
patientInstructions?: string;
|
|
14
|
+
asNeeded?: boolean;
|
|
15
|
+
asNeededCondition?: string;
|
|
16
|
+
indication?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AddDrugPrescriptionProps {
|
|
20
|
+
initialData?: PrescriptionDrugItem;
|
|
21
|
+
onSave: (item: PrescriptionDrugItem) => void;
|
|
22
|
+
onCancel: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const UNITS = [{ value: 'mg' }, { value: 'ml' }, { value: 'g' }, { value: 'mcg' }, { value: 'comprimido(s)' }];
|
|
26
|
+
|
|
27
|
+
const ROUTES = [
|
|
28
|
+
{ value: 'Oral' },
|
|
29
|
+
{ value: 'Intravenoso (IV)' },
|
|
30
|
+
{ value: 'Intramuscular (IM)' },
|
|
31
|
+
{ value: 'Subcutâneo (SC)' },
|
|
32
|
+
{ value: 'Tópico' },
|
|
33
|
+
{ value: 'Inalatório' },
|
|
34
|
+
{ value: 'Sublingual' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const FREQUENCIES = [
|
|
38
|
+
{ value: '1x ao dia' },
|
|
39
|
+
{ value: '2x ao dia' },
|
|
40
|
+
{ value: '3x ao dia' },
|
|
41
|
+
{ value: '4x ao dia' },
|
|
42
|
+
{ value: '6/6h' },
|
|
43
|
+
{ value: '8/8h' },
|
|
44
|
+
{ value: '12/12h' },
|
|
45
|
+
{ value: 'Se necessário' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function useDrugSearch(query: string) {
|
|
49
|
+
const url =
|
|
50
|
+
query?.length >= 3
|
|
51
|
+
? `${restBaseUrl}/drug?q=${encodeURIComponent(query)}&v=custom:(uuid,name,display)&limit=10`
|
|
52
|
+
: null;
|
|
53
|
+
const { data, isValidating } = useSWR<any>(url, openmrsFetch);
|
|
54
|
+
return {
|
|
55
|
+
drugs: (data?.data?.results ?? []) as Array<{ uuid: string; name: string; display: string }>,
|
|
56
|
+
isLoading: isValidating,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const AddDrugPrescription: React.FC<AddDrugPrescriptionProps> = ({ initialData, onSave, onCancel }) => {
|
|
61
|
+
const { t } = useTranslation();
|
|
62
|
+
|
|
63
|
+
const [drugQuery, setDrugQuery] = useState(initialData?.drug?.name ?? '');
|
|
64
|
+
const [selectedDrug, setSelectedDrug] = useState<PrescriptionDrugItem['drug'] | null>(initialData?.drug ?? null);
|
|
65
|
+
const [dosage, setDosage] = useState(initialData?.dosage ?? '');
|
|
66
|
+
const [unit, setUnit] = useState(initialData?.unit ?? '');
|
|
67
|
+
const [route, setRoute] = useState(initialData?.route ?? '');
|
|
68
|
+
const [frequency, setFrequency] = useState(initialData?.frequency ?? '');
|
|
69
|
+
const [patientInstructions, setPatientInstructions] = useState(initialData?.patientInstructions ?? '');
|
|
70
|
+
const [asNeeded, setAsNeeded] = useState(initialData?.asNeeded ?? false);
|
|
71
|
+
const [asNeededCondition, setAsNeededCondition] = useState(initialData?.asNeededCondition ?? '');
|
|
72
|
+
const [indication, setIndication] = useState(initialData?.indication ?? '');
|
|
73
|
+
|
|
74
|
+
const { drugs, isLoading } = useDrugSearch(drugQuery);
|
|
75
|
+
|
|
76
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
if (!selectedDrug || !dosage || !unit || !frequency) return;
|
|
79
|
+
onSave({
|
|
80
|
+
drug: selectedDrug,
|
|
81
|
+
dosage,
|
|
82
|
+
unit,
|
|
83
|
+
route,
|
|
84
|
+
frequency,
|
|
85
|
+
patientInstructions: patientInstructions || undefined,
|
|
86
|
+
asNeeded,
|
|
87
|
+
asNeededCondition: asNeeded ? asNeededCondition || undefined : undefined,
|
|
88
|
+
indication: indication || undefined,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
94
|
+
<ComboBox
|
|
95
|
+
id="drug-search"
|
|
96
|
+
titleText={t('drugName', 'Medicamento')}
|
|
97
|
+
placeholder={t('drugSearchPlaceholder', 'Digite para buscar (mín. 3 letras)...')}
|
|
98
|
+
items={drugs.map((d) => ({ id: d.uuid, label: d.display || d.name, ...d }))}
|
|
99
|
+
itemToString={(item: any) => item?.label ?? ''}
|
|
100
|
+
selectedItem={selectedDrug ? { id: selectedDrug.uuid, label: selectedDrug.display } : null}
|
|
101
|
+
onInputChange={(v: string) => {
|
|
102
|
+
setDrugQuery(v);
|
|
103
|
+
if (!v) setSelectedDrug(null);
|
|
104
|
+
}}
|
|
105
|
+
onChange={({ selectedItem }: any) => {
|
|
106
|
+
if (selectedItem) {
|
|
107
|
+
setSelectedDrug({
|
|
108
|
+
uuid: selectedItem.uuid,
|
|
109
|
+
name: selectedItem.name,
|
|
110
|
+
display: selectedItem.display ?? selectedItem.name,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}}
|
|
114
|
+
disabled={isLoading}
|
|
115
|
+
helperText={isLoading ? t('searching', 'Buscando...') : undefined}
|
|
116
|
+
/>
|
|
117
|
+
<TextInput
|
|
118
|
+
id="dosage"
|
|
119
|
+
labelText={t('dosage', 'Dose')}
|
|
120
|
+
value={dosage}
|
|
121
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDosage(e.target.value)}
|
|
122
|
+
required
|
|
123
|
+
/>
|
|
124
|
+
<ComboBox
|
|
125
|
+
id="unit"
|
|
126
|
+
titleText={t('unit', 'Unidade')}
|
|
127
|
+
items={UNITS}
|
|
128
|
+
itemToString={(item: any) => item?.value ?? ''}
|
|
129
|
+
selectedItem={UNITS.find((u) => u.value === unit) ?? null}
|
|
130
|
+
onChange={({ selectedItem }: any) => setUnit(selectedItem?.value ?? '')}
|
|
131
|
+
/>
|
|
132
|
+
<ComboBox
|
|
133
|
+
id="route"
|
|
134
|
+
titleText={t('route', 'Via de administração')}
|
|
135
|
+
items={ROUTES}
|
|
136
|
+
itemToString={(item: any) => item?.value ?? ''}
|
|
137
|
+
selectedItem={ROUTES.find((r) => r.value === route) ?? null}
|
|
138
|
+
onChange={({ selectedItem }: any) => setRoute(selectedItem?.value ?? '')}
|
|
139
|
+
/>
|
|
140
|
+
<ComboBox
|
|
141
|
+
id="frequency"
|
|
142
|
+
titleText={t('frequency', 'Frequência')}
|
|
143
|
+
items={FREQUENCIES}
|
|
144
|
+
itemToString={(item: any) => item?.value ?? ''}
|
|
145
|
+
selectedItem={FREQUENCIES.find((f) => f.value === frequency) ?? null}
|
|
146
|
+
onChange={({ selectedItem }: any) => setFrequency(selectedItem?.value ?? '')}
|
|
147
|
+
/>
|
|
148
|
+
<TextInput
|
|
149
|
+
id="patient-instructions"
|
|
150
|
+
labelText={t('patientInstructions', 'Instruções ao paciente')}
|
|
151
|
+
value={patientInstructions}
|
|
152
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPatientInstructions(e.target.value)}
|
|
153
|
+
/>
|
|
154
|
+
<Checkbox
|
|
155
|
+
id="as-needed"
|
|
156
|
+
labelText={t('asNeeded', 'Tomar se necessário (P.R.N.)')}
|
|
157
|
+
checked={asNeeded}
|
|
158
|
+
onChange={(_: any, { checked }: any) => setAsNeeded(checked)}
|
|
159
|
+
/>
|
|
160
|
+
{asNeeded && (
|
|
161
|
+
<TextInput
|
|
162
|
+
id="as-needed-condition"
|
|
163
|
+
labelText={t('asNeededCondition', 'Motivo P.R.N.')}
|
|
164
|
+
value={asNeededCondition}
|
|
165
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAsNeededCondition(e.target.value)}
|
|
166
|
+
/>
|
|
167
|
+
)}
|
|
168
|
+
<TextInput
|
|
169
|
+
id="indication"
|
|
170
|
+
labelText={t('indication', 'Indicação')}
|
|
171
|
+
value={indication}
|
|
172
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setIndication(e.target.value)}
|
|
173
|
+
/>
|
|
174
|
+
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.5rem' }}>
|
|
175
|
+
<Button type="submit" kind="primary" disabled={!selectedDrug || !dosage || !unit || !frequency}>
|
|
176
|
+
{t('saveMedication', 'Salvar medicamento')}
|
|
177
|
+
</Button>
|
|
178
|
+
<Button type="button" kind="secondary" onClick={onCancel}>
|
|
179
|
+
{t('cancel', 'Cancelar')}
|
|
180
|
+
</Button>
|
|
181
|
+
</div>
|
|
182
|
+
</form>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export default AddDrugPrescription;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "../add-drug-order/add-drug-order.scss";
|
package/src/root.component.tsx
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React, { useMemo, useState } from 'react';
|
|
12
|
-
import { PrescriptionService
|
|
13
|
-
import
|
|
14
|
-
import { openmrsFetch, restBaseUrl, Workspace2 } from '@openmrs/esm-framework';
|
|
12
|
+
import { PrescriptionService } from './prescriptions.service';
|
|
13
|
+
import { Workspace2 } from '@openmrs/esm-framework';
|
|
15
14
|
import {
|
|
15
|
+
Button,
|
|
16
16
|
Checkbox,
|
|
17
17
|
ComboBox,
|
|
18
18
|
InlineLoading,
|
|
@@ -22,176 +22,23 @@ import {
|
|
|
22
22
|
TabPanel,
|
|
23
23
|
TabPanels,
|
|
24
24
|
Tabs,
|
|
25
|
+
TextArea,
|
|
25
26
|
TextInput,
|
|
26
27
|
} from '@carbon/react';
|
|
27
28
|
import { useTranslation } from 'react-i18next';
|
|
28
29
|
import { useLocations, type Location } from '@openmrs/esm-framework';
|
|
29
|
-
import { usePatientOrders } from './prescriptions-orders/usePatientOrders';
|
|
30
30
|
import styles from './root.scss';
|
|
31
|
+
import AddDrugPrescription, { type PrescriptionDrugItem } from './prescriptions-actions/add-drug-prescription/add-drug-prescription.component';
|
|
31
32
|
|
|
32
33
|
interface RootProps {
|
|
33
34
|
patientUuid?: string;
|
|
34
|
-
patient?: {
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
function useConceptDrugs(query: string) {
|
|
39
|
-
const url = query ? `${restBaseUrl}/drug?q=${encodeURIComponent(query)}&v=custom:(uuid,name,display)` : null;
|
|
40
|
-
const swr = useSWR<any>(url, openmrsFetch);
|
|
41
|
-
return {
|
|
42
|
-
drugs: swr.data?.data?.results || [],
|
|
43
|
-
error: swr.error,
|
|
44
|
-
isLoading: swr.isValidating,
|
|
35
|
+
patient?: {
|
|
36
|
+
name?: Array<{ given?: string[]; family?: string }>;
|
|
37
|
+
birthDate?: string;
|
|
38
|
+
gender?: string;
|
|
45
39
|
};
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
// Tipo para dados do medicamento
|
|
49
|
-
type MedicationFormData = {
|
|
50
|
-
drugName: string;
|
|
51
|
-
dosage: string;
|
|
52
|
-
unit: string;
|
|
53
|
-
route: string;
|
|
54
|
-
frequency: string;
|
|
55
|
-
patientInstructions?: string;
|
|
56
|
-
asNeeded?: boolean;
|
|
57
|
-
asNeededCondition?: string;
|
|
58
|
-
startDate?: string;
|
|
59
|
-
duration?: string;
|
|
60
|
-
durationUnit?: string;
|
|
61
|
-
indication?: string;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const MOCK_UNITS = [{ value: 'mg' }, { value: 'ml' }, { value: 'g' }];
|
|
65
|
-
const MOCK_FREQUENCIES = [{ value: '1x ao dia' }, { value: '2x ao dia' }, { value: '8/8h' }, { value: '12/12h' }];
|
|
66
|
-
|
|
67
|
-
type MedicationFormProps = {
|
|
68
|
-
initialData?: MedicationFormData;
|
|
69
|
-
onSubmit: (data: MedicationFormData) => void;
|
|
70
|
-
onCancel: () => void;
|
|
71
|
-
t: any;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const MedicationForm: React.FC<MedicationFormProps> = ({ initialData, onSubmit, onCancel, t }) => {
|
|
75
|
-
const [drugName, setDrugName] = useState(initialData?.drugName || '');
|
|
76
|
-
const [dosage, setDosage] = useState(initialData?.dosage || '');
|
|
77
|
-
const [unit, setUnit] = useState(initialData?.unit || '');
|
|
78
|
-
const [route, setRoute] = useState(initialData?.route || '');
|
|
79
|
-
const [frequency, setFrequency] = useState(initialData?.frequency || '');
|
|
80
|
-
const [patientInstructions, setPatientInstructions] = useState(initialData?.patientInstructions || '');
|
|
81
|
-
const [asNeeded, setAsNeeded] = useState(initialData?.asNeeded || false);
|
|
82
|
-
const [asNeededCondition, setAsNeededCondition] = useState(initialData?.asNeededCondition || '');
|
|
83
|
-
const [indication, setIndication] = useState(initialData?.indication || '');
|
|
84
|
-
|
|
85
|
-
// Busca ConceptDrugs conforme digitação
|
|
86
|
-
const { drugs, isLoading } = useConceptDrugs(drugName);
|
|
87
|
-
const drugOptions = drugs.map((d: any) => ({ value: d.name, uuid: d.uuid, display: d.display }));
|
|
88
|
-
|
|
89
|
-
const handleDrugInput = (input: string) => {
|
|
90
|
-
setDrugName(input);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const handleSubmit = (e: React.FormEvent) => {
|
|
94
|
-
e.preventDefault();
|
|
95
|
-
if (!drugName || !dosage || !unit || !frequency) return;
|
|
96
|
-
onSubmit({
|
|
97
|
-
drugName,
|
|
98
|
-
dosage,
|
|
99
|
-
unit,
|
|
100
|
-
route,
|
|
101
|
-
frequency,
|
|
102
|
-
patientInstructions,
|
|
103
|
-
asNeeded,
|
|
104
|
-
asNeededCondition,
|
|
105
|
-
indication,
|
|
106
|
-
});
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
111
|
-
<ComboBox
|
|
112
|
-
id="drug-name"
|
|
113
|
-
items={drugOptions}
|
|
114
|
-
itemToString={(item) => item?.value || ''}
|
|
115
|
-
selectedItem={drugOptions.find((d: any) => d.value === drugName) || null}
|
|
116
|
-
onInputChange={handleDrugInput}
|
|
117
|
-
onChange={({ selectedItem }) => setDrugName(selectedItem?.value || '')}
|
|
118
|
-
placeholder={t('drugName', 'Nome do medicamento')}
|
|
119
|
-
titleText={t('drugName', 'Nome do medicamento')}
|
|
120
|
-
required
|
|
121
|
-
disabled={isLoading}
|
|
122
|
-
/>
|
|
123
|
-
<TextInput
|
|
124
|
-
id="dosage"
|
|
125
|
-
labelText={t('dosage', 'Dose')}
|
|
126
|
-
value={dosage}
|
|
127
|
-
onChange={(e) => setDosage(e.target.value)}
|
|
128
|
-
required
|
|
129
|
-
/>
|
|
130
|
-
<ComboBox
|
|
131
|
-
id="unit"
|
|
132
|
-
items={MOCK_UNITS}
|
|
133
|
-
itemToString={(item) => item?.value || ''}
|
|
134
|
-
selectedItem={MOCK_UNITS.find((u) => u.value === unit) || null}
|
|
135
|
-
onChange={({ selectedItem }) => setUnit(selectedItem?.value || '')}
|
|
136
|
-
placeholder={t('unit', 'Unidade')}
|
|
137
|
-
titleText={t('unit', 'Unidade')}
|
|
138
|
-
required
|
|
139
|
-
/>
|
|
140
|
-
<TextInput
|
|
141
|
-
id="route"
|
|
142
|
-
labelText={t('route', 'Via de administração')}
|
|
143
|
-
value={route}
|
|
144
|
-
onChange={(e) => setRoute(e.target.value)}
|
|
145
|
-
/>
|
|
146
|
-
<ComboBox
|
|
147
|
-
id="frequency"
|
|
148
|
-
items={MOCK_FREQUENCIES}
|
|
149
|
-
itemToString={(item) => item?.value || ''}
|
|
150
|
-
selectedItem={MOCK_FREQUENCIES.find((f) => f.value === frequency) || null}
|
|
151
|
-
onChange={({ selectedItem }) => setFrequency(selectedItem?.value || '')}
|
|
152
|
-
placeholder={t('frequency', 'Frequência')}
|
|
153
|
-
titleText={t('frequency', 'Frequência')}
|
|
154
|
-
required
|
|
155
|
-
/>
|
|
156
|
-
<TextInput
|
|
157
|
-
id="patient-instructions"
|
|
158
|
-
labelText={t('patientInstructions', 'Instruções ao paciente')}
|
|
159
|
-
value={patientInstructions}
|
|
160
|
-
onChange={(e) => setPatientInstructions(e.target.value)}
|
|
161
|
-
/>
|
|
162
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
163
|
-
<Checkbox
|
|
164
|
-
id="as-needed"
|
|
165
|
-
labelText={t('asNeeded', 'Tomar se necessário (P.R.N.)')}
|
|
166
|
-
checked={asNeeded}
|
|
167
|
-
onChange={() => setAsNeeded((prev) => !prev)}
|
|
168
|
-
/>
|
|
169
|
-
<TextInput
|
|
170
|
-
id="as-needed-condition"
|
|
171
|
-
labelText={t('asNeededCondition', 'Motivo P.R.N.')}
|
|
172
|
-
value={asNeededCondition}
|
|
173
|
-
onChange={(e) => setAsNeededCondition(e.target.value)}
|
|
174
|
-
disabled={!asNeeded}
|
|
175
|
-
/>
|
|
176
|
-
</div>
|
|
177
|
-
<TextInput
|
|
178
|
-
id="indication"
|
|
179
|
-
labelText={t('indication', 'Indicação')}
|
|
180
|
-
value={indication}
|
|
181
|
-
onChange={(e) => setIndication(e.target.value)}
|
|
182
|
-
/>
|
|
183
|
-
<div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
|
|
184
|
-
<button type="submit" style={{ flex: 1 }}>
|
|
185
|
-
{t('saveMedication', 'Salvar medicamento')}
|
|
186
|
-
</button>
|
|
187
|
-
<button type="button" onClick={onCancel} style={{ flex: 1 }}>
|
|
188
|
-
{t('cancel', 'Cancelar')}
|
|
189
|
-
</button>
|
|
190
|
-
</div>
|
|
191
|
-
</form>
|
|
192
|
-
);
|
|
193
|
-
};
|
|
194
|
-
|
|
195
42
|
const Root: React.FC<RootProps> = ({ patientUuid, patient }) => {
|
|
196
43
|
const { t } = useTranslation();
|
|
197
44
|
const [activeTab, setActiveTab] = useState<'new' | 'past'>('new');
|
|
@@ -199,46 +46,74 @@ const Root: React.FC<RootProps> = ({ patientUuid, patient }) => {
|
|
|
199
46
|
const [policyNumber, setPolicyNumber] = useState('');
|
|
200
47
|
const [digitallySigned, setDigitallySigned] = useState(false);
|
|
201
48
|
const [printRequested, setPrintRequested] = useState(false);
|
|
49
|
+
const [observations, setObservations] = useState('');
|
|
202
50
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
203
|
-
const [
|
|
51
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
52
|
+
|
|
53
|
+
// Lista de medicamentos da prescrição — escopo da prescrição
|
|
54
|
+
const [prescriptionOrders, setPrescriptionOrders] = useState<PrescriptionDrugItem[]>([]);
|
|
204
55
|
const [showAddMedication, setShowAddMedication] = useState(false);
|
|
205
56
|
const [editIndex, setEditIndex] = useState<number | null>(null);
|
|
206
57
|
|
|
207
58
|
const locations = useLocations();
|
|
208
59
|
const locationItems = useMemo(() => locations ?? [], [locations]);
|
|
209
60
|
|
|
210
|
-
|
|
211
|
-
const handleAddMedication = (med: MedicationFormData) => {
|
|
212
|
-
setMedications((prev) => [...prev, med]);
|
|
213
|
-
setShowAddMedication(false);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Edita medicamento existente
|
|
217
|
-
const handleEditMedication = (med: MedicationFormData) => {
|
|
61
|
+
const handleSaveMedication = (item: PrescriptionDrugItem) => {
|
|
218
62
|
if (editIndex !== null) {
|
|
219
|
-
|
|
63
|
+
setPrescriptionOrders((prev) => prev.map((m, idx) => (idx === editIndex ? item : m)));
|
|
220
64
|
setEditIndex(null);
|
|
221
|
-
|
|
65
|
+
} else {
|
|
66
|
+
setPrescriptionOrders((prev) => [...prev, item]);
|
|
222
67
|
}
|
|
68
|
+
setShowAddMedication(false);
|
|
223
69
|
};
|
|
224
70
|
|
|
225
|
-
// Remove medicamento
|
|
226
71
|
const handleRemoveMedication = (idx: number) => {
|
|
227
|
-
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
// Abre tela de adicionar
|
|
231
|
-
const openAddMedication = () => {
|
|
232
|
-
setEditIndex(null);
|
|
233
|
-
setShowAddMedication(true);
|
|
72
|
+
setPrescriptionOrders((prev) => prev.filter((_, i) => i !== idx));
|
|
234
73
|
};
|
|
235
74
|
|
|
236
|
-
// Abre tela de editar
|
|
237
75
|
const openEditMedication = (idx: number) => {
|
|
238
76
|
setEditIndex(idx);
|
|
239
77
|
setShowAddMedication(true);
|
|
240
78
|
};
|
|
241
79
|
|
|
80
|
+
const handleSubmitPrescription = async () => {
|
|
81
|
+
if (!patientUuid || !selectedLocation || prescriptionOrders.length === 0) return;
|
|
82
|
+
setIsSubmitting(true);
|
|
83
|
+
setSubmitError(null);
|
|
84
|
+
try {
|
|
85
|
+
const payload = {
|
|
86
|
+
patientUuid,
|
|
87
|
+
patientName: patient?.name?.[0]
|
|
88
|
+
? `${patient.name[0].given?.join(' ')} ${patient.name[0].family}`.trim()
|
|
89
|
+
: '',
|
|
90
|
+
patientBirthDate: patient?.birthDate ?? '',
|
|
91
|
+
patientGender: patient?.gender ?? '',
|
|
92
|
+
providerUuid: '',
|
|
93
|
+
providerName: '',
|
|
94
|
+
locationUuid: selectedLocation.uuid,
|
|
95
|
+
locationName: selectedLocation.display,
|
|
96
|
+
policyNumber,
|
|
97
|
+
orderUuids: prescriptionOrders.map((o) => o.drug.uuid),
|
|
98
|
+
digitallySigned,
|
|
99
|
+
printRequested,
|
|
100
|
+
observations,
|
|
101
|
+
};
|
|
102
|
+
await PrescriptionService.createPrescription(payload as any);
|
|
103
|
+
// Limpar após sucesso
|
|
104
|
+
setPrescriptionOrders([]);
|
|
105
|
+
setSelectedLocation(null);
|
|
106
|
+
setPolicyNumber('');
|
|
107
|
+
setDigitallySigned(false);
|
|
108
|
+
setPrintRequested(false);
|
|
109
|
+
setObservations('');
|
|
110
|
+
} catch (e: any) {
|
|
111
|
+
setSubmitError(e?.message ?? t('submitError', 'Erro ao salvar a prescrição.'));
|
|
112
|
+
} finally {
|
|
113
|
+
setIsSubmitting(false);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
242
117
|
return (
|
|
243
118
|
<Workspace2 title={t('activePrescriptionsWorkspaceTitle', 'Prescrições ativas')} hasUnsavedChanges={false}>
|
|
244
119
|
<div className={styles.workspace}>
|
|
@@ -264,6 +139,7 @@ const Root: React.FC<RootProps> = ({ patientUuid, patient }) => {
|
|
|
264
139
|
</TabList>
|
|
265
140
|
<TabPanels>
|
|
266
141
|
<TabPanel className={styles.tabContent}>
|
|
142
|
+
{/* Local da visita */}
|
|
267
143
|
<div className={styles.fieldGroup}>
|
|
268
144
|
<ComboBox
|
|
269
145
|
id="visit-location"
|
|
@@ -277,6 +153,7 @@ const Root: React.FC<RootProps> = ({ patientUuid, patient }) => {
|
|
|
277
153
|
/>
|
|
278
154
|
</div>
|
|
279
155
|
|
|
156
|
+
{/* Número da apólice */}
|
|
280
157
|
<div className={styles.fieldGroup}>
|
|
281
158
|
<TextInput
|
|
282
159
|
id="insurance-policy"
|
|
@@ -286,68 +163,119 @@ const Root: React.FC<RootProps> = ({ patientUuid, patient }) => {
|
|
|
286
163
|
onChange={(event) => setPolicyNumber(event.target.value)}
|
|
287
164
|
/>
|
|
288
165
|
</div>
|
|
166
|
+
|
|
167
|
+
{/* Assinatura digital e impressão */}
|
|
289
168
|
<div className={styles.fieldGroup}>
|
|
290
169
|
<Checkbox
|
|
291
170
|
id="sign-prescription-checkbox"
|
|
292
171
|
labelText={t('signPrescriptionLabel', 'Assinar digitalmente a prescrição')}
|
|
293
172
|
checked={digitallySigned}
|
|
294
|
-
onChange={() => setDigitallySigned(
|
|
173
|
+
onChange={(_: any, { checked }: any) => setDigitallySigned(checked)}
|
|
295
174
|
/>
|
|
296
175
|
<Checkbox
|
|
297
176
|
id="print-prescription-checkbox"
|
|
298
177
|
labelText={t('printPrescriptionLabel', 'Imprimir a prescrição')}
|
|
299
178
|
checked={printRequested}
|
|
300
|
-
onChange={() => setPrintRequested(
|
|
179
|
+
onChange={(_: any, { checked }: any) => setPrintRequested(checked)}
|
|
301
180
|
/>
|
|
302
181
|
</div>
|
|
303
182
|
|
|
183
|
+
{/* Observações */}
|
|
304
184
|
<div className={styles.fieldGroup}>
|
|
305
|
-
<
|
|
306
|
-
|
|
185
|
+
<TextArea
|
|
186
|
+
id="observations"
|
|
187
|
+
labelText={t('observations', 'Observações')}
|
|
188
|
+
placeholder={t('observationsPlaceholder', 'Observações da prescrição...')}
|
|
189
|
+
value={observations}
|
|
190
|
+
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => setObservations(event.target.value)}
|
|
191
|
+
rows={3}
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Lista de medicamentos da prescrição */}
|
|
196
|
+
<div className={styles.fieldGroup}>
|
|
197
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
|
|
198
|
+
<h3 style={{ margin: 0 }}>{t('medicationsList', 'Medicamentos')}</h3>
|
|
199
|
+
<Button
|
|
200
|
+
size="sm"
|
|
201
|
+
hasIconOnly
|
|
202
|
+
kind="ghost"
|
|
203
|
+
renderIcon={() => (
|
|
204
|
+
<svg width="20" height="20" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
205
|
+
<path d="M16 6V26" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
206
|
+
<path d="M6 16H26" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
207
|
+
</svg>
|
|
208
|
+
)}
|
|
209
|
+
iconDescription={t('addMedication', 'Adicionar medicamento')}
|
|
210
|
+
onClick={() => { setEditIndex(null); setShowAddMedication(true); }}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{prescriptionOrders.length === 0 ? (
|
|
307
215
|
<p>{t('noMedicationsAdded', 'Nenhum medicamento adicionado ainda.')}</p>
|
|
308
216
|
) : (
|
|
309
217
|
<ul className={styles.medicationsList}>
|
|
310
|
-
{
|
|
218
|
+
{prescriptionOrders.map((order, idx) => (
|
|
311
219
|
<li key={idx} className={styles.medicationItem}>
|
|
312
220
|
<span>
|
|
313
|
-
<b>{
|
|
221
|
+
<b>{order.drug.display}</b> — {order.dosage} {order.unit}
|
|
222
|
+
{order.route ? ` · ${order.route}` : ''} · {order.frequency}
|
|
314
223
|
</span>
|
|
315
|
-
<
|
|
224
|
+
<Button size="sm" kind="ghost" onClick={() => openEditMedication(idx)}>
|
|
316
225
|
{t('edit', 'Editar')}
|
|
317
|
-
</
|
|
318
|
-
<
|
|
226
|
+
</Button>
|
|
227
|
+
<Button size="sm" kind="danger--ghost" onClick={() => handleRemoveMedication(idx)}>
|
|
319
228
|
{t('remove', 'Remover')}
|
|
320
|
-
</
|
|
229
|
+
</Button>
|
|
321
230
|
</li>
|
|
322
231
|
))}
|
|
323
232
|
</ul>
|
|
324
233
|
)}
|
|
325
|
-
<button type="button" onClick={openAddMedication} className={styles.addMedicationBtn}>
|
|
326
|
-
{t('addMedication', '+ Adicionar medicamento')}
|
|
327
|
-
</button>
|
|
328
234
|
</div>
|
|
329
235
|
|
|
236
|
+
{/* Formulário de adição/edição de medicamento */}
|
|
330
237
|
{showAddMedication && (
|
|
331
238
|
<div className={styles.overlayForm}>
|
|
332
239
|
<div className={styles.overlayContent}>
|
|
333
|
-
<h3>
|
|
334
|
-
{editIndex
|
|
335
|
-
? t('
|
|
336
|
-
: t('
|
|
240
|
+
<h3 style={{ marginTop: 0 }}>
|
|
241
|
+
{editIndex !== null
|
|
242
|
+
? t('editMedication', 'Editar medicamento')
|
|
243
|
+
: t('addMedication', 'Adicionar medicamento')}
|
|
337
244
|
</h3>
|
|
338
|
-
<
|
|
339
|
-
initialData={editIndex !== null ?
|
|
340
|
-
|
|
341
|
-
onCancel={() => {
|
|
342
|
-
setShowAddMedication(false);
|
|
343
|
-
setEditIndex(null);
|
|
344
|
-
}}
|
|
345
|
-
t={t}
|
|
245
|
+
<AddDrugPrescription
|
|
246
|
+
initialData={editIndex !== null ? prescriptionOrders[editIndex] : undefined}
|
|
247
|
+
onSave={handleSaveMedication}
|
|
248
|
+
onCancel={() => { setShowAddMedication(false); setEditIndex(null); }}
|
|
346
249
|
/>
|
|
347
250
|
</div>
|
|
348
251
|
</div>
|
|
349
252
|
)}
|
|
253
|
+
|
|
254
|
+
{/* Erro de envio */}
|
|
255
|
+
{submitError && (
|
|
256
|
+
<InlineNotification
|
|
257
|
+
lowContrast
|
|
258
|
+
kind="error"
|
|
259
|
+
title={t('submitError', 'Erro ao salvar a prescrição.')}
|
|
260
|
+
subtitle={submitError}
|
|
261
|
+
onCloseButtonClick={() => setSubmitError(null)}
|
|
262
|
+
/>
|
|
263
|
+
)}
|
|
264
|
+
|
|
265
|
+
{/* Botão de salvar prescrição */}
|
|
266
|
+
<div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
|
|
267
|
+
<Button
|
|
268
|
+
kind="primary"
|
|
269
|
+
disabled={!patientUuid || !selectedLocation || prescriptionOrders.length === 0 || isSubmitting}
|
|
270
|
+
onClick={handleSubmitPrescription}
|
|
271
|
+
>
|
|
272
|
+
{isSubmitting
|
|
273
|
+
? <InlineLoading description={t('saving', 'Salvando...')} />
|
|
274
|
+
: t('createPrescriptionButtonLabel', 'Salvar Prescrição')}
|
|
275
|
+
</Button>
|
|
276
|
+
</div>
|
|
350
277
|
</TabPanel>
|
|
278
|
+
|
|
351
279
|
<TabPanel className={styles.tabContent}>
|
|
352
280
|
<InlineNotification
|
|
353
281
|
lowContrast
|