Skip to content

Commit e9e080f

Browse files
authored
Merge pull request #9 from helpwave/station-views
Station views
2 parents d03649b + 7616d66 commit e9e080f

File tree

22 files changed

+642
-354
lines changed

22 files changed

+642
-354
lines changed

backend/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
f"postgresql+asyncpg://{_db_username}:{_db_password}@{_db_hostname}:{_db_port}/{_db_name}",
1616
)
1717

18-
_redis_host = os.getenv("REDIS_HOST", "localhost")
18+
_redis_host = os.getenv("REDIS_HOSTNAME", "localhost")
1919
_redis_port = int(os.getenv("REDIS_PORT", 6379))
2020
_redis_password = os.getenv("REDIS_PASSWORD", None)
2121

docker-compose.dev.yml

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,13 @@ services:
7373
web:
7474
build: web
7575
ports:
76-
- "3000:3000"
76+
- "3000:80"
7777
environment:
7878
RUNTIME_ISSUER_URI: "http://localhost:8080/realms/tasks"
7979
RUNTIME_CLIENT_ID: "tasks-web"
8080
depends_on:
8181
- backend
8282

83-
proxy:
84-
build: proxy
85-
environment:
86-
BACKEND_HOST: backend:80
87-
FRONTEND_HOST: host.docker.internal:3000
88-
extra_hosts:
89-
- "host.docker.internal:host-gateway"
90-
ports:
91-
- "80:80"
92-
depends_on:
93-
- backend
94-
9583
volumes:
9684
keycloak-data:
9785
postgres-data:

web/components/layout/ContentPanel.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
import type { HTMLAttributes } from 'react'
1+
import type { HTMLAttributes, ReactNode } from 'react'
22
import clsx from 'clsx'
33

44
type ContentPanelProps = HTMLAttributes<HTMLDivElement> & {
5-
title?: string,
6-
description?: string,
5+
titleElement?: ReactNode,
6+
description?: ReactNode,
77
}
88

