source: frontend/src/pages/Dashboard.js@ 84249b1

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

Add authentication and role-based route protection and add Dashboard page

  • Property mode set to 100644
File size: 25.8 KB
Line 
1import React, { useState, useEffect } from 'react';
2import { Link } from 'react-router-dom';
3import { patientService } from '../services/patientService';
4import { doctorService } from '../services/doctorService';
5import { appointmentService } from '../services/appointmentService';
6import { labService } from '../services/labService';
7import { billingService } from '../services/billingService';
8import { medicalRecordService } from '../services/medicalRecordService';
9import Loading from '../components/Loading';
10
11function Dashboard() {
12 const [stats, setStats] = useState({
13 patients: 0,
14 doctors: 0,
15 appointments: 0,
16 });
17 const [userAppointments, setUserAppointments] = useState([]);
18 const [doctorInfo, setDoctorInfo] = useState(null);
19 const [pendingLabTests, setPendingLabTests] = useState([]);
20 const [billings, setBillings] = useState([]);
21 const [medicalRecords, setMedicalRecords] = useState([]);
22 const [patientBillings, setPatientBillings] = useState([]);
23 const [loading, setLoading] = useState(true);
24 const user = JSON.parse(localStorage.getItem('user') || '{}');
25 const isPatient = user.role === 'PATIENT';
26 const isDoctor = user.role === 'DOCTOR';
27 const isLabTechnician = user.role === 'LAB_TECHNICIAN';
28 const isBillingAdmin = user.role === 'BILLING_ADMIN';
29
30 useEffect(() => {
31 const fetchStats = async () => {
32 try {
33 if (isPatient) {
34 // For patients, fetch their own appointments, medical records, and billing
35 const [appointmentsRes, medicalRes, billingRes] = await Promise.all([
36 appointmentService.getAppointmentsForPatient(user.patientId),
37 medicalRecordService.getMedicalRecordByPatientId(user.patientId),
38 billingService.getBillingHistoryForPatient(user.patientId)
39 ]);
40 setUserAppointments(appointmentsRes.data || []);
41 // Handle medical records - could be single object or array
42 const medicalData = medicalRes.data;
43 const medicalArray = Array.isArray(medicalData) ? medicalData : (medicalData ? [medicalData] : []);
44 setMedicalRecords(medicalArray);
45 setPatientBillings(billingRes.data || []);
46 } else if (isDoctor) {
47 // For doctors, fetch their own appointments and full doctor information
48 const [appointmentsRes, doctorRes] = await Promise.all([
49 appointmentService.getAppointmentsForDoctor(user.doctorId),
50 doctorService.getDoctorById(user.doctorId),
51 ]);
52 setUserAppointments(appointmentsRes.data || []);
53 setDoctorInfo(doctorRes.data);
54 } else if (isLabTechnician) {
55 // For lab technicians, fetch pending lab tests
56 const pendingRes = await labService.getPendingLabTests();
57 setPendingLabTests(pendingRes.data || []);
58 } else if (isBillingAdmin) {
59 // For billing admins, fetch all billing records
60 const billingsRes = await billingService.getAllBillings();
61 setBillings(billingsRes.data || []);
62 } else {
63 // For admin/staff, fetch all stats
64 const [patientsRes, doctorsRes, appointmentsRes] = await Promise.all([
65 patientService.getAllPatients(),
66 doctorService.getAllDoctors(),
67 appointmentService.getAllAppointments(),
68 ]);
69
70 setStats({
71 patients: patientsRes.data.length,
72 doctors: doctorsRes.data.length,
73 appointments: appointmentsRes.data.length,
74 });
75 }
76 } catch (error) {
77 console.error('Error fetching stats:', error);
78 } finally {
79 setLoading(false);
80 }
81 };
82
83 fetchStats();
84 }, [user.userId, isPatient, isDoctor, isLabTechnician]);
85
86 if (loading) return <Loading />;
87
88 if (isPatient) {
89 return (
90 <div className="page-wrapper">
91 <div className="page-heading">
92 <h1 className="page-title">Welcome, <em>{user.firstName} {user.lastName}</em></h1>
93 <div className="page-date">Saturday · {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</div>
94 </div>
95 <div className="divider"></div>
96
97 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
98 <div className="card">
99 <div className="card-header">
100 <h2 className="card-title">Your Information</h2>
101 </div>
102 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '15px' }}>
103 <div><strong>Name:</strong> {user.firstName} {user.lastName}</div>
104 <div><strong>EMBG:</strong> {user.username}</div>
105 <div><strong>Role:</strong> Patient</div>
106 </div>
107 </div>
108
109 <div className="card">
110 <div className="card-header">
111 <h2 className="card-title">Quick Actions</h2>
112 </div>
113 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
114 <Link to="/appointments/new" style={{
115 display: 'block',
116 padding: '8px 12px',
117 background: '#e9d5ff',
118 color: '#1e1035',
119 borderRadius: '6px',
120 textAlign: 'center',
121 textDecoration: 'none',
122 fontSize: '12px',
123 fontWeight: '500'
124 }} onMouseEnter={(e) => e.currentTarget.style.background = '#d8b4fe'} onMouseLeave={(e) => e.currentTarget.style.background = '#e9d5ff'}>
125 + Schedule Appointment
126 </Link>
127 <Link to="/appointments" style={{
128 display: 'block',
129 padding: '8px 12px',
130 background: '#bfdbfe',
131 color: '#1e1035',
132 borderRadius: '6px',
133 textAlign: 'center',
134 textDecoration: 'none',
135 fontSize: '12px',
136 fontWeight: '400'
137 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>
138 View My Appointments
139 </Link>
140 </div>
141 </div>
142 </div>
143
144 <div className="card">
145 <div className="card-header">
146 <h2 className="card-title">Your Appointments</h2>
147 </div>
148 {userAppointments.length === 0 ? (
149 <p style={{ color: 'var(--color-neutral-600)', fontSize: '12px' }}>No appointments scheduled</p>
150 ) : (
151 <div className="overflow-x-auto">
152 <table className="table">
153 <thead>
154 <tr>
155 <th>Doctor</th>
156 <th>Date</th>
157 <th>Time</th>
158 <th>Status</th>
159 </tr>
160 </thead>
161 <tbody>
162 {userAppointments.map(apt => (
163 <tr key={apt.appointmentId}>
164 <td>{apt.doctor?.firstName} {apt.doctor?.lastName}</td>
165 <td>{apt.appointmentDate}</td>
166 <td>{apt.appointmentTime}</td>
167 <td><span className={`badge ${apt.status === 'SCHEDULED' ? 'badge-obs' : apt.status === 'COMPLETED' ? 'badge-stable' : 'badge-critical'}`}>{apt.status}</span></td>
168 </tr>
169 ))}
170 </tbody>
171 </table>
172 </div>
173 )}
174 </div>
175
176 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', alignItems: 'start' }}>
177 <div className="card" style={{ paddingTop: '20px', paddingRight: '24px', paddingLeft: '24px', paddingBottom: '20px' }}>
178 <div className="card-header" style={{ marginBottom: '4px' }}>
179 <h2 className="card-title">Medical Records</h2>
180 </div>
181 <Link to="/medical-records" style={{
182 display: 'block',
183 padding: '10px 14px',
184 background: '#5b21b6',
185 color: 'white',
186 borderRadius: '6px',
187 textAlign: 'center',
188 textDecoration: 'none',
189 fontSize: '13px',
190 fontWeight: '600',
191 marginTop: '14px'
192 }} onMouseEnter={(e) => e.currentTarget.style.background = '#4c1d95'} onMouseLeave={(e) => e.currentTarget.style.background = '#5b21b6'}>
193 View Medical Records
194 </Link>
195 </div>
196
197 <div className="card">
198 <div className="card-header">
199 <h2 className="card-title">Billing Stats</h2>
200 </div>
201 {patientBillings.length === 0 ? (
202 <p style={{ color: 'var(--color-neutral-600)', fontSize: '14px' }}>No billing records</p>
203 ) : (
204 <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
205 <div style={{ padding: '10px', background: 'var(--color-neutral-200)', borderRadius: '6px', fontSize: '14px' }}>
206 <strong>Total Bills:</strong> {patientBillings.length}
207 </div>
208 <div style={{ padding: '10px', background: '#d1fae5', borderRadius: '6px', fontSize: '14px' }}>
209 <strong>Paid:</strong> ${patientBillings.filter(b => b.paymentStatus === 'PAID').reduce((sum, b) => sum + (b.totalCost || 0), 0).toFixed(2)}
210 </div>
211 <div style={{ padding: '10px', background: '#fef3c7', borderRadius: '6px', fontSize: '14px' }}>
212 <strong>Pending:</strong> ${patientBillings.filter(b => b.paymentStatus === 'PENDING').reduce((sum, b) => sum + (b.totalCost || 0), 0).toFixed(2)}
213 </div>
214 <Link to="/billing" style={{
215 display: 'block',
216 padding: '10px 14px',
217 background: '#5b21b6',
218 color: 'white',
219 borderRadius: '6px',
220 textAlign: 'center',
221 textDecoration: 'none',
222 fontSize: '13px',
223 fontWeight: '600'
224 }} onMouseEnter={(e) => e.currentTarget.style.background = '#4c1d95'} onMouseLeave={(e) => e.currentTarget.style.background = '#5b21b6'}>
225 View Billing Details
226 </Link>
227 </div>
228 )}
229 </div>
230 </div>
231 </div>
232 );
233 }
234
235 if (isDoctor) {
236 return (
237 <div className="page-wrapper">
238 <div className="page-heading">
239 <h1 className="page-title">Welcome Dr. <em>{user.firstName} {user.lastName}</em></h1>
240 <div className="page-date">Saturday · {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</div>
241 </div>
242 <div className="divider"></div>
243
244 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
245 <div className="card">
246 <div className="card-header">
247 <h2 className="card-title">Your Information</h2>
248 </div>
249 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '15px' }}>
250 <div><strong>Name:</strong> {user.firstName} {user.lastName}</div>
251 <div><strong>Email:</strong> {user.username}</div>
252 <div><strong>Role:</strong> Doctor</div>
253 {doctorInfo && (
254 <>
255 <div><strong>Department:</strong> {doctorInfo.department?.departmentName ? doctorInfo.department.departmentName.replace(/_DEPT$/, '').replace(/_/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ') : 'N/A'}</div>
256 <div><strong>Level:</strong> {doctorInfo.level?.level || 'N/A'}</div>
257 </>
258 )}
259 </div>
260 </div>
261
262 <div className="card">
263 <div className="card-header">
264 <h2 className="card-title">Quick Actions</h2>
265 </div>
266 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
267 <Link to="/appointments/new" style={{
268 display: 'block',
269 padding: '8px 12px',
270 background: '#e9d5ff',
271 color: '#1e1035',
272 borderRadius: '6px',
273 textAlign: 'center',
274 textDecoration: 'none',
275 fontSize: '12px',
276 fontWeight: '500'
277 }} onMouseEnter={(e) => e.currentTarget.style.background = '#d8b4fe'} onMouseLeave={(e) => e.currentTarget.style.background = '#e9d5ff'}>
278 + Schedule Appointment
279 </Link>
280 <Link to="/appointments" style={{
281 display: 'block',
282 padding: '8px 12px',
283 background: '#bfdbfe',
284 color: '#1e1035',
285 borderRadius: '6px',
286 textAlign: 'center',
287 textDecoration: 'none',
288 fontSize: '12px',
289 fontWeight: '400'
290 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>
291 View My Appointments
292 </Link>
293 </div>
294 </div>
295 </div>
296
297 <div className="card">
298 <div className="card-header">
299 <h2 className="card-title">Your Appointments</h2>
300 </div>
301 {userAppointments.length === 0 ? (
302 <p style={{ color: 'var(--color-neutral-600)', fontSize: '12px' }}>No appointments scheduled</p>
303 ) : (
304 <div className="overflow-x-auto">
305 <table className="table">
306 <thead>
307 <tr>
308 <th>Patient</th>
309 <th>Date</th>
310 <th>Time</th>
311 <th>Status</th>
312 </tr>
313 </thead>
314 <tbody>
315 {userAppointments.map(apt => (
316 <tr key={apt.appointmentId}>
317 <td>{apt.patient?.firstName} {apt.patient?.lastName}</td>
318 <td>{apt.appointmentDate}</td>
319 <td>{apt.appointmentTime}</td>
320 <td><span className={`badge ${apt.status === 'SCHEDULED' ? 'badge-obs' : apt.status === 'COMPLETED' ? 'badge-stable' : 'badge-critical'}`}>{apt.status}</span></td>
321 </tr>
322 ))}
323 </tbody>
324 </table>
325 </div>
326 )}
327 </div>
328 </div>
329 );
330 }
331
332 if (isLabTechnician) {
333 return (
334 <div className="page-wrapper">
335 <div className="page-heading">
336 <h1 className="page-title">Welcome, <em>{user.firstName} {user.lastName}</em></h1>
337 <div className="page-date">Saturday · {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</div>
338 </div>
339 <div className="divider"></div>
340
341 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
342 <div className="card">
343 <div className="card-header">
344 <h2 className="card-title">Your Information</h2>
345 </div>
346 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '15px' }}>
347 <div><strong>Name:</strong> {user.firstName} {user.lastName}</div>
348 <div><strong>Username:</strong> {user.username}</div>
349 <div><strong>Role:</strong> Lab Technician</div>
350 </div>
351 </div>
352
353 <div className="card">
354 <div className="card-header">
355 <h2 className="card-title">Quick Actions</h2>
356 </div>
357 <Link to="/lab-tests" style={{
358 display: 'block',
359 padding: '10px 14px',
360 background: '#bfdbfe',
361 color: '#1e1035',
362 borderRadius: '6px',
363 textAlign: 'center',
364 textDecoration: 'none',
365 fontSize: '13px',
366 fontWeight: '400'
367 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>
368 View Lab Tests
369 </Link>
370 </div>
371 </div>
372
373 <div className="card">
374 <div className="card-header">
375 <h2 className="card-title">Pending Lab Tests ({pendingLabTests.length})</h2>
376 </div>
377 {pendingLabTests.length === 0 ? (
378 <p style={{ color: 'var(--color-neutral-600)', fontSize: '12px' }}>No pending lab tests</p>
379 ) : (
380 <div className="overflow-x-auto">
381 <table className="table">
382 <thead>
383 <tr>
384 <th>Test Name</th>
385 <th>Patient</th>
386 <th>Doctor</th>
387 <th>Test Date</th>
388 <th>Notes</th>
389 </tr>
390 </thead>
391 <tbody>
392 {pendingLabTests.map(test => (
393 <tr key={test.testId}>
394 <td>{test.testName}</td>
395 <td>{test.patientName}</td>
396 <td>{test.doctorName}</td>
397 <td>{test.testDate}</td>
398 <td>{test.notes || 'N/A'}</td>
399 </tr>
400 ))}
401 </tbody>
402 </table>
403 </div>
404 )}
405 </div>
406 </div>
407 );
408 }
409
410 if (isBillingAdmin) {
411 return (
412 <div className="page-wrapper">
413 <div className="page-heading">
414 <h1 className="page-title">Welcome, <em>{user.firstName} {user.lastName}</em></h1>
415 <div className="page-date">Saturday · {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</div>
416 </div>
417 <div className="divider"></div>
418
419 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
420 <div className="card">
421 <div className="card-header">
422 <h2 className="card-title">Your Information</h2>
423 </div>
424 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '15px' }}>
425 <div><strong>Name:</strong> {user.firstName} {user.lastName}</div>
426 <div><strong>Username:</strong> {user.username}</div>
427 <div><strong>Role:</strong> Billing Administrator</div>
428 </div>
429 </div>
430
431 <div className="card">
432 <div className="card-header">
433 <h2 className="card-title">Quick Actions</h2>
434 </div>
435 <Link to="/billing" style={{
436 display: 'block',
437 padding: '10px 14px',
438 background: '#bfdbfe',
439 color: '#1e1035',
440 borderRadius: '6px',
441 textAlign: 'center',
442 textDecoration: 'none',
443 fontSize: '13px',
444 fontWeight: '400'
445 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>
446 View Billing
447 </Link>
448 </div>
449 </div>
450
451 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
452 <div className="card">
453 <div className="card-header">
454 <h2 className="card-title">Quick Links</h2>
455 </div>
456 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
457 <Link to="/doctors" style={{
458 display: 'block',
459 padding: '10px 14px',
460 background: '#e9d5ff',
461 color: '#1e1035',
462 borderRadius: '6px',
463 textDecoration: 'none',
464 fontSize: '13px',
465 fontWeight: '400'
466 }} onMouseEnter={(e) => e.currentTarget.style.background = '#d8b4fe'} onMouseLeave={(e) => e.currentTarget.style.background = '#e9d5ff'}>
467 View Doctors
468 </Link>
469 <Link to="/departments" style={{
470 display: 'block',
471 padding: '10px 14px',
472 background: '#bfdbfe',
473 color: '#1e1035',
474 borderRadius: '6px',
475 textDecoration: 'none',
476 fontSize: '13px',
477 fontWeight: '400'
478 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>
479 View Departments
480 </Link>
481 </div>
482 </div>
483 </div>
484
485 <div className="card">
486 <div className="card-header">
487 <h2 className="card-title">Billing Records ({billings.length})</h2>
488 </div>
489 {billings.length === 0 ? (
490 <p style={{ color: 'var(--color-neutral-600)', fontSize: '12px' }}>No billing records</p>
491 ) : (
492 <div className="overflow-x-auto">
493 <table className="table">
494 <thead>
495 <tr>
496 <th>Patient</th>
497 <th>Amount</th>
498 <th>Status</th>
499 <th>Date</th>
500 </tr>
501 </thead>
502 <tbody>
503 {billings.map(bill => (
504 <tr key={bill.billId}>
505 <td>{bill.patientName}</td>
506 <td>${bill.totalCost}</td>
507 <td><span className={`badge ${bill.paymentStatus === 'PAID' ? 'badge-paid' : bill.paymentStatus === 'PENDING' ? 'badge-pending' : 'badge-cancelled'}`}>{bill.paymentStatus}</span></td>
508 <td>{bill.paymentDate || 'N/A'}</td>
509 </tr>
510 ))}
511 </tbody>
512 </table>
513 </div>
514 )}
515 </div>
516 </div>
517 );
518 }
519
520 return (
521 <div className="page-wrapper">
522 <div className="page-heading">
523 <h1 className="page-title">Welcome, <em>{user.firstName} {user.lastName}</em></h1>
524 <div className="page-date">Saturday · {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</div>
525 </div>
526 <div className="divider"></div>
527
528 <div className="stat-grid">
529 <StatCard
530 title="Total Patients"
531 count={stats.patients}
532 delta="+2.5% this week"
533 statClass="s1"
534 />
535 <StatCard
536 title="Total Doctors"
537 count={stats.doctors}
538 delta="+1 this month"
539 statClass="s2"
540 />
541 <StatCard
542 title="Appointments Today"
543 count={stats.appointments}
544 delta="+5 newly scheduled"
545 statClass="s3"
546 />
547 <StatCard
548 title="Departments"
549 count={5}
550 delta="All active"
551 statClass="s4"
552 />
553 </div>
554
555 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
556 <QuickActionsCard />
557 <RecentActivityCard />
558 </div>
559 </div>
560 );
561}
562
563function StatCard({ title, count, delta, statClass }) {
564 return (
565 <div className={`stat-card ${statClass}`}>
566 <div className="stat-accent"></div>
567 <p className="stat-label">{title}</p>
568 <p className="stat-value">{count.toLocaleString()}</p>
569 <p className="stat-delta">{delta}</p>
570 </div>
571 );
572}
573
574function QuickActionsCard() {
575 const user = JSON.parse(localStorage.getItem('user') || '{}');
576
577 // Lab technicians and billing admins should not see admin quick actions
578 if (user.role === 'LAB_TECHNICIAN' || user.role === 'BILLING_ADMIN') {
579 return null;
580 }
581
582 return (
583 <div className="card">
584 <div className="card-header">
585 <h2 className="card-title">Quick Actions</h2>
586 </div>
587 <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
588 <Link to="/patients/new" style={{
589 display: 'block',
590 padding: '10px 14px',
591 background: '#e9d5ff',
592 color: '#1e1035',
593 borderRadius: '6px',
594 textDecoration: 'none',
595 transition: 'background 0.2s',
596 fontSize: '13px',
597 fontWeight: '600'
598 }} onMouseEnter={(e) => e.currentTarget.style.background = '#d8b4fe'} onMouseLeave={(e) => e.currentTarget.style.background = '#e9d5ff'}>
599 + Add New Patient
600 </Link>
601 <Link to="/doctors/new" style={{
602 display: 'block',
603 padding: '10px 14px',
604 background: '#bfdbfe',
605 color: '#1e1035',
606 borderRadius: '6px',
607 textDecoration: 'none',
608 transition: 'background 0.2s',
609 fontSize: '13px',
610 fontWeight: '600'
611 }} onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'} onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}>
612 + Add New Doctor
613 </Link>
614 <Link to="/appointments/new" style={{
615 display: 'block',
616 padding: '10px 14px',
617 background: '#e9d5ff',
618 color: '#1e1035',
619 borderRadius: '6px',
620 textDecoration: 'none',
621 transition: 'background 0.2s',
622 fontSize: '13px',
623 fontWeight: '600'
624 }} onMouseEnter={(e) => e.currentTarget.style.background = '#d8b4fe'} onMouseLeave={(e) => e.currentTarget.style.background = '#e9d5ff'}>
625 + Create Appointment
626 </Link>
627 </div>
628 </div>
629 );
630}
631
632function RecentActivityCard() {
633 const user = JSON.parse(localStorage.getItem('user') || '{}');
634
635 // Lab technicians and billing admins should not see admin dashboard cards
636 if (user.role === 'LAB_TECHNICIAN' || user.role === 'BILLING_ADMIN') {
637 return null;
638 }
639
640 return (
641 <div className="card">
642 <div className="card-header">
643 <h2 className="card-title">Recent Activity</h2>
644 <button className="card-link">View all</button>
645 </div>
646 <p style={{ color: 'var(--color-neutral-600)', fontSize: '12px' }}>Activity feed coming soon...</p>
647 </div>
648 );
649}
650
651export default Dashboard;
Note: See TracBrowser for help on using the repository browser.