Changeset a64c772 for frontend/src
- Timestamp:
- 05/25/26 13:43:53 (4 months ago)
- Branches:
- master
- Children:
- f18b11b
- Parents:
- 84249b1
- Location:
- frontend/src/pages
- Files:
-
- 3 edited
-
appointments/AppointmentList.js (modified) (3 diffs)
-
billing/BillingDetail.js (modified) (2 diffs)
-
billing/BillingList.js (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
frontend/src/pages/appointments/AppointmentList.js
r84249b1 ra64c772 47 47 await appointmentService.cancelAppointment(id); 48 48 setAppointments(appointments.map(apt => 49 apt.appointmentId === id ? { ...apt, status: 'CANCELLED' } : apt49 apt.appointmentId === id ? { ...apt, status: 'CANCELLED' } : apt 50 50 )); 51 51 } catch (err) { … … 58 58 59 59 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>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 Appointment 76 </Link> 77 </div> 78 78 79 {error && <ErrorAlert message={error} onClose={() => setError(null)} />}79 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 80 80 81 <div className="bg-white rounded-lg shadow overflow-hidden">82 <table className="w-full">83 <thead className="bg-gray-100">81 <div className="bg-white rounded-lg shadow overflow-hidden"> 82 <table className="w-full"> 83 <thead className="bg-gray-100"> 84 84 <tr> 85 85 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> … … 90 90 <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th> 91 91 </tr> 92 </thead>93 <tbody>92 </thead> 93 <tbody> 94 94 {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">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"> 101 101 <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'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' 105 105 }`}> 106 106 {appointment.status} 107 107 </span> 108 </td> 109 <td className="px-6 py-3"> 110 {appointment.status === 'SCHEDULED' && ( 111 <button 112 onClick={() => handleCancelAppointment(appointment.appointmentId)} 113 className="text-red-600 hover:underline px-3 py-2 text-sm font-medium" 114 > 115 Cancel 116 </button> 117 )} 118 </td> 119 </tr> 108 </td> 109 <td className="px-6 py-3"> 110 {appointment.status === 'SCHEDULED' && ( 111 (user.role === 'DOCTOR' && appointment.doctor?.doctorId !== user.doctorId) ? null : ( 112 <button 113 onClick={() => handleCancelAppointment(appointment.appointmentId)} 114 className="text-red-600 hover:underline px-3 py-2 text-sm font-medium" 115 > 116 Cancel 117 </button> 118 ) 119 )} 120 </td> 121 </tr> 120 122 ))} 121 </tbody> 122 </table> 123 </tbody> 124 </table> 125 </div> 123 126 </div> 124 </div>125 127 ); 126 128 } -
frontend/src/pages/billing/BillingDetail.js
r84249b1 ra64c772 78 78 if (loading) return <Loading />; 79 79 80 // Prevent unauthorized access to billing 81 if (user.role === 'DOCTOR' || user.role === 'LAB_TECHNICIAN') { 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> 87 ); 88 } 89 80 90 if (!billing) { 81 91 return ( 82 <div>83 <ErrorAlert message="Billing record not found" onClose={() => navigate('/billing')} />84 </div>92 <div> 93 <ErrorAlert message="Billing record not found" onClose={() => navigate('/billing')} /> 94 </div> 85 95 ); 86 96 } 87 97 88 98 return ( 89 <div> 90 <div className="flex justify-between items-center mb-6"> 91 <h1 className="text-3xl font-bold" style={{ color: '#7c3aed' }}>Billing Record #{billing.billId}</h1> 92 <div className="space-x-2"> 93 <button 94 onClick={handleDownloadInvoice} 95 disabled={downloading} 96 className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 disabled:opacity-50" 97 > 98 {downloading ? 'Downloading...' : 'Download Invoice (PDF)'} 99 </button> 100 <button onClick={() => navigate('/billing')} className="bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400"> 101 Back 102 </button> 103 </div> 104 </div> 105 106 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 107 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />} 108 109 <div className={`grid gap-6 mb-6 ${isPatient ? 'grid-cols-1 lg:grid-cols-2' : 'grid-cols-1 lg:grid-cols-3'}`}> 110 <div className="bg-white rounded-lg shadow p-6"> 111 <h2 className="text-xl font-bold mb-4">Billing Information</h2> 112 <div className="space-y-3"> 113 <InfoRow label="Patient" value={billing.patientName} /> 114 <InfoRow label="Total Cost" value={`$${billing.totalCost}`} /> 115 <InfoRow label="Current Status" value={billing.paymentStatus} /> 116 <InfoRow label="Payment Date" value={billing.paymentDate || 'Not paid'} /> 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> 117 113 </div> 118 114 </div> 119 115 120 {!isPatient && ( 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'}`}> 121 120 <div className="bg-white rounded-lg shadow p-6"> 122 <h2 className="text-xl font-bold mb-4">Update Payment Status</h2> 123 <div className="space-y-4"> 124 <div> 125 <label className="block text-sm font-semibold mb-2">Payment Status</label> 126 <select 127 value={paymentStatus} 128 onChange={(e) => setPaymentStatus(e.target.value)} 129 className="w-full px-4 py-2 border rounded-lg" 130 > 131 <option value="PENDING">Pending</option> 132 <option value="PAID">Paid</option> 133 <option value="CANCELLED">Cancelled</option> 134 </select> 135 </div> 136 <button 137 onClick={handleUpdatePaymentStatus} 138 disabled={updating || paymentStatus === billing.paymentStatus} 139 className="w-full bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 disabled:opacity-50" 140 > 141 {updating ? 'Updating...' : 'Update Status'} 142 </button> 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'} /> 143 127 </div> 144 128 </div> 145 )} 146 147 <div className="bg-white rounded-lg shadow p-6"> 148 <h2 className="text-xl font-bold mb-4">Patient Details</h2> 149 <div className="space-y-3"> 150 {billingDetail && ( 151 <> 152 <InfoRow label="EMBG" value={billingDetail.patientEmbg} /> 153 <InfoRow label="Phone" value={billingDetail.patientPhone} /> 154 <InfoRow label="Bill Date" value={billingDetail.billDate} /> 155 </> 156 )} 157 </div> 158 </div> 159 </div> 160 161 <div className="bg-white rounded-lg shadow p-6"> 162 <h2 className="text-xl font-bold mb-4">Itemized Services</h2> 163 164 {billingDetail && (billingDetail.procedures.length > 0 || billingDetail.labTests.length > 0) ? ( 165 <div className="space-y-4"> 166 {billingDetail.procedures.length > 0 && ( 167 <div> 168 <h3 className="font-semibold text-lg mb-2">Procedures</h3> 169 <table className="w-full"> 170 <thead className="bg-gray-100"> 171 <tr> 172 <th className="px-4 py-2 text-left">Description</th> 173 <th className="px-4 py-2 text-right">Cost</th> 174 </tr> 175 </thead> 176 <tbody> 177 {billingDetail.procedures.map((proc, idx) => ( 178 <tr key={idx} className="border-t"> 179 <td className="px-4 py-2">{proc.description}</td> 180 <td className="px-4 py-2 text-right">${proc.cost}</td> 181 </tr> 182 ))} 183 </tbody> 184 </table> 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> 185 154 </div> 186 )} 187 188 {billingDetail.labTests.length > 0 && ( 189 <div> 190 <h3 className="font-semibold text-lg mb-2">Lab Tests</h3> 191 <table className="w-full"> 192 <thead className="bg-gray-100"> 193 <tr> 194 <th className="px-4 py-2 text-left">Description</th> 195 <th className="px-4 py-2 text-right">Cost</th> 196 </tr> 197 </thead> 198 <tbody> 199 {billingDetail.labTests.map((test, idx) => ( 200 <tr key={idx} className="border-t"> 201 <td className="px-4 py-2">{test.description}</td> 202 <td className="px-4 py-2 text-right">${test.cost}</td> 203 </tr> 204 ))} 205 </tbody> 206 </table> 207 </div> 208 )} 209 210 <div className="border-t-2 pt-4 mt-4 flex justify-end"> 211 <div className="text-right"> 212 <p className="text-gray-600">Total Amount:</p> 213 <p className="text-2xl font-bold text-purple-600">${billing.totalCost}</p> 214 </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 )} 215 167 </div> 216 168 </div> 217 ) : ( 218 <p className="text-gray-500">No services itemized for this billing record.</p> 219 )} 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> 220 231 </div> 221 </div>222 232 ); 223 233 } … … 225 235 function InfoRow({ label, value }) { 226 236 return ( 227 <div className="flex justify-between">228 <span className="text-gray-800">{label}:</span>229 <span className="text-gray-800">{value || 'N/A'}</span>230 </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> 231 241 ); 232 242 } -
frontend/src/pages/billing/BillingList.js
r84249b1 ra64c772 13 13 const user = JSON.parse(localStorage.getItem('user') || '{}'); 14 14 15 useEffect(() => {16 fetchBillings();17 }, [patientId]);18 19 15 const fetchBillings = async () => { 20 16 try { … … 24 20 response = await billingService.getBillingHistoryForPatient(patientId); 25 21 } else if (user.role === 'PATIENT') { 26 // Patients can only view their own billing history27 22 response = await billingService.getBillingHistoryForPatient(user.patientId); 28 23 } else { … … 38 33 }; 39 34 35 useEffect(() => { 36 fetchBillings(); 37 }, [patientId]); 38 39 // Prevent unauthorized access to billing 40 if (user.role === 'DOCTOR' || user.role === 'LAB_TECHNICIAN') { 41 return ( 42 <div style={{ padding: '20px', textAlign: 'center' }}> 43 <h1 className="text-2xl font-bold" style={{ color: '#7c3aed', marginBottom: '10px' }}>Access Denied</h1> 44 <p style={{ color: 'var(--color-neutral-600)' }}>You do not have permission to access billing records.</p> 45 </div> 46 ); 47 } 48 40 49 if (loading) return <Loading />; 41 50 42 51 return ( 43 <div>44 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>{patientId ? 'Patient Billing History' : 'Billing Records'}</h1>52 <div> 53 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>{patientId ? 'Patient Billing History' : 'Billing Records'}</h1> 45 54 46 {error && <ErrorAlert message={error} onClose={() => setError(null)} />}55 {error && <ErrorAlert message={error} onClose={() => setError(null)} />} 47 56 48 <div className="bg-white rounded-lg shadow overflow-hidden">49 <table className="w-full">50 <thead className="bg-gray-100">57 <div className="bg-white rounded-lg shadow overflow-hidden"> 58 <table className="w-full"> 59 <thead className="bg-gray-100"> 51 60 <tr> 52 61 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th> … … 56 65 <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th> 57 66 </tr> 58 </thead>59 <tbody>67 </thead> 68 <tbody> 60 69 {billings.map(billing => ( 61 <tr key={billing.billId} className="border-t hover:bg-gray-50">62 <td className="px-6 py-3">{billing.patientName}</td>63 <td className="px-6 py-3">${billing.totalCost}</td>64 <td className="px-6 py-3">70 <tr key={billing.billId} className="border-t hover:bg-gray-50"> 71 <td className="px-6 py-3">{billing.patientName}</td> 72 <td className="px-6 py-3">${billing.totalCost}</td> 73 <td className="px-6 py-3"> 65 74 <span className={`px-3 py-1 rounded text-sm font-semibold ${ 66 billing.paymentStatus === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :67 billing.paymentStatus === 'PAID' ? 'bg-green-100 text-green-800' :68 'bg-red-100 text-red-800'75 billing.paymentStatus === 'PENDING' ? 'bg-yellow-100 text-yellow-800' : 76 billing.paymentStatus === 'PAID' ? 'bg-green-100 text-green-800' : 77 'bg-red-100 text-red-800' 69 78 }`}> 70 79 {billing.paymentStatus} 71 80 </span> 72 </td>73 <td className="px-6 py-3">{billing.paymentDate || 'Not paid'}</td>74 <td className="px-6 py-3">75 <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'}>76 View77 </Link>78 </td>79 </tr>81 </td> 82 <td className="px-6 py-3">{billing.paymentDate || 'Not paid'}</td> 83 <td className="px-6 py-3"> 84 <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'}> 85 View 86 </Link> 87 </td> 88 </tr> 80 89 ))} 81 </tbody> 82 </table> 90 </tbody> 91 </table> 92 </div> 83 93 </div> 84 </div>85 94 ); 86 95 }
Note:
See TracChangeset
for help on using the changeset viewer.
