Changeset 9af201e for frontend/src/pages
- Timestamp:
- 09/04/26 19:08:08 (2 weeks ago)
- Branches:
- master
- Children:
- cdcff72
- Parents:
- e1f74f6
- Location:
- frontend/src/pages
- Files:
-
- 3 added
- 1 deleted
- 17 edited
-
Dashboard.js (modified) (4 diffs)
-
Login.js (modified) (1 diff)
-
ProtectedRoute.js (deleted)
-
appointments/AppointmentForm.js (modified) (1 diff)
-
appointments/AppointmentList.js (modified) (4 diffs)
-
billing/BillingDetail.js (modified) (5 diffs)
-
billing/BillingList.js (modified) (4 diffs)
-
departments/DepartmentDetail.js (modified) (2 diffs)
-
departments/DoctorsByDepartment.js (modified) (2 diffs)
-
doctors/DoctorDetail.js (modified) (1 diff)
-
doctors/DoctorForm.js (modified) (1 diff)
-
lab-tests/LabResultForm.js (modified) (1 diff)
-
lab-tests/LabTestList.js (modified) (5 diffs)
-
medical-records/MedicalRecordDetail.js (modified) (5 diffs)
-
medical-records/MedicalRecordList.js (modified) (3 diffs)
-
medical-reports/MedicalReportList.js (modified) (5 diffs)
-
patients/PatientDetail.js (added)
-
patients/PatientForm.js (added)
-
patients/PatientList.js (added)
-
procedures/ProcedureList.js (modified) (5 diffs)
-
procedures/ProcedureResultForm.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/pages/Dashboard.js
re1f74f6 r9af201e 6 6 import { labService } from '../services/labService'; 7 7 import { billingService } from '../services/billingService'; 8 import { medicalRecordService } from '../services/medicalRecordService';9 8 import Loading from '../components/Loading'; 10 9 … … 19 18 const [pendingLabTests, setPendingLabTests] = useState([]); 20 19 const [billings, setBillings] = useState([]); 21 const [medicalRecords, setMedicalRecords] = useState([]);22 20 const [patientBillings, setPatientBillings] = useState([]); 23 21 const [loading, setLoading] = useState(true); … … 33 31 if (isPatient) { 34 32 // For patients, fetch their own appointments, medical records, and billing 35 const [appointmentsRes, medicalRes,billingRes] = await Promise.all([33 const [appointmentsRes, billingRes] = await Promise.all([ 36 34 appointmentService.getAppointmentsForPatient(user.patientId), 37 medicalRecordService.getMedicalRecordByPatientId(user.patientId),38 35 billingService.getBillingHistoryForPatient(user.patientId) 39 36 ]); 40 37 setUserAppointments(appointmentsRes.data || []); 41 // Handle medical records - could be single object or array42 const medicalData = medicalRes.data;43 const medicalArray = Array.isArray(medicalData) ? medicalData : (medicalData ? [medicalData] : []);44 setMedicalRecords(medicalArray);45 38 setPatientBillings(billingRes.data || []); 46 39 } else if (isDoctor) { … … 82 75 83 76 fetchStats(); 84 }, [ user.userId, isPatient, isDoctor, isLabTechnician]);77 }, []); 85 78 86 79 if (loading) return <Loading />; -
frontend/src/pages/Login.js
re1f74f6 r9af201e 75 75 76 76 return ( 77 <div className="min-h-screen bg-blue-200 flex items-center justify-center p-4"> 78 <div className="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md"> 79 <div className="text-center mb-8"> 80 <h1 className="text-5xl font-bold text-purple-600 mb-2">Medora</h1> 81 <p className="text-gray-600">Hospital Management System</p> 77 <div className="min-h-screen bg-blue-200 flex items-center justify-center p-4"> 78 <div className="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md"> 79 <div className="text-center mb-8"> 80 <h1 className="text-5xl font-bold text-purple-600 mb-2">Medora</h1> 81 <p className="text-gray-600">Hospital Management System</p> 82 </div> 83 84 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 85 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 86 87 <form onSubmit={handleLogin} className="space-y-6"> 88 <div> 89 <label className="block text-sm font-normal text-gray-700 mb-2"> 90 Username (EMBG for Patients) 91 </label> 92 <input 93 type="text" 94 value={username} 95 onChange={(e) => setUsername(e.target.value)} 96 placeholder="Enter your username or EMBG" 97 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 98 disabled={loading} 99 /> 82 100 </div> 83 101 84 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 85 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 102 <div> 103 <label className="block text-sm font-normal text-gray-700 mb-2"> 104 Password 105 </label> 106 <input 107 type="password" 108 value={password} 109 onChange={(e) => setPassword(e.target.value)} 110 placeholder="Enter your password" 111 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 112 disabled={loading} 113 /> 114 </div> 86 115 87 <form onSubmit={handleLogin} className="space-y-6"> 88 <div> 89 <label className="block text-sm font-normal text-gray-700 mb-2"> 90 Username (EMBG for Patients) 91 </label> 92 <input 93 type="text" 94 value={username} 95 onChange={(e) => setUsername(e.target.value)} 96 placeholder="Enter your username or EMBG" 97 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 98 disabled={loading} 99 /> 100 </div> 116 <button 117 type="submit" 118 disabled={loading} 119 className="w-full bg-purple-600 text-white font-bold py-3 rounded-lg hover:bg-purple-700 transition disabled:opacity-50 disabled:cursor-not-allowed" 120 > 121 {loading ? 'Logging in...' : 'Login'} 122 </button> 123 </form> 101 124 102 <div> 103 <label className="block text-sm font-normal text-gray-700 mb-2"> 104 Password 105 </label> 106 <input 107 type="password" 108 value={password} 109 onChange={(e) => setPassword(e.target.value)} 110 placeholder="Enter your password" 111 className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition" 112 disabled={loading} 113 /> 114 </div> 115 116 <button 117 type="submit" 118 disabled={loading} 119 className="w-full bg-purple-600 text-white font-bold py-3 rounded-lg hover:bg-purple-700 transition disabled:opacity-50 disabled:cursor-not-allowed" 120 > 121 {loading ? 'Logging in...' : 'Login'} 122 </button> 123 </form> 124 125 <div className="mt-8 p-4 bg-purple-50 rounded-lg border border-purple-200"> 126 <p className="text-sm text-gray-700 font-normal mb-2">Demo Credentials:</p> 127 <p className="text-sm text-gray-600 mb-2"> 128 <strong>Admin:</strong> username: admin | password: admin123 129 </p> 130 <p className="text-sm text-gray-600 mb-2"> 131 <strong>Doctor:</strong> username: ivan.stojanov@medora.com | password: doctor123 132 </p> 133 <p className="text-sm text-gray-600"> 134 <strong>Patient:</strong> username: [EMBG] | password: password123 135 </p> 136 </div> 125 <div className="mt-8 p-4 bg-purple-50 rounded-lg border border-purple-200"> 126 <p className="text-sm text-gray-700 font-normal mb-2">Demo Credentials:</p> 127 <p className="text-sm text-gray-600 mb-2"> 128 <strong>Admin:</strong> username: admin | password: admin123 129 </p> 130 <p className="text-sm text-gray-600 mb-2"> 131 <strong>Doctor:</strong> username: ivan.stojanov@medora.com | password: doctor123 132 </p> 133 <p className="text-sm text-gray-600"> 134 <strong>Patient:</strong> username: [EMBG] | password: password123 135 </p> 137 136 </div> 138 137 </div> 138 </div> 139 139 ); 140 140 } -
frontend/src/pages/appointments/AppointmentForm.js
re1f74f6 r9af201e 26 26 27 27 useEffect(() => { 28 const fetchData = async () => { 29 try { 30 if (!isPatient) { 31 const patientsRes = await patientService.getAllPatients(); 32 setPatients(patientsRes.data); 33 } 34 35 const doctorsRes = await doctorService.getAllDoctors(); 36 setDoctors(doctorsRes.data); 37 } catch (err) { 38 setError('Failed to fetch doctors'); 39 } 40 }; 41 28 42 fetchData(); 29 }, []); 30 31 const fetchData = async () => { 32 try { 33 // For patients, we don't need to fetch all patients 34 if (!isPatient) { 35 const patientsRes = await patientService.getAllPatients(); 36 setPatients(patientsRes.data); 37 } 38 39 const doctorsRes = await doctorService.getAllDoctors(); 40 setDoctors(doctorsRes.data); 41 } catch (err) { 42 setError('Failed to fetch doctors'); 43 } 44 }; 43 }, [isPatient]); 45 44 46 45 const handleChange = (e) => { -
frontend/src/pages/appointments/AppointmentList.js
re1f74f6 r9af201e 15 15 16 16 useEffect(() => { 17 fetchAppointments(); 17 const fetchAppointmentsData = async () => { 18 try { 19 setLoading(true); 20 let response; 21 if (patientId) { 22 response = await appointmentService.getAppointmentsForPatient(patientId); 23 } else if (doctorId) { 24 response = await appointmentService.getAppointmentsForDoctor(doctorId); 25 } else { 26 response = await appointmentService.getAllAppointments(); 27 } 28 setAppointments(response.data || []); 29 } catch (err) { 30 setError('Failed to fetch appointments'); 31 console.error(err); 32 } finally { 33 setLoading(false); 34 } 35 }; 36 37 fetchAppointmentsData(); 18 38 }, [doctorId, patientId]); 19 20 const fetchAppointments = async () => {21 try {22 setLoading(true);23 let response;24 if (patientId) {25 response = await appointmentService.getAppointmentsForPatient(patientId);26 } else if (doctorId) {27 response = await appointmentService.getAppointmentsForDoctor(doctorId);28 } else if (user.role === 'PATIENT') {29 response = await appointmentService.getAppointmentsForPatient(user.patientId);30 } else if (user.role === 'DOCTOR') {31 response = await appointmentService.getAppointmentsForDoctor(user.doctorId);32 } else {33 response = await appointmentService.getAllAppointments();34 }35 setAppointments(response.data);36 } catch (err) {37 setError('Failed to fetch appointments');38 console.error(err);39 } finally {40 setLoading(false);41 }42 };43 39 44 40 const handleCancelAppointment = async (id) => { … … 47 43 await appointmentService.cancelAppointment(id); 48 44 setAppointments(appointments.map(apt => 49 apt.appointmentId === id ? { ...apt, status: 'CANCELLED' } : apt45 apt.appointmentId === id ? { ...apt, status: 'CANCELLED' } : apt 50 46 )); 51 47 } catch (err) { … … 58 54 59 55 return ( 60 <div>61 <div className="flex justify-between items-center mb-6">62 <h1 className="text-3xl font-bold" style={{ color: '#7c3aed' }}>63 {patientId ? 'Patient Appointments' : doctorId ? 'My Appointments' : 'All Appointments'}64 </h1>65 <Link to="/appointments/new" style={{66 display: 'inline-block',67 background: '#bfdbfe',68 color: '#1e1035',69 padding: '8px 16px',70 borderRadius: '6px',71 textDecoration: 'none',72 fontSize: '14px',73 fontWeight: '400'74 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>75 New Appointment76 </Link>77 </div>56 <div> 57 <div className="flex justify-between items-center mb-6"> 58 <h1 className="text-3xl font-bold" style={{ color: '#7c3aed' }}> 59 {patientId ? 'Patient Appointments' : doctorId ? 'My Appointments' : 'All Appointments'} 60 </h1> 61 <Link to="/appointments/new" style={{ 62 display: 'inline-block', 63 background: '#bfdbfe', 64 color: '#1e1035', 65 padding: '8px 16px', 66 borderRadius: '6px', 67 textDecoration: 'none', 68 fontSize: '14px', 69 fontWeight: '400' 70 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}> 71 New Appointment 72 </Link> 73 </div> 78 74 79 {error && <ErrorAlert message={error} onClose={() => setError(null)} />}75 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 80 76 81 <div className="bg-white rounded-lg shadow overflow-hidden">82 <table className="w-full">83 <thead className="bg-gray-100">77 <div className="bg-white rounded-lg shadow overflow-hidden"> 78 <table className="w-full"> 79 <thead className="bg-gray-100"> 84 80 <tr> 85 81 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> … … 90 86 <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th> 91 87 </tr> 92 </thead>93 <tbody>88 </thead> 89 <tbody> 94 90 {appointments.map(appointment => ( 95 <tr key={appointment.appointmentId} className="border-t hover:bg-gray-50">96 <td className="px-6 py-3">{appointment.patient?.firstName} {appointment.patient?.lastName}</td>97 <td className="px-6 py-3">Dr. {appointment.doctor?.firstName} {appointment.doctor?.lastName}</td>98 <td className="px-6 py-3">{appointment.appointmentDate}</td>99 <td className="px-6 py-3">{appointment.appointmentTime}</td>100 <td className="px-6 py-3">91 <tr key={appointment.appointmentId} className="border-t hover:bg-gray-50"> 92 <td className="px-6 py-3">{appointment.patient?.firstName} {appointment.patient?.lastName}</td> 93 <td className="px-6 py-3">Dr. {appointment.doctor?.firstName} {appointment.doctor?.lastName}</td> 94 <td className="px-6 py-3">{appointment.appointmentDate}</td> 95 <td className="px-6 py-3">{appointment.appointmentTime}</td> 96 <td className="px-6 py-3"> 101 97 <span className={`px-3 py-1 rounded text-sm font-semibold ${ 102 appointment.status === 'SCHEDULED' ? 'bg-purple-100 text-purple-800' :103 appointment.status === 'COMPLETED' ? 'bg-green-100 text-green-800' :104 'bg-red-100 text-red-800'98 appointment.status === 'SCHEDULED' ? 'bg-purple-100 text-purple-800' : 99 appointment.status === 'COMPLETED' ? 'bg-green-100 text-green-800' : 100 'bg-red-100 text-red-800' 105 101 }`}> 106 102 {appointment.status} 107 103 </span> 108 </td>109 <td className="px-6 py-3">110 {appointment.status === 'SCHEDULED' && (111 (user.role === 'DOCTOR' && appointment.doctor?.doctorId !== user.doctorId) ? null : (112 <button113 onClick={() => handleCancelAppointment(appointment.appointmentId)}114 className="text-red-600 hover:underline px-3 py-2 text-sm font-medium"115 >116 Cancel117 </button>118 )119 )}120 </td>121 </tr>104 </td> 105 <td className="px-6 py-3"> 106 {appointment.status === 'SCHEDULED' && ( 107 (user.role === 'DOCTOR' && appointment.doctor?.doctorId !== user.doctorId) ? null : ( 108 <button 109 onClick={() => handleCancelAppointment(appointment.appointmentId)} 110 className="text-red-600 hover:underline px-3 py-2 text-sm font-medium" 111 > 112 Cancel 113 </button> 114 ) 115 )} 116 </td> 117 </tr> 122 118 ))} 123 </tbody> 124 </table> 125 </div> 119 </tbody> 120 </table> 126 121 </div> 122 </div> 127 123 ); 128 124 } -
frontend/src/pages/billing/BillingDetail.js
re1f74f6 r9af201e 22 22 23 23 useEffect(() => { 24 fetchBilling(); 25 }, [id]); 26 27 const fetchBilling = async () => { 28 try { 29 setLoading(true); 30 const response = await billingService.getBillingById(id); 24 const fetchBilling = async () => { 25 try { 26 setLoading(true); 27 const response = await billingService.getBillingById(id); 31 28 setBilling(response.data); 32 29 setPaymentStatus(response.data.paymentStatus); … … 39 36 console.error('Could not fetch billing details:', err); 40 37 } 41 } catch (err) { 42 setError('Failed to fetch billing record'); 43 console.error(err); 44 } finally { 45 setLoading(false); 46 } 47 }; 38 } catch (err) { 39 setError('Failed to fetch billing record'); 40 console.error(err); 41 } finally { 42 setLoading(false); 43 } 44 }; 45 46 fetchBilling(); 47 }, [id]); 48 48 49 49 const handleUpdatePaymentStatus = async () => { … … 81 81 if (user.role === 'DOCTOR' || user.role === 'LAB_TECHNICIAN') { 82 82 return ( 83 <div style={{ padding: '20px', textAlign: 'center' }}>84 <h1 className="text-2xl font-bold" style={{ color: '#7c3aed', marginBottom: '10px' }}>Access Denied</h1>85 <p style={{ color: 'var(--color-neutral-600)' }}>You do not have permission to access billing records.</p>86 </div>83 <div style={{ padding: '20px', textAlign: 'center' }}> 84 <h1 className="text-2xl font-bold" style={{ color: '#7c3aed', marginBottom: '10px' }}>Access Denied</h1> 85 <p style={{ color: 'var(--color-neutral-600)' }}>You do not have permission to access billing records.</p> 86 </div> 87 87 ); 88 88 } … … 90 90 if (!billing) { 91 91 return ( 92 <div>93 <ErrorAlert message="Billing record not found" onClose={() => navigate('/billing')} />94 </div>92 <div> 93 <ErrorAlert message="Billing record not found" onClose={() => navigate('/billing')} /> 94 </div> 95 95 ); 96 96 } 97 97 98 98 return ( 99 <div> 100 <div className="flex justify-between items-center mb-6"> 101 <h1 className="text-3xl font-bold" style={{ color: '#7c3aed' }}>Billing Record #{billing.billId}</h1> 102 <div className="space-x-2"> 103 <button 104 onClick={handleDownloadInvoice} 105 disabled={downloading} 106 className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 disabled:opacity-50" 107 > 108 {downloading ? 'Downloading...' : 'Download Invoice (PDF)'} 109 </button> 110 <button onClick={() => navigate('/billing')} className="bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400"> 111 Back 112 </button> 113 </div> 99 <div> 100 <div className="flex justify-between items-center mb-6"> 101 <h1 className="text-3xl font-bold" style={{ color: '#7c3aed' }}>Billing Record #{billing.billId}</h1> 102 <div className="space-x-2"> 103 <button 104 onClick={handleDownloadInvoice} 105 disabled={downloading} 106 className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 disabled:opacity-50" 107 > 108 {downloading ? 'Downloading...' : 'Download Invoice (PDF)'} 109 </button> 110 <button onClick={() => navigate('/billing')} className="bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400"> 111 Back 112 </button> 114 113 </div> 115 116 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 117 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 118 119 <div className={`grid gap-6 mb-6 ${isPatient ? 'grid-cols-1 lg:grid-cols-2' : 'grid-cols-1 lg:grid-cols-3'}`}> 114 </div> 115 116 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 117 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 118 119 <div className={`grid gap-6 mb-6 ${isPatient ? 'grid-cols-1 lg:grid-cols-2' : 'grid-cols-1 lg:grid-cols-3'}`}> 120 <div className="bg-white rounded-lg shadow p-6"> 121 <h2 className="text-xl font-bold mb-4">Billing Information</h2> 122 <div className="space-y-3"> 123 <InfoRow label="Patient" value={billing.patientName} /> 124 <InfoRow label="Total Cost" value={`$${billing.totalCost}`} /> 125 <InfoRow label="Current Status" value={billing.paymentStatus} /> 126 <InfoRow label="Payment Date" value={billing.paymentDate || 'Not paid'} /> 127 </div> 128 </div> 129 130 {!isPatient && ( 120 131 <div className="bg-white rounded-lg shadow p-6"> 121 <h2 className="text-xl font-bold mb-4">Billing Information</h2> 122 <div className="space-y-3"> 123 <InfoRow label="Patient" value={billing.patientName} /> 124 <InfoRow label="Total Cost" value={`$${billing.totalCost}`} /> 125 <InfoRow label="Current Status" value={billing.paymentStatus} /> 126 <InfoRow label="Payment Date" value={billing.paymentDate || 'Not paid'} /> 132 <h2 className="text-xl font-bold mb-4">Update Payment Status</h2> 133 <div className="space-y-4"> 134 <div> 135 <label className="block text-sm font-semibold mb-2">Payment Status</label> 136 <select 137 value={paymentStatus} 138 onChange={(e) => setPaymentStatus(e.target.value)} 139 className="w-full px-4 py-2 border rounded-lg" 140 > 141 <option value="PENDING">Pending</option> 142 <option value="PAID">Paid</option> 143 <option value="CANCELLED">Cancelled</option> 144 </select> 145 </div> 146 <button 147 onClick={handleUpdatePaymentStatus} 148 disabled={updating || paymentStatus === billing.paymentStatus} 149 className="w-full bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 disabled:opacity-50" 150 > 151 {updating ? 'Updating...' : 'Update Status'} 152 </button> 127 153 </div> 128 154 </div> 129 130 {!isPatient && ( 131 <div className="bg-white rounded-lg shadow p-6"> 132 <h2 className="text-xl font-bold mb-4">Update Payment Status</h2> 133 <div className="space-y-4"> 134 <div> 135 <label className="block text-sm font-semibold mb-2">Payment Status</label> 136 <select 137 value={paymentStatus} 138 onChange={(e) => setPaymentStatus(e.target.value)} 139 className="w-full px-4 py-2 border rounded-lg" 140 > 141 <option value="PENDING">Pending</option> 142 <option value="PAID">Paid</option> 143 <option value="CANCELLED">Cancelled</option> 144 </select> 145 </div> 146 <button 147 onClick={handleUpdatePaymentStatus} 148 disabled={updating || paymentStatus === billing.paymentStatus} 149 className="w-full bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 disabled:opacity-50" 150 > 151 {updating ? 'Updating...' : 'Update Status'} 152 </button> 153 </div> 154 </div> 155 )} 156 157 <div className="bg-white rounded-lg shadow p-6"> 158 <h2 className="text-xl font-bold mb-4">Patient Details</h2> 159 <div className="space-y-3"> 160 {billingDetail && ( 161 <> 162 <InfoRow label="EMBG" value={billingDetail.patientEmbg} /> 163 <InfoRow label="Phone" value={billingDetail.patientPhone} /> 164 <InfoRow label="Bill Date" value={billingDetail.billDate} /> 165 </> 166 )} 155 )} 156 157 <div className="bg-white rounded-lg shadow p-6"> 158 <h2 className="text-xl font-bold mb-4">Patient Details</h2> 159 <div className="space-y-3"> 160 {billingDetail && ( 161 <> 162 <InfoRow label="EMBG" value={billingDetail.patientEmbg} /> 163 <InfoRow label="Phone" value={billingDetail.patientPhone} /> 164 <InfoRow label="Bill Date" value={billingDetail.billDate} /> 165 </> 166 )} 167 </div> 168 </div> 169 </div> 170 171 <div className="bg-white rounded-lg shadow p-6"> 172 <h2 className="text-xl font-bold mb-4">Itemized Services</h2> 173 174 {billingDetail && (billingDetail.procedures.length > 0 || billingDetail.labTests.length > 0) ? ( 175 <div className="space-y-4"> 176 {billingDetail.procedures.length > 0 && ( 177 <div> 178 <h3 className="font-semibold text-lg mb-2">Procedures</h3> 179 <table className="w-full"> 180 <thead className="bg-gray-100"> 181 <tr> 182 <th className="px-4 py-2 text-left">Description</th> 183 <th className="px-4 py-2 text-right">Cost</th> 184 </tr> 185 </thead> 186 <tbody> 187 {billingDetail.procedures.map((proc, idx) => ( 188 <tr key={idx} className="border-t"> 189 <td className="px-4 py-2">{proc.description}</td> 190 <td className="px-4 py-2 text-right">${proc.cost}</td> 191 </tr> 192 ))} 193 </tbody> 194 </table> 195 </div> 196 )} 197 198 {billingDetail.labTests.length > 0 && ( 199 <div> 200 <h3 className="font-semibold text-lg mb-2">Lab Tests</h3> 201 <table className="w-full"> 202 <thead className="bg-gray-100"> 203 <tr> 204 <th className="px-4 py-2 text-left">Description</th> 205 <th className="px-4 py-2 text-right">Cost</th> 206 </tr> 207 </thead> 208 <tbody> 209 {billingDetail.labTests.map((test, idx) => ( 210 <tr key={idx} className="border-t"> 211 <td className="px-4 py-2">{test.description}</td> 212 <td className="px-4 py-2 text-right">${test.cost}</td> 213 </tr> 214 ))} 215 </tbody> 216 </table> 217 </div> 218 )} 219 220 <div className="border-t-2 pt-4 mt-4 flex justify-end"> 221 <div className="text-right"> 222 <p className="text-gray-600">Total Amount:</p> 223 <p className="text-2xl font-bold text-purple-600">${billing.totalCost}</p> 224 </div> 167 225 </div> 168 226 </div> 169 </div> 170 171 <div className="bg-white rounded-lg shadow p-6"> 172 <h2 className="text-xl font-bold mb-4">Itemized Services</h2> 173 174 {billingDetail && (billingDetail.procedures.length > 0 || billingDetail.labTests.length > 0) ? ( 175 <div className="space-y-4"> 176 {billingDetail.procedures.length > 0 && ( 177 <div> 178 <h3 className="font-semibold text-lg mb-2">Procedures</h3> 179 <table className="w-full"> 180 <thead className="bg-gray-100"> 181 <tr> 182 <th className="px-4 py-2 text-left">Description</th> 183 <th className="px-4 py-2 text-right">Cost</th> 184 </tr> 185 </thead> 186 <tbody> 187 {billingDetail.procedures.map((proc, idx) => ( 188 <tr key={idx} className="border-t"> 189 <td className="px-4 py-2">{proc.description}</td> 190 <td className="px-4 py-2 text-right">${proc.cost}</td> 191 </tr> 192 ))} 193 </tbody> 194 </table> 195 </div> 196 )} 197 198 {billingDetail.labTests.length > 0 && ( 199 <div> 200 <h3 className="font-semibold text-lg mb-2">Lab Tests</h3> 201 <table className="w-full"> 202 <thead className="bg-gray-100"> 203 <tr> 204 <th className="px-4 py-2 text-left">Description</th> 205 <th className="px-4 py-2 text-right">Cost</th> 206 </tr> 207 </thead> 208 <tbody> 209 {billingDetail.labTests.map((test, idx) => ( 210 <tr key={idx} className="border-t"> 211 <td className="px-4 py-2">{test.description}</td> 212 <td className="px-4 py-2 text-right">${test.cost}</td> 213 </tr> 214 ))} 215 </tbody> 216 </table> 217 </div> 218 )} 219 220 <div className="border-t-2 pt-4 mt-4 flex justify-end"> 221 <div className="text-right"> 222 <p className="text-gray-600">Total Amount:</p> 223 <p className="text-2xl font-bold text-purple-600">${billing.totalCost}</p> 224 </div> 225 </div> 226 </div> 227 ) : ( 228 <p className="text-gray-500">No services itemized for this billing record.</p> 229 )} 230 </div> 231 </div> 227 ) : ( 228 <p className="text-gray-500">No services itemized for this billing record.</p> 229 )} 230 </div> 231 </div> 232 232 ); 233 233 } … … 235 235 function InfoRow({ label, value }) { 236 236 return ( 237 <div className="flex justify-between">238 <span className="text-gray-800">{label}:</span>239 <span className="text-gray-800">{value || 'N/A'}</span>240 </div>237 <div className="flex justify-between"> 238 <span className="text-gray-800">{label}:</span> 239 <span className="text-gray-800">{value || 'N/A'}</span> 240 </div> 241 241 ); 242 242 } -
frontend/src/pages/billing/BillingList.js
re1f74f6 r9af201e 89 89 // Refresh the list 90 90 const response = user.role === 'BILLING_ADMIN' 91 ? await billingService.getAllBillings()92 : await billingService.getBillingHistoryForPatient(patientId || user.patientId);91 ? await billingService.getAllBillings() 92 : await billingService.getBillingHistoryForPatient(patientId || user.patientId); 93 93 setBillings(response.data || []); 94 94 } catch (err) { … … 101 101 if (user.role === 'DOCTOR' || user.role === 'LAB_TECHNICIAN') { 102 102 return ( 103 <div style={{ padding: '20px', textAlign: 'center' }}>104 <h1 className="text-2xl font-bold" style={{ color: '#7c3aed', marginBottom: '10px' }}>Access Denied</h1>105 <p style={{ color: 'var(--color-neutral-600)' }}>You do not have permission to access billing records.</p>106 </div>103 <div style={{ padding: '20px', textAlign: 'center' }}> 104 <h1 className="text-2xl font-bold" style={{ color: '#7c3aed', marginBottom: '10px' }}>Access Denied</h1> 105 <p style={{ color: 'var(--color-neutral-600)' }}>You do not have permission to access billing records.</p> 106 </div> 107 107 ); 108 108 } … … 111 111 112 112 return ( 113 <div> 114 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>{patientId ? 'Patient Billing History' : 'Billing Records'}</h1> 115 116 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 117 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 118 119 {/* Filters for Billing Admin */} 120 {user.role === 'BILLING_ADMIN' && ( 121 <div className="bg-white rounded-lg shadow p-6 mb-6"> 122 <h2 className="text-lg font-bold mb-4">Filters</h2> 123 <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> 113 <div> 114 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>{patientId ? 'Patient Billing History' : 'Billing Records'}</h1> 115 116 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 117 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 118 119 {/* Filters for Billing Admin */} 120 {user.role === 'BILLING_ADMIN' && ( 121 <div className="bg-white rounded-lg shadow p-6 mb-6"> 122 <h2 className="text-lg font-bold mb-4">Filters</h2> 123 <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> 124 <div> 125 <label className="block text-sm font-semibold mb-2">Status</label> 126 <select 127 value={statusFilter} 128 onChange={(e) => setStatusFilter(e.target.value)} 129 className="w-full px-4 py-2 border rounded-lg" 130 > 131 <option value="">All Statuses</option> 132 <option value="PENDING">Pending</option> 133 <option value="PAID">Paid</option> 134 <option value="PROCESSING">Processing</option> 135 </select> 136 </div> 137 <div> 138 <label className="block text-sm font-semibold mb-2">Patient</label> 139 <select 140 value={patientFilter} 141 onChange={(e) => setPatientFilter(e.target.value)} 142 className="w-full px-4 py-2 border rounded-lg" 143 > 144 <option value="">All Patients</option> 145 {patients.map(p => ( 146 <option key={p.patientId} value={`${p.firstName} ${p.lastName}`}> 147 {p.firstName} {p.lastName} 148 </option> 149 ))} 150 </select> 151 </div> 152 </div> 153 </div> 154 )} 155 156 {/* Pending Billing Records Section (for Billing Admin) */} 157 {user.role === 'BILLING_ADMIN' && pendingBillings.length > 0 && ( 158 <div className="bg-yellow-50 rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-500"> 159 <h2 className="text-lg font-bold mb-4">Pending Billing Records ({pendingBillings.length})</h2> 160 <div className="space-y-3"> 161 {pendingBillings.map(billing => ( 162 <div key={billing.billId} className="bg-white rounded p-4 flex justify-between items-center"> 124 163 <div> 125 <label className="block text-sm font-semibold mb-2">Status</label> 126 <select 127 value={statusFilter} 128 onChange={(e) => setStatusFilter(e.target.value)} 129 className="w-full px-4 py-2 border rounded-lg" 130 > 131 <option value="">All Statuses</option> 132 <option value="PENDING">Pending</option> 133 <option value="PAID">Paid</option> 134 <option value="PROCESSING">Processing</option> 135 </select> 164 <p className="font-semibold">{billing.patientName}</p> 165 <p className="text-sm text-gray-600">${billing.totalCost}</p> 136 166 </div> 137 <div> 138 <label className="block text-sm font-semibold mb-2">Patient</label> 139 <select 140 value={patientFilter} 141 onChange={(e) => setPatientFilter(e.target.value)} 142 className="w-full px-4 py-2 border rounded-lg" 143 > 144 <option value="">All Patients</option> 145 {patients.map(p => ( 146 <option key={p.patientId} value={`${p.firstName} ${p.lastName}`}> 147 {p.firstName} {p.lastName} 148 </option> 149 ))} 150 </select> 151 </div> 167 <button 168 onClick={() => handleUpdateStatus(billing.billId, 'PAID')} 169 className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 text-sm" 170 > 171 Mark Paid 172 </button> 152 173 </div> 153 </div> 154 )} 155 156 {/* Pending Billing Records Section (for Billing Admin) */} 157 {user.role === 'BILLING_ADMIN' && pendingBillings.length > 0 && ( 158 <div className="bg-yellow-50 rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-500"> 159 <h2 className="text-lg font-bold mb-4">Pending Billing Records ({pendingBillings.length})</h2> 160 <div className="space-y-3"> 161 {pendingBillings.map(billing => ( 162 <div key={billing.billId} className="bg-white rounded p-4 flex justify-between items-center"> 163 <div> 164 <p className="font-semibold">{billing.patientName}</p> 165 <p className="text-sm text-gray-600">${billing.totalCost}</p> 166 </div> 167 <button 168 onClick={() => handleUpdateStatus(billing.billId, 'PAID')} 169 className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 text-sm" 170 > 171 Mark Paid 172 </button> 173 </div> 174 ))} 175 </div> 176 </div> 177 )} 178 179 <div className="bg-white rounded-lg shadow overflow-hidden"> 180 <table className="w-full"> 181 <thead className="bg-gray-100"> 174 ))} 175 </div> 176 </div> 177 )} 178 179 <div className="bg-white rounded-lg shadow overflow-hidden"> 180 <table className="w-full"> 181 <thead className="bg-gray-100"> 182 182 <tr> 183 183 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> … … 187 187 <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th> 188 188 </tr> 189 </thead>190 <tbody>189 </thead> 190 <tbody> 191 191 {filteredBillings.map(billing => ( 192 <tr key={billing.billId} className="border-t hover:bg-gray-50">193 <td className="px-6 py-3">{billing.patientName}</td>194 <td className="px-6 py-3">${billing.totalCost}</td>195 <td className="px-6 py-3">192 <tr key={billing.billId} className="border-t hover:bg-gray-50"> 193 <td className="px-6 py-3">{billing.patientName}</td> 194 <td className="px-6 py-3">${billing.totalCost}</td> 195 <td className="px-6 py-3"> 196 196 <span className={`px-3 py-1 rounded text-sm font-semibold ${ 197 billing.paymentStatus === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :198 billing.paymentStatus === 'PAID' ? 'bg-green-100 text-green-800' :199 'bg-red-100 text-red-800'197 billing.paymentStatus === 'PENDING' ? 'bg-yellow-100 text-yellow-800' : 198 billing.paymentStatus === 'PAID' ? 'bg-green-100 text-green-800' : 199 'bg-red-100 text-red-800' 200 200 }`}> 201 201 {billing.paymentStatus} 202 202 </span> 203 </td>204 <td className="px-6 py-3">{billing.paymentDate || 'Not paid'}</td>205 <td className="px-6 py-3">206 <Link to={`/billing/${billing.billId}`} style={{ color: '#7c3aed', textDecoration: 'none', fontWeight: '400' }} onMouseEnter={(e) => e.currentTarget.style.textDecoration = 'underline'} onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'}>207 View208 </Link>209 </td>210 </tr>203 </td> 204 <td className="px-6 py-3">{billing.paymentDate || 'Not paid'}</td> 205 <td className="px-6 py-3"> 206 <Link to={`/billing/${billing.billId}`} style={{ color: '#7c3aed', textDecoration: 'none', fontWeight: '400' }} onMouseEnter={(e) => e.currentTarget.style.textDecoration = 'underline'} onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'}> 207 View 208 </Link> 209 </td> 210 </tr> 211 211 ))} 212 </tbody> 213 </table> 214 215 {filteredBillings.length === 0 && ( 216 <div className="p-6 text-center text-gray-500"> 217 {billings.length === 0 ? 'No billing records found' : 'No records match the selected filters'} 218 </div> 219 )} 220 </div> 212 </tbody> 213 </table> 214 215 {filteredBillings.length === 0 && ( 216 <div className="p-6 text-center text-gray-500"> 217 {billings.length === 0 ? 'No billing records found' : 'No records match the selected filters'} 218 </div> 219 )} 221 220 </div> 221 </div> 222 222 ); 223 223 } -
frontend/src/pages/departments/DepartmentDetail.js
re1f74f6 r9af201e 19 19 20 20 useEffect(() => { 21 fetchDepartmentAndDoctors(); 22 }, [departmentId]); 23 24 const fetchDepartmentAndDoctors = async () => { 21 const fetchDepartmentAndDoctors = async () => { 25 22 try { 26 23 setLoading(true); … … 39 36 setLoading(false); 40 37 } 41 }; 38 }; 39 40 fetchDepartmentAndDoctors(); 41 }, [departmentId]); 42 42 43 43 if (loading) return <Loading />; -
frontend/src/pages/departments/DoctorsByDepartment.js
re1f74f6 r9af201e 19 19 20 20 useEffect(() => { 21 fetchDepartmentAndDoctors(); 22 }, [departmentId]); 23 24 const fetchDepartmentAndDoctors = async () => { 21 const fetchDepartmentAndDoctors = async () => { 25 22 try { 26 23 setLoading(true); … … 39 36 setLoading(false); 40 37 } 41 }; 38 }; 39 40 fetchDepartmentAndDoctors(); 41 }, [departmentId]); 42 42 43 43 if (loading) return <Loading />; -
frontend/src/pages/doctors/DoctorDetail.js
re1f74f6 r9af201e 24 24 25 25 useEffect(() => { 26 const fetchDoctor = async () => { 27 try { 28 setLoading(true); 29 const response = await doctorService.getDoctorById(id); 30 setDoctor(response.data); 31 } catch (err) { 32 setError('Failed to fetch doctor details'); 33 console.error(err); 34 } finally { 35 setLoading(false); 36 } 37 }; 38 26 39 fetchDoctor(); 27 40 }, [id]); 28 29 const fetchDoctor = async () => {30 try {31 setLoading(true);32 const response = await doctorService.getDoctorById(id);33 setDoctor(response.data);34 } catch (err) {35 setError('Failed to fetch doctor details');36 console.error(err);37 } finally {38 setLoading(false);39 }40 };41 41 42 42 if (loading) return <Loading />; -
frontend/src/pages/doctors/DoctorForm.js
re1f74f6 r9af201e 22 22 useEffect(() => { 23 23 if (id) { 24 const fetchDoctor = async () => { 25 try { 26 setLoading(true); 27 const response = await doctorService.getDoctorById(id); 28 setFormData({ 29 firstName: response.data.firstName, 30 lastName: response.data.lastName, 31 emailAddress: response.data.emailAddress, 32 levelId: response.data.level?.levelId || '', 33 specializationId: response.data.specialization?.specializationId || '', 34 departmentId: response.data.department?.departmentId || '', 35 }); 36 } catch (err) { 37 setError('Failed to fetch doctor'); 38 } finally { 39 setLoading(false); 40 } 41 }; 42 24 43 fetchDoctor(); 25 44 } 26 45 }, [id]); 27 28 const fetchDoctor = async () => {29 try {30 setLoading(true);31 const response = await doctorService.getDoctorById(id);32 setFormData({33 firstName: response.data.firstName,34 lastName: response.data.lastName,35 emailAddress: response.data.emailAddress,36 levelId: response.data.level?.levelId || '',37 specializationId: response.data.specialization?.specializationId || '',38 departmentId: response.data.department?.departmentId || '',39 });40 } catch (err) {41 setError('Failed to fetch doctor');42 } finally {43 setLoading(false);44 }45 };46 46 47 47 const handleChange = (e) => { -
frontend/src/pages/lab-tests/LabResultForm.js
re1f74f6 r9af201e 1 import React, { useState , useEffect} from 'react';1 import React, { useState } from 'react'; 2 2 import { useNavigate } from 'react-router-dom'; 3 3 import { labService } from '../../services/labService'; -
frontend/src/pages/lab-tests/LabTestList.js
re1f74f6 r9af201e 5 5 6 6 function LabTestList() { 7 const user = JSON.parse(localStorage.getItem('user') || '{}');7 const [user, setUser] = useState({}); 8 8 const isLabTechnician = user.role === 'LAB_TECHNICIAN'; 9 9 … … 46 46 testDate: '', 47 47 }); 48 49 // Load user from localStorage and listen for changes 50 useEffect(() => { 51 const loadUser = () => { 52 const userStr = localStorage.getItem('user'); 53 console.log('[LabTestList] Loading user from localStorage:', userStr); 54 if (userStr) { 55 try { 56 const parsedUser = JSON.parse(userStr); 57 console.log('[LabTestList] Parsed user:', parsedUser); 58 setUser(parsedUser); 59 } catch (err) { 60 console.error('Failed to parse user:', err); 61 setUser({}); 62 } 63 } else { 64 console.log('[LabTestList] No user in localStorage'); 65 setUser({}); 66 } 67 }; 68 69 console.log('[LabTestList] useEffect running - loading user'); 70 loadUser(); 71 72 // Listen for storage changes (other tabs/windows) 73 window.addEventListener('storage', loadUser); 74 75 // Listen for custom event (same tab changes) 76 window.addEventListener('userStorageChange', loadUser); 77 78 return () => { 79 window.removeEventListener('storage', loadUser); 80 window.removeEventListener('userStorageChange', loadUser); 81 }; 82 }, []); 48 83 49 84 useEffect(() => { … … 134 169 setLoading(true); 135 170 136 // Get doctor ID from lo calStorage (set during login)137 const doctorId = localStorage.getItem('doctorId') || 1;171 // Get doctor ID from logged-in user 172 const doctorId = user.doctorId; 138 173 139 174 const request = { … … 145 180 notes: requestData.notes, 146 181 }; 182 183 console.log('User object:', user); 184 console.log('Sending lab test request with doctorId:', doctorId, 'Full request:', request); 147 185 148 186 await labService.requestLabTest(request); … … 228 266 if (isLabTechnician) { 229 267 return ( 230 <div> 231 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests - Submit Results</h1> 232 233 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 234 235 {selectedTest ? ( 236 // Submit Result Form 237 <div className="bg-white rounded-lg shadow p-6 mb-6"> 238 <h2 className="text-xl font-bold mb-4">Submit Lab Result</h2> 239 <div className="mb-4 p-4 bg-gray-50 rounded-lg"> 240 <p className="mb-2"><strong>Test:</strong> {selectedTest.testName}</p> 241 <p className="mb-2"><strong>Patient:</strong> {selectedTest.patientName}</p> 242 <p className="mb-2"><strong>Doctor:</strong> {selectedTest.doctorName}</p> 243 <p className="mb-2"><strong>Test Date:</strong> {selectedTest.testDate}</p> 244 {selectedTest.notes && <p><strong>Notes:</strong> {selectedTest.notes}</p>} 268 <div> 269 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests - Submit Results</h1> 270 271 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 272 273 {selectedTest ? ( 274 // Submit Result Form 275 <div className="bg-white rounded-lg shadow p-6 mb-6"> 276 <h2 className="text-xl font-bold mb-4">Submit Lab Result</h2> 277 <div className="mb-4 p-4 bg-gray-50 rounded-lg"> 278 <p className="mb-2"><strong>Test:</strong> {selectedTest.testName}</p> 279 <p className="mb-2"><strong>Patient:</strong> {selectedTest.patientName}</p> 280 <p className="mb-2"><strong>Doctor:</strong> {selectedTest.doctorName}</p> 281 <p className="mb-2"><strong>Test Date:</strong> {selectedTest.testDate}</p> 282 {selectedTest.notes && <p><strong>Notes:</strong> {selectedTest.notes}</p>} 283 </div> 284 285 <form onSubmit={handleSubmitResult} className="space-y-4"> 286 <div> 287 <label className="block text-sm font-semibold mb-2">Test Results *</label> 288 <textarea 289 value={submitFormData.results} 290 onChange={(e) => setSubmitFormData({ ...submitFormData, results: e.target.value })} 291 placeholder="Enter detailed test results" 292 className="w-full px-4 py-2 border rounded-lg" 293 rows="4" 294 required 295 /> 296 </div> 297 298 <div> 299 <label className="block text-sm font-semibold mb-2">Result Date *</label> 300 <input 301 type="date" 302 value={submitFormData.resultDate} 303 onChange={(e) => setSubmitFormData({ ...submitFormData, resultDate: e.target.value })} 304 className="w-full px-4 py-2 border rounded-lg" 305 required 306 /> 307 </div> 308 309 <div className="flex gap-4"> 310 <button 311 type="submit" 312 disabled={loading} 313 style={{ 314 background: loading ? '#d1d5db' : '#bfdbfe', 315 color: '#1e1035', 316 padding: '8px 24px', 317 borderRadius: '6px', 318 border: 'none', 319 cursor: loading ? 'not-allowed' : 'pointer', 320 fontSize: '14px', 321 fontWeight: '400' 322 }} 323 onMouseEnter={(e) => !loading && (e.currentTarget.style.background = '#93c5fd')} 324 onMouseLeave={(e) => !loading && (e.currentTarget.style.background = '#bfdbfe')} 325 > 326 {loading ? 'Submitting...' : 'Submit Result'} 327 </button> 328 <button 329 type="button" 330 onClick={() => { 331 setSelectedTest(null); 332 setSubmitFormData({ 333 results: '', 334 resultDate: new Date().toISOString().split('T')[0], 335 }); 336 }} 337 className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500" 338 > 339 Cancel 340 </button> 341 </div> 342 </form> 343 </div> 344 ) : ( 345 // Pending Tests List 346 <div className="bg-white rounded-lg shadow overflow-hidden"> 347 <div className="p-6 border-b"> 348 <h2 className="text-xl font-bold">Pending Lab Tests ({pendingTests.length})</h2> 349 </div> 350 351 {pendingTests.length === 0 ? ( 352 <div className="p-6 text-center text-gray-600"> 353 No pending lab tests 354 </div> 355 ) : ( 356 <> 357 {/* Filters */} 358 <div className="p-6 border-b bg-gray-50"> 359 <h3 className="text-sm font-semibold mb-4">Filters</h3> 360 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> 361 <div> 362 <label className="block text-sm font-semibold mb-2">Test</label> 363 <input 364 type="text" 365 placeholder="Filter by test name..." 366 value={pendingFilters.testName} 367 onChange={(e) => setPendingFilters({...pendingFilters, testName: e.target.value})} 368 className="w-full px-3 py-2 border rounded-lg text-sm" 369 /> 370 </div> 371 <div> 372 <label className="block text-sm font-semibold mb-2">Patient</label> 373 <input 374 type="text" 375 placeholder="Filter by patient name..." 376 value={pendingFilters.patientName} 377 onChange={(e) => setPendingFilters({...pendingFilters, patientName: e.target.value})} 378 className="w-full px-3 py-2 border rounded-lg text-sm" 379 /> 380 </div> 381 <div> 382 <label className="block text-sm font-semibold mb-2">Test Date</label> 383 <input 384 type="date" 385 value={pendingFilters.testDate} 386 onChange={(e) => setPendingFilters({...pendingFilters, testDate: e.target.value})} 387 className="w-full px-3 py-2 border rounded-lg text-sm" 388 /> 389 </div> 390 </div> 391 <button 392 onClick={() => setPendingFilters({testName: '', patientName: '', testDate: ''})} 393 className="mt-4 bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400" 394 > 395 Clear Filters 396 </button> 245 397 </div> 246 398 247 <form onSubmit={handleSubmitResult} className="space-y-4"> 248 <div> 249 <label className="block text-sm font-semibold mb-2">Test Results *</label> 250 <textarea 251 value={submitFormData.results} 252 onChange={(e) => setSubmitFormData({ ...submitFormData, results: e.target.value })} 253 placeholder="Enter detailed test results" 254 className="w-full px-4 py-2 border rounded-lg" 255 rows="4" 256 required 257 /> 399 {/* Filtered Table */} 400 <table className="w-full"> 401 <thead className="bg-gray-100"> 402 <tr> 403 <th className="px-6 py-3 text-left text-sm font-semibold">Test</th> 404 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> 405 <th className="px-6 py-3 text-left text-sm font-semibold">Doctor</th> 406 <th className="px-6 py-3 text-left text-sm font-semibold">Requested</th> 407 <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th> 408 <th className="px-6 py-3 text-left text-sm font-semibold">Notes</th> 409 <th className="px-6 py-3 text-left text-sm font-semibold">Action</th> 410 </tr> 411 </thead> 412 <tbody> 413 {pendingTests 414 .filter((test) => { 415 const testName = (test.testName || '').toLowerCase(); 416 const patientName = (test.patientName || '').toLowerCase(); 417 const testDate = (test.testDate || ''); 418 419 return ( 420 testName.includes(pendingFilters.testName.toLowerCase()) && 421 patientName.includes(pendingFilters.patientName.toLowerCase()) && 422 (pendingFilters.testDate === '' || testDate === pendingFilters.testDate) 423 ); 424 }) 425 .map((test, index) => ( 426 <tr key={index} className="border-t hover:bg-gray-50"> 427 <td className="px-6 py-3 font-medium">{test.testName}</td> 428 <td className="px-6 py-3">{test.patientName}</td> 429 <td className="px-6 py-3">{test.doctorName}</td> 430 <td className="px-6 py-3 text-green-600">{test.requestDate}</td> 431 <td className="px-6 py-3 text-purple-600">{test.testDate}</td> 432 <td className="px-6 py-3 text-gray-600 text-sm">{test.notes || '-'}</td> 433 <td className="px-6 py-3"> 434 <button 435 onClick={() => setSelectedTest(test)} 436 style={{ 437 background: '#bfdbfe', 438 color: '#1e1035', 439 padding: '6px 12px', 440 borderRadius: '4px', 441 border: 'none', 442 cursor: 'pointer', 443 fontSize: '12px', 444 fontWeight: '400' 445 }} 446 onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} 447 onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'} 448 > 449 Submit Result 450 </button> 451 </td> 452 </tr> 453 ))} 454 </tbody> 455 </table> 456 </> 457 )} 458 </div> 459 )} 460 461 {/* Submitted Tests History */} 462 {!selectedTest && ( 463 <div className="bg-white rounded-lg shadow overflow-hidden mt-6"> 464 <div className="p-6 border-b"> 465 <h2 className="text-xl font-bold">Submitted Lab Results History ({submittedTests.length})</h2> 466 </div> 467 468 {submittedTests.length === 0 ? ( 469 <div className="p-6 text-center text-gray-600"> 470 No submitted lab results yet 471 </div> 472 ) : ( 473 <> 474 {/* Filters */} 475 <div className="p-6 border-b bg-gray-50"> 476 <h3 className="text-sm font-semibold mb-4">Filters</h3> 477 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> 478 <div> 479 <label className="block text-sm font-semibold mb-2">Test</label> 480 <input 481 type="text" 482 placeholder="Filter by test name..." 483 value={submittedFilters.testName} 484 onChange={(e) => setSubmittedFilters({...submittedFilters, testName: e.target.value})} 485 className="w-full px-3 py-2 border rounded-lg text-sm" 486 /> 487 </div> 488 <div> 489 <label className="block text-sm font-semibold mb-2">Patient</label> 490 <input 491 type="text" 492 placeholder="Filter by patient name..." 493 value={submittedFilters.patientName} 494 onChange={(e) => setSubmittedFilters({...submittedFilters, patientName: e.target.value})} 495 className="w-full px-3 py-2 border rounded-lg text-sm" 496 /> 497 </div> 498 <div> 499 <label className="block text-sm font-semibold mb-2">Test Date</label> 500 <input 501 type="date" 502 value={submittedFilters.testDate} 503 onChange={(e) => setSubmittedFilters({...submittedFilters, testDate: e.target.value})} 504 className="w-full px-3 py-2 border rounded-lg text-sm" 505 /> 506 </div> 258 507 </div> 259 260 <div> 261 <label className="block text-sm font-semibold mb-2">Result Date *</label> 262 <input 263 type="date" 264 value={submitFormData.resultDate} 265 onChange={(e) => setSubmitFormData({ ...submitFormData, resultDate: e.target.value })} 266 className="w-full px-4 py-2 border rounded-lg" 267 required 268 /> 269 </div> 270 271 <div className="flex gap-4"> 272 <button 273 type="submit" 274 disabled={loading} 275 style={{ 276 background: loading ? '#d1d5db' : '#bfdbfe', 277 color: '#1e1035', 278 padding: '8px 24px', 279 borderRadius: '6px', 280 border: 'none', 281 cursor: loading ? 'not-allowed' : 'pointer', 282 fontSize: '14px', 283 fontWeight: '400' 284 }} 285 onMouseEnter={(e) => !loading && (e.currentTarget.style.background = '#93c5fd')} 286 onMouseLeave={(e) => !loading && (e.currentTarget.style.background = '#bfdbfe')} 287 > 288 {loading ? 'Submitting...' : 'Submit Result'} 289 </button> 290 <button 291 type="button" 292 onClick={() => { 293 setSelectedTest(null); 294 setSubmitFormData({ 295 results: '', 296 resultDate: new Date().toISOString().split('T')[0], 297 }); 298 }} 299 className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500" 300 > 301 Cancel 302 </button> 303 </div> 304 </form> 305 </div> 306 ) : ( 307 // Pending Tests List 308 <div className="bg-white rounded-lg shadow overflow-hidden"> 309 <div className="p-6 border-b"> 310 <h2 className="text-xl font-bold">Pending Lab Tests ({pendingTests.length})</h2> 508 <button 509 onClick={() => setSubmittedFilters({testName: '', patientName: '', testDate: ''})} 510 className="mt-4 bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400" 511 > 512 Clear Filters 513 </button> 311 514 </div> 312 515 313 {pendingTests.length === 0 ? ( 314 <div className="p-6 text-center text-gray-600"> 315 No pending lab tests 316 </div> 317 ) : ( 318 <> 319 {/* Filters */} 320 <div className="p-6 border-b bg-gray-50"> 321 <h3 className="text-sm font-semibold mb-4">Filters</h3> 322 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> 323 <div> 324 <label className="block text-sm font-semibold mb-2">Test</label> 325 <input 326 type="text" 327 placeholder="Filter by test name..." 328 value={pendingFilters.testName} 329 onChange={(e) => setPendingFilters({...pendingFilters, testName: e.target.value})} 330 className="w-full px-3 py-2 border rounded-lg text-sm" 331 /> 332 </div> 333 <div> 334 <label className="block text-sm font-semibold mb-2">Patient</label> 335 <input 336 type="text" 337 placeholder="Filter by patient name..." 338 value={pendingFilters.patientName} 339 onChange={(e) => setPendingFilters({...pendingFilters, patientName: e.target.value})} 340 className="w-full px-3 py-2 border rounded-lg text-sm" 341 /> 342 </div> 343 <div> 344 <label className="block text-sm font-semibold mb-2">Test Date</label> 345 <input 346 type="date" 347 value={pendingFilters.testDate} 348 onChange={(e) => setPendingFilters({...pendingFilters, testDate: e.target.value})} 349 className="w-full px-3 py-2 border rounded-lg text-sm" 350 /> 351 </div> 352 </div> 353 <button 354 onClick={() => setPendingFilters({testName: '', patientName: '', testDate: ''})} 355 className="mt-4 bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400" 356 > 357 Clear Filters 358 </button> 359 </div> 360 361 {/* Filtered Table */} 362 <table className="w-full"> 363 <thead className="bg-gray-100"> 364 <tr> 365 <th className="px-6 py-3 text-left text-sm font-semibold">Test</th> 366 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> 367 <th className="px-6 py-3 text-left text-sm font-semibold">Doctor</th> 368 <th className="px-6 py-3 text-left text-sm font-semibold">Requested</th> 369 <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th> 370 <th className="px-6 py-3 text-left text-sm font-semibold">Notes</th> 371 <th className="px-6 py-3 text-left text-sm font-semibold">Action</th> 372 </tr> 373 </thead> 374 <tbody> 375 {pendingTests 376 .filter((test) => { 377 const testName = (test.testName || '').toLowerCase(); 378 const patientName = (test.patientName || '').toLowerCase(); 379 const testDate = (test.testDate || ''); 380 381 return ( 382 testName.includes(pendingFilters.testName.toLowerCase()) && 383 patientName.includes(pendingFilters.patientName.toLowerCase()) && 384 (pendingFilters.testDate === '' || testDate === pendingFilters.testDate) 385 ); 386 }) 387 .map((test, index) => ( 388 <tr key={index} className="border-t hover:bg-gray-50"> 389 <td className="px-6 py-3 font-medium">{test.testName}</td> 390 <td className="px-6 py-3">{test.patientName}</td> 391 <td className="px-6 py-3">{test.doctorName}</td> 392 <td className="px-6 py-3 text-green-600">{test.requestDate}</td> 393 <td className="px-6 py-3 text-purple-600">{test.testDate}</td> 394 <td className="px-6 py-3 text-gray-600 text-sm">{test.notes || '-'}</td> 395 <td className="px-6 py-3"> 396 <button 397 onClick={() => setSelectedTest(test)} 398 style={{ 399 background: '#bfdbfe', 400 color: '#1e1035', 401 padding: '6px 12px', 402 borderRadius: '4px', 403 border: 'none', 404 cursor: 'pointer', 405 fontSize: '12px', 406 fontWeight: '400' 407 }} 408 onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} 409 onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'} 410 > 411 Submit Result 412 </button> 413 </td> 414 </tr> 415 ))} 416 </tbody> 417 </table> 418 </> 419 )} 420 </div> 421 )} 422 423 {/* Submitted Tests History */} 424 {!selectedTest && ( 425 <div className="bg-white rounded-lg shadow overflow-hidden mt-6"> 426 <div className="p-6 border-b"> 427 <h2 className="text-xl font-bold">Submitted Lab Results History ({submittedTests.length})</h2> 428 </div> 429 430 {submittedTests.length === 0 ? ( 431 <div className="p-6 text-center text-gray-600"> 432 No submitted lab results yet 433 </div> 434 ) : ( 435 <> 436 {/* Filters */} 437 <div className="p-6 border-b bg-gray-50"> 438 <h3 className="text-sm font-semibold mb-4">Filters</h3> 439 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> 440 <div> 441 <label className="block text-sm font-semibold mb-2">Test</label> 442 <input 443 type="text" 444 placeholder="Filter by test name..." 445 value={submittedFilters.testName} 446 onChange={(e) => setSubmittedFilters({...submittedFilters, testName: e.target.value})} 447 className="w-full px-3 py-2 border rounded-lg text-sm" 448 /> 449 </div> 450 <div> 451 <label className="block text-sm font-semibold mb-2">Patient</label> 452 <input 453 type="text" 454 placeholder="Filter by patient name..." 455 value={submittedFilters.patientName} 456 onChange={(e) => setSubmittedFilters({...submittedFilters, patientName: e.target.value})} 457 className="w-full px-3 py-2 border rounded-lg text-sm" 458 /> 459 </div> 460 <div> 461 <label className="block text-sm font-semibold mb-2">Test Date</label> 462 <input 463 type="date" 464 value={submittedFilters.testDate} 465 onChange={(e) => setSubmittedFilters({...submittedFilters, testDate: e.target.value})} 466 className="w-full px-3 py-2 border rounded-lg text-sm" 467 /> 468 </div> 469 </div> 470 <button 471 onClick={() => setSubmittedFilters({testName: '', patientName: '', testDate: ''})} 472 className="mt-4 bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400" 473 > 474 Clear Filters 475 </button> 476 </div> 477 478 {/* Filtered Results Table */} 479 <table className="w-full"> 480 <thead className="bg-gray-100"> 481 <tr> 482 <th className="px-6 py-3 text-left text-sm font-semibold">Test</th> 483 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> 484 <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th> 485 <th className="px-6 py-3 text-left text-sm font-semibold">Result Date</th> 486 <th className="px-6 py-3 text-left text-sm font-semibold">Status</th> 487 </tr> 488 </thead> 489 <tbody> 490 {submittedTests 491 .filter((test) => { 492 const testName = (test.testName || '').toLowerCase(); 493 const patientName = (test.patientName || '').toLowerCase(); 494 const testDate = (test.testDate || ''); 495 496 return ( 497 testName.includes(submittedFilters.testName.toLowerCase()) && 498 patientName.includes(submittedFilters.patientName.toLowerCase()) && 499 (submittedFilters.testDate === '' || testDate === submittedFilters.testDate) 500 ); 501 }) 502 .map((test, index) => ( 503 <tr key={index} className="border-t hover:bg-gray-50"> 504 <td className="px-6 py-3 font-medium">{test.testName || test.description || 'Lab Test'}</td> 505 <td className="px-6 py-3">{test.patientName || 'N/A'}</td> 506 <td className="px-6 py-3">{test.testDate || 'N/A'}</td> 507 <td className="px-6 py-3 text-green-600">{test.resultDate || test.createdDate || 'N/A'}</td> 508 <td className="px-6 py-3"> 516 {/* Filtered Results Table */} 517 <table className="w-full"> 518 <thead className="bg-gray-100"> 519 <tr> 520 <th className="px-6 py-3 text-left text-sm font-semibold">Test</th> 521 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> 522 <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th> 523 <th className="px-6 py-3 text-left text-sm font-semibold">Result Date</th> 524 <th className="px-6 py-3 text-left text-sm font-semibold">Status</th> 525 </tr> 526 </thead> 527 <tbody> 528 {submittedTests 529 .filter((test) => { 530 const testName = (test.testName || '').toLowerCase(); 531 const patientName = (test.patientName || '').toLowerCase(); 532 const testDate = (test.testDate || ''); 533 534 return ( 535 testName.includes(submittedFilters.testName.toLowerCase()) && 536 patientName.includes(submittedFilters.patientName.toLowerCase()) && 537 (submittedFilters.testDate === '' || testDate === submittedFilters.testDate) 538 ); 539 }) 540 .map((test, index) => ( 541 <tr key={index} className="border-t hover:bg-gray-50"> 542 <td className="px-6 py-3 font-medium">{test.testName || test.description || 'Lab Test'}</td> 543 <td className="px-6 py-3">{test.patientName || 'N/A'}</td> 544 <td className="px-6 py-3">{test.testDate || 'N/A'}</td> 545 <td className="px-6 py-3 text-green-600">{test.resultDate || test.createdDate || 'N/A'}</td> 546 <td className="px-6 py-3"> 509 547 <span className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm font-semibold"> 510 548 Submitted 511 549 </span> 512 </td> 513 </tr> 514 ))} 515 </tbody> 516 </table> 517 </> 518 )} 519 </div> 550 </td> 551 </tr> 552 ))} 553 </tbody> 554 </table> 555 </> 556 )} 557 </div> 558 )} 559 </div> 560 ); 561 } 562 563 return ( 564 <div> 565 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests</h1> 566 567 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 568 569 {/* Search Form */} 570 <div className="bg-white rounded-lg shadow p-6 mb-6"> 571 <h2 className="text-xl font-bold mb-4">Search Patient</h2> 572 <form onSubmit={handleSearch} className="space-y-4"> 573 <div className="flex gap-4"> 574 <div className="flex-1"> 575 <label className="block text-sm font-semibold mb-2">Patient EMBG</label> 576 <input 577 type="text" 578 value={embg} 579 onChange={(e) => setEmbg(e.target.value)} 580 placeholder="e.g., 1402994123456" 581 className="w-full px-4 py-2 border rounded-lg" 582 /> 583 </div> 584 <div className="flex items-end"> 585 <button 586 type="submit" 587 disabled={loading} 588 className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400" 589 > 590 {loading ? 'Searching...' : 'Search'} 591 </button> 592 </div> 593 </div> 594 </form> 595 </div> 596 597 {/* Patient Lab Tests */} 598 {searched && patient && ( 599 <div className="space-y-6"> 600 {/* Patient Info */} 601 <div className="bg-white rounded-lg shadow p-6"> 602 <div className="flex justify-between items-start mb-4"> 603 <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2> 604 {!showRequestForm && ( 605 <button 606 onClick={() => setShowRequestForm(true)} 607 className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700" 608 > 609 Request Lab Test 610 </button> 611 )} 612 </div> 613 <div className="grid grid-cols-4 gap-4"> 614 <div> 615 <p className="text-sm text-gray-600">EMBG</p> 616 <p className="font-semibold">{patient.embg}</p> 617 </div> 618 <div> 619 <p className="text-sm text-gray-600">Blood Type</p> 620 <p className="font-semibold">{patient.bloodType || 'N/A'}</p> 621 </div> 622 <div> 623 <p className="text-sm text-gray-600">Date of Birth</p> 624 <p className="font-semibold">{patient.dateOfBirth}</p> 625 </div> 626 </div> 627 </div> 628 629 {/* Request Lab Test Form */} 630 {showRequestForm && ( 631 <div className="bg-white rounded-lg shadow p-6"> 632 <h3 className="text-xl font-bold mb-4">Request Lab Test</h3> 633 <form onSubmit={handleRequestTest} className="space-y-4"> 634 <div className="grid grid-cols-2 gap-4"> 635 <div> 636 <label className="block text-sm font-semibold mb-2">Test</label> 637 <select 638 value={requestData.testId} 639 onChange={(e) => setRequestData({ ...requestData, testId: e.target.value })} 640 className="w-full px-4 py-2 border rounded-lg" 641 required 642 > 643 <option value="">Select a test</option> 644 {availableTests.map((test) => ( 645 <option key={test.testId} value={test.testId}> 646 {test.testName} (${test.cost}) 647 </option> 648 ))} 649 </select> 650 </div> 651 <div> 652 <label className="block text-sm font-semibold mb-2">Test Date</label> 653 <input 654 type="date" 655 value={requestData.testDate} 656 onChange={(e) => setRequestData({ ...requestData, testDate: e.target.value })} 657 className="w-full px-4 py-2 border rounded-lg" 658 /> 659 </div> 660 </div> 661 <div> 662 <label className="block text-sm font-semibold mb-2">Notes</label> 663 <textarea 664 value={requestData.notes} 665 onChange={(e) => setRequestData({ ...requestData, notes: e.target.value })} 666 placeholder="Additional notes for the lab technician" 667 className="w-full px-4 py-2 border rounded-lg" 668 rows="3" 669 /> 670 </div> 671 <div className="flex gap-4"> 672 <button 673 type="submit" 674 disabled={loading} 675 className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700 disabled:bg-gray-400" 676 > 677 {loading ? 'Requesting...' : 'Request Test'} 678 </button> 679 <button 680 type="button" 681 onClick={() => setShowRequestForm(false)} 682 className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500" 683 > 684 Cancel 685 </button> 686 </div> 687 </form> 688 </div> 689 )} 690 691 {/* Test Requests */} 692 {labTestRequests && labTestRequests.length > 0 && ( 693 <div className="bg-white rounded-lg shadow p-6"> 694 <h3 className="text-xl font-bold mb-4">Lab Test Requests</h3> 695 <div className="space-y-3"> 696 {labTestRequests.map((request) => ( 697 <div key={request.testId} className="border-l-4 border-purple-500 pl-4 py-2"> 698 <p className="font-semibold text-lg">{request.testName}</p> 699 <p className="text-sm text-gray-600">Requested by: {request.doctorName}</p> 700 <p className="text-sm text-gray-600">Test Date: {request.requestDate}</p> 701 {request.notes && ( 702 <p className="text-sm text-gray-600">Notes: {request.notes}</p> 703 )} 704 </div> 705 ))} 706 </div> 707 </div> 708 )} 709 710 {/* Lab Test Results */} 711 {labResults && labResults.length > 0 && ( 712 <div className="bg-white rounded-lg shadow p-6"> 713 <h3 className="text-xl font-bold mb-4">Lab Test Results</h3> 714 <div className="space-y-4"> 715 {labResults.map((result) => ( 716 <div key={result.resultId} className="border-l-4 border-green-500 pl-4 py-3 bg-green-50 rounded"> 717 <p className="font-semibold text-lg text-green-700">{result.testName}</p> 718 <p className="text-sm text-gray-700 mt-2"><strong>Results:</strong> {result.results}</p> 719 <p className="text-sm text-gray-600">Result Date: {result.resultDate}</p> 720 </div> 721 ))} 722 </div> 723 </div> 724 )} 725 726 {/* Link to submit results */} 727 {labTestRequests && labTestRequests.length > 0 && ( 728 <div className="bg-yellow-50 rounded-lg p-6"> 729 <h3 className="text-lg font-semibold text-yellow-800 mb-3">Lab Technician: Submit Test Results</h3> 730 <p className="text-sm text-gray-700 mb-4"> 731 {labTestRequests.length} test{labTestRequests.length !== 1 ? 's' : ''} awaiting results 732 </p> 733 <a 734 href="/lab-tests/results" 735 className="inline-block bg-yellow-600 text-white px-6 py-2 rounded hover:bg-yellow-700" 736 > 737 Submit Lab Results 738 </a> 739 </div> 740 )} 741 742 {/* No requests message */} 743 {(!labTestRequests || labTestRequests.length === 0) && !labResults?.length && ( 744 <div className="bg-blue-50 rounded-lg p-6 text-center"> 745 <p className="text-gray-600">No lab test requests for this patient</p> 746 </div> 520 747 )} 521 748 </div> 522 ); 523 } 524 525 return ( 526 <div> 527 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests</h1> 528 529 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 530 531 {/* Search Form */} 532 <div className="bg-white rounded-lg shadow p-6 mb-6"> 533 <h2 className="text-xl font-bold mb-4">Search Patient</h2> 534 <form onSubmit={handleSearch} className="space-y-4"> 535 <div className="flex gap-4"> 536 <div className="flex-1"> 537 <label className="block text-sm font-semibold mb-2">Patient EMBG</label> 538 <input 539 type="text" 540 value={embg} 541 onChange={(e) => setEmbg(e.target.value)} 542 placeholder="e.g., 1402994123456" 543 className="w-full px-4 py-2 border rounded-lg" 544 /> 545 </div> 546 <div className="flex items-end"> 547 <button 548 type="submit" 549 disabled={loading} 550 className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400" 551 > 552 {loading ? 'Searching...' : 'Search'} 553 </button> 554 </div> 555 </div> 556 </form> 749 )} 750 751 {/* No search performed */} 752 {!searched && ( 753 <div className="bg-gray-50 rounded-lg p-12 text-center"> 754 <p className="text-gray-600 text-lg">Enter a patient EMBG to request lab tests</p> 557 755 </div> 558 559 {/* Patient Lab Tests */} 560 {searched && patient && ( 561 <div className="space-y-6"> 562 {/* Patient Info */} 563 <div className="bg-white rounded-lg shadow p-6"> 564 <div className="flex justify-between items-start mb-4"> 565 <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2> 566 {!showRequestForm && ( 567 <button 568 onClick={() => setShowRequestForm(true)} 569 className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700" 570 > 571 Request Lab Test 572 </button> 573 )} 574 </div> 575 <div className="grid grid-cols-4 gap-4"> 576 <div> 577 <p className="text-sm text-gray-600">EMBG</p> 578 <p className="font-semibold">{patient.embg}</p> 579 </div> 580 <div> 581 <p className="text-sm text-gray-600">Blood Type</p> 582 <p className="font-semibold">{patient.bloodType || 'N/A'}</p> 583 </div> 584 <div> 585 <p className="text-sm text-gray-600">Date of Birth</p> 586 <p className="font-semibold">{patient.dateOfBirth}</p> 587 </div> 588 </div> 589 </div> 590 591 {/* Request Lab Test Form */} 592 {showRequestForm && ( 593 <div className="bg-white rounded-lg shadow p-6"> 594 <h3 className="text-xl font-bold mb-4">Request Lab Test</h3> 595 <form onSubmit={handleRequestTest} className="space-y-4"> 596 <div className="grid grid-cols-2 gap-4"> 597 <div> 598 <label className="block text-sm font-semibold mb-2">Test</label> 599 <select 600 value={requestData.testId} 601 onChange={(e) => setRequestData({ ...requestData, testId: e.target.value })} 602 className="w-full px-4 py-2 border rounded-lg" 603 required 604 > 605 <option value="">Select a test</option> 606 {availableTests.map((test) => ( 607 <option key={test.testId} value={test.testId}> 608 {test.testName} (${test.cost}) 609 </option> 610 ))} 611 </select> 612 </div> 613 <div> 614 <label className="block text-sm font-semibold mb-2">Test Date</label> 615 <input 616 type="date" 617 value={requestData.testDate} 618 onChange={(e) => setRequestData({ ...requestData, testDate: e.target.value })} 619 className="w-full px-4 py-2 border rounded-lg" 620 /> 621 </div> 622 </div> 623 <div> 624 <label className="block text-sm font-semibold mb-2">Notes</label> 625 <textarea 626 value={requestData.notes} 627 onChange={(e) => setRequestData({ ...requestData, notes: e.target.value })} 628 placeholder="Additional notes for the lab technician" 629 className="w-full px-4 py-2 border rounded-lg" 630 rows="3" 631 /> 632 </div> 633 <div className="flex gap-4"> 634 <button 635 type="submit" 636 disabled={loading} 637 className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700 disabled:bg-gray-400" 638 > 639 {loading ? 'Requesting...' : 'Request Test'} 640 </button> 641 <button 642 type="button" 643 onClick={() => setShowRequestForm(false)} 644 className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500" 645 > 646 Cancel 647 </button> 648 </div> 649 </form> 650 </div> 651 )} 652 653 {/* Test Requests */} 654 {labTestRequests && labTestRequests.length > 0 && ( 655 <div className="bg-white rounded-lg shadow p-6"> 656 <h3 className="text-xl font-bold mb-4">Lab Test Requests</h3> 657 <div className="space-y-3"> 658 {labTestRequests.map((request) => ( 659 <div key={request.testId} className="border-l-4 border-purple-500 pl-4 py-2"> 660 <p className="font-semibold text-lg">{request.testName}</p> 661 <p className="text-sm text-gray-600">Requested by: {request.doctorName}</p> 662 <p className="text-sm text-gray-600">Test Date: {request.requestDate}</p> 663 {request.notes && ( 664 <p className="text-sm text-gray-600">Notes: {request.notes}</p> 665 )} 666 </div> 667 ))} 668 </div> 669 </div> 670 )} 671 672 {/* Lab Test Results */} 673 {labResults && labResults.length > 0 && ( 674 <div className="bg-white rounded-lg shadow p-6"> 675 <h3 className="text-xl font-bold mb-4">Lab Test Results</h3> 676 <div className="space-y-4"> 677 {labResults.map((result) => ( 678 <div key={result.resultId} className="border-l-4 border-green-500 pl-4 py-3 bg-green-50 rounded"> 679 <p className="font-semibold text-lg text-green-700">{result.testName}</p> 680 <p className="text-sm text-gray-700 mt-2"><strong>Results:</strong> {result.results}</p> 681 <p className="text-sm text-gray-600">Result Date: {result.resultDate}</p> 682 </div> 683 ))} 684 </div> 685 </div> 686 )} 687 688 {/* Link to submit results */} 689 {labTestRequests && labTestRequests.length > 0 && ( 690 <div className="bg-yellow-50 rounded-lg p-6"> 691 <h3 className="text-lg font-semibold text-yellow-800 mb-3">Lab Technician: Submit Test Results</h3> 692 <p className="text-sm text-gray-700 mb-4"> 693 {labTestRequests.length} test{labTestRequests.length !== 1 ? 's' : ''} awaiting results 694 </p> 695 <a 696 href="/lab-tests/results" 697 className="inline-block bg-yellow-600 text-white px-6 py-2 rounded hover:bg-yellow-700" 698 > 699 Submit Lab Results 700 </a> 701 </div> 702 )} 703 704 {/* No requests message */} 705 {!labTestRequests || labTestRequests.length === 0 && !labResults?.length && ( 706 <div className="bg-blue-50 rounded-lg p-6 text-center"> 707 <p className="text-gray-600">No lab test requests for this patient</p> 708 </div> 709 )} 710 </div> 711 )} 712 713 {/* No search performed */} 714 {!searched && ( 715 <div className="bg-gray-50 rounded-lg p-12 text-center"> 716 <p className="text-gray-600 text-lg">Enter a patient EMBG to request lab tests</p> 717 </div> 718 )} 719 </div> 756 )} 757 </div> 720 758 ); 721 759 } -
frontend/src/pages/medical-records/MedicalRecordDetail.js
re1f74f6 r9af201e 1 import React, { useState, useEffect } from 'react';1 import React, { useState, useEffect, useCallback } from 'react'; 2 2 import { useParams, useNavigate } from 'react-router-dom'; 3 3 import { medicalRecordService } from '../../services/medicalRecordService'; … … 77 77 const [procedureResults, setProcedureResults] = useState([]); 78 78 79 useEffect(() => { 80 fetchRecord(); 81 fetchDoctors(); 82 fetchDropdownOptions(); 83 }, [id]); 84 85 const fetchDropdownOptions = async () => { 86 try { 87 console.log('Fetching dropdown options...'); 88 const diagnosesRes = await apiClient.get('/medical-records/dropdown/diagnoses'); 89 console.log('Diagnoses:', diagnosesRes.data); 90 91 const symptomsRes = await apiClient.get('/medical-records/dropdown/symptoms'); 92 console.log('Symptoms:', symptomsRes.data); 93 94 const allergiesRes = await apiClient.get('/medical-records/dropdown/allergies'); 95 console.log('Allergies:', allergiesRes.data); 96 97 const prescriptionsRes = await apiClient.get('/medical-records/dropdown/prescriptions'); 98 console.log('Prescriptions:', prescriptionsRes.data); 99 100 const testsRes = await labService.getAllLabTests(); 101 console.log('Lab Tests:', testsRes.data); 102 103 const proceduresRes = await procedureService.getAllProcedures(); 104 console.log('Procedures:', proceduresRes.data); 105 106 setDiagnoses(diagnosesRes.data || []); 107 setSymptoms(symptomsRes.data || []); 108 setAllergies(allergiesRes.data || []); 109 setPrescriptions(prescriptionsRes.data || []); 110 setLabTests(testsRes.data || []); 111 setProcedures(proceduresRes.data || []); 112 console.log('Dropdown options set successfully'); 113 } catch (err) { 114 console.error('Error fetching dropdown options:', err); 115 setError('Failed to load dropdown options: ' + (err.response?.data?.error || err.message)); 116 } 117 }; 118 119 const fetchRecord = async () => { 79 const fetchRecord = useCallback(async () => { 120 80 try { 121 81 setLoading(true); … … 146 106 setLoading(false); 147 107 } 148 }; 149 150 const fetchDoctors = async () => { 151 try { 152 if (!isDoctor) { 153 const response = await doctorService.getAllDoctors(); 154 setDoctors(response.data); 155 if (response.data.length > 0 && !selectedDoctorId) { 156 setSelectedDoctorId(response.data[0].doctorId); 108 }, [id]); 109 110 useEffect(() => { 111 const fetchDoctors = async () => { 112 try { 113 if (!isDoctor) { 114 const response = await doctorService.getAllDoctors(); 115 setDoctors(response.data); 116 if (response.data.length > 0 && !selectedDoctorId) { 117 setSelectedDoctorId(response.data[0].doctorId); 118 } 157 119 } 158 } 159 } catch (err) { 160 console.error('Failed to fetch doctors', err); 161 } 162 }; 120 } catch (err) { 121 console.error('Failed to fetch doctors', err); 122 } 123 }; 124 125 const fetchDropdownOptions = async () => { 126 try { 127 console.log('Fetching dropdown options...'); 128 const diagnosesRes = await apiClient.get('/medical-records/dropdown/diagnoses'); 129 console.log('Diagnoses:', diagnosesRes.data); 130 131 const symptomsRes = await apiClient.get('/medical-records/dropdown/symptoms'); 132 console.log('Symptoms:', symptomsRes.data); 133 134 const allergiesRes = await apiClient.get('/medical-records/dropdown/allergies'); 135 console.log('Allergies:', allergiesRes.data); 136 137 const prescriptionsRes = await apiClient.get('/medical-records/dropdown/prescriptions'); 138 console.log('Prescriptions:', prescriptionsRes.data); 139 140 const testsRes = await labService.getAllLabTests(); 141 console.log('Lab Tests:', testsRes.data); 142 143 const proceduresRes = await procedureService.getAllProcedures(); 144 console.log('Procedures:', proceduresRes.data); 145 146 setDiagnoses(diagnosesRes.data || []); 147 setSymptoms(symptomsRes.data || []); 148 setAllergies(allergiesRes.data || []); 149 setPrescriptions(prescriptionsRes.data || []); 150 setLabTests(testsRes.data || []); 151 setProcedures(proceduresRes.data || []); 152 console.log('Dropdown options set successfully'); 153 } catch (err) { 154 console.error('Error fetching dropdown options:', err); 155 setError('Failed to load dropdown options: ' + (err.response?.data?.error || err.message)); 156 } 157 }; 158 159 fetchRecord(); 160 fetchDoctors(); 161 fetchDropdownOptions(); 162 }, [id, isDoctor, fetchRecord, selectedDoctorId]); 163 163 164 164 const handleAddDiagnosis = async (e) => { … … 857 857 try { 858 858 setLoading(true); 859 const doctorId = localStorage.getItem('doctorId')|| 1;859 const doctorId = user.doctorId || 1; 860 860 861 861 const request = { … … 964 964 try { 965 965 setLoading(true); 966 const doctorId = localStorage.getItem('doctorId')|| 1;966 const doctorId = user.doctorId || 1; 967 967 968 968 const request = { -
frontend/src/pages/medical-records/MedicalRecordList.js
re1f74f6 r9af201e 23 23 const [loading, setLoading] = useState(false); 24 24 25 25 // Auto-load patient's own records if logged in as patient, or load specific patient records if patientId is provided 26 26 React.useEffect(() => { 27 if (patientId) { 28 29 loadPatientRecords(patientId); 30 } else if (isPatient && user.username) { 31 handleAutoSearch(); 32 } 33 }, [patientId]); 34 35 const loadPatientRecords = async (pId) => { 27 const loadPatientRecords = async (pId) => { 36 28 setError(null); 37 29 setLoading(true); 38 30 39 31 try { 40 // Get patient by id32 // Get patient by ID 41 33 const patientResponse = await patientService.getPatientById(pId); 42 34 setPatient(patientResponse.data); … … 73 65 setLoading(false); 74 66 } 75 }; 76 77 const handleAutoSearch = async () => { 78 const searchEmbg = user.username; 79 setError(null); 80 setLoading(true); 81 82 try { 83 // Search patient by EMBG 84 const patientResponse = await patientService.getPatientByEmbg(searchEmbg); 85 setPatient(patientResponse.data); 86 87 // Get medical records for this patient 88 const recordsResponse = await medicalRecordService.getMedicalRecordByPatientId(patientResponse.data.patientId); 89 setMedicalData(recordsResponse.data); 90 91 // Get lab results 67 }; 68 69 const handleAutoSearch = async () => { 70 const searchEmbg = user.username; 71 setError(null); 72 setLoading(true); 73 92 74 try { 93 const labRes = await labService.getLabResultsForMedicalRecord(recordsResponse.data.recordId); 94 setLabResults(labRes.data || []); 75 // Search patient by EMBG 76 const patientResponse = await patientService.getPatientByEmbg(searchEmbg); 77 setPatient(patientResponse.data); 78 79 // Get medical records for this patient 80 const recordsResponse = await medicalRecordService.getMedicalRecordByPatientId(patientResponse.data.patientId); 81 setMedicalData(recordsResponse.data); 82 83 // Get lab results 84 try { 85 const labRes = await labService.getLabResultsForMedicalRecord(recordsResponse.data.recordId); 86 setLabResults(labRes.data || []); 87 } catch (err) { 88 setLabResults([]); 89 } 90 91 // Get procedure results 92 try { 93 const procRes = await procedureService.getProcedureResultsForMedicalRecord(recordsResponse.data.recordId); 94 setProcedureResults(procRes.data || []); 95 } catch (err) { 96 setProcedureResults([]); 97 } 98 99 setSearched(true); 95 100 } catch (err) { 101 setError(`Patient with EMBG ${searchEmbg} not found`); 102 setSearched(true); 103 setPatient(null); 104 setMedicalData(null); 96 105 setLabResults([]); 97 }98 99 // Get procedure results100 try {101 const procRes = await procedureService.getProcedureResultsForMedicalRecord(recordsResponse.data.recordId);102 setProcedureResults(procRes.data || []);103 } catch (err) {104 106 setProcedureResults([]); 105 } 106 107 setSearched(true); 108 } catch (err) { 109 setError(`Patient with EMBG ${searchEmbg} not found`); 110 setSearched(true); 111 setPatient(null); 112 setMedicalData(null); 113 setLabResults([]); 114 setProcedureResults([]); 115 } finally { 116 setLoading(false); 107 } finally { 108 setLoading(false); 109 } 110 }; 111 112 // Call appropriate function based on conditions 113 if (patientId) { 114 loadPatientRecords(patientId); 115 } else if (isPatient && user.username) { 116 handleAutoSearch(); 117 117 } 118 } ;118 }, [patientId, isPatient, user.username]); 119 119 120 120 const handleSearch = async (e) => { … … 168 168 169 169 return ( 170 <div> 171 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Medical Records</h1> 172 173 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 174 175 {/* Search Form - Only show for non-patients */} 176 {!isPatient && ( 177 <div className="bg-white rounded-lg shadow p-6 mb-6"> 178 <h2 className="text-xl font-bold mb-4">Search Medical Records</h2> 179 <form onSubmit={handleSearch} className="space-y-4"> 180 <div className="flex gap-4"> 181 <div className="flex-1"> 182 <label className="block text-sm font-semibold mb-2">Patient EMBG</label> 183 <input 184 type="text" 185 value={embg} 186 onChange={(e) => setEmbg(e.target.value)} 187 placeholder="e.g., 1402994123456" 188 className="w-full px-4 py-2 border rounded-lg" 189 /> 190 </div> 191 <div className="flex items-end"> 192 <button 193 type="submit" 194 disabled={loading} 195 className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400" 196 > 197 {loading ? 'Searching...' : 'Search'} 198 </button> 199 </div> 200 </div> 201 </form> 170 <div> 171 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Medical Records</h1> 172 173 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 174 175 {/* Search Form - Only show for non-patients */} 176 {!isPatient && ( 177 <div className="bg-white rounded-lg shadow p-6 mb-6"> 178 <h2 className="text-xl font-bold mb-4">Search Medical Records</h2> 179 <form onSubmit={handleSearch} className="space-y-4"> 180 <div className="flex gap-4"> 181 <div className="flex-1"> 182 <label className="block text-sm font-semibold mb-2">Patient EMBG</label> 183 <input 184 type="text" 185 value={embg} 186 onChange={(e) => setEmbg(e.target.value)} 187 placeholder="e.g., 1402994123456" 188 className="w-full px-4 py-2 border rounded-lg" 189 /> 190 </div> 191 <div className="flex items-end"> 192 <button 193 type="submit" 194 disabled={loading} 195 className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400" 196 > 197 {loading ? 'Searching...' : 'Search'} 198 </button> 199 </div> 202 200 </div> 203 )} 204 205 {/* Loading indicator for patients fetching their records */} 206 {isPatient && loading && ( 207 <div className="bg-blue-50 rounded-lg shadow p-6 mb-6"> 208 <p className="text-sm text-gray-700"> 209 <strong>Loading your medical records...</strong> 210 </p> 201 </form> 202 </div> 203 )} 204 205 {/* Loading indicator for patients fetching their records */} 206 {isPatient && loading && ( 207 <div className="bg-blue-50 rounded-lg shadow p-6 mb-6"> 208 <p className="text-sm text-gray-700"> 209 <strong>Loading your medical records...</strong> 210 </p> 211 </div> 212 )} 213 214 {/* Patient Info Message for Patients */} 215 {isPatient && searched && !loading && ( 216 <div className="bg-blue-50 rounded-lg shadow p-6 mb-6"> 217 <p className="text-sm text-gray-700"> 218 <strong>Viewing your medical records</strong> 219 </p> 220 </div> 221 )} 222 223 {/* Patient Medical Records */} 224 {searched && patient && ( 225 <div className="space-y-6"> 226 {/* Patient Info */} 227 <div className="bg-white rounded-lg shadow p-6"> 228 <div className="flex justify-between items-start mb-4"> 229 <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2> 230 {!isPatient && ( 231 <button 232 onClick={() => navigate(`/medical-records/${patient.patientId}`)} 233 className="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700" 234 > 235 Add Medical Data 236 </button> 237 )} 211 238 </div> 212 )} 213 214 {/* Patient Info Message for Patients */} 215 {isPatient && searched && !loading && ( 216 <div className="bg-blue-50 rounded-lg shadow p-6 mb-6"> 217 <p className="text-sm text-gray-700"> 218 <strong>Viewing your medical records</strong> 219 </p> 239 <div className="grid grid-cols-4 gap-4"> 240 <div> 241 <p className="text-sm text-gray-600">EMBG</p> 242 <p className="font-semibold">{patient.embg}</p> 243 </div> 244 <div> 245 <p className="text-sm text-gray-600">Email</p> 246 <p className="font-semibold">{patient.emailAddress}</p> 247 </div> 248 <div> 249 <p className="text-sm text-gray-600">Blood Type</p> 250 <p className="font-semibold">{patient.bloodType || 'N/A'}</p> 251 </div> 252 <div> 253 <p className="text-sm text-gray-600">Date of Birth</p> 254 <p className="font-semibold">{patient.dateOfBirth}</p> 255 </div> 220 256 </div> 221 )} 222 223 {/* Patient Medical Records */} 224 {searched && patient && ( 225 <div className="space-y-6"> 226 {/* Patient Info */} 227 <div className="bg-white rounded-lg shadow p-6"> 228 <div className="flex justify-between items-start mb-4"> 229 <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2> 230 {!isPatient && ( 231 <button 232 onClick={() => navigate(`/medical-records/${patient.patientId}`)} 233 className="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700" 234 > 235 Add Medical Data 236 </button> 237 )} 238 </div> 239 <div className="grid grid-cols-4 gap-4"> 240 <div> 241 <p className="text-sm text-gray-600">EMBG</p> 242 <p className="font-semibold">{patient.embg}</p> 243 </div> 244 <div> 245 <p className="text-sm text-gray-600">Email</p> 246 <p className="font-semibold">{patient.emailAddress}</p> 247 </div> 248 <div> 249 <p className="text-sm text-gray-600">Blood Type</p> 250 <p className="font-semibold">{patient.bloodType || 'N/A'}</p> 251 </div> 252 <div> 253 <p className="text-sm text-gray-600">Date of Birth</p> 254 <p className="font-semibold">{patient.dateOfBirth}</p> 255 </div> 256 </div> 257 </div> 258 259 {/* Medical Data Sections */} 260 {medicalData && ( 261 <> 262 {/* Diagnoses */} 263 {medicalData.diagnoses && medicalData.diagnoses.length > 0 && ( 264 <div className="bg-white rounded-lg shadow p-6"> 265 <h3 className="text-xl font-bold mb-4">Diagnoses</h3> 266 <div className="space-y-3"> 267 {medicalData.diagnoses.map((diagnosis) => ( 268 <div key={diagnosis.diagnosisId} className="border-l-4 border-purple-500 pl-4 py-2"> 269 <p className="font-semibold text-lg">{diagnosis.name}</p> 270 {diagnosis.description && ( 271 <p className="text-gray-600 text-sm">{diagnosis.description}</p> 272 )} 273 <p className="text-xs text-gray-500">By: {diagnosis.doctorName}</p> 274 </div> 275 ))} 276 </div> 277 </div> 278 )} 279 280 {/* Symptoms */} 281 {medicalData.symptoms && medicalData.symptoms.length > 0 && ( 282 <div className="bg-white rounded-lg shadow p-6"> 283 <h3 className="text-xl font-bold mb-4">Symptoms</h3> 284 <div className="flex flex-wrap gap-2"> 285 {medicalData.symptoms.map((symptom) => ( 286 <span key={symptom.symptomId} className="bg-yellow-100 text-yellow-800 px-3 py-1 rounded-full text-sm"> 257 </div> 258 259 {/* Medical Data Sections */} 260 {medicalData && ( 261 <> 262 {/* Diagnoses */} 263 {medicalData.diagnoses && medicalData.diagnoses.length > 0 && ( 264 <div className="bg-white rounded-lg shadow p-6"> 265 <h3 className="text-xl font-bold mb-4">Diagnoses</h3> 266 <div className="space-y-3"> 267 {medicalData.diagnoses.map((diagnosis) => ( 268 <div key={diagnosis.diagnosisId} className="border-l-4 border-purple-500 pl-4 py-2"> 269 <p className="font-semibold text-lg">{diagnosis.name}</p> 270 {diagnosis.description && ( 271 <p className="text-gray-600 text-sm">{diagnosis.description}</p> 272 )} 273 <p className="text-xs text-gray-500">By: {diagnosis.doctorName}</p> 274 </div> 275 ))} 276 </div> 277 </div> 278 )} 279 280 {/* Symptoms */} 281 {medicalData.symptoms && medicalData.symptoms.length > 0 && ( 282 <div className="bg-white rounded-lg shadow p-6"> 283 <h3 className="text-xl font-bold mb-4">Symptoms</h3> 284 <div className="flex flex-wrap gap-2"> 285 {medicalData.symptoms.map((symptom) => ( 286 <span key={symptom.symptomId} className="bg-yellow-100 text-yellow-800 px-3 py-1 rounded-full text-sm"> 287 287 {symptom.symptomName} 288 288 </span> 289 ))}290 </div>291 </div>292 )}293 294 {/* Allergies */}295 {medicalData.allergies && medicalData.allergies.length > 0 && (296 <div className="bg-white rounded-lg shadow p-6">297 <h3 className="text-xl font-bold mb-4">Allergies</h3>298 <div className="space-y-3">299 {medicalData.allergies.map((allergy) => (300 <div key={allergy.allergyId} className="border-l-4 border-red-500 pl-4 py-2">301 <p className="font-semibold">{allergy.allergyName}</p>302 <p className="text-sm">289 ))} 290 </div> 291 </div> 292 )} 293 294 {/* Allergies */} 295 {medicalData.allergies && medicalData.allergies.length > 0 && ( 296 <div className="bg-white rounded-lg shadow p-6"> 297 <h3 className="text-xl font-bold mb-4">Allergies</h3> 298 <div className="space-y-3"> 299 {medicalData.allergies.map((allergy) => ( 300 <div key={allergy.allergyId} className="border-l-4 border-red-500 pl-4 py-2"> 301 <p className="font-semibold">{allergy.allergyName}</p> 302 <p className="text-sm"> 303 303 <span className={`px-2 py-1 rounded text-white text-xs ${ 304 allergy.severity === 'CRITICAL' ? 'bg-red-600' :305 allergy.severity === 'HIGH' ? 'bg-red-500' :306 allergy.severity === 'MEDIUM' ? 'bg-yellow-500' :307 'bg-green-500'304 allergy.severity === 'CRITICAL' ? 'bg-red-600' : 305 allergy.severity === 'HIGH' ? 'bg-red-500' : 306 allergy.severity === 'MEDIUM' ? 'bg-yellow-500' : 307 'bg-green-500' 308 308 }`}> 309 309 {allergy.severity} Severity 310 310 </span> 311 </p>312 {allergy.reaction && (313 <p className="text-gray-600 text-sm">Reaction: {allergy.reaction}</p>314 )}315 </div>316 ))}317 </div>318 </div>319 )}320 321 {/* Medical Reports */}322 {medicalData.reports && medicalData.reports.length > 0 && (323 <div className="bg-white rounded-lg shadow p-6">324 <h3 className="text-xl font-bold mb-4">Medical Reports</h3>325 <div className="space-y-3">326 {medicalData.reports.map((report) => (327 <div key={report.reportId} className="border-l-4 border-green-500 pl-4 py-2">328 <p className="font-semibold">Report from {report.doctorName}</p>329 <p className="text-gray-600 text-sm">{report.description}</p>330 <p className="text-xs text-gray-500">Date: {report.reportDate}</p>331 </div>332 ))}333 </div>334 </div>335 )}336 337 {/* Lab Test Results */}338 {labResults && labResults.length > 0 && (339 <div className="bg-white rounded-lg shadow p-6">340 <h3 className="text-xl font-bold mb-4">Lab Test Results</h3>341 <div className="space-y-3">342 {labResults.map((result) => (343 <div key={result.labResultId} className="border-l-4 border-purple-500 pl-4 py-2">344 <p className="font-semibold">{result.testName}</p>345 <p className="text-gray-600 text-sm">{result.result}</p>346 {result.notes && (347 <p className="text-gray-600 text-sm">Notes: {result.notes}</p>348 )}349 <p className="text-xs text-gray-500">Date: {result.resultDate}</p>350 {result.technicianName && (351 <p className="text-xs text-gray-500">Technician: {result.technicianName}</p>352 )}353 </div>354 ))}355 </div>356 </div>357 )}358 359 {/* Procedure Results */}360 {procedureResults && procedureResults.length > 0 && (361 <div className="bg-white rounded-lg shadow p-6">362 <h3 className="text-xl font-bold mb-4">Procedure Results</h3>363 <div className="space-y-3">364 {procedureResults.map((result) => (365 <div key={result.procedureResultId} className="border-l-4 border-orange-500 pl-4 py-2">366 <p className="font-semibold">{result.procedureName}</p>367 <p className="text-gray-600 text-sm">{result.result}</p>368 {result.notes && (369 <p className="text-gray-600 text-sm">Notes: {result.notes}</p>370 )}371 <p className="text-xs text-gray-500">Date: {result.resultDate}</p>372 {result.doctorName && (373 <p className="text-xs text-gray-500">Doctor: {result.doctorName}</p>374 )}375 </div>376 ))}377 </div>378 </div>379 )}380 381 {/* No data message */}382 {(!medicalData.diagnoses || medicalData.diagnoses.length === 0) &&383 (!medicalData.symptoms || medicalData.symptoms.length === 0) &&384 (!medicalData.allergies || medicalData.allergies.length === 0) &&385 (!medicalData.reports || medicalData.reports.length === 0) &&386 (!labResults || labResults.length === 0) &&387 (!procedureResults || procedureResults.length === 0) && (388 <div className="bg-blue-50 rounded-lg p-6 text-center">389 <p className="text-gray-600">No medical records found for this patient</p>390 </div>391 )}392 </>393 )}394 </div>395 )}396 397 {/* No search performed */}398 {!searched && !isPatient && (399 <div className="bg-gray-50 rounded-lg p-12 text-center">400 <p className="text-gray-600 text-lg">Enter a patient EMBG to view their medical records</p>401 </div>402 )}403 404 {/* Loading message for patients */}405 {isPatient && loading && (406 <div className="bg-gray-50 rounded-lg p-12 text-center">407 <p className="text-gray-600 text-lg">Loading your medical records...</p>408 </div>409 )}410 </div>311 </p> 312 {allergy.reaction && ( 313 <p className="text-gray-600 text-sm">Reaction: {allergy.reaction}</p> 314 )} 315 </div> 316 ))} 317 </div> 318 </div> 319 )} 320 321 {/* Medical Reports */} 322 {medicalData.reports && medicalData.reports.length > 0 && ( 323 <div className="bg-white rounded-lg shadow p-6"> 324 <h3 className="text-xl font-bold mb-4">Medical Reports</h3> 325 <div className="space-y-3"> 326 {medicalData.reports.map((report) => ( 327 <div key={report.reportId} className="border-l-4 border-green-500 pl-4 py-2"> 328 <p className="font-semibold">Report from {report.doctorName}</p> 329 <p className="text-gray-600 text-sm">{report.description}</p> 330 <p className="text-xs text-gray-500">Date: {report.reportDate}</p> 331 </div> 332 ))} 333 </div> 334 </div> 335 )} 336 337 {/* Lab Test Results */} 338 {labResults && labResults.length > 0 && ( 339 <div className="bg-white rounded-lg shadow p-6"> 340 <h3 className="text-xl font-bold mb-4">Lab Test Results</h3> 341 <div className="space-y-3"> 342 {labResults.map((result) => ( 343 <div key={result.labResultId} className="border-l-4 border-purple-500 pl-4 py-2"> 344 <p className="font-semibold">{result.testName}</p> 345 <p className="text-gray-600 text-sm">{result.result}</p> 346 {result.notes && ( 347 <p className="text-gray-600 text-sm">Notes: {result.notes}</p> 348 )} 349 <p className="text-xs text-gray-500">Date: {result.resultDate}</p> 350 {result.technicianName && ( 351 <p className="text-xs text-gray-500">Technician: {result.technicianName}</p> 352 )} 353 </div> 354 ))} 355 </div> 356 </div> 357 )} 358 359 {/* Procedure Results */} 360 {procedureResults && procedureResults.length > 0 && ( 361 <div className="bg-white rounded-lg shadow p-6"> 362 <h3 className="text-xl font-bold mb-4">Procedure Results</h3> 363 <div className="space-y-3"> 364 {procedureResults.map((result) => ( 365 <div key={result.procedureResultId} className="border-l-4 border-orange-500 pl-4 py-2"> 366 <p className="font-semibold">{result.procedureName}</p> 367 <p className="text-gray-600 text-sm">{result.result}</p> 368 {result.notes && ( 369 <p className="text-gray-600 text-sm">Notes: {result.notes}</p> 370 )} 371 <p className="text-xs text-gray-500">Date: {result.resultDate}</p> 372 {result.doctorName && ( 373 <p className="text-xs text-gray-500">Doctor: {result.doctorName}</p> 374 )} 375 </div> 376 ))} 377 </div> 378 </div> 379 )} 380 381 {/* No data message */} 382 {(!medicalData.diagnoses || medicalData.diagnoses.length === 0) && 383 (!medicalData.symptoms || medicalData.symptoms.length === 0) && 384 (!medicalData.allergies || medicalData.allergies.length === 0) && 385 (!medicalData.reports || medicalData.reports.length === 0) && 386 (!labResults || labResults.length === 0) && 387 (!procedureResults || procedureResults.length === 0) && ( 388 <div className="bg-blue-50 rounded-lg p-6 text-center"> 389 <p className="text-gray-600">No medical records found for this patient</p> 390 </div> 391 )} 392 </> 393 )} 394 </div> 395 )} 396 397 {/* No search performed */} 398 {!searched && !isPatient && ( 399 <div className="bg-gray-50 rounded-lg p-12 text-center"> 400 <p className="text-gray-600 text-lg">Enter a patient EMBG to view their medical records</p> 401 </div> 402 )} 403 404 {/* Loading message for patients */} 405 {isPatient && loading && ( 406 <div className="bg-gray-50 rounded-lg p-12 text-center"> 407 <p className="text-gray-600 text-lg">Loading your medical records...</p> 408 </div> 409 )} 410 </div> 411 411 ); 412 412 } -
frontend/src/pages/medical-reports/MedicalReportList.js
re1f74f6 r9af201e 4 4 import { doctorService } from '../../services/doctorService'; 5 5 import { medicalRecordService } from '../../services/medicalRecordService'; 6 import { medicalItemsService } from '../../services/medicalItemsService';7 6 import ErrorAlert from '../../components/ErrorAlert'; 8 7 import SuccessAlert from '../../components/SuccessAlert'; … … 19 18 const [loading, setLoading] = useState(false); 20 19 21 const [availableDiagnoses, setAvailableDiagnoses] = useState([]);22 const [availablePrescriptions, setAvailablePrescriptions] = useState([]);23 const [availableAllergies, setAvailableAllergies] = useState([]);24 const [availableSymptoms, setAvailableSymptoms] = useState([]);25 26 20 const [selectedDiagnosisIds, setSelectedDiagnosisIds] = useState(new Set()); 27 21 const [selectedPrescriptionIds, setSelectedPrescriptionIds] = useState(new Set()); … … 70 64 const medicalRecordRes = await medicalRecordService.getMedicalRecordByPatientId(patientRes.data.patientId); 71 65 const medicalRecordId = medicalRecordRes.data.recordId; 72 const patientId = patientRes.data.patientId;73 66 74 67 const reportsRes = await medicalReportService.getReportsForMedicalRecord(medicalRecordId); 75 68 setReports(Array.isArray(reportsRes.data) ? reportsRes.data : [reportsRes.data]); 76 77 // Fetch available medical items78 try {79 const diagnosesRes = await medicalItemsService.getDiagnosesForPatient(patientId);80 setAvailableDiagnoses(diagnosesRes.data || []);81 } catch (err) {82 console.error('Error loading diagnoses:', err);83 setAvailableDiagnoses([]);84 }85 86 try {87 const prescriptionsRes = await medicalItemsService.getPrescriptionsForMedicalRecord(medicalRecordId);88 setAvailablePrescriptions(prescriptionsRes.data || []);89 } catch (err) {90 console.error('Error loading prescriptions:', err);91 setAvailablePrescriptions([]);92 }93 94 try {95 const allergiesRes = await medicalItemsService.getAllergiesForMedicalRecord(medicalRecordId);96 setAvailableAllergies(allergiesRes.data || []);97 } catch (err) {98 console.error('Error loading allergies:', err);99 setAvailableAllergies([]);100 }101 102 try {103 const symptomsRes = await medicalItemsService.getSymptomsForMedicalRecord(medicalRecordId);104 setAvailableSymptoms(symptomsRes.data || []);105 } catch (err) {106 console.error('Error loading symptoms:', err);107 setAvailableSymptoms([]);108 }109 69 110 70 setSelectedReport(null); … … 117 77 setReports([]); 118 78 setSearchedPatient(null); 119 setAvailableDiagnoses([]);120 setAvailablePrescriptions([]);121 setAvailableAllergies([]);122 setAvailableSymptoms([]);123 79 } finally { 124 80 setLoading(false); … … 175 131 [name]: value 176 132 })); 177 };178 179 const toggleDiagnosisSelection = (diagnosisId) => {180 setSelectedDiagnosisIds(prev => {181 const newSet = new Set(prev);182 if (newSet.has(diagnosisId)) {183 newSet.delete(diagnosisId);184 } else {185 newSet.add(diagnosisId);186 }187 return newSet;188 });189 };190 191 const togglePrescriptionSelection = (prescriptionId) => {192 setSelectedPrescriptionIds(prev => {193 const newSet = new Set(prev);194 if (newSet.has(prescriptionId)) {195 newSet.delete(prescriptionId);196 } else {197 newSet.add(prescriptionId);198 }199 return newSet;200 });201 };202 203 const toggleAllergySelection = (allergyId) => {204 setSelectedAllergyIds(prev => {205 const newSet = new Set(prev);206 if (newSet.has(allergyId)) {207 newSet.delete(allergyId);208 } else {209 newSet.add(allergyId);210 }211 return newSet;212 });213 };214 215 const toggleSymptomSelection = (symptomId) => {216 setSelectedSymptomIds(prev => {217 const newSet = new Set(prev);218 if (newSet.has(symptomId)) {219 newSet.delete(symptomId);220 } else {221 newSet.add(symptomId);222 }223 return newSet;224 });225 133 }; 226 134 -
frontend/src/pages/procedures/ProcedureList.js
re1f74f6 r9af201e 6 6 7 7 function ProcedureList() { 8 const [user, setUser] = useState({}); 8 9 const [patient, setPatient] = useState(null); 9 10 const [embg, setEmbg] = useState(''); … … 22 23 notes: '', 23 24 }); 25 26 // Load user from localStorage and listen for changes 27 useEffect(() => { 28 const loadUser = () => { 29 const userStr = localStorage.getItem('user'); 30 console.log('[ProcedureList] Loading user from localStorage:', userStr); 31 if (userStr) { 32 try { 33 const parsedUser = JSON.parse(userStr); 34 console.log('[ProcedureList] Parsed user:', parsedUser); 35 setUser(parsedUser); 36 } catch (err) { 37 console.error('Failed to parse user:', err); 38 setUser({}); 39 } 40 } else { 41 console.log('[ProcedureList] No user in localStorage'); 42 setUser({}); 43 } 44 }; 45 46 console.log('[ProcedureList] useEffect running - loading user'); 47 loadUser(); 48 49 // Listen for storage changes (other tabs/windows) 50 window.addEventListener('storage', loadUser); 51 52 // Listen for custom event (same tab changes) 53 window.addEventListener('userStorageChange', loadUser); 54 55 return () => { 56 window.removeEventListener('storage', loadUser); 57 window.removeEventListener('userStorageChange', loadUser); 58 }; 59 }, []); 24 60 25 61 const handleSearch = async (e) => { … … 97 133 setLoading(true); 98 134 99 // Get doctor ID from lo calStorage (set during login)100 const doctorId = localStorage.getItem('doctorId') || 1;135 // Get doctor ID from logged-in user 136 const doctorId = user.doctorId; 101 137 102 138 const request = { … … 108 144 }; 109 145 110 console.log('Sending request:', request); 146 console.log('User object:', user); 147 console.log('Sending procedure request with doctorId:', doctorId, 'Full request:', request); 111 148 await procedureService.recordProcedure(request); 112 149 … … 313 350 314 351 {/* No procedures message */} 315 { !performedProcedures || (performedProcedures.length === 0 && !procedureResults?.length)&& (352 {(!performedProcedures || performedProcedures.length === 0) && !procedureResults?.length && ( 316 353 <div className="bg-blue-50 rounded-lg p-6 text-center"> 317 354 <p className="text-gray-600">No procedures for this patient</p> -
frontend/src/pages/procedures/ProcedureResultForm.js
re1f74f6 r9af201e 1 import React, { useState , useEffect} from 'react';1 import React, { useState } from 'react'; 2 2 import { useNavigate } from 'react-router-dom'; 3 3 import { procedureService } from '../../services/procedureService';
Note:
See TracChangeset
for help on using the changeset viewer.
