Skip to content

Commit 6a9ac76

Browse files
committed
feat: add ward overviews page
1 parent 099e277 commit 6a9ac76

5 files changed

Lines changed: 273 additions & 5 deletions

File tree

web/i18n/translations.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// AUTO-GENERATED. DO NOT EDIT.
22
/* eslint-disable @stylistic/quote-props */
3-
4-
3+
/* eslint-disable no-useless-escape */
4+
/* eslint-disable @typescript-eslint/no-unused-vars */
55
import type { Translation } from '@helpwave/internationalization'
66
import { TranslationGen } from '@helpwave/internationalization'
77

@@ -34,6 +34,7 @@ export type TasksTranslationEntries = {
3434
'editPatient': string,
3535
'female': string,
3636
'firstName': string,
37+
'freeBeds': string,
3738
'homePage': string,
3839
'imprint': string,
3940
'itsYou': string,
@@ -45,6 +46,7 @@ export type TasksTranslationEntries = {
4546
'loginRequiredDescription': string,
4647
'logout': string,
4748
'male': string,
49+
'myFavorites': string,
4850
'myOpenTasks': string,
4951
'myTasks': string,
5052
'name': string,
@@ -63,6 +65,7 @@ export type TasksTranslationEntries = {
6365
'nTeam': (values: { count: number }) => string,
6466
'nWard': (values: { count: number }) => string,
6567
'nYear': (values: { count: number }) => string,
68+
'occupancy': string,
6669
'openTasks': string,
6770
'overview': string,
6871
'pages.404.notFound': string,
@@ -126,6 +129,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
126129
'editPatient': `Patient bearbeiten`,
127130
'female': `Weiblich`,
128131
'firstName': `Vorname`,
132+
'freeBeds': `Freie Betten`,
129133
'homePage': `Startseite`,
130134
'imprint': `Impressum`,
131135
'itsYou': `Du`,
@@ -137,6 +141,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
137141
'loginRequiredDescription': `Um diese Seite benutzen zu können musst du eingeloggt sein.`,
138142
'logout': `Abmelden`,
139143
'male': `Männlich`,
144+
'myFavorites': `Meine Favoriten`,
140145
'myOpenTasks': `Meine offenen Aufgaben`,
141146
'myTasks': `Meine Aufgaben`,
142147
'name': `Name`,
@@ -203,6 +208,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
203208
'other': `${count} Jahre alt`,
204209
})
205210
},
211+
'occupancy': `Belegung`,
206212
'openTasks': `Offene Aufgaben`,
207213
'overview': `Übersicht`,
208214
'pages.404.notFound': `404 - Seite nicht gefunden`,
@@ -271,6 +277,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
271277
'editPatient': `Edit Patient`,
272278
'female': `Female`,
273279
'firstName': `First Name`,
280+
'freeBeds': `Free Beds`,
274281
'homePage': `Home Page`,
275282
'imprint': `Imprint`,
276283
'itsYou': `You`,
@@ -282,6 +289,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
282289
'loginRequiredDescription': `To use this site you need to be logged in.`,
283290
'logout': `Logout`,
284291
'male': `Male`,
292+
'myFavorites': `My Favorites`,
285293
'myOpenTasks': `My Open Tasks`,
286294
'myTasks': `My tasks`,
287295
'name': `Name`,
@@ -348,6 +356,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
348356
'other': `${count} years old`,
349357
})
350358
},
359+
'occupancy': `Occupancy`,
351360
'openTasks': `Open Tasks`,
352361
'overview': `Overview`,
353362
'pages.404.notFound': `404 - Page not found`,

web/locales/de-DE.arb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,8 @@
160160
"male": "Männlich",
161161
"female": "Weiblich",
162162
"diverse": "Divers",
163-
"time.today": "Heute"
163+
"time.today": "Heute",
164+
"myFavorites": "Meine Favoriten",
165+
"freeBeds": "Freie Betten",
166+
"occupancy": "Belegung"
164167
}

web/locales/en-US.arb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,8 @@
160160
"male": "Male",
161161
"female": "Female",
162162
"diverse": "Diverse",
163-
"time.today": "Today"
163+
"time.today": "Today",
164+
"myFavorites": "My Favorites",
165+
"freeBeds": "Free Beds",
166+
"occupancy": "Occupancy"
164167
}

