| 1 | import React, { useState, useEffect, useCallback } from 'react';
|
|---|
| 2 | import { useParams, useNavigate } from 'react-router-dom';
|
|---|
| 3 | import { medicalRecordService } from '../../services/medicalRecordService';
|
|---|
| 4 | import { doctorService } from '../../services/doctorService';
|
|---|
| 5 | import { labService } from '../../services/labService';
|
|---|
| 6 | import { procedureService } from '../../services/procedureService';
|
|---|
| 7 | import ErrorAlert from '../../components/ErrorAlert';
|
|---|
| 8 | import SuccessAlert from '../../components/SuccessAlert';
|
|---|
| 9 | import Loading from '../../components/Loading';
|
|---|
| 10 | import apiClient from '../../services/api';
|
|---|
| 11 |
|
|---|
| 12 | function MedicalRecordDetail() {
|
|---|
| 13 | const { id } = useParams();
|
|---|
| 14 | const navigate = useNavigate();
|
|---|
| 15 | const user = JSON.parse(localStorage.getItem('user') || '{}');
|
|---|
| 16 | const isDoctor = user.role === 'DOCTOR';
|
|---|
| 17 |
|
|---|
| 18 | const [record, setRecord] = useState(null);
|
|---|
| 19 | const [loading, setLoading] = useState(true);
|
|---|
| 20 | const [error, setError] = useState(null);
|
|---|
| 21 | const [success, setSuccess] = useState(null);
|
|---|
| 22 | const [activeTab, setActiveTab] = useState('view');
|
|---|
| 23 | const [doctors, setDoctors] = useState([]);
|
|---|
| 24 | const [selectedDoctorId, setSelectedDoctorId] = useState(isDoctor ? user.doctorId : '');
|
|---|
| 25 |
|
|---|
| 26 | // Diagnosis form
|
|---|
| 27 | const [diagnosisForm, setDiagnosisForm] = useState({
|
|---|
| 28 | diagnosisId: '',
|
|---|
| 29 | doctorId: '',
|
|---|
| 30 | });
|
|---|
| 31 |
|
|---|
| 32 | // Symptoms form
|
|---|
| 33 | const [symptomsForm, setSymptomsForm] = useState({
|
|---|
| 34 | symptomId: '',
|
|---|
| 35 | });
|
|---|
| 36 |
|
|---|
| 37 | // Allergies form
|
|---|
| 38 | const [allergyForm, setAllergyForm] = useState({
|
|---|
| 39 | allergyId: '',
|
|---|
| 40 | severity: 'MEDIUM',
|
|---|
| 41 | reaction: '',
|
|---|
| 42 | });
|
|---|
| 43 |
|
|---|
| 44 | // Prescription form
|
|---|
| 45 | const [prescriptionForm, setPrescriptionForm] = useState({
|
|---|
| 46 | prescriptionId: '',
|
|---|
| 47 | dosage: '',
|
|---|
| 48 | frequency: '',
|
|---|
| 49 | duration: '',
|
|---|
| 50 | reason: '',
|
|---|
| 51 | });
|
|---|
| 52 |
|
|---|
| 53 | // Dropdown options
|
|---|
| 54 | const [diagnoses, setDiagnoses] = useState([]);
|
|---|
| 55 | const [symptoms, setSymptoms] = useState([]);
|
|---|
| 56 | const [allergies, setAllergies] = useState([]);
|
|---|
| 57 | const [prescriptions, setPrescriptions] = useState([]);
|
|---|
| 58 | const [labTests, setLabTests] = useState([]);
|
|---|
| 59 | const [procedures, setProcedures] = useState([]);
|
|---|
| 60 |
|
|---|
| 61 | // Lab test form
|
|---|
| 62 | const [labTestForm, setLabTestForm] = useState({
|
|---|
| 63 | testId: '',
|
|---|
| 64 | testDate: new Date().toISOString().split('T')[0],
|
|---|
| 65 | notes: '',
|
|---|
| 66 | });
|
|---|
| 67 |
|
|---|
| 68 | // Procedure form
|
|---|
| 69 | const [procedureForm, setProcedureForm] = useState({
|
|---|
| 70 | procedureId: '',
|
|---|
| 71 | procedureDate: new Date().toISOString().split('T')[0],
|
|---|
| 72 | notes: '',
|
|---|
| 73 | diagnosisId: '',
|
|---|
| 74 | });
|
|---|
| 75 |
|
|---|
| 76 | const [labResults, setLabResults] = useState([]);
|
|---|
| 77 | const [procedureResults, setProcedureResults] = useState([]);
|
|---|
| 78 |
|
|---|
| 79 | const fetchRecord = useCallback(async () => {
|
|---|
| 80 | try {
|
|---|
| 81 | setLoading(true);
|
|---|
| 82 | const response = await medicalRecordService.getMedicalRecordByPatientId(id);
|
|---|
| 83 | setRecord(response.data);
|
|---|
| 84 |
|
|---|
| 85 | // Fetch lab results
|
|---|
| 86 | try {
|
|---|
| 87 | const resultsResponse = await labService.getLabResultsForMedicalRecord(response.data.recordId);
|
|---|
| 88 | setLabResults(resultsResponse.data || []);
|
|---|
| 89 | } catch (err) {
|
|---|
| 90 | console.error('Failed to fetch lab results:', err);
|
|---|
| 91 | setLabResults([]);
|
|---|
| 92 | }
|
|---|
| 93 |
|
|---|
| 94 | // Fetch procedure results
|
|---|
| 95 | try {
|
|---|
| 96 | const procedureResultsResponse = await procedureService.getProcedureResultsForMedicalRecord(response.data.recordId);
|
|---|
| 97 | setProcedureResults(procedureResultsResponse.data || []);
|
|---|
| 98 | } catch (err) {
|
|---|
| 99 | console.error('Failed to fetch procedure results:', err);
|
|---|
| 100 | setProcedureResults([]);
|
|---|
| 101 | }
|
|---|
| 102 | } catch (err) {
|
|---|
| 103 | setError('Failed to fetch medical record');
|
|---|
| 104 | console.error(err);
|
|---|
| 105 | } finally {
|
|---|
| 106 | setLoading(false);
|
|---|
| 107 | }
|
|---|
| 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 | }
|
|---|
| 119 | }
|
|---|
| 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 |
|
|---|
| 164 | const handleAddDiagnosis = async (e) => {
|
|---|
| 165 | e.preventDefault();
|
|---|
| 166 | try {
|
|---|
| 167 | if (!diagnosisForm.diagnosisId) {
|
|---|
| 168 | setError('Please select a diagnosis');
|
|---|
| 169 | return;
|
|---|
| 170 | }
|
|---|
| 171 | if (!selectedDoctorId) {
|
|---|
| 172 | setError('Please select a doctor');
|
|---|
| 173 | return;
|
|---|
| 174 | }
|
|---|
| 175 | const selectedDiagnosis = diagnoses.find(d => d.id === parseInt(diagnosisForm.diagnosisId));
|
|---|
| 176 | await apiClient.post('/diagnoses', {
|
|---|
| 177 | patientId: parseInt(record.patientId),
|
|---|
| 178 | doctorId: parseInt(selectedDoctorId),
|
|---|
| 179 | name: selectedDiagnosis.name,
|
|---|
| 180 | description: '',
|
|---|
| 181 | });
|
|---|
| 182 | setSuccess('Diagnosis recorded successfully');
|
|---|
| 183 | setDiagnosisForm({ diagnosisId: '', doctorId: '' });
|
|---|
| 184 | setTimeout(() => fetchRecord(), 1000);
|
|---|
| 185 | } catch (err) {
|
|---|
| 186 | setError(err.response?.data?.error || 'Failed to record diagnosis');
|
|---|
| 187 | }
|
|---|
| 188 | };
|
|---|
| 189 |
|
|---|
| 190 | const handleAddSymptom = async (e) => {
|
|---|
| 191 | e.preventDefault();
|
|---|
| 192 | try {
|
|---|
| 193 | if (!symptomsForm.symptomId) {
|
|---|
| 194 | setError('Please select a symptom');
|
|---|
| 195 | return;
|
|---|
| 196 | }
|
|---|
| 197 | await apiClient.post(`/medical-records/${record.recordId}/symptoms`, {
|
|---|
| 198 | symptomId: parseInt(symptomsForm.symptomId),
|
|---|
| 199 | severity: 'MEDIUM',
|
|---|
| 200 | });
|
|---|
| 201 | setSuccess('Symptom recorded successfully');
|
|---|
| 202 | setSymptomsForm({ symptomId: '' });
|
|---|
| 203 | setTimeout(() => fetchRecord(), 1000);
|
|---|
| 204 | } catch (err) {
|
|---|
| 205 | setError(err.response?.data?.error || 'Failed to record symptom');
|
|---|
| 206 | }
|
|---|
| 207 | };
|
|---|
| 208 |
|
|---|
| 209 | const handleAddAllergy = async (e) => {
|
|---|
| 210 | e.preventDefault();
|
|---|
| 211 | try {
|
|---|
| 212 | if (!allergyForm.allergyId) {
|
|---|
| 213 | setError('Please select an allergy');
|
|---|
| 214 | return;
|
|---|
| 215 | }
|
|---|
| 216 | await apiClient.post(`/medical-records/${record.recordId}/allergies`, {
|
|---|
| 217 | allergyId: parseInt(allergyForm.allergyId),
|
|---|
| 218 | severity: allergyForm.severity,
|
|---|
| 219 | reaction: allergyForm.reaction,
|
|---|
| 220 | });
|
|---|
| 221 | setSuccess('Allergy recorded successfully');
|
|---|
| 222 | setAllergyForm({ allergyId: '', severity: 'MEDIUM', reaction: '' });
|
|---|
| 223 | setTimeout(() => fetchRecord(), 1000);
|
|---|
| 224 | } catch (err) {
|
|---|
| 225 | setError(err.response?.data?.error || 'Failed to record allergy');
|
|---|
| 226 | }
|
|---|
| 227 | };
|
|---|
| 228 |
|
|---|
| 229 | const handleAddPrescription = async (e) => {
|
|---|
| 230 | e.preventDefault();
|
|---|
| 231 | try {
|
|---|
| 232 | if (!prescriptionForm.prescriptionId) {
|
|---|
| 233 | setError('Please select a prescription medication');
|
|---|
| 234 | return;
|
|---|
| 235 | }
|
|---|
| 236 | if (!prescriptionForm.dosage.trim()) {
|
|---|
| 237 | setError('Dosage is required');
|
|---|
| 238 | return;
|
|---|
| 239 | }
|
|---|
| 240 | if (!prescriptionForm.frequency.trim()) {
|
|---|
| 241 | setError('Frequency is required');
|
|---|
| 242 | return;
|
|---|
| 243 | }
|
|---|
| 244 | if (!prescriptionForm.duration.trim()) {
|
|---|
| 245 | setError('Duration is required');
|
|---|
| 246 | return;
|
|---|
| 247 | }
|
|---|
| 248 | const selectedPrescription = prescriptions.find(p => p.id === parseInt(prescriptionForm.prescriptionId));
|
|---|
| 249 | await apiClient.post('/prescriptions', {
|
|---|
| 250 | medicalRecordId: record.recordId,
|
|---|
| 251 | medicationName: selectedPrescription.name,
|
|---|
| 252 | dosage: prescriptionForm.dosage,
|
|---|
| 253 | frequency: prescriptionForm.frequency,
|
|---|
| 254 | duration: prescriptionForm.duration,
|
|---|
| 255 | notes: prescriptionForm.reason,
|
|---|
| 256 | });
|
|---|
| 257 | setSuccess('Prescription recorded successfully');
|
|---|
| 258 | setPrescriptionForm({
|
|---|
| 259 | prescriptionId: '',
|
|---|
| 260 | dosage: '',
|
|---|
| 261 | frequency: '',
|
|---|
| 262 | duration: '',
|
|---|
| 263 | reason: '',
|
|---|
| 264 | });
|
|---|
| 265 | setTimeout(() => fetchRecord(), 1000);
|
|---|
| 266 | } catch (err) {
|
|---|
| 267 | setError(err.response?.data?.error || 'Failed to record prescription');
|
|---|
| 268 | }
|
|---|
| 269 | };
|
|---|
| 270 |
|
|---|
| 271 | if (loading) return <Loading />;
|
|---|
| 272 |
|
|---|
| 273 | if (!record) {
|
|---|
| 274 | return (
|
|---|
| 275 | <div>
|
|---|
| 276 | <ErrorAlert message="Medical record not found" onClose={() => navigate('/medical-records')} />
|
|---|
| 277 | </div>
|
|---|
| 278 | );
|
|---|
| 279 | }
|
|---|
| 280 |
|
|---|
| 281 | return (
|
|---|
| 282 | <div>
|
|---|
| 283 | <div className="flex justify-between items-center mb-6">
|
|---|
| 284 | <h1 style={{ fontSize: '36px', fontWeight: 'normal' }}>Medical Record</h1>
|
|---|
| 285 | <button
|
|---|
| 286 | onClick={() => navigate('/medical-records')}
|
|---|
| 287 | className="bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400"
|
|---|
| 288 | >
|
|---|
| 289 | Back
|
|---|
| 290 | </button>
|
|---|
| 291 | </div>
|
|---|
| 292 |
|
|---|
| 293 | {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
|
|---|
| 294 | {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />}
|
|---|
| 295 |
|
|---|
| 296 | {/* Patient Info */}
|
|---|
| 297 | <div className="bg-white rounded-lg shadow p-6 mb-6">
|
|---|
| 298 | <h2 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Patient Information</h2>
|
|---|
| 299 | <div className="grid grid-cols-4 gap-4">
|
|---|
| 300 | <div>
|
|---|
| 301 | <p className="font-normal text-sm text-gray-600">Patient Name</p>
|
|---|
| 302 | <p className="font-normal">{record.patientName}</p>
|
|---|
| 303 | </div>
|
|---|
| 304 | <div>
|
|---|
| 305 | <p className="font-normal text-sm text-gray-600">EMBG</p>
|
|---|
| 306 | <p className="font-normal">{record.embg}</p>
|
|---|
| 307 | </div>
|
|---|
| 308 | <div>
|
|---|
| 309 | <p className="font-normal text-sm text-gray-600">Record ID</p>
|
|---|
| 310 | <p className="font-normal">{record.recordId}</p>
|
|---|
| 311 | </div>
|
|---|
| 312 | <div>
|
|---|
| 313 | <p className="font-normal text-sm text-gray-600">Patient ID</p>
|
|---|
| 314 | <p className="font-normal">{record.patientId}</p>
|
|---|
| 315 | </div>
|
|---|
| 316 | </div>
|
|---|
| 317 | </div>
|
|---|
| 318 |
|
|---|
| 319 | {/* Tabs */}
|
|---|
| 320 | <div className="flex gap-2 mb-6 border-b">
|
|---|
| 321 | <button
|
|---|
| 322 | onClick={() => setActiveTab('view')}
|
|---|
| 323 | style={{ fontWeight: 'normal' }}
|
|---|
| 324 | className={`px-6 py-3 ${
|
|---|
| 325 | activeTab === 'view'
|
|---|
| 326 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 327 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 328 | }`}
|
|---|
| 329 | >
|
|---|
| 330 | View Medical Data
|
|---|
| 331 | </button>
|
|---|
| 332 | <span className="text-gray-600 px-3" style={{ fontWeight: 'normal' }}>|</span>
|
|---|
| 333 | <button
|
|---|
| 334 | onClick={() => setActiveTab('diagnosis')}
|
|---|
| 335 | style={{ fontWeight: 'normal' }}
|
|---|
| 336 | className={`px-6 py-3 ${
|
|---|
| 337 | activeTab === 'diagnosis'
|
|---|
| 338 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 339 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 340 | }`}
|
|---|
| 341 | >
|
|---|
| 342 | Record Diagnosis
|
|---|
| 343 | </button>
|
|---|
| 344 | <span className="text-gray-600 px-3" style={{ fontWeight: 'normal' }}>|</span>
|
|---|
| 345 | <button
|
|---|
| 346 | onClick={() => setActiveTab('symptoms')}
|
|---|
| 347 | style={{ fontWeight: 'normal' }}
|
|---|
| 348 | className={`px-6 py-3 ${
|
|---|
| 349 | activeTab === 'symptoms'
|
|---|
| 350 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 351 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 352 | }`}
|
|---|
| 353 | >
|
|---|
| 354 | Record Symptoms
|
|---|
| 355 | </button>
|
|---|
| 356 | <span className="text-gray-600 px-3" style={{ fontWeight: 'normal' }}>|</span>
|
|---|
| 357 | <button
|
|---|
| 358 | onClick={() => setActiveTab('allergies')}
|
|---|
| 359 | style={{ fontWeight: 'normal' }}
|
|---|
| 360 | className={`px-6 py-3 ${
|
|---|
| 361 | activeTab === 'allergies'
|
|---|
| 362 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 363 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 364 | }`}
|
|---|
| 365 | >
|
|---|
| 366 | Record Allergies
|
|---|
| 367 | </button>
|
|---|
| 368 | <span className="text-gray-600 px-3" style={{ fontWeight: 'normal' }}>|</span>
|
|---|
| 369 | <button
|
|---|
| 370 | onClick={() => setActiveTab('prescription')}
|
|---|
| 371 | style={{ fontWeight: 'normal' }}
|
|---|
| 372 | className={`px-6 py-3 ${
|
|---|
| 373 | activeTab === 'prescription'
|
|---|
| 374 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 375 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 376 | }`}
|
|---|
| 377 | >
|
|---|
| 378 | Record Prescription
|
|---|
| 379 | </button>
|
|---|
| 380 | <span className="text-gray-600 px-3" style={{ fontWeight: 'normal' }}>|</span>
|
|---|
| 381 | <button
|
|---|
| 382 | onClick={() => setActiveTab('lab-tests')}
|
|---|
| 383 | style={{ fontWeight: 'normal' }}
|
|---|
| 384 | className={`px-6 py-3 ${
|
|---|
| 385 | activeTab === 'lab-tests'
|
|---|
| 386 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 387 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 388 | }`}
|
|---|
| 389 | >
|
|---|
| 390 | Request Lab Test
|
|---|
| 391 | </button>
|
|---|
| 392 | <span className="text-gray-600 px-3" style={{ fontWeight: 'normal' }}>|</span>
|
|---|
| 393 | <button
|
|---|
| 394 | onClick={() => setActiveTab('procedures')}
|
|---|
| 395 | style={{ fontWeight: 'normal' }}
|
|---|
| 396 | className={`px-6 py-3 ${
|
|---|
| 397 | activeTab === 'procedures'
|
|---|
| 398 | ? 'border-b-2 border-purple-600 text-purple-600'
|
|---|
| 399 | : 'text-purple-600 hover:text-purple-700'
|
|---|
| 400 | }`}
|
|---|
| 401 | >
|
|---|
| 402 | Request Procedure
|
|---|
| 403 | </button>
|
|---|
| 404 | </div>
|
|---|
| 405 |
|
|---|
| 406 | {/* View Medical Data Tab */}
|
|---|
| 407 | {activeTab === 'view' && (
|
|---|
| 408 | <div className="space-y-6">
|
|---|
| 409 | {/* Diagnoses */}
|
|---|
| 410 | {record.diagnoses && record.diagnoses.length > 0 && (
|
|---|
| 411 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 412 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Diagnoses</h3>
|
|---|
| 413 | <div className="space-y-3">
|
|---|
| 414 | {record.diagnoses.map((diagnosis) => (
|
|---|
| 415 | <div key={diagnosis.diagnosisId} className="border-l-4 border-purple-500 pl-4 py-2">
|
|---|
| 416 | <p className="font-normal" style={{ fontSize: '18px' }}>{diagnosis.name}</p>
|
|---|
| 417 | {diagnosis.description && (
|
|---|
| 418 | <p className="font-normal text-gray-600 text-sm">{diagnosis.description}</p>
|
|---|
| 419 | )}
|
|---|
| 420 | <p className="font-normal text-xs text-gray-500">By: {diagnosis.doctorName}</p>
|
|---|
| 421 | </div>
|
|---|
| 422 | ))}
|
|---|
| 423 | </div>
|
|---|
| 424 | </div>
|
|---|
| 425 | )}
|
|---|
| 426 |
|
|---|
| 427 | {/* Symptoms */}
|
|---|
| 428 | {record.symptoms && record.symptoms.length > 0 && (
|
|---|
| 429 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 430 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Symptoms</h3>
|
|---|
| 431 | <div className="flex flex-wrap gap-2">
|
|---|
| 432 | {record.symptoms.map((symptom) => (
|
|---|
| 433 | <span
|
|---|
| 434 | key={symptom.symptomId}
|
|---|
| 435 | className="bg-yellow-100 text-yellow-800 px-3 py-1 rounded-full text-sm font-normal"
|
|---|
| 436 | >
|
|---|
| 437 | {symptom.symptomName}
|
|---|
| 438 | </span>
|
|---|
| 439 | ))}
|
|---|
| 440 | </div>
|
|---|
| 441 | </div>
|
|---|
| 442 | )}
|
|---|
| 443 |
|
|---|
| 444 | {/* Allergies */}
|
|---|
| 445 | {record.allergies && record.allergies.length > 0 && (
|
|---|
| 446 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 447 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Allergies</h3>
|
|---|
| 448 | <div className="space-y-3">
|
|---|
| 449 | {record.allergies.map((allergy) => (
|
|---|
| 450 | <div key={allergy.allergyId} className="border-l-4 border-red-500 pl-4 py-2">
|
|---|
| 451 | <p className="font-normal">{allergy.allergyName}</p>
|
|---|
| 452 | <p className="font-normal" style={{ fontSize: '14px' }}>
|
|---|
| 453 | <span
|
|---|
| 454 | className="font-normal"
|
|---|
| 455 | style={{
|
|---|
| 456 | padding: '4px 8px',
|
|---|
| 457 | borderRadius: '4px',
|
|---|
| 458 | color: 'white',
|
|---|
| 459 | fontSize: '12px',
|
|---|
| 460 | background: allergy.severity === 'CRITICAL' ? '#dc2626' : allergy.severity === 'HIGH' ? '#ef4444' : allergy.severity === 'MEDIUM' ? '#eab308' : '#22c55e'
|
|---|
| 461 | }}
|
|---|
| 462 | >
|
|---|
| 463 | {allergy.severity} Severity
|
|---|
| 464 | </span>
|
|---|
| 465 | </p>
|
|---|
| 466 | {allergy.reaction && (
|
|---|
| 467 | <p className="font-normal text-gray-600 text-sm">Reaction: {allergy.reaction}</p>
|
|---|
| 468 | )}
|
|---|
| 469 | </div>
|
|---|
| 470 | ))}
|
|---|
| 471 | </div>
|
|---|
| 472 | </div>
|
|---|
| 473 | )}
|
|---|
| 474 |
|
|---|
| 475 | {/* Prescriptions */}
|
|---|
| 476 | {record.prescriptions && record.prescriptions.length > 0 && (
|
|---|
| 477 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 478 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Prescriptions</h3>
|
|---|
| 479 | <div className="space-y-3">
|
|---|
| 480 | {record.prescriptions.map((prescription) => (
|
|---|
| 481 | <div key={prescription.prescriptionId} className="border-l-4 border-purple-500 pl-4 py-2">
|
|---|
| 482 | <p className="font-normal">{prescription.medicationName}</p>
|
|---|
| 483 | <p className="font-normal text-sm text-gray-600">Dosage: {prescription.dosage}</p>
|
|---|
| 484 | <p className="font-normal text-sm text-gray-600">Frequency: {prescription.frequency}</p>
|
|---|
| 485 | <p className="font-normal text-sm text-gray-600">Duration: {prescription.duration}</p>
|
|---|
| 486 | </div>
|
|---|
| 487 | ))}
|
|---|
| 488 | </div>
|
|---|
| 489 | </div>
|
|---|
| 490 | )}
|
|---|
| 491 |
|
|---|
| 492 | {/* Reports */}
|
|---|
| 493 | {record.reports && record.reports.length > 0 && (
|
|---|
| 494 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 495 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Medical Reports</h3>
|
|---|
| 496 | <div className="space-y-3">
|
|---|
| 497 | {record.reports.map((report) => (
|
|---|
| 498 | <div key={report.reportId} className="border-l-4 border-green-500 pl-4 py-2">
|
|---|
| 499 | <p style={{ fontWeight: 'normal' }}>Report from {report.doctorName}</p>
|
|---|
| 500 | <p className="font-normal text-gray-600 text-sm">{report.description}</p>
|
|---|
| 501 | <p className="font-normal text-xs text-gray-500">Date: {report.reportDate}</p>
|
|---|
| 502 | </div>
|
|---|
| 503 | ))}
|
|---|
| 504 | </div>
|
|---|
| 505 | </div>
|
|---|
| 506 | )}
|
|---|
| 507 |
|
|---|
| 508 | {/* Lab Results */}
|
|---|
| 509 | {labResults && labResults.length > 0 && (
|
|---|
| 510 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 511 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Lab Test Results</h3>
|
|---|
| 512 | <div className="space-y-3">
|
|---|
| 513 | {labResults.map((result) => (
|
|---|
| 514 | <div key={result.resultId} className="border-l-4 border-cyan-500 pl-4 py-3 bg-cyan-50 rounded">
|
|---|
| 515 | <p className="font-normal" style={{ fontSize: '18px', color: '#0891b2' }}>{result.testName}</p>
|
|---|
| 516 | <p className="font-normal text-sm text-gray-700 mt-2">Results: {result.results}</p>
|
|---|
| 517 | <p className="font-normal text-sm text-gray-600">Result Date: {result.resultDate}</p>
|
|---|
| 518 | </div>
|
|---|
| 519 | ))}
|
|---|
| 520 | </div>
|
|---|
| 521 | </div>
|
|---|
| 522 | )}
|
|---|
| 523 |
|
|---|
| 524 | {/* Procedure Results */}
|
|---|
| 525 | {procedureResults && procedureResults.length > 0 && (
|
|---|
| 526 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 527 | <h3 className="font-normal" style={{ fontSize: '20px', marginBottom: '16px' }}>Procedure Results</h3>
|
|---|
| 528 | <div className="space-y-3">
|
|---|
| 529 | {procedureResults.map((result) => (
|
|---|
| 530 | <div key={result.resultId} className="border-l-4 border-orange-500 pl-4 py-3 bg-orange-50 rounded">
|
|---|
| 531 | <p className="font-normal" style={{ fontSize: '18px', color: '#b45309' }}>{result.procedure?.procedureType || 'Procedure'}</p>
|
|---|
| 532 | <p className="font-normal text-sm text-gray-700 mt-2">Outcome: {result.resultDescription}</p>
|
|---|
| 533 | <p className="font-normal text-sm text-gray-600">Result Date: {result.resultDate}</p>
|
|---|
| 534 | </div>
|
|---|
| 535 | ))}
|
|---|
| 536 | </div>
|
|---|
| 537 | </div>
|
|---|
| 538 | )}
|
|---|
| 539 |
|
|---|
| 540 | {(!record.diagnoses || record.diagnoses.length === 0) &&
|
|---|
| 541 | (!record.symptoms || record.symptoms.length === 0) &&
|
|---|
| 542 | (!record.allergies || record.allergies.length === 0) &&
|
|---|
| 543 | (!record.reports || record.reports.length === 0) &&
|
|---|
| 544 | (!labResults || labResults.length === 0) &&
|
|---|
| 545 | (!procedureResults || procedureResults.length === 0) && (
|
|---|
| 546 | <div className="bg-blue-50 rounded-lg p-6 text-center">
|
|---|
| 547 | <p className="text-gray-600">No medical data recorded yet</p>
|
|---|
| 548 | </div>
|
|---|
| 549 | )}
|
|---|
| 550 | </div>
|
|---|
| 551 | )}
|
|---|
| 552 |
|
|---|
| 553 | {/* Record Diagnosis Tab */}
|
|---|
| 554 | {activeTab === 'diagnosis' && (
|
|---|
| 555 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 556 | <h3 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Record New Diagnosis</h3>
|
|---|
| 557 | <form onSubmit={handleAddDiagnosis} className="space-y-4 max-w-2xl">
|
|---|
| 558 | <div>
|
|---|
| 559 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Doctor *</label>
|
|---|
| 560 | {isDoctor ? (
|
|---|
| 561 | <p className="px-4 py-2 bg-gray-100 rounded-lg text-gray-700">
|
|---|
| 562 | Dr. {user.firstName} {user.lastName}
|
|---|
| 563 | </p>
|
|---|
| 564 | ) : (
|
|---|
| 565 | <select
|
|---|
| 566 | value={selectedDoctorId}
|
|---|
| 567 | onChange={(e) => setSelectedDoctorId(e.target.value)}
|
|---|
| 568 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 569 | required
|
|---|
| 570 | >
|
|---|
| 571 | <option value="">Select doctor</option>
|
|---|
| 572 | {doctors.map((doc) => (
|
|---|
| 573 | <option key={doc.doctorId} value={doc.doctorId}>
|
|---|
| 574 | Dr. {doc.firstName} {doc.lastName}
|
|---|
| 575 | </option>
|
|---|
| 576 | ))}
|
|---|
| 577 | </select>
|
|---|
| 578 | )}
|
|---|
| 579 | </div>
|
|---|
| 580 | <div>
|
|---|
| 581 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Diagnosis *</label>
|
|---|
| 582 | <select
|
|---|
| 583 | value={diagnosisForm.diagnosisId}
|
|---|
| 584 | onChange={(e) => setDiagnosisForm({ ...diagnosisForm, diagnosisId: e.target.value })}
|
|---|
| 585 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 586 | required
|
|---|
| 587 | >
|
|---|
| 588 | <option value="">{diagnoses.length === 0 ? 'Loading diagnoses...' : 'Select diagnosis'}</option>
|
|---|
| 589 | {diagnoses && diagnoses.length > 0 && diagnoses.map((diagnosis) => (
|
|---|
| 590 | <option key={diagnosis.id} value={diagnosis.id}>
|
|---|
| 591 | {diagnosis.name}
|
|---|
| 592 | </option>
|
|---|
| 593 | ))}
|
|---|
| 594 | </select>
|
|---|
| 595 | {diagnoses.length === 0 && (
|
|---|
| 596 | <p className="text-sm text-gray-500 mt-1">No diagnoses available. Please wait...</p>
|
|---|
| 597 | )}
|
|---|
| 598 | </div>
|
|---|
| 599 | <button
|
|---|
| 600 | type="submit"
|
|---|
| 601 | style={{background: '#9333ea', color: 'white', padding: '6px 14px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '14px', fontWeight: '600'}} onMouseEnter={(e) => e.currentTarget.style.background = '#7e22ce'} onMouseLeave={(e) => e.currentTarget.style.background = '#9333ea'}
|
|---|
| 602 | >
|
|---|
| 603 | Record Diagnosis
|
|---|
| 604 | </button>
|
|---|
| 605 | </form>
|
|---|
| 606 | </div>
|
|---|
| 607 | )}
|
|---|
| 608 |
|
|---|
| 609 | {/* Record Symptoms Tab */}
|
|---|
| 610 | {activeTab === 'symptoms' && (
|
|---|
| 611 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 612 | <h3 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Record Patient Symptoms</h3>
|
|---|
| 613 | <form onSubmit={handleAddSymptom} className="space-y-4 max-w-2xl">
|
|---|
| 614 | <div>
|
|---|
| 615 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Doctor *</label>
|
|---|
| 616 | {isDoctor ? (
|
|---|
| 617 | <p className="px-4 py-2 bg-gray-100 rounded-lg text-gray-700">
|
|---|
| 618 | Dr. {user.firstName} {user.lastName}
|
|---|
| 619 | </p>
|
|---|
| 620 | ) : (
|
|---|
| 621 | <select
|
|---|
| 622 | value={selectedDoctorId}
|
|---|
| 623 | onChange={(e) => setSelectedDoctorId(e.target.value)}
|
|---|
| 624 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 625 | required
|
|---|
| 626 | >
|
|---|
| 627 | <option value="">Select doctor</option>
|
|---|
| 628 | {doctors.map((doc) => (
|
|---|
| 629 | <option key={doc.doctorId} value={doc.doctorId}>
|
|---|
| 630 | Dr. {doc.firstName} {doc.lastName}
|
|---|
| 631 | </option>
|
|---|
| 632 | ))}
|
|---|
| 633 | </select>
|
|---|
| 634 | )}
|
|---|
| 635 | </div>
|
|---|
| 636 | <div>
|
|---|
| 637 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Symptom *</label>
|
|---|
| 638 | <select
|
|---|
| 639 | value={symptomsForm.symptomId}
|
|---|
| 640 | onChange={(e) => setSymptomsForm({ ...symptomsForm, symptomId: e.target.value })}
|
|---|
| 641 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 642 | required
|
|---|
| 643 | >
|
|---|
| 644 | <option value="">Select symptom</option>
|
|---|
| 645 | {symptoms.filter(s => !record.symptoms?.some(rs => rs.symptomId === s.id)).map((symptom) => (
|
|---|
| 646 | <option key={symptom.id} value={symptom.id}>
|
|---|
| 647 | {symptom.name}
|
|---|
| 648 | </option>
|
|---|
| 649 | ))}
|
|---|
| 650 | </select>
|
|---|
| 651 | </div>
|
|---|
| 652 | <button
|
|---|
| 653 | type="submit"
|
|---|
| 654 | style={{background: '#9333ea', color: 'white', padding: '6px 14px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '14px', fontWeight: '600'}} onMouseEnter={(e) => e.currentTarget.style.background = '#7e22ce'} onMouseLeave={(e) => e.currentTarget.style.background = '#9333ea'}
|
|---|
| 655 | >
|
|---|
| 656 | Record Symptom
|
|---|
| 657 | </button>
|
|---|
| 658 | </form>
|
|---|
| 659 | </div>
|
|---|
| 660 | )}
|
|---|
| 661 |
|
|---|
| 662 | {/* Record Allergies Tab */}
|
|---|
| 663 | {activeTab === 'allergies' && (
|
|---|
| 664 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 665 | <h3 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Record Patient Allergies</h3>
|
|---|
| 666 | <form onSubmit={handleAddAllergy} className="space-y-4 max-w-2xl">
|
|---|
| 667 | <div>
|
|---|
| 668 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Doctor *</label>
|
|---|
| 669 | {isDoctor ? (
|
|---|
| 670 | <p className="px-4 py-2 bg-gray-100 rounded-lg text-gray-700">
|
|---|
| 671 | Dr. {user.firstName} {user.lastName}
|
|---|
| 672 | </p>
|
|---|
| 673 | ) : (
|
|---|
| 674 | <select
|
|---|
| 675 | value={selectedDoctorId}
|
|---|
| 676 | onChange={(e) => setSelectedDoctorId(e.target.value)}
|
|---|
| 677 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 678 | required
|
|---|
| 679 | >
|
|---|
| 680 | <option value="">Select doctor</option>
|
|---|
| 681 | {doctors.map((doc) => (
|
|---|
| 682 | <option key={doc.doctorId} value={doc.doctorId}>
|
|---|
| 683 | Dr. {doc.firstName} {doc.lastName}
|
|---|
| 684 | </option>
|
|---|
| 685 | ))}
|
|---|
| 686 | </select>
|
|---|
| 687 | )}
|
|---|
| 688 | </div>
|
|---|
| 689 | <div>
|
|---|
| 690 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Allergy *</label>
|
|---|
| 691 | <select
|
|---|
| 692 | value={allergyForm.allergyId}
|
|---|
| 693 | onChange={(e) => setAllergyForm({ ...allergyForm, allergyId: e.target.value })}
|
|---|
| 694 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 695 | required
|
|---|
| 696 | >
|
|---|
| 697 | <option value="">Select allergy</option>
|
|---|
| 698 | {allergies.filter(a => !record.allergies?.some(ra => ra.allergyId === a.id)).map((allergy) => (
|
|---|
| 699 | <option key={allergy.id} value={allergy.id}>
|
|---|
| 700 | {allergy.name}
|
|---|
| 701 | </option>
|
|---|
| 702 | ))}
|
|---|
| 703 | </select>
|
|---|
| 704 | </div>
|
|---|
| 705 | <div>
|
|---|
| 706 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Severity *</label>
|
|---|
| 707 | <select
|
|---|
| 708 | value={allergyForm.severity}
|
|---|
| 709 | onChange={(e) => setAllergyForm({ ...allergyForm, severity: e.target.value })}
|
|---|
| 710 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 711 | >
|
|---|
| 712 | <option value="LOW">Low</option>
|
|---|
| 713 | <option value="MEDIUM">Medium</option>
|
|---|
| 714 | <option value="HIGH">High</option>
|
|---|
| 715 | <option value="CRITICAL">Critical</option>
|
|---|
| 716 | </select>
|
|---|
| 717 | </div>
|
|---|
| 718 | <div>
|
|---|
| 719 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Reaction</label>
|
|---|
| 720 | <textarea
|
|---|
| 721 | value={allergyForm.reaction}
|
|---|
| 722 | onChange={(e) => setAllergyForm({ ...allergyForm, reaction: e.target.value })}
|
|---|
| 723 | placeholder="Describe the allergic reaction"
|
|---|
| 724 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 725 | rows="3"
|
|---|
| 726 | />
|
|---|
| 727 | </div>
|
|---|
| 728 | <button
|
|---|
| 729 | type="submit"
|
|---|
| 730 | style={{background: '#9333ea', color: 'white', padding: '6px 14px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '14px', fontWeight: '600'}} onMouseEnter={(e) => e.currentTarget.style.background = '#7e22ce'} onMouseLeave={(e) => e.currentTarget.style.background = '#9333ea'}
|
|---|
| 731 | >
|
|---|
| 732 | Record Allergy
|
|---|
| 733 | </button>
|
|---|
| 734 | </form>
|
|---|
| 735 | </div>
|
|---|
| 736 | )}
|
|---|
| 737 |
|
|---|
| 738 | {/* Record Prescription Tab */}
|
|---|
| 739 | {activeTab === 'prescription' && (
|
|---|
| 740 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 741 | <h3 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Record Prescription</h3>
|
|---|
| 742 | <form onSubmit={handleAddPrescription} className="space-y-4 max-w-2xl">
|
|---|
| 743 | <div>
|
|---|
| 744 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Doctor *</label>
|
|---|
| 745 | {isDoctor ? (
|
|---|
| 746 | <p className="px-4 py-2 bg-gray-100 rounded-lg text-gray-700">
|
|---|
| 747 | Dr. {user.firstName} {user.lastName}
|
|---|
| 748 | </p>
|
|---|
| 749 | ) : (
|
|---|
| 750 | <select
|
|---|
| 751 | value={selectedDoctorId}
|
|---|
| 752 | onChange={(e) => setSelectedDoctorId(e.target.value)}
|
|---|
| 753 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 754 | required
|
|---|
| 755 | >
|
|---|
| 756 | <option value="">Select doctor</option>
|
|---|
| 757 | {doctors.map((doc) => (
|
|---|
| 758 | <option key={doc.doctorId} value={doc.doctorId}>
|
|---|
| 759 | Dr. {doc.firstName} {doc.lastName}
|
|---|
| 760 | </option>
|
|---|
| 761 | ))}
|
|---|
| 762 | </select>
|
|---|
| 763 | )}
|
|---|
| 764 | </div>
|
|---|
| 765 | <div>
|
|---|
| 766 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Medication *</label>
|
|---|
| 767 | <select
|
|---|
| 768 | value={prescriptionForm.prescriptionId}
|
|---|
| 769 | onChange={(e) =>
|
|---|
| 770 | setPrescriptionForm({ ...prescriptionForm, prescriptionId: e.target.value })
|
|---|
| 771 | }
|
|---|
| 772 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 773 | required
|
|---|
| 774 | >
|
|---|
| 775 | <option value="">Select medication</option>
|
|---|
| 776 | {prescriptions.map((prescription) => (
|
|---|
| 777 | <option key={prescription.id} value={prescription.id}>
|
|---|
| 778 | {prescription.name}
|
|---|
| 779 | </option>
|
|---|
| 780 | ))}
|
|---|
| 781 | </select>
|
|---|
| 782 | </div>
|
|---|
| 783 | <div className="grid grid-cols-2 gap-4">
|
|---|
| 784 | <div>
|
|---|
| 785 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Dosage *</label>
|
|---|
| 786 | <input
|
|---|
| 787 | type="text"
|
|---|
| 788 | value={prescriptionForm.dosage}
|
|---|
| 789 | onChange={(e) =>
|
|---|
| 790 | setPrescriptionForm({ ...prescriptionForm, dosage: e.target.value })
|
|---|
| 791 | }
|
|---|
| 792 | placeholder="e.g., 500mg"
|
|---|
| 793 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 794 | required
|
|---|
| 795 | />
|
|---|
| 796 | </div>
|
|---|
| 797 | <div>
|
|---|
| 798 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Frequency *</label>
|
|---|
| 799 | <input
|
|---|
| 800 | type="text"
|
|---|
| 801 | value={prescriptionForm.frequency}
|
|---|
| 802 | onChange={(e) =>
|
|---|
| 803 | setPrescriptionForm({ ...prescriptionForm, frequency: e.target.value })
|
|---|
| 804 | }
|
|---|
| 805 | placeholder="e.g., Twice daily"
|
|---|
| 806 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 807 | required
|
|---|
| 808 | />
|
|---|
| 809 | </div>
|
|---|
| 810 | <div>
|
|---|
| 811 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Duration *</label>
|
|---|
| 812 | <input
|
|---|
| 813 | type="text"
|
|---|
| 814 | value={prescriptionForm.duration}
|
|---|
| 815 | onChange={(e) =>
|
|---|
| 816 | setPrescriptionForm({ ...prescriptionForm, duration: e.target.value })
|
|---|
| 817 | }
|
|---|
| 818 | placeholder="e.g., 7 days"
|
|---|
| 819 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 820 | required
|
|---|
| 821 | />
|
|---|
| 822 | </div>
|
|---|
| 823 | </div>
|
|---|
| 824 | <div>
|
|---|
| 825 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Reason for Prescription</label>
|
|---|
| 826 | <textarea
|
|---|
| 827 | value={prescriptionForm.reason}
|
|---|
| 828 | onChange={(e) => setPrescriptionForm({ ...prescriptionForm, reason: e.target.value })}
|
|---|
| 829 | placeholder="Describe why this medication is prescribed"
|
|---|
| 830 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 831 | rows="3"
|
|---|
| 832 | />
|
|---|
| 833 | </div>
|
|---|
| 834 | <button
|
|---|
| 835 | type="submit"
|
|---|
| 836 | style={{background: '#9333ea', color: 'white', padding: '6px 14px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '14px', fontWeight: '600'}} onMouseEnter={(e) => e.currentTarget.style.background = '#7e22ce'} onMouseLeave={(e) => e.currentTarget.style.background = '#9333ea'}
|
|---|
| 837 | >
|
|---|
| 838 | Record Prescription
|
|---|
| 839 | </button>
|
|---|
| 840 | </form>
|
|---|
| 841 | </div>
|
|---|
| 842 | )}
|
|---|
| 843 |
|
|---|
| 844 | {/* Procedure Request Tab */}
|
|---|
| 845 | {activeTab === 'procedures' && (
|
|---|
| 846 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 847 | <h3 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Request Procedure</h3>
|
|---|
| 848 | <form onSubmit={async (e) => {
|
|---|
| 849 | e.preventDefault();
|
|---|
| 850 | setError(null);
|
|---|
| 851 |
|
|---|
| 852 | if (!procedureForm.procedureId) {
|
|---|
| 853 | setError('Please select a procedure');
|
|---|
| 854 | return;
|
|---|
| 855 | }
|
|---|
| 856 |
|
|---|
| 857 | try {
|
|---|
| 858 | setLoading(true);
|
|---|
| 859 | const doctorId = user.doctorId || 1;
|
|---|
| 860 |
|
|---|
| 861 | const request = {
|
|---|
| 862 | patientId: record.patientId,
|
|---|
| 863 | doctorId: parseInt(doctorId),
|
|---|
| 864 | procedureId: parseInt(procedureForm.procedureId),
|
|---|
| 865 | procedureDate: procedureForm.procedureDate,
|
|---|
| 866 | notes: procedureForm.notes,
|
|---|
| 867 | diagnosisId: procedureForm.diagnosisId ? parseInt(procedureForm.diagnosisId) : null,
|
|---|
| 868 | };
|
|---|
| 869 |
|
|---|
| 870 | await procedureService.requestProcedure(request);
|
|---|
| 871 |
|
|---|
| 872 | setSuccess('Procedure requested successfully');
|
|---|
| 873 | setProcedureForm({
|
|---|
| 874 | procedureId: '',
|
|---|
| 875 | procedureDate: new Date().toISOString().split('T')[0],
|
|---|
| 876 | notes: '',
|
|---|
| 877 | diagnosisId: '',
|
|---|
| 878 | });
|
|---|
| 879 |
|
|---|
| 880 | setTimeout(() => setSuccess(null), 3000);
|
|---|
| 881 | } catch (err) {
|
|---|
| 882 | setError('Failed to request procedure: ' + (err.response?.data?.error || err.message));
|
|---|
| 883 | } finally {
|
|---|
| 884 | setLoading(false);
|
|---|
| 885 | }
|
|---|
| 886 | }} className="space-y-4">
|
|---|
| 887 | <div className="grid grid-cols-2 gap-4">
|
|---|
| 888 | <div>
|
|---|
| 889 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Procedure *</label>
|
|---|
| 890 | <select
|
|---|
| 891 | value={procedureForm.procedureId}
|
|---|
| 892 | onChange={(e) => setProcedureForm({ ...procedureForm, procedureId: e.target.value })}
|
|---|
| 893 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 894 | required
|
|---|
| 895 | >
|
|---|
| 896 | <option value="">Select a procedure</option>
|
|---|
| 897 | {procedures.map((proc) => (
|
|---|
| 898 | <option key={proc.procedureId} value={proc.procedureId}>
|
|---|
| 899 | {proc.procedureType} (${proc.cost})
|
|---|
| 900 | </option>
|
|---|
| 901 | ))}
|
|---|
| 902 | </select>
|
|---|
| 903 | </div>
|
|---|
| 904 | <div>
|
|---|
| 905 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Procedure Date *</label>
|
|---|
| 906 | <input
|
|---|
| 907 | type="date"
|
|---|
| 908 | value={procedureForm.procedureDate}
|
|---|
| 909 | onChange={(e) => setProcedureForm({ ...procedureForm, procedureDate: e.target.value })}
|
|---|
| 910 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 911 | required
|
|---|
| 912 | />
|
|---|
| 913 | </div>
|
|---|
| 914 | </div>
|
|---|
| 915 | <div>
|
|---|
| 916 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Related Diagnosis (Optional)</label>
|
|---|
| 917 | <select
|
|---|
| 918 | value={procedureForm.diagnosisId}
|
|---|
| 919 | onChange={(e) => setProcedureForm({ ...procedureForm, diagnosisId: e.target.value })}
|
|---|
| 920 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 921 | >
|
|---|
| 922 | <option value="">Select diagnosis (optional)</option>
|
|---|
| 923 | {diagnoses.map((diagnosis) => (
|
|---|
| 924 | <option key={diagnosis.id} value={diagnosis.id}>
|
|---|
| 925 | {diagnosis.name}
|
|---|
| 926 | </option>
|
|---|
| 927 | ))}
|
|---|
| 928 | </select>
|
|---|
| 929 | </div>
|
|---|
| 930 | <div>
|
|---|
| 931 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Notes</label>
|
|---|
| 932 | <textarea
|
|---|
| 933 | value={procedureForm.notes}
|
|---|
| 934 | onChange={(e) => setProcedureForm({ ...procedureForm, notes: e.target.value })}
|
|---|
| 935 | placeholder="Pre-procedure instructions or special notes"
|
|---|
| 936 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 937 | rows="3"
|
|---|
| 938 | />
|
|---|
| 939 | </div>
|
|---|
| 940 | <button
|
|---|
| 941 | type="submit"
|
|---|
| 942 | disabled={loading}
|
|---|
| 943 | style={{background: loading ? '#9ca3af' : '#9333ea', color: 'white', padding: '6px 14px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '14px', fontWeight: '600'}} onMouseEnter={(e) => !loading && (e.currentTarget.style.background = '#7e22ce')} onMouseLeave={(e) => !loading && (e.currentTarget.style.background = '#9333ea')}
|
|---|
| 944 | >
|
|---|
| 945 | {loading ? 'Requesting...' : 'Request Procedure'}
|
|---|
| 946 | </button>
|
|---|
| 947 | </form>
|
|---|
| 948 | </div>
|
|---|
| 949 | )}
|
|---|
| 950 |
|
|---|
| 951 | {/* Lab Test Tab */}
|
|---|
| 952 | {activeTab === 'lab-tests' && (
|
|---|
| 953 | <div className="bg-white rounded-lg shadow p-6">
|
|---|
| 954 | <h3 style={{ fontSize: '20px', fontWeight: 'normal', marginBottom: '16px' }}>Request Lab Test</h3>
|
|---|
| 955 | <form onSubmit={async (e) => {
|
|---|
| 956 | e.preventDefault();
|
|---|
| 957 | setError(null);
|
|---|
| 958 |
|
|---|
| 959 | if (!labTestForm.testId) {
|
|---|
| 960 | setError('Please select a test');
|
|---|
| 961 | return;
|
|---|
| 962 | }
|
|---|
| 963 |
|
|---|
| 964 | try {
|
|---|
| 965 | setLoading(true);
|
|---|
| 966 | const doctorId = user.doctorId || 1;
|
|---|
| 967 |
|
|---|
| 968 | const request = {
|
|---|
| 969 | patientId: record.patientId,
|
|---|
| 970 | medicalRecordId: record.recordId,
|
|---|
| 971 | doctorId: parseInt(doctorId),
|
|---|
| 972 | testId: parseInt(labTestForm.testId),
|
|---|
| 973 | testDate: labTestForm.testDate,
|
|---|
| 974 | notes: labTestForm.notes,
|
|---|
| 975 | };
|
|---|
| 976 |
|
|---|
| 977 | await labService.requestLabTest(request);
|
|---|
| 978 |
|
|---|
| 979 | setSuccess('Lab test requested successfully');
|
|---|
| 980 | setLabTestForm({
|
|---|
| 981 | testId: '',
|
|---|
| 982 | testDate: new Date().toISOString().split('T')[0],
|
|---|
| 983 | notes: '',
|
|---|
| 984 | });
|
|---|
| 985 |
|
|---|
| 986 | setTimeout(() => setSuccess(null), 3000);
|
|---|
| 987 | } catch (err) {
|
|---|
| 988 | setError('Failed to request lab test: ' + (err.response?.data?.error || err.message));
|
|---|
| 989 | } finally {
|
|---|
| 990 | setLoading(false);
|
|---|
| 991 | }
|
|---|
| 992 | }} className="space-y-4">
|
|---|
| 993 | <div className="grid grid-cols-2 gap-4">
|
|---|
| 994 | <div>
|
|---|
| 995 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Test</label>
|
|---|
| 996 | <select
|
|---|
| 997 | value={labTestForm.testId}
|
|---|
| 998 | onChange={(e) => setLabTestForm({ ...labTestForm, testId: e.target.value })}
|
|---|
| 999 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 1000 | required
|
|---|
| 1001 | >
|
|---|
| 1002 | <option value="">Select a test</option>
|
|---|
| 1003 | {labTests.map((test) => (
|
|---|
| 1004 | <option key={test.testId} value={test.testId}>
|
|---|
| 1005 | {test.testName} (${test.cost})
|
|---|
| 1006 | </option>
|
|---|
| 1007 | ))}
|
|---|
| 1008 | </select>
|
|---|
| 1009 | </div>
|
|---|
| 1010 | <div>
|
|---|
| 1011 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Test Date</label>
|
|---|
| 1012 | <input
|
|---|
| 1013 | type="date"
|
|---|
| 1014 | value={labTestForm.testDate}
|
|---|
| 1015 | onChange={(e) => setLabTestForm({ ...labTestForm, testDate: e.target.value })}
|
|---|
| 1016 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 1017 | />
|
|---|
| 1018 | </div>
|
|---|
| 1019 | </div>
|
|---|
| 1020 | <div>
|
|---|
| 1021 | <label style={{ fontWeight: 'normal' }} className="block text-sm mb-2">Notes</label>
|
|---|
| 1022 | <textarea
|
|---|
| 1023 | value={labTestForm.notes}
|
|---|
| 1024 | onChange={(e) => setLabTestForm({ ...labTestForm, notes: e.target.value })}
|
|---|
| 1025 | placeholder="Additional notes for the lab technician"
|
|---|
| 1026 | className="w-full px-4 py-2 border rounded-lg"
|
|---|
| 1027 | rows="3"
|
|---|
| 1028 | />
|
|---|
| 1029 | </div>
|
|---|
| 1030 | <button
|
|---|
| 1031 | type="submit"
|
|---|
| 1032 | disabled={loading}
|
|---|
| 1033 | style={{background: loading ? '#9ca3af' : '#9333ea', color: 'white', padding: '6px 14px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '14px', fontWeight: '600'}} onMouseEnter={(e) => !loading && (e.currentTarget.style.background = '#7e22ce')} onMouseLeave={(e) => !loading && (e.currentTarget.style.background = '#9333ea')}
|
|---|
| 1034 | >
|
|---|
| 1035 | {loading ? 'Requesting...' : 'Request Test'}
|
|---|
| 1036 | </button>
|
|---|
| 1037 | </form>
|
|---|
| 1038 | </div>
|
|---|
| 1039 | )}
|
|---|
| 1040 | </div>
|
|---|
| 1041 | );
|
|---|
| 1042 | }
|
|---|
| 1043 |
|
|---|
| 1044 | export default MedicalRecordDetail;
|
|---|