99
/**
1010
* The base for every content section of the page
1111
*/
1212
export const ContentPanel = ({
1313
children,
14-
title,
14+
titleElement,
1515
description,
1616
...props
1717
}: ContentPanelProps) => {
1818
return (
1919
<div {...props} className={clsx('flex-col-2 w-full h-full pt-6', props.className)}>
2020
<div className="flex-col-0">
21-
<h1 className="typography-title-lg">{title}</h1>
22-
<p className="typography-label-md text-description">
21+
<h1 className="typography-title-lg">{titleElement}</h1>
22+
<div className="typography-label-md text-description">
2323
{description ?? <span className="invisible">placeholder</span>}
24-
</p>
24+
</div>
2525
</div>
2626
{children}
2727
</div>

web/components/layout/Page.tsx

Lines changed: 90 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,22 @@ import Link from 'next/link'
88
import {
99
Avatar,
1010
Dialog,
11+
Expandable,
1112
ExpansionIcon,
1213
IconButton,
13-
MarkdownInterpreter,
14-
Menu,
15-
MenuItem,
14+
LoadingContainer,
15+
MarkdownInterpreter, Menu, MenuItem,
1616
SolidButton,
1717
useLocalStorage
1818
} from '@helpwave/hightide'
1919
import { getConfig } from '@/utils/config'
2020
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
2121
import clsx from 'clsx'
22-
import { BellIcon, CircleCheck, Grid2X2PlusIcon, SettingsIcon, User, Building2, Users } from 'lucide-react'
23-
import { usePathname } from 'next/navigation'
22+
import { BellIcon, Building2, CircleCheck, Grid2X2PlusIcon, SettingsIcon, User, Users } from 'lucide-react'
2423
import { TasksLogo } from '@/components/TasksLogo'
2524
import { useRouter } from 'next/router'
26-
import { useGlobalContext } from '@/context/GlobalContext'
27-
import { logout } from '@/api/auth/authService'
25+
import { useTasksContext } from '@/hooks/useTasksContext'
26+
import { useAuth } from '@/hooks/useAuth'
2827

2928
export const StagingDisclaimerDialog = () => {
3029
const config = getConfig()
@@ -77,10 +76,14 @@ export const StagingDisclaimerDialog = () => {
7776

7877
type HeaderProps = HTMLAttributes<HTMLHeadElement>
7978

79+
/**
80+
* The basic header for most pages
81+
*/
8082
export const Header = ({ ...props }: HeaderProps) => {
81-
const router = useRouter()
82-
const { user } = useGlobalContext()
8383
const translation = useTasksTranslation()
84+
const { user } = useTasksContext()
85+
const router = useRouter()
86+
const { logout } = useAuth()
8487

8588
return (
8689
<header
@@ -134,72 +137,36 @@ export const Header = ({ ...props }: HeaderProps) => {
134137

135138
type SidebarLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
136139
href: string,
137-
route?: string,
138-
isActive?: boolean,
139140
}
140141

141-
const SidebarLink = ({ children, route, isActive, className, ...props }: SidebarLinkProps) => {
142-
const active = isActive !== undefined ? isActive : (route === props.href)
142+
const SidebarLink = ({ children, ...props }: SidebarLinkProps) => {
143+
const { route } = useTasksContext()
144+
143145
return (
144146
<Link
145147
{...props}
146148
className={clsx(
147149
'flex-row-1.5 w-full px-2.5 py-1.5 items-center rounded-md hover:bg-black/30',
148-
{ 'text-primary font-bold bg-black/10': active },
149-
className
150+
{ 'text-primary font-bold': route === props.href },
151+
props.className
150152
)}
151153
>
152154
{children}
153155
</Link>
154156
)
155157
}
156158

157-
type SidebarGroupProps = {
158-
title: string,
159-
icon: React.ReactNode,
160-
children: React.ReactNode,
161-
initiallyExpanded?: boolean,
162-
}
163-
164-
const SidebarGroup = ({ title, icon, children, initiallyExpanded = false }: SidebarGroupProps) => {
165-
const [isExpanded, setIsExpanded] = useState(initiallyExpanded)
166-
167-
return (
168-
<div className="flex-col-1">
169-
<button
170-
onClick={() => setIsExpanded(!isExpanded)}
171-
className={clsx(
172-
'flex-row-1.5 w-full px-2.5 py-1.5 items-center rounded-md hover:bg-black/30 text-left transition-colors',
173-
{ 'text-primary font-bold bg-black/10': isExpanded }
174-
)}
175-
>
176-
{icon}
177-
<span className="flex grow">{title}</span>
178-
<ExpansionIcon isExpanded={isExpanded} />
179-
</button>
180-
{isExpanded && (
181-
<div className="flex-col-1 animate-in fade-in slide-in-from-top-1 duration-200">
182-
{children}
183-
</div>
184-
)}
185-
</div>
186-
)
187-
}
188159

189160
type SidebarProps = HTMLAttributes<HTMLDivElement>
190161

162+
/**
163+
* The basic sidebar for most pages
164+
*/
191165
export const Sidebar = ({ ...props }: SidebarProps) => {
192166
const translation = useTasksTranslation()
193-
const route = usePathname()
194-
const {
195-
wards,
196-
teams,
197-
selectedLocation,
198-
setSelectedLocation,
199-
stats
200-
} = useGlobalContext()
201-
202-
const isPatientsRoute = route === '/patients'
167+
const wardsRoute = '/wards'
168+
const teamsRoute = '/teams'
169+
const context = useTasksContext()
203170

204171
return (
205172
<aside
@@ -214,71 +181,81 @@ export const Sidebar = ({ ...props }: SidebarProps) => {
214181
<TasksLogo />
215182
<span className="typography-title-md whitespace-nowrap">{'helpwave tasks'}</span>
216183
</Link>
217-
218-
<SidebarLink href="/" route={route}>
219-
<Grid2X2PlusIcon className="-rotate-90 size-5" />
184+
<SidebarLink href="/">
185+
<Grid2X2PlusIcon className="-rotate-90 size-5"/>
220186
<span className="flex grow">{translation('dashboard')}</span>
221187
</SidebarLink>
222-
223-
<SidebarLink href="/tasks" route={route}>
224-
<CircleCheck className="size-5" />
188+
<SidebarLink href="/tasks">
189+
<CircleCheck className="size-5"/>
225190
<span className="flex grow">{translation('myTasks')}</span>
226-
{stats.myOpenTasksCount > 0 && (<span className="text-description">{stats.myOpenTasksCount}</span>)}
191+
{context?.myTasksCount !== undefined && (<span className="text-description">{context.myTasksCount}</span>)}
227192
</SidebarLink>
228-
229-
<SidebarLink
230-
href="/patients"
231-
isActive={isPatientsRoute && selectedLocation === null}
232-
onClick={() => setSelectedLocation(null)}
233-
>
234-
<User className="size-5" />
193+
<SidebarLink href="/patients">
194+
<User className="size-5"/>
235195
<span className="flex grow">{translation('patients')}</span>
236-
<span className="text-description">{stats.totalPatientsCount}</span>
237196
</SidebarLink>
238197

239-
{/* Teams Group */}
240-
{teams.length > 0 && (
241-
<SidebarGroup
242-
title={translation('teams')}
243-
icon={<Users className="size-5" />}
244-
initiallyExpanded={isPatientsRoute && teams.some(t => t.id === selectedLocation)}
245-
>
246-
{teams.map(team => (
247-
<SidebarLink
248-
key={team.id}
249-
href="/patients"
250-
className="pl-9"
251-
isActive={isPatientsRoute && selectedLocation === team.id}
252-
onClick={() => setSelectedLocation(team.id)}
253-
>
254-
<span className="flex grow truncate">{team.title}</span>
255-
</SidebarLink>
256-
))}
257-
</SidebarGroup>
258-
)}
259-
260-
{/* Wards Group */}
261-
{wards.length > 0 && (
262-
<SidebarGroup
263-
title={translation('wards')}
264-
icon={<Building2 className="size-5" />}
265-
initiallyExpanded={isPatientsRoute && wards.some(w => w.id === selectedLocation)}
266-
>
267-
{wards.map(ward => (
268-
<SidebarLink
269-
key={ward.id}
270-
href="/patients"
271-
className="pl-9"
272-
isActive={isPatientsRoute && selectedLocation === ward.id}
273-
onClick={() => setSelectedLocation(ward.id)}
274-
>
275-
<span className="flex grow truncate">{ward.title}</span>
276-
</SidebarLink>
277-
))}
278-
</SidebarGroup>
279-
)}
280-
198+
<Expandable
199+
label={(
200+
<div className="flex-row-2">
201+
<Users className="size-5"/>
202+
{translation('teams')}
203+
</div>
204+
)}
205+
headerClassName="!px-2.5 !py-1.5 hover:bg-black/30"
206+
contentClassName="!px-0"
207+
className="!shadow-none"
208+
isExpanded={context.sidebar.isShowingTeams}
209+
onChange={isExpanded => context.update(prevState => ({
210+
...prevState,
211+
sidebar: {
212+
...prevState.sidebar,
213+
isShowingTeams: isExpanded,
214+
}
215+
}))}
216+
>
217+
<SidebarLink href={teamsRoute} className="pl-9.5">
218+
{translation('overview')}
219+
</SidebarLink>
220+
{!context?.teams ? (
221+
<LoadingContainer className="w-full h-10"/>
222+
) : context.teams.map(ward => (
223+
<SidebarLink key={ward.id} href={`${teamsRoute}/${ward.id}`} className="pl-9.5">
224+
{ward.title}
225+
</SidebarLink>
226+
))}
227+
</Expandable>
281228

229+
<Expandable
230+
label={(
231+
<div className="flex-row-2">
232+
<Building2 className="size-5"/>
233+
{translation('wards')}
234+
</div>
235+
)}
236+
headerClassName="!px-2.5 !py-1.5 hover:bg-black/30"
237+
contentClassName="!px-0"
238+
className="!shadow-none"
239+
isExpanded={context.sidebar.isShowingWards}
240+
onChange={isExpanded => context.update(prevState => ({
241+
...prevState,
242+
sidebar: {
243+
...prevState.sidebar,
244+
isShowingWards: isExpanded,
245+
}
246+
}))}
247+
>
248+
<SidebarLink href={wardsRoute} className="pl-9.5">
249+
{translation('overview')}
250+
</SidebarLink>
251+
{!context?.wards ? (
252+
<LoadingContainer className="w-full h-10"/>
253+
) : context.wards.map(ward => (
254+
<SidebarLink key={ward.id} href={`${wardsRoute}/${ward.id}`} className="pl-9.5">
255+
{ward.title}
256+
</SidebarLink>
257+
))}
258+
</Expandable>
282259
</nav>
283260
</aside>
284261
)

0 commit comments

Comments
 (0)