web/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import './build/dev/types/routes.d.ts'
3+
import "./build/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

web/pages/wards/index.tsx

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,268 @@ import { Page } from '@/components/layout/Page'
33
import titleWrapper from '@/utils/titleWrapper'
44
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
55
import { ContentPanel } from '@/components/layout/ContentPanel'
6+
import { useMemo } from 'react'
7+
import { useQuery } from '@tanstack/react-query'
8+
import { ExpandableUncontrolled, LoadingAndErrorComponent } from '@helpwave/hightide'
9+
import { Cross, Star } from 'lucide-react'
10+
import clsx from 'clsx'
11+
import { useRouter } from 'next/router'
12+
13+
type Ward = {
14+
id: string,
15+
name: string,
16+
patientCount: number,
17+
freeBedCount: number,
18+
totalBedCount: number,
19+
isFavorite: boolean,
20+
}
21+
22+
type WardGroup = {
23+
name: string,
24+
wards: Ward[],
25+
}
26+
27+
const defaultData: WardGroup[] = [
28+
{
29+
name: 'Emergency',
30+
wards: [
31+
{
32+
id: 'ER-1',
33+
name: 'Trauma Unit',
34+
patientCount: 18,
35+
freeBedCount: 2,
36+
totalBedCount: 20,
37+
isFavorite: true,
38+
},
39+
{
40+
id: 'ER-2',
41+
name: 'Acute Care',
42+
patientCount: 22,
43+
freeBedCount: 3,
44+
totalBedCount: 25,
45+
isFavorite: false,
46+
},
47+
{
48+
id: 'ER-3',
49+
name: 'Observation',
50+
patientCount: 10,
51+
freeBedCount: 5,
52+
totalBedCount: 15,
53+
isFavorite: false,
54+
},
55+
],
56+
},
57+
{
58+
name: 'Surgery',
59+
wards: [
60+
{
61+
id: 'SUR-1',
62+
name: 'General Surgery',
63+
patientCount: 16,
64+
freeBedCount: 4,
65+
totalBedCount: 20,
66+
isFavorite: true,
67+
},
68+
{
69+
id: 'SUR-2',
70+
name: 'Orthopedics',
71+
patientCount: 14,
72+
freeBedCount: 6,
73+
totalBedCount: 20,
74+
isFavorite: false,
75+
},
76+
{
77+
id: 'SUR-3',
78+
name: 'Neurosurgery',
79+
patientCount: 9,
80+
freeBedCount: 1,
81+
totalBedCount: 10,
82+
isFavorite: false,
83+
},
84+
],
85+
},
86+
{
87+
name: 'Internal Medicine',
88+
wards: [
89+
{
90+
id: 'IM-1',
91+
name: 'Cardiology',
92+
patientCount: 20,
93+
freeBedCount: 2,
94+
totalBedCount: 22,
95+
isFavorite: true,
96+
},
97+
{
98+
id: 'IM-2',
99+
name: 'Pulmonology',
100+
patientCount: 15,
101+
freeBedCount: 5,
102+
totalBedCount: 20,
103+
isFavorite: false,
104+
},
105+
],
106+
},
107+
{
108+
name: 'Pediatrics',
109+
wards: [
110+
{
111+
id: 'PED-1',
112+
name: 'Neonatal',
113+
patientCount: 8,
114+
freeBedCount: 2,
115+
totalBedCount: 10,
116+
isFavorite: false,
117+
},
118+
{
119+
id: 'PED-2',
120+
name: 'Child Care',
121+
patientCount: 12,
122+
freeBedCount: 6,
123+
totalBedCount: 18,
124+
isFavorite: true,
125+
},
126+
],
127+
},
128+
]
129+
130+
type WardCardProps = {
131+
ward: Ward,
132+
}
133+
134+
const WardCard = ({ ward }: WardCardProps) => {
135+
const translation = useTasksTranslation()
136+
const { isFavorite, name, patientCount, totalBedCount, freeBedCount } = ward
137+
const occupancyPercentage = Math.round((1 - (freeBedCount / Math.max(totalBedCount, 1))) * 100) / 100
138+
const router = useRouter()
139+
140+
return (
141+
<div
142+
className="flex-col-4 p-4 border-1 border-secondary/40 hover:border-secondary bg-secondary/10 rounded-lg hover:cursor-pointer"
143+
onClick={() => {
144+
router.push(`/wards/${ward.id}`).catch(console.error)
145+
}}
146+
>
147+
<div className="flex-col-1">
148+
<div className="flex-row-4 justify-between items-center">
149+
<div className="flex-row-1 items-center text-primary">
150+
<Cross className="size-4"/>
151+
<span className="typography-title-sm font-bold">{name}</span>
152+
</div>
153+
<button
154+
className={clsx(
155+
'hover:text-primary/50 size-4',
156+
{
157+
'text-primary': isFavorite,
158+
'text-disabled': !isFavorite,
159+
}
160+
)}
161+
onClick={(event) => {
162+
event.stopPropagation()
163+
// TODO make set favorite request here
164+
}}
165+
>
166+
<Star size="size-4"/>
167+
</button>
168+
</div>
169+
<span className="typography-label-md font-medium text-description">
170+
{translation('nPatient', { count: patientCount })}
171+
</span>
172+
</div>
173+
<div className="grid grid-cols-2 gap-x-4 w-full">
174+
<div className="flex-col-0">
175+
<span className="typography-label-lg font-bold">
176+
{freeBedCount}
177+
</span>
178+
{/* TODO add typography-label-sm here once it exists */}
179+
<span className="text-sm text-description">
180+
{translation('freeBeds')}
181+
</span>
182+
</div>
183+
<div className="flex-col-0">
184+
<span className="typography-label-lg font-bold">
185+
{occupancyPercentage + '%'}
186+
</span>
187+
{/* TODO add typography-label-sm here once it exists */}
188+
<span className="text-sm text-description">
189+
{translation('occupancy')}
190+
</span>
191+
</div>
192+
</div>
193+
</div>
194+
)
195+
}
196+
6197

