source: frontend/src/pages/Dashboard.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance

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