Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HospitalRun Frontend
# Open Health Frontend

<div align="center">

Expand All @@ -9,7 +9,7 @@

</div>

React frontend for [HospitalRun](http://hospitalrun.io/): free software for developing world hospitals.
React frontend for Open Health: free software for healthcare delivery teams.

---

Expand Down
4 changes: 2 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="theme-color" content="#1abc9c" />
<meta
name="description"
content="HospitalRun"
content="Open Health"
/>
<link rel="apple-touch-icon" href="logo.png" />
<!--
Expand All @@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>HospitalRun</title>
<title>Open Health</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/shared/components/navbar/Navbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ describe('Navbar', () => {
})

describe('header', () => {
it('should render a HospitalRun Navbar', () => {
it('should render an Open Health Navbar', () => {
setup(allPermissions)

expect(screen.getByText(/hospitalrun/i)).toBeInTheDocument()
expect(screen.getByText(/open health/i)).toBeInTheDocument()
expect(screen.getByRole('button', { hidden: false })).toBeInTheDocument()
})

Expand All @@ -101,7 +101,7 @@ describe('Navbar', () => {

history.location.pathname = '/enterprise-1701'
expect(history.location.pathname).not.toEqual('/')
userEvent.click(screen.getByText(/hospitalrun/i))
userEvent.click(screen.getByText(/open health/i))

expect(history.location.pathname).toEqual('/')
})
Expand Down
81 changes: 79 additions & 2 deletions src/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,92 @@
import React, { useEffect } from 'react'
import { Alert, Card, Col, Container, Row, Spinner } from 'react-bootstrap'
import { useQuery } from 'react-query'

import { useUpdateTitle } from '../page-header/title/TitleContext'
import AppointmentRepository from '../shared/db/AppointmentRepository'
import ImagingRepository from '../shared/db/ImagingRepository'
import IncidentRepository from '../shared/db/IncidentRepository'
import LabRepository from '../shared/db/LabRepository'
import MedicationRepository from '../shared/db/MedicationRepository'
import PatientRepository from '../shared/db/PatientRepository'
import useTranslator from '../shared/hooks/useTranslator'

type DataDomain = 'patients' | 'appointments' | 'labs' | 'medications' | 'imagings' | 'incidents'

type DataMetric = {
domain: DataDomain
value: number
}

const fetchPlatformMetrics = async (): Promise<DataMetric[]> => {
const [patients, appointments, labs, medications, imagings, incidents] = await Promise.all([
PatientRepository.count(),
AppointmentRepository.count(),
LabRepository.count(),
MedicationRepository.count(),
Comment on lines +22 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate dashboard metrics by user permissions

fetchPlatformMetrics always queries counts for patients, appointments, labs, medications, imaging, and incidents before rendering, but this component does not check state.user.permissions first. Users who are denied module access are redirected to / by PrivateRoute, so they can still see record counts for domains they are not authorized to read, which leaks restricted operational data. Filter metric queries/rendering by the corresponding read permissions (or skip unauthorized repositories entirely) before requesting these counts.

Useful? React with 👍 / 👎.

ImagingRepository.count(),
IncidentRepository.count(),
])

return [
{ domain: 'patients', value: patients },
{ domain: 'appointments', value: appointments },
{ domain: 'labs', value: labs },
{ domain: 'medications', value: medications },
{ domain: 'imagings', value: imagings },
{ domain: 'incidents', value: incidents },
]
}

const Dashboard: React.FC = () => {
const { t } = useTranslator()
const updateTitle = useUpdateTitle()
const { data: metrics = [], isLoading, isError } = useQuery('dashboard-metrics', fetchPlatformMetrics)

useEffect(() => {
updateTitle(t('dashboard.label'))
})
return <h3>Example</h3>
}, [t, updateTitle])

const totalRecords = metrics.reduce((runningTotal, metric) => runningTotal + metric.value, 0)

return (
<Container fluid>
<Row className="mb-4">
<Col>
<h3>{t('dashboard.centralHubTitle')}</h3>
<p className="text-muted mb-0">{t('dashboard.centralHubDescription')}</p>
</Col>
</Row>
{isError && <Alert variant="danger">{t('dashboard.metricsLoadError')}</Alert>}
{isLoading && <Spinner animation="border" role="status" />}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The loading spinner could be made more accessible and visually integrated with the layout.

Consider wrapping the spinner in a Row/Col or flex container and centering it, and include accessible text per Bootstrap’s pattern, e.g.:

<Spinner animation="border" role="status">
  <span className="visually-hidden">Loading...</span>
</Spinner>

This will make the loading state clearer and more accessible.

Suggested implementation:

      {isError && <Alert variant="danger">{t('dashboard.metricsLoadError')}</Alert>}
      {isLoading && (
        <Row className="justify-content-center my-4">
          <Col xs="auto" className="text-center">
            <Spinner animation="border" role="status">
              <span className="visually-hidden">{t('dashboard.loading')}</span>
            </Spinner>
          </Col>
        </Row>
      )}
      {!isLoading && !isError && (
  1. Ensure that a dashboard.loading key exists in your i18n translation files (e.g., "dashboard.loading": "Loading..."), or change the key in the code to match an existing translation such as t('common.loading') if that already exists in your project.

{!isLoading && !isError && (
<>
<Row className="mb-4">
<Col md={6} lg={4}>
<Card>
<Card.Body>
<Card.Title>{t('dashboard.totalRecordsLabel')}</Card.Title>
<h2 className="mb-0">{totalRecords}</h2>
</Card.Body>
</Card>
</Col>
</Row>
<Row>
{metrics.map((metric) => (
<Col key={metric.domain} sm={6} lg={4} className="mb-3">
<Card className="h-100">
<Card.Body>
<Card.Title>{t(`dashboard.metrics.${metric.domain}`)}</Card.Title>
<h4 className="mb-0">{metric.value}</h4>
</Card.Body>
</Card>
</Col>
))}
</Row>
</>
)}
</Container>
)
}

export default Dashboard
2 changes: 1 addition & 1 deletion src/shared/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const Navbar = () => {
},
{
type: 'header',
label: 'HospitalRun',
label: 'Open Health',
onClick: () => {
navigateTo('/')
},
Expand Down
12 changes: 12 additions & 0 deletions src/shared/locales/ar/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'لوحة القيادة',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
Comment on lines +4 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: New dashboard strings are left in English across non-English locale files.

These new keys (centralHubTitle, centralHubDescription, totalRecordsLabel, metricsLoadError, and metrics.*) are still in English in this non-en locale. If this bundle is user-facing, please either add translations now or mark them clearly as pending (e.g., with a TODO) to avoid a mixed-language UI.

incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/de/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Dashboard',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/enUs/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Dashboard',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/es/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Panel',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/fr/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Tableau de bord',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/id/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Dasbor',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/it/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Dashboard',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/ja/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'ダッシュボード',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/ptBr/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Painel de controle',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/ru/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Панель приборов',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/tr/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: 'Panel',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
12 changes: 12 additions & 0 deletions src/shared/locales/zhCN/translations/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
export default {
dashboard: {
label: '仪表板',
centralHubTitle: 'Digital Health Command Center',
centralHubDescription: 'Centralize your care data across patients, appointments, labs, medications, imaging, and incidents.',
totalRecordsLabel: 'Total clinical records',
metricsLoadError: 'Unable to load centralized healthcare metrics. Please try again.',
metrics: {
patients: 'Patients',
appointments: 'Appointments',
labs: 'Lab requests',
medications: 'Medication requests',
imagings: 'Imaging requests',
incidents: 'Incidents',
},
},
}
6 changes: 3 additions & 3 deletions src/user/user-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export interface UserState {

const initialState: UserState = {
user: {
givenName: 'HospitalRun',
givenName: 'Open Health',
familyName: 'Test',
fullName: 'HospitalRun Test',
id: 'test-hospitalrun',
fullName: 'Open Health Test',
id: 'test-open-health',
},
permissions: [
Permissions.ReadPatients,
Expand Down