7198
const WardsOverviewPage: NextPage = () => {
8199
const translation = useTasksTranslation()
9200

201+
const { data, isLoading, isError } = useQuery({
202+
queryKey: ['ward', 'overview'],
203+
queryFn: async () => {
204+
await new Promise(r => setTimeout(r, 1000))
205+
return defaultData
206+
}
207+
})
208+
const favorites = useMemo(
209+
() =>
210+
data?.reduce<Ward[]>((acc, group) => {
211+
group.wards.forEach(ward => {
212+
if (ward.isFavorite) acc.push(ward)
213+
})
214+
return acc
215+
}, []) ?? [],
216+
[data]
217+
)
218+
10219
return (
11220
<Page pageTitle={titleWrapper(translation('wards'))}>
12221
<ContentPanel
13222
titleElement={translation('wards')}
14223
>
224+
<LoadingAndErrorComponent
225+
isLoading={isLoading}
226+
hasError={isError}
227+
className="w-full min-h-24"
228+
>
229+
<ExpandableUncontrolled
230+
label={(
231+
<div className="flex-row-1 items-center">
232+
<Star className="text-primary size-4"/>
233+
{translation('myFavorites')}
234+
</div>
235+
)}
236+
headerClassName="typography-label-md font-bold !px-4 !py-4 rounded-xl"
237+
contentClassName="pb-4"
238+
className="rounded-xl"
239+
isExpanded={true}
240+
>
241+
<ul className="flex flex-wrap gap-4">
242+
{favorites.map(ward => (
243+
<li key={ward.id} className="min-w-60">
244+
<WardCard ward={ward}/>
245+
</li>
246+
))}
247+
</ul>
248+
</ExpandableUncontrolled>
249+
{data?.map((wardGroup, index) => (
250+
<ExpandableUncontrolled
251+
key={index}
252+
label={wardGroup.name}
253+
headerClassName="typography-label-md font-bold !px-4 !py-4 rounded-xl"
254+
contentClassName="pb-4"
255+
className="rounded-xl"
256+
isExpanded={false}
257+
>
258+
<ul className="flex flex-wrap gap-4">
259+
{wardGroup.wards.map(ward => (
260+
<li key={ward.id} className="min-w-60">
261+
<WardCard ward={ward}/>
262+
</li>
263+
))}
264+
</ul>
265+
</ExpandableUncontrolled>
266+
))}
267+
</LoadingAndErrorComponent>
15268
</ContentPanel>
16269
</Page>
17270
)

0 commit comments

Comments
 (0)