Changeset 20d16ca for frontend/src


Ignore:
Timestamp:
08/30/26 00:27:08 (2 weeks ago)
Author:
MBK <marija.karapandzova@…>
Branches:
master
Children:
e1f74f6
Parents:
b63d7b5
Message:

Implement advanced database queries

Location:
frontend/src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • frontend/src/App.js

    rb63d7b5 r20d16ca  
    11import React, { useEffect, useState } from 'react';
    2 import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
     2import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
    33import Navbar from './components/Navbar';
     4import ProtectedRoute from './components/ProtectedRoute';
     5import Login from './pages/Login';
    46import Dashboard from './pages/Dashboard';
    57import PatientList from './pages/patients/PatientList';
     
    2729function App() {
    2830  const [user, setUser] = useState(null);
     31  const [refresh, setRefresh] = useState(0);
    2932
    3033  useEffect(() => {
    31     const userStr = localStorage.getItem('user');
    32     if (userStr) {
    33       setUser(JSON.parse(userStr));
    34     }
    35   }, []);
     34    const loadUser = () => {
     35      const userStr = localStorage.getItem('user');
     36      if (userStr) {
     37        try {
     38          setUser(JSON.parse(userStr));
     39        } catch (err) {
     40          console.error('Failed to parse user:', err);
     41          setUser(null);
     42        }
     43      } else {
     44        setUser(null);
     45      }
     46    };
     47
     48    loadUser();
     49
     50    // Listen for storage changes (other tabs/windows)
     51    window.addEventListener('storage', loadUser);
     52
     53    // Create a custom event listener for same-tab changes
     54    const handleStorageChange = (e) => {
     55      console.log('Storage change detected:', e.detail);
     56      loadUser();
     57    };
     58    window.addEventListener('userStorageChange', handleStorageChange);
     59
     60    return () => {
     61      window.removeEventListener('storage', loadUser);
     62      window.removeEventListener('userStorageChange', handleStorageChange);
     63    };
     64  }, [refresh]);
    3665
    3766  return (
     
    77106              <Route path="/medical-reports" element={<ProtectedRoute><MedicalReportList /></ProtectedRoute>} />
    78107
    79               {/* Referral Routes */}
    80               <Route path="/referrals" element={<ProtectedRoute><ReferralList /></ProtectedRoute>} />
    81 
    82108              {/* Lab Test Routes */}
    83109              <Route path="/lab-tests" element={<ProtectedRoute><LabTestList /></ProtectedRoute>} />
     
    92118              <Route path="/billing/:id" element={<ProtectedRoute><BillingDetail /></ProtectedRoute>} />
    93119
    94 
     120              {/* Referral Routes */}
     121              <Route path="/referrals" element={<ProtectedRoute><ReferralList /></ProtectedRoute>} />
    95122            </Routes>
    96123          </main>
  • frontend/src/components/Navbar.js

    rb63d7b5 r20d16ca  
    99
    1010  useEffect(() => {
    11     const userStr = localStorage.getItem('user');
    12     if (userStr) {
    13       setUser(JSON.parse(userStr));
    14     }
     11    const loadUser = () => {
     12      const userStr = localStorage.getItem('user');
     13      if (userStr) {
     14        setUser(JSON.parse(userStr));
     15      } else {
     16        setUser(null);
     17      }
     18    };
     19
     20    loadUser();
     21
     22    window.addEventListener('storage', loadUser);
     23    return () => window.removeEventListener('storage', loadUser);
    1524  }, []);
    1625
     
    1928    localStorage.removeItem('user');
    2029    setUser(null);
     30
     31
     32    window.dispatchEvent(new CustomEvent('userStorageChange', {
     33      detail: { userData: null }
     34    }));
     35
    2136    navigate('/login');
    2237  };
     
    3045
    3146  return (
    32     <nav className="navbar">
    33       <div className="navbar-brand">
    34         <img src="/logo.png" alt="Medora Logo" style={{ height: '40px', width: 'auto' }} />
    35       </div>
     47      <nav className="navbar">
     48        <div className="navbar-brand">
     49          <img src="/logo.png" alt="Medora Logo" style={{ height: '40px', width: 'auto' }} />
     50        </div>
    3651
    37       <div className="navbar-links">
    38         <Link to="/" className={`navbar-link ${isActive('/') ? 'active' : ''}`}>
    39           Overview
    40         </Link>
    41         {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
    42           <Link to="/patients" className={`navbar-link ${isActive('/patients') ? 'active' : ''}`}>
    43             Patients
     52        <div className="navbar-links">
     53          <Link to="/" className={`navbar-link ${isActive('/') ? 'active' : ''}`}>
     54            Overview
    4455          </Link>
    45         )}
    46         <Link to="/doctors" className={`navbar-link ${isActive('/doctors') ? 'active' : ''}`}>
    47           Doctors
    48         </Link>
    49         <Link to="/departments" className={`navbar-link ${isActive('/departments') ? 'active' : ''}`}>
    50           Departments
    51         </Link>
    52         {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
    53           <>
    54             <Link to="/appointments" className={`navbar-link ${isActive('/appointments') ? 'active' : ''}`}>
    55               Appointments
    56             </Link>
    57             {user?.role === 'PATIENT' && (
     56          {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
     57              <Link to="/patients" className={`navbar-link ${isActive('/patients') ? 'active' : ''}`}>
     58                Patients
     59              </Link>
     60          )}
     61          <Link to="/doctors" className={`navbar-link ${isActive('/doctors') ? 'active' : ''}`}>
     62            Doctors
     63          </Link>
     64          <Link to="/departments" className={`navbar-link ${isActive('/departments') ? 'active' : ''}`}>
     65            Departments
     66          </Link>
     67          {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
    5868              <>
    59                 <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}>
    60                   Medical Records
     69                <Link to="/appointments" className={`navbar-link ${isActive('/appointments') ? 'active' : ''}`}>
     70                  Appointments
    6171                </Link>
    62                 <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}>
    63                   Billing
    64                 </Link>
     72                {user?.role === 'PATIENT' && (
     73                    <>
     74                      <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}>
     75                        Medical Records
     76                      </Link>
     77                      <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}>
     78                        Billing
     79                      </Link>
     80                    </>
     81                )}
    6582              </>
    66             )}
    67           </>
    68         )}
    69         {(user?.role === 'ADMIN' || user?.role === 'LAB_TECHNICIAN' || user?.role === 'DOCTOR') && (
    70           <Link to="/lab-tests" className={`navbar-link ${isActive('/lab-tests') ? 'active' : ''}`}>
    71             Lab Tests
    72           </Link>
    73         )}
    74         {!user?.role || user?.role === 'ADMIN' || user?.role === 'DOCTOR' ? (
    75           <>
    76             {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
    77               <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}>
    78                 Medical Records
     83          )}
     84          {(user?.role === 'ADMIN' || user?.role === 'LAB_TECHNICIAN' || user?.role === 'DOCTOR') && (
     85              <Link to="/lab-tests" className={`navbar-link ${isActive('/lab-tests') ? 'active' : ''}`}>
     86                Lab Tests
    7987              </Link>
    80             )}
    81             {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
     88          )}
     89          {!user?.role || user?.role === 'ADMIN' || user?.role === 'DOCTOR' ? (
    8290              <>
    83                 <Link to="/medical-reports" className={`navbar-link ${isActive('/medical-reports') ? 'active' : ''}`}>
    84                   Medical Reports
    85                 </Link>
    86                 <Link to="/procedures" className={`navbar-link ${isActive('/procedures') ? 'active' : ''}`}>
    87                   Procedures
    88                 </Link>
    89                 <Link to="/referrals" className={`navbar-link ${isActive('/referrals') ? 'active' : ''}`}>
    90                   Referrals
    91                 </Link>
     91                {user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
     92                    <Link to="/medical-records" className={`navbar-link ${isActive('/medical-records') ? 'active' : ''}`}>
     93                      Medical Records
     94                    </Link>
     95                )}
     96                {user?.role !== 'PATIENT' && user?.role !== 'LAB_TECHNICIAN' && user?.role !== 'BILLING_ADMIN' && (
     97                    <>
     98                      <Link to="/medical-reports" className={`navbar-link ${isActive('/medical-reports') ? 'active' : ''}`}>
     99                        Medical Reports
     100                      </Link>
     101                      <Link to="/procedures" className={`navbar-link ${isActive('/procedures') ? 'active' : ''}`}>
     102                        Procedures
     103                      </Link>
     104                      <Link to="/referrals" className={`navbar-link ${isActive('/referrals') ? 'active' : ''}`}>
     105                        Referrals
     106                      </Link>
     107                    </>
     108                )}
    92109              </>
    93             )}
    94           </>
    95         ) : null}
    96         {(user?.role === 'ADMIN' || user?.role === 'BILLING_ADMIN') && (
    97           <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}>
    98             Billing
    99           </Link>
    100         )}
    101       </div>
     110          ) : null}
     111          {(user?.role === 'ADMIN' || user?.role === 'BILLING_ADMIN') && (
     112              <Link to="/billing" className={`navbar-link ${isActive('/billing') ? 'active' : ''}`}>
     113                Billing
     114              </Link>
     115          )}
     116        </div>
    102117
    103       <div className="navbar-right">
    104         {user && (
    105           <div className="navbar-avatar" style={{ position: 'relative' }}>
    106             <div className="navbar-avatar-circle">{getInitials()}</div>
    107             <div className="navbar-avatar-name">{user.firstName}</div>
    108             <button
    109               onClick={() => setShowMenu(!showMenu)}
    110               style={{
    111                 background: 'none',
    112                 border: 'none',
    113                 cursor: 'pointer',
    114                 marginLeft: '4px',
    115                 color: 'var(--color-neutral-600)',
    116                 fontSize: '12px'
    117               }}
    118             >
    119               ▼
    120             </button>
    121             {showMenu && (
    122               <div style={{
    123                 position: 'absolute',
    124                 top: '100%',
    125                 right: 0,
    126                 background: 'white',
    127                 border: '1px solid var(--color-neutral-400)',
    128                 borderRadius: '6px',
    129                 minWidth: '150px',
    130                 marginTop: '4px',
    131                 boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
    132                 zIndex: 1000
    133               }}>
    134                 <div style={{
    135                   padding: '8px 0',
    136                   fontSize: '11px',
    137                   color: 'var(--color-neutral-600)',
    138                   borderBottom: '1px solid var(--color-neutral-400)',
    139                   paddingLeft: '12px',
    140                   paddingRight: '12px',
    141                   paddingTop: '8px',
    142                   paddingBottom: '8px'
    143                 }}>
    144                   {user.role}
    145                 </div>
     118        <div className="navbar-right">
     119          {user && (
     120              <div className="navbar-avatar" style={{ position: 'relative' }}>
     121                <div className="navbar-avatar-circle">{getInitials()}</div>
     122                <div className="navbar-avatar-name">{user.firstName}</div>
    146123                <button
    147                   onClick={handleLogout}
    148                   style={{
    149                     width: '100%',
    150                     padding: '8px 12px',
    151                     background: 'none',
    152                     border: 'none',
    153                     textAlign: 'left',
    154                     cursor: 'pointer',
    155                     fontSize: '12px',
    156                     color: 'var(--color-status-red)',
    157                     transition: 'background 0.2s'
    158                   }}
    159                   onMouseEnter={(e) => e.target.style.background = 'var(--color-neutral-200)'}
    160                   onMouseLeave={(e) => e.target.style.background = 'none'}
     124                    onClick={() => setShowMenu(!showMenu)}
     125                    style={{
     126                      background: 'none',
     127                      border: 'none',
     128                      cursor: 'pointer',
     129                      marginLeft: '4px',
     130                      color: 'var(--color-neutral-600)',
     131                      fontSize: '12px'
     132                    }}
    161133                >
    162                   Logout
     134                 
    163135                </button>
     136                {showMenu && (
     137                    <div style={{
     138                      position: 'absolute',
     139                      top: '100%',
     140                      right: 0,
     141                      background: 'white',
     142                      border: '1px solid var(--color-neutral-400)',
     143                      borderRadius: '6px',
     144                      minWidth: '150px',
     145                      marginTop: '4px',
     146                      boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
     147                      zIndex: 1000
     148                    }}>
     149                      <div style={{
     150                        padding: '8px 0',
     151                        fontSize: '11px',
     152                        color: 'var(--color-neutral-600)',
     153                        borderBottom: '1px solid var(--color-neutral-400)',
     154                        paddingLeft: '12px',
     155                        paddingRight: '12px',
     156                        paddingTop: '8px',
     157                        paddingBottom: '8px'
     158                      }}>
     159                        {user.role}
     160                      </div>
     161                      <button
     162                          onClick={handleLogout}
     163                          style={{
     164                            width: '100%',
     165                            padding: '8px 12px',
     166                            background: 'none',
     167                            border: 'none',
     168                            textAlign: 'left',
     169                            cursor: 'pointer',
     170                            fontSize: '12px',
     171                            color: 'var(--color-status-red)',
     172                            transition: 'background 0.2s'
     173                          }}
     174                          onMouseEnter={(e) => e.target.style.background = 'var(--color-neutral-200)'}
     175                          onMouseLeave={(e) => e.target.style.background = 'none'}
     176                      >
     177                        Logout
     178                      </button>
     179                    </div>
     180                )}
    164181              </div>
    165             )}
    166           </div>
    167         )}
    168       </div>
    169     </nav>
     182          )}
     183        </div>
     184      </nav>
    170185  );
    171186}
  • frontend/src/pages/Login.js

    rb63d7b5 r20d16ca  
    4747
    4848      localStorage.setItem('token', data.token);
    49       localStorage.setItem('user', JSON.stringify({
     49      const userData = {
    5050        userId: data.userId,
    5151        patientId: data.patientId,
     
    5555        firstName: data.firstName,
    5656        lastName: data.lastName,
     57      };
     58      localStorage.setItem('user', JSON.stringify(userData));
     59
     60      // Dispatch custom event to notify components on the same tab
     61      window.dispatchEvent(new CustomEvent('userStorageChange', {
     62        detail: { userData }
    5763      }));
    5864
     
    6066      setTimeout(() => {
    6167        navigate('/');
    62       }, 1000);
     68      }, 500);
    6369    } catch (err) {
    6470      setError(err.message || 'Login failed');
     
    6975
    7076  return (
    71     <div className="min-h-screen bg-blue-200 flex items-center justify-center p-4">
    72       <div className="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md">
    73         <div className="text-center mb-8">
    74           <h1 className="text-5xl font-bold text-purple-600 mb-2">Medora</h1>
    75           <p className="text-gray-600">Hospital Management System</p>
    76         </div>
    77 
    78         {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
    79         {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />}
    80 
    81         <form onSubmit={handleLogin} className="space-y-6">
    82           <div>
    83             <label className="block text-sm font-normal text-gray-700 mb-2">
    84               Username (EMBG for Patients)
    85             </label>
    86             <input
    87               type="text"
    88               value={username}
    89               onChange={(e) => setUsername(e.target.value)}
    90               placeholder="Enter your username or EMBG"
    91               className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition"
    92               disabled={loading}
    93             />
     77      <div className="min-h-screen bg-blue-200 flex items-center justify-center p-4">
     78        <div className="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md">
     79          <div className="text-center mb-8">
     80            <h1 className="text-5xl font-bold text-purple-600 mb-2">Medora</h1>
     81            <p className="text-gray-600">Hospital Management System</p>
    9482          </div>
    9583
    96           <div>
    97             <label className="block text-sm font-normal text-gray-700 mb-2">
    98               Password
    99             </label>
    100             <input
    101               type="password"
    102               value={password}
    103               onChange={(e) => setPassword(e.target.value)}
    104               placeholder="Enter your password"
    105               className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition"
    106               disabled={loading}
    107             />
     84          {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
     85          {success && <SuccessAlert message={success} onClose={() => setSuccess(null)} />}
     86
     87          <form onSubmit={handleLogin} className="space-y-6">
     88            <div>
     89              <label className="block text-sm font-normal text-gray-700 mb-2">
     90                Username (EMBG for Patients)
     91              </label>
     92              <input
     93                  type="text"
     94                  value={username}
     95                  onChange={(e) => setUsername(e.target.value)}
     96                  placeholder="Enter your username or EMBG"
     97                  className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition"
     98                  disabled={loading}
     99              />
     100            </div>
     101
     102            <div>
     103              <label className="block text-sm font-normal text-gray-700 mb-2">
     104                Password
     105              </label>
     106              <input
     107                  type="password"
     108                  value={password}
     109                  onChange={(e) => setPassword(e.target.value)}
     110                  placeholder="Enter your password"
     111                  className="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-purple-500 transition"
     112                  disabled={loading}
     113              />
     114            </div>
     115
     116            <button
     117                type="submit"
     118                disabled={loading}
     119                className="w-full bg-purple-600 text-white font-bold py-3 rounded-lg hover:bg-purple-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
     120            >
     121              {loading ? 'Logging in...' : 'Login'}
     122            </button>
     123          </form>
     124
     125          <div className="mt-8 p-4 bg-purple-50 rounded-lg border border-purple-200">
     126            <p className="text-sm text-gray-700 font-normal mb-2">Demo Credentials:</p>
     127            <p className="text-sm text-gray-600 mb-2">
     128              <strong>Admin:</strong> username: admin | password: admin123
     129            </p>
     130            <p className="text-sm text-gray-600 mb-2">
     131              <strong>Doctor:</strong> username: ivan.stojanov@medora.com | password: doctor123
     132            </p>
     133            <p className="text-sm text-gray-600">
     134              <strong>Patient:</strong> username: [EMBG] | password: password123
     135            </p>
    108136          </div>
    109 
    110           <button
    111             type="submit"
    112             disabled={loading}
    113             className="w-full bg-purple-600 text-white font-bold py-3 rounded-lg hover:bg-purple-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
    114           >
    115             {loading ? 'Logging in...' : 'Login'}
    116           </button>
    117         </form>
    118 
    119         <div className="mt-8 p-4 bg-purple-50 rounded-lg border border-purple-200">
    120           <p className="text-sm text-gray-700 font-normal mb-2">Demo Credentials:</p>
    121           <p className="text-sm text-gray-600 mb-2">
    122             <strong>Admin:</strong> username: admin | password: admin123
    123           </p>
    124           <p className="text-sm text-gray-600 mb-2">
    125             <strong>Doctor:</strong> username: ivan.stojanov@medora.com | password: doctor123
    126           </p>
    127           <p className="text-sm text-gray-600">
    128             <strong>Patient:</strong> username: [EMBG] | password: password123
    129           </p>
    130137        </div>
    131138      </div>
    132     </div>
    133139  );
    134140}
Note: See TracChangeset for help on using the changeset viewer.