Changeset a64c772 for frontend/src


Ignore:
Timestamp:
05/25/26 13:43:53 (4 months ago)
Author:
MBK <marija.karapandzova@…>
Branches:
master
Children:
f18b11b
Parents:
84249b1
Message:

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

Location:
frontend/src/pages
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/pages/appointments/AppointmentList.js

    r84249b1 ra64c772  
    4747        await appointmentService.cancelAppointment(id);
    4848        setAppointments(appointments.map(apt =>
    49           apt.appointmentId === id ? { ...apt, status: 'CANCELLED' } : apt
     49            apt.appointmentId === id ? { ...apt, status: 'CANCELLED' } : apt
    5050        ));
    5151      } catch (err) {
     
    5858
    5959  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 Appointment
    76         </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>
    7878
    79       {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
     79        {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
    8080
    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">
    8484            <tr>
    8585              <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th>
     
    9090              <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th>
    9191            </tr>
    92           </thead>
    93           <tbody>
     92            </thead>
     93            <tbody>
    9494            {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">
    101101                  <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'
    105105                  }`}>
    106106                    {appointment.status}
    107107                  </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>
    120122            ))}
    121           </tbody>
    122         </table>
     123            </tbody>
     124          </table>
     125        </div>
    123126      </div>
    124     </div>
    125127  );
    126128}
  • frontend/src/pages/billing/BillingDetail.js

    r84249b1 ra64c772  
    7878  if (loading) return <Loading />;
    7979
     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
    8090  if (!billing) {
    8191    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>
    8595    );
    8696  }
    8797
    8898  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>
    117113          </div>
    118114        </div>
    119115
    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'}`}>
    121120          <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'} />
    143127            </div>
    144128          </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>
    185154              </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              )}
    215167            </div>
    216168          </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>
    220231      </div>
    221     </div>
    222232  );
    223233}
     
    225235function InfoRow({ label, value }) {
    226236  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>
    231241  );
    232242}
  • frontend/src/pages/billing/BillingList.js

    r84249b1 ra64c772  
    1313  const user = JSON.parse(localStorage.getItem('user') || '{}');
    1414
    15   useEffect(() => {
    16     fetchBillings();
    17   }, [patientId]);
    18 
    1915  const fetchBillings = async () => {
    2016    try {
     
    2420        response = await billingService.getBillingHistoryForPatient(patientId);
    2521      } else if (user.role === 'PATIENT') {
    26         // Patients can only view their own billing history
    2722        response = await billingService.getBillingHistoryForPatient(user.patientId);
    2823      } else {
     
    3833  };
    3934
     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
    4049  if (loading) return <Loading />;
    4150
    4251  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>
    4554
    46       {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
     55        {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
    4756
    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">
    5160            <tr>
    5261              <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th>
     
    5665              <th className="px-6 py-3 text-left text-sm font-semibold">Actions</th>
    5766            </tr>
    58           </thead>
    59           <tbody>
     67            </thead>
     68            <tbody>
    6069            {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">
    6574                  <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'
    6978                  }`}>
    7079                    {billing.paymentStatus}
    7180                  </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                     View
    77                   </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>
    8089            ))}
    81           </tbody>
    82         </table>
     90            </tbody>
     91          </table>
     92        </div>
    8393      </div>
    84     </div>
    8594  );
    8695}
Note: See TracChangeset for help on using the changeset viewer.