source: frontend/src/pages/billing/BillingDetail.js@ 84249b1

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

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

  • Property mode set to 100644
File size: 8.5 KB
Line 
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
80 if (!billing) {
81 return (
82 <div>
83 <ErrorAlert message="Billing record not found" onClose={() => navigate('/billing')} />
84 </div>
85 );
86 }
87
88 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'} />
117 </div>
118 </div>
119
120 {!isPatient && (
121 <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>
143 </div>
144 </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>
185 </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>
215 </div>
216 </div>
217 ) : (
218 <p className="text-gray-500">No services itemized for this billing record.</p>
219 )}
220 </div>
221 </div>
222 );
223}
224
225function InfoRow({ label, value }) {
226 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>
231 );
232}
233
234export default BillingDetail;
Note: See TracBrowser for help on using the repository browser.