source: frontend/src/pages/procedures/ProcedureList.js@ f05de05

Last change on this file since f05de05 was f05de05, checked in by MBK <marija.karapandzova@…>, 4 months ago

Add pages and routing for lab tests, procedures and billing with api services

  • Property mode set to 100644
File size: 13.0 KB
Line 
1import React, { useState, useEffect } from 'react';
2import { patientService } from '../../services/patientService';
3import { procedureService } from '../../services/procedureService';
4import { medicalRecordService } from '../../services/medicalRecordService';
5import ErrorAlert from '../../components/ErrorAlert';
6
7function ProcedureList() {
8 const [patient, setPatient] = useState(null);
9 const [embg, setEmbg] = useState('');
10 const [searched, setSearched] = useState(false);
11 const [loading, setLoading] = useState(false);
12 const [error, setError] = useState(null);
13
14 const [availableProcedures, setAvailableProcedures] = useState([]);
15 const [performedProcedures, setPerformedProcedures] = useState([]);
16 const [procedureResults, setProcedureResults] = useState([]);
17
18 const [showRequestForm, setShowRequestForm] = useState(false);
19 const [requestData, setRequestData] = useState({
20 procedureId: '',
21 procedureDate: new Date().toISOString().split('T')[0],
22 notes: '',
23 });
24
25 const handleSearch = async (e) => {
26 e.preventDefault();
27 setError(null);
28 setLoading(true);
29
30 try {
31 if (!embg.trim()) {
32 setError('Please enter an EMBG');
33 setLoading(false);
34 return;
35 }
36
37 const patientResponse = await patientService.getPatientByEmbg(embg);
38 setPatient(patientResponse.data);
39 setSearched(true);
40
41 // Fetch medical record for patient
42 const medicalRecordRes = await medicalRecordService.getMedicalRecordByPatientId(patientResponse.data.patientId);
43
44 // Fetch available procedures from database
45 try {
46 const proceduresResponse = await procedureService.getAllProcedures();
47 console.log('Procedures response:', proceduresResponse);
48 console.log('Procedures data:', proceduresResponse.data);
49 setAvailableProcedures(Array.isArray(proceduresResponse.data) ? proceduresResponse.data : []);
50 } catch (procErr) {
51 console.error('Failed to fetch procedures:', procErr);
52 setError('Failed to fetch procedures: ' + procErr.message);
53 setAvailableProcedures([]);
54 }
55
56 // Fetch performed procedures for patient
57 try {
58 const performedRes = await procedureService.getPerformedProceduresForPatient(patientResponse.data.patientId);
59 setPerformedProcedures(Array.isArray(performedRes.data) ? performedRes.data : []);
60 } catch (err) {
61 console.error('Failed to fetch performed procedures:', err);
62 setPerformedProcedures([]);
63 }
64
65 // Fetch procedure results for medical record
66 try {
67 const resultsResponse = await procedureService.getProcedureResultsForMedicalRecord(medicalRecordRes.data.recordId);
68 setProcedureResults(Array.isArray(resultsResponse.data) ? resultsResponse.data : []);
69 } catch (err) {
70 console.error('Failed to fetch procedure results:', err);
71 setProcedureResults([]);
72 }
73 } catch (err) {
74 console.error('Search error:', err);
75 setError(`Error: ${err.response?.data?.error || err.message || 'Patient not found'}`);
76 setPatient(null);
77 } finally {
78 setLoading(false);
79 }
80 };
81
82 const handleRequestProcedure = async (e) => {
83 e.preventDefault();
84 setError(null);
85
86 if (!requestData.procedureId) {
87 setError('Please select a procedure');
88 return;
89 }
90
91 if (!patient) {
92 setError('Patient not found');
93 return;
94 }
95
96 try {
97 setLoading(true);
98
99 // Get doctor ID from localStorage (set during login)
100 const doctorId = localStorage.getItem('doctorId') || 1;
101
102 const request = {
103 procedureId: parseInt(requestData.procedureId),
104 doctorId: parseInt(doctorId),
105 patientId: patient.patientId,
106 procedureDate: requestData.procedureDate,
107 notes: requestData.notes,
108 };
109
110 console.log('Sending request:', request);
111 await procedureService.recordProcedure(request);
112
113 // Refresh the performed procedures
114 const performedRes = await procedureService.getPerformedProceduresForPatient(patient.patientId);
115 setPerformedProcedures(performedRes.data);
116
117 // Reset form
118 setRequestData({
119 procedureId: '',
120 procedureDate: new Date().toISOString().split('T')[0],
121 notes: '',
122 });
123 setShowRequestForm(false);
124 setError(null);
125 } catch (err) {
126 console.error('Error details:', err);
127 console.error('Response:', err.response);
128 const errorMsg = err.response?.data?.error || err.message || 'Unknown error';
129 setError('Failed to request procedure: ' + errorMsg);
130 } finally {
131 setLoading(false);
132 }
133 };
134
135 return (
136 <div>
137 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Procedures</h1>
138
139 {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
140
141 {/* Search Form */}
142 <div className="bg-white rounded-lg shadow p-6 mb-6">
143 <h2 className="text-xl font-bold mb-4">Search Patient</h2>
144 <form onSubmit={handleSearch} className="space-y-4">
145 <div className="flex gap-4">
146 <div className="flex-1">
147 <label className="block text-sm font-semibold mb-2">Patient EMBG</label>
148 <input
149 type="text"
150 value={embg}
151 onChange={(e) => setEmbg(e.target.value)}
152 placeholder="e.g., 1402994123456"
153 className="w-full px-4 py-2 border rounded-lg"
154 />
155 </div>
156 <div className="flex items-end">
157 <button
158 type="submit"
159 disabled={loading}
160 className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400"
161 >
162 {loading ? 'Searching...' : 'Search'}
163 </button>
164 </div>
165 </div>
166 </form>
167 </div>
168
169 {/* Patient Procedures */}
170 {searched && patient && (
171 <div className="space-y-6">
172 {/* Patient Info */}
173 <div className="bg-white rounded-lg shadow p-6">
174 <div className="flex justify-between items-start mb-4">
175 <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2>
176 {!showRequestForm && (
177 <button
178 onClick={() => setShowRequestForm(true)}
179 className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700"
180 >
181 Request Procedure
182 </button>
183 )}
184 </div>
185 <div className="grid grid-cols-4 gap-4">
186 <div>
187 <p className="text-sm text-gray-600">EMBG</p>
188 <p className="font-semibold">{patient.embg}</p>
189 </div>
190 <div>
191 <p className="text-sm text-gray-600">Blood Type</p>
192 <p className="font-semibold">{patient.bloodType || 'N/A'}</p>
193 </div>
194 <div>
195 <p className="text-sm text-gray-600">Date of Birth</p>
196 <p className="font-semibold">{patient.dateOfBirth}</p>
197 </div>
198 </div>
199 </div>
200
201 {/* Request Procedure Form */}
202 {showRequestForm && (
203 <div className="bg-white rounded-lg shadow p-6">
204 <h3 className="text-xl font-bold mb-4">Request Procedure</h3>
205 <form onSubmit={handleRequestProcedure} className="space-y-4">
206 <div className="grid grid-cols-2 gap-4">
207 <div>
208 <label className="block text-sm font-semibold mb-2">Procedure</label>
209 <select
210 value={requestData.procedureId}
211 onChange={(e) => setRequestData({ ...requestData, procedureId: e.target.value })}
212 className="w-full px-4 py-2 border rounded-lg"
213 required
214 >
215 <option value="">Select a procedure</option>
216 {availableProcedures.map((proc) => (
217 <option key={proc.procedureId} value={proc.procedureId}>
218 {proc.procedureType}
219 </option>
220 ))}
221 </select>
222 </div>
223 <div>
224 <label className="block text-sm font-semibold mb-2">Procedure Date</label>
225 <input
226 type="date"
227 value={requestData.procedureDate}
228 onChange={(e) => setRequestData({ ...requestData, procedureDate: e.target.value })}
229 className="w-full px-4 py-2 border rounded-lg"
230 />
231 </div>
232 </div>
233 <div>
234 <label className="block text-sm font-semibold mb-2">Notes</label>
235 <textarea
236 value={requestData.notes}
237 onChange={(e) => setRequestData({ ...requestData, notes: e.target.value })}
238 placeholder="Additional notes for the procedure"
239 className="w-full px-4 py-2 border rounded-lg"
240 rows="3"
241 />
242 </div>
243 <div className="flex gap-4">
244 <button
245 type="submit"
246 disabled={loading}
247 className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700 disabled:bg-gray-400"
248 >
249 {loading ? 'Requesting...' : 'Request Procedure'}
250 </button>
251 <button
252 type="button"
253 onClick={() => setShowRequestForm(false)}
254 className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500"
255 >
256 Cancel
257 </button>
258 </div>
259 </form>
260 </div>
261 )}
262
263 {/* Performed Procedures */}
264 {performedProcedures && performedProcedures.length > 0 && (
265 <div className="bg-white rounded-lg shadow p-6">
266 <h3 className="text-xl font-bold mb-4">Performed Procedures</h3>
267 <div className="space-y-3">
268 {performedProcedures.map((proc) => (
269 <div key={proc.performedId} className="border-l-4 border-purple-500 pl-4 py-2">
270 <p className="font-semibold text-lg">{proc.procedure?.procedureType || 'Procedure'}</p>
271 <p className="text-sm text-gray-600">Requested by: {proc.doctor?.firstName} {proc.doctor?.lastName}</p>
272 <p className="text-sm text-gray-600">Procedure Date: {proc.procedureDate}</p>
273 {proc.notes && (
274 <p className="text-sm text-gray-600">Notes: {proc.notes}</p>
275 )}
276 </div>
277 ))}
278 </div>
279 </div>
280 )}
281
282 {/* Procedure Results */}
283 {procedureResults && procedureResults.length > 0 && (
284 <div className="bg-white rounded-lg shadow p-6">
285 <h3 className="text-xl font-bold mb-4">Procedure Results</h3>
286 <div className="space-y-4">
287 {procedureResults.map((result) => (
288 <div key={result.resultId} className="border-l-4 border-orange-500 pl-4 py-3 bg-orange-50 rounded">
289 <p className="font-semibold text-lg text-orange-700">{result.procedure?.procedureType || 'Procedure'}</p>
290 <p className="text-sm text-gray-700 mt-2"><strong>Outcome:</strong> {result.resultDescription}</p>
291 <p className="text-sm text-gray-600">Result Date: {result.resultDate}</p>
292 </div>
293 ))}
294 </div>
295 </div>
296 )}
297
298 {/* Link to submit results */}
299 {performedProcedures && performedProcedures.length > 0 && (
300 <div className="bg-yellow-50 rounded-lg p-6">
301 <h3 className="text-lg font-semibold text-yellow-800 mb-3">Submit Procedure Results</h3>
302 <p className="text-sm text-gray-700 mb-4">
303 {performedProcedures.length} procedure{performedProcedures.length !== 1 ? 's' : ''} awaiting results
304 </p>
305 <a
306 href="/procedures/results"
307 className="inline-block bg-yellow-600 text-white px-6 py-2 rounded hover:bg-yellow-700"
308 >
309 Submit Procedure Results
310 </a>
311 </div>
312 )}
313
314 {/* No procedures message */}
315 {!performedProcedures || (performedProcedures.length === 0 && !procedureResults?.length) && (
316 <div className="bg-blue-50 rounded-lg p-6 text-center">
317 <p className="text-gray-600">No procedures for this patient</p>
318 </div>
319 )}
320 </div>
321 )}
322
323 {/* No search performed */}
324 {!searched && (
325 <div className="bg-gray-50 rounded-lg p-12 text-center">
326 <p className="text-gray-600 text-lg">Enter a patient EMBG to request procedures</p>
327 </div>
328 )}
329 </div>
330 );
331}
332
333export default ProcedureList;
Note: See TracBrowser for help on using the repository browser.