source: frontend/src/pages/billing/BillingList.js@ b63d7b5

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

Add pending requests functionality and filters for billing admins

  • Property mode set to 100644
File size: 9.1 KB
Line 
1import React, { useState, useEffect } from 'react';
2import { Link, useSearchParams } from 'react-router-dom';
3import { billingService } from '../../services/billingService';
4import { patientService } from '../../services/patientService';
5import Loading from '../../components/Loading';
6import ErrorAlert from '../../components/ErrorAlert';
7import SuccessAlert from '../../components/SuccessAlert';
8
9function BillingList() {
10 const [billings, setBillings] = useState([]);
11 const [loading, setLoading] = useState(true);
12 const [error, setError] = useState(null);
13 const [success, setSuccess] = useState(null);
14 const [searchParams] = useSearchParams();
15 const patientId = searchParams.get('patientId');
16 const user = JSON.parse(localStorage.getItem('user') || '{}');
17
18 // Filters
19 const [statusFilter, setStatusFilter] = useState('');
20 const [patientFilter, setPatientFilter] = useState('');
21 const [patients, setPatients] = useState([]);
22
23 useEffect(() => {
24 const fetchBillings = async () => {
25 try {
26 setLoading(true);
27 let response;
28 if (patientId) {
29 response = await billingService.getBillingHistoryForPatient(patientId);
30 } else if (user.role === 'PATIENT') {
31 response = await billingService.getBillingHistoryForPatient(user.patientId);
32 } else {
33 response = await billingService.getAllBillings();
34 }
35 setBillings(response.data || []);
36 } catch (err) {
37 setError('Failed to fetch billing records');
38 console.error(err);
39 } finally {
40 setLoading(false);
41 }
42 };
43
44 fetchBillings();
45 }, [patientId, user.role, user.patientId]);
46
47 // Load patients for filter dropdown (for billing admin)
48 useEffect(() => {
49 if (user.role === 'BILLING_ADMIN') {
50 const loadPatients = async () => {
51 try {
52 const response = await patientService.getAllPatients();
53 setPatients(response.data || []);
54 } catch (err) {
55 console.error('Failed to load patients:', err);
56 }
57 };
58 loadPatients();
59 }
60 }, [user.role]);
61
62 // Filter billings based on selected filters
63 const filteredBillings = billings.filter(billing => {
64 if (statusFilter && billing.paymentStatus !== statusFilter) {
65 return false;
66 }
67 if (patientFilter && billing.patientName !== patientFilter) {
68 return false;
69 }
70 return true;
71 });
72
73 const pendingBillings = billings.filter(b => b.paymentStatus === 'PENDING');
74
75 const handleUpdateStatus = async (billId, newStatus) => {
76 try {
77 const updateData = {
78 paymentStatus: newStatus
79 };
80 if (newStatus === 'PAID') {
81 updateData.paymentDate = new Date().toISOString().split('T')[0];
82 }
83
84 console.log('Updating billing status:', { billId, updateData });
85 await billingService.updatePaymentStatus(billId, updateData);
86 setSuccess(`Billing status updated to ${newStatus}`);
87
88 // Refresh the list
89 const response = user.role === 'BILLING_ADMIN'
90 ? await billingService.getAllBillings()
91 : await billingService.getBillingHistoryForPatient(patientId || user.patientId);
92 setBillings(response.data || []);
93 } catch (err) {
94 setError(`Failed to update billing status: ${err.response?.data?.error || err.message}`);
95 console.error('Update status error:', err);
96 }
97 };
98
99 // Prevent unauthorized access to billing
100 if (user.role === 'DOCTOR' || user.role === 'LAB_TECHNICIAN') {
101 return (
102 <div style={{ padding: '20px', textAlign: 'center' }}>
103 <h1 className="text-2xl font-bold" style={{ color: '#7c3aed', marginBottom: '10px' }}>Access Denied</h1>
104 <p style={{ color: 'var(--color-neutral-600)' }}>You do not have permission to access billing records.</p>
105 </div>
106 );
107 }
108
109 if (loading) return <Loading />;
110
111 return (
112 <div>
113 <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>{patientId ? 'Patient Billing History' : 'Billing Records'}</h1>
114
115 {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
116 {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />}
117
118 {/* Filters for Billing Admin */}
119 {user.role === 'BILLING_ADMIN' && (
120 <div className="bg-white rounded-lg shadow p-6 mb-6">
121 <h2 className="text-lg font-bold mb-4">Filters</h2>
122 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
123 <div>
124 <label className="block text-sm font-semibold mb-2">Status</label>
125 <select
126 value={statusFilter}
127 onChange={(e) => setStatusFilter(e.target.value)}
128 className="w-full px-4 py-2 border rounded-lg"
129 >
130 <option value="">All Statuses</option>
131 <option value="PENDING">Pending</option>
132 <option value="PAID">Paid</option>
133 <option value="PROCESSING">Processing</option>
134 </select>
135 </div>
136 <div>
137 <label className="block text-sm font-semibold mb-2">Patient</label>
138 <select
139 value={patientFilter}
140 onChange={(e) => setPatientFilter(e.target.value)}
141 className="w-full px-4 py-2 border rounded-lg"
142 >
143 <option value="">All Patients</option>
144 {patients.map(p => (
145 <option key={p.patientId} value={`${p.firstName} ${p.lastName}`}>
146 {p.firstName} {p.lastName}
147 </option>
148 ))}
149 </select>
150 </div>
151 </div>
152 </div>
153 )}
154
155 {/* Pending Billing Records Section (for Billing Admin) */}
156 {user.role === 'BILLING_ADMIN' && pendingBillings.length > 0 && (
157 <div className="bg-yellow-50 rounded-lg shadow p-6 mb-6 border-l-4 border-yellow-500">
158 <h2 className="text-lg font-bold mb-4">Pending Billing Records ({pendingBillings.length})</h2>
159 <div className="space-y-3">
160 {pendingBillings.map(billing => (
161 <div key={billing.billId} className="bg-white rounded p-4 flex justify-between items-center">
162 <div>
163 <p className="font-semibold">{billing.patientName}</p>
164 <p className="text-sm text-gray-600">${billing.totalCost}</p>
165 </div>
166 <button
167 onClick={() => handleUpdateStatus(billing.billId, 'PAID')}
168 className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 text-sm"
169 >
170 Mark Paid
171 </button>
172 </div>
173 ))}
174 </div>
175 </div>
176 )}
177
178 <div className="bg-white rounded-lg shadow overflow-hidden">
179 <table className="w-full">
180 <thead className="bg-gray-100">
181 <tr>
182 <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th>
183 <th className="px-6 py-3 text-left text-sm font-semibold">Total Cost</th>
184 <th className="px-6 py-3 text-left text-sm font-semibold">Payment Status</th>
185 <th className="px-6 py-3 text-left text-sm font-semibold">Payment Date</th>
186 <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th>
187 </tr>
188 </thead>
189 <tbody>
190 {filteredBillings.map(billing => (
191 <tr key={billing.billId} className="border-t hover:bg-gray-50">
192 <td className="px-6 py-3">{billing.patientName}</td>
193 <td className="px-6 py-3">${billing.totalCost}</td>
194 <td className="px-6 py-3">
195 <span className={`px-3 py-1 rounded text-sm font-semibold ${
196 billing.paymentStatus === 'PENDING' ? 'bg-yellow-100 text-yellow-800' :
197 billing.paymentStatus === 'PAID' ? 'bg-green-100 text-green-800' :
198 'bg-red-100 text-red-800'
199 }`}>
200 {billing.paymentStatus}
201 </span>
202 </td>
203 <td className="px-6 py-3">{billing.paymentDate || 'Not paid'}</td>
204 <td className="px-6 py-3">
205 <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'}>
206 View
207 </Link>
208 </td>
209 </tr>
210 ))}
211 </tbody>
212 </table>
213
214 {filteredBillings.length === 0 && (
215 <div className="p-6 text-center text-gray-500">
216 {billings.length === 0 ? 'No billing records found' : 'No records match the selected filters'}
217 </div>
218 )}
219 </div>
220 </div>
221 );
222}
223
224export default BillingList;
Note: See TracBrowser for help on using the repository browser.