source: frontend/src/pages/billing/BillingDetail.js@ a64c772

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

Fix doctor authorization and prevent canceling other doctors appointments and restrict them from billing access

  • Property mode set to 100644
File size: 9.4 KB
RevLine 
[f05de05]1import React, { useState, useEffect } from 'react';
2import { useParams, useNavigate } from 'react-router-dom';
3import { billingService } from '../../services/billingService';
4import Loading from '../../components/Loading';
5import ErrorAlert from '../../components/ErrorAlert';
6import SuccessAlert from '../../components/SuccessAlert';
7
8function BillingDetail() {
9 const { id } = useParams();
10 const navigate = useNavigate();
11 const user = JSON.parse(localStorage.getItem('user') || '{}');
12 const isPatient = user.role === 'PATIENT';
13
14 const [billing, setBilling] = useState(null);
15 const [billingDetail, setBillingDetail] = useState(null);
16 const [loading, setLoading] = useState(true);
17 const [error, setError] = useState(null);
18 const [success, setSuccess] = useState(null);
19 const [updating, setUpdating] = useState(false);
20 const [paymentStatus, setPaymentStatus] = useState('');
21 const [downloading, setDownloading] = useState(false);
22
23 useEffect(() => {
24 fetchBilling();
25 }, [id]);
26
27 const fetchBilling = async () => {
28 try {
29 setLoading(true);
30 const response = await billingService.getBillingById(id);
31 setBilling(response.data);
32 setPaymentStatus(response.data.paymentStatus);
33
34 // Fetch detailed billing information
35 try {
36 const detailResponse = await billingService.getBillingDetail(id);
37 setBillingDetail(detailResponse.data);
38 } catch (err) {
39 console.error('Could not fetch billing details:', err);
40 }
41 } catch (err) {
42 setError('Failed to fetch billing record');
43 console.error(err);
44 } finally {
45 setLoading(false);
46 }
47 };
48
49 const handleUpdatePaymentStatus = async () => {
50 try {
51 setUpdating(true);
52 const response = await billingService.updatePaymentStatus(id, {
53 paymentStatus,
54 paymentDate: new Date().toISOString().split('T')[0],
55 });
56 setBilling(response.data);
57 setSuccess('Payment status updated successfully!');
58 } catch (err) {
59 setError('Failed to update payment status');
60 } finally {
61 setUpdating(false);
62 }
63 };
64
65 const handleDownloadInvoice = async () => {
66 try {
67 setDownloading(true);
68 await billingService.downloadInvoicePDF(id);
69 setSuccess('Invoice downloaded successfully!');
70 } catch (err) {
71 setError('Failed to download invoice');
72 console.error(err);
73 } finally {
74 setDownloading(false);
75 }
76 };
77
78 if (loading) return <Loading />;
79
[a64c772]80 // Prevent unauthorized access to billing
81 if (user.role === 'DOCTOR' || user.role === 'LAB_TECHNICIAN') {
[f05de05]82 return (
[a64c772]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>
[f05de05]87 );
88 }
89
[a64c772]90 if (!billing) {
91 return (
92 <div>
93 <ErrorAlert message="Billing record not found" onClose={() => navigate('/billing')} />
[f05de05]94 </div>
[a64c772]95 );
96 }
[f05de05]97
[a64c772]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>
[f05de05]113 </div>
114 </div>
115
[a64c772]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'}`}>
[f05de05]120 <div className="bg-white rounded-lg shadow p-6">
[a64c772]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'} />
[f05de05]127 </div>
128 </div>
129
[a64c772]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>
[f05de05]154 </div>
[a64c772]155 )}
[f05de05]156
[a64c772]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 )}
[f05de05]167 </div>
168 </div>
[a64c772]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>
[f05de05]231 </div>
232 );
233}
234
235function InfoRow({ label, value }) {
236 return (
[a64c772]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>
[f05de05]241 );
242}
243
244export default BillingDetail;
Note: See TracBrowser for help on using the repository browser.