Changeset b63d7b5


Ignore:
Timestamp:
05/29/26 22:52:04 (4 months ago)
Author:
MBK <marija.karapandzova@…>
Branches:
master
Children:
20d16ca
Parents:
6c00695
Message:

Add pending requests functionality and filters for billing admins

Files:
3 edited

Legend:

Unmodified
Added
Removed
  • backend/src/main/java/medora/controller/BillingController.java

    r6c00695 rb63d7b5  
    253253            }
    254254
    255             // Only ADMIN can update payment status
    256             if (!role.equals("ADMIN")) {
    257                 return ResponseEntity.status(HttpStatus.FORBIDDEN)
    258                         .body(Map.of("error", "Only administrators can update payment status"));
     255            // ADMIN and BILLING_ADMIN can update payment status
     256            if (!role.equals("ADMIN") && !role.equals("BILLING_ADMIN")) {
     257                return ResponseEntity.status(HttpStatus.FORBIDDEN)
     258                        .body(Map.of("error", "Only administrators and billing admins can update payment status"));
    259259            }
    260260
  • frontend/src/pages/billing/BillingList.js

    r6c00695 rb63d7b5  
    22import { Link, useSearchParams } from 'react-router-dom';
    33import { billingService } from '../../services/billingService';
     4import { patientService } from '../../services/patientService';
    45import Loading from '../../components/Loading';
    56import ErrorAlert from '../../components/ErrorAlert';
     7import SuccessAlert from '../../components/SuccessAlert';
    68
    79function BillingList() {
     
    911  const [loading, setLoading] = useState(true);
    1012  const [error, setError] = useState(null);
     13  const [success, setSuccess] = useState(null);
    1114  const [searchParams] = useSearchParams();
    1215  const patientId = searchParams.get('patientId');
    1316  const user = JSON.parse(localStorage.getItem('user') || '{}');
    1417
    15   const fetchBillings = async () => {
     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) => {
    1676    try {
    17       setLoading(true);
    18       let response;
    19       if (patientId) {
    20         response = await billingService.getBillingHistoryForPatient(patientId);
    21       } else if (user.role === 'PATIENT') {
    22         response = await billingService.getBillingHistoryForPatient(user.patientId);
    23       } else {
    24         response = await billingService.getAllBillings();
     77      const updateData = {
     78        paymentStatus: newStatus
     79      };
     80      if (newStatus === 'PAID') {
     81        updateData.paymentDate = new Date().toISOString().split('T')[0];
    2582      }
    26       setBillings(response.data);
     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 || []);
    2793    } catch (err) {
    28       setError('Failed to fetch billing records');
    29       console.error(err);
    30     } finally {
    31       setLoading(false);
     94      setError(`Failed to update billing status: ${err.response?.data?.error || err.message}`);
     95      console.error('Update status error:', err);
    3296    }
    3397  };
    34 
    35   useEffect(() => {
    36     fetchBillings();
    37   }, [patientId]);
    3898
    3999  // Prevent unauthorized access to billing
     
    54114
    55115        {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        )}
    56177
    57178        <div className="bg-white rounded-lg shadow overflow-hidden">
     
    67188            </thead>
    68189            <tbody>
    69             {billings.map(billing => (
     190            {filteredBillings.map(billing => (
    70191                <tr key={billing.billId} className="border-t hover:bg-gray-50">
    71192                  <td className="px-6 py-3">{billing.patientName}</td>
     
    90211            </tbody>
    91212          </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          )}
    92219        </div>
    93220      </div>
  • frontend/src/services/api.js

    r6c00695 rb63d7b5  
    44
    55const apiClient = axios.create({
    6   baseURL: API_URL,
    7   headers: {
    8     'Content-Type': 'application/json',
    9   },
     6    baseURL: API_URL,
     7    headers: {
     8        'Content-Type': 'application/json',
     9    },
    1010});
    1111
    1212// Request interceptor to add JWT token
    1313apiClient.interceptors.request.use(
    14   config => {
    15     const token = localStorage.getItem('token');
    16     if (token) {
    17       config.headers.Authorization = `Bearer ${token}`;
    18     }
    19     return config;
    20   },
    21   error => Promise.reject(error)
     14    config => {
     15        const token = localStorage.getItem('token');
     16        if (token) {
     17            config.headers.Authorization = `Bearer ${token}`;
     18            console.log(' Token added to request:', config.url);
     19        } else {
     20            console.warn(' No token found for request:', config.url);
     21        }
     22        return config;
     23    },
     24    error => Promise.reject(error)
    2225);
    2326
    2427// Error handling interceptor
    2528apiClient.interceptors.response.use(
    26   response => response,
    27   error => {
    28     console.error('API Error:', error.response?.data || error.message);
    29     return Promise.reject(error);
    30   }
     29    response => response,
     30    error => {
     31        console.error('API Error:', error.response?.data || error.message);
     32        return Promise.reject(error);
     33    }
    3134);
    3235
Note: See TracChangeset for help on using the changeset viewer.