@@ -3,15 +3,268 @@ import { Page } from '@/components/layout/Page'
33import titleWrapper from '@/utils/titleWrapper'
44import { useTasksTranslation } from '@/i18n/useTasksTranslation'
55import { 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
7198const 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