Skip to content
Merged
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
15 changes: 15 additions & 0 deletions backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ def get_token_source(connection: HTTPConnection) -> str | None:
if len(parts) == 2 and parts[0].lower() == "bearer":
return parts[1]

try:
if hasattr(connection, "query_params"):
token_param = connection.query_params.get("token")
if token_param:
return token_param
except (AttributeError, KeyError):
try:
from urllib.parse import urlparse, parse_qs
parsed_url = urlparse(str(connection.url))
query_params = parse_qs(parsed_url.query)
if "token" in query_params and query_params["token"]:
return query_params["token"][0]
except Exception:
pass

return connection.cookies.get(AUTH_COOKIE_NAME)


Expand Down
2 changes: 1 addition & 1 deletion web/components/AuditLogTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const AuditLogTimeline: React.FC<AuditLogTimelineProps> = ({ caseId, clas
</div>
{isLoading && (
<div className="flex items-center justify-center py-12">
<HelpwaveLogo className="w-16 h-16 animate-spin" />
<HelpwaveLogo className="w-16 h-16" animate="loading" />
</div>
)}
{!isLoading && (
Expand Down
53 changes: 47 additions & 6 deletions web/components/patients/PatientList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMemo, useState, forwardRef, useImperativeHandle, useEffect } from 'react'
import { Table, Chip, FillerRowElement, Button, SearchBar, ProgressIndicator, Tooltip } from '@helpwave/hightide'
import { Table, Chip, FillerRowElement, Button, SearchBar, ProgressIndicator, Tooltip, Checkbox } from '@helpwave/hightide'
import { PlusIcon, Table as TableIcon, LayoutGrid, Printer } from 'lucide-react'
import { GetPatientsDocument, Sex, type GetPatientsQuery, type TaskType, type PatientState } from '@/api/gql/generated'
import { GetPatientsDocument, Sex, PatientState, type GetPatientsQuery, type TaskType } from '@/api/gql/generated'
import { usePaginatedGraphQLQuery } from '@/hooks/usePaginatedQuery'
import { SidePanel } from '@/components/layout/SidePanel'
import { PatientDetailView } from '@/components/patients/PatientDetailView'
Expand All @@ -28,6 +28,8 @@ export type PatientViewModel = {
tasks: TaskType[],
}

const STORAGE_KEY_SHOW_ALL_PATIENTS = 'patient-show-all-states'

export type PatientListRef = {
openCreate: () => void,
openPatient: (patientId: string) => void,
Expand All @@ -37,25 +39,57 @@ type PatientListProps = {
initialPatientId?: string,
onInitialPatientOpened?: () => void,
acceptedStates?: PatientState[],
rootLocationIds?: string[],
}

export const PatientList = forwardRef<PatientListRef, PatientListProps>(({ initialPatientId, onInitialPatientOpened, acceptedStates }, ref) => {
export const PatientList = forwardRef<PatientListRef, PatientListProps>(({ initialPatientId, onInitialPatientOpened, acceptedStates, rootLocationIds }, ref) => {
const translation = useTasksTranslation()
const { selectedRootLocationIds } = useTasksContext()
const effectiveRootLocationIds = rootLocationIds ?? selectedRootLocationIds
const { viewType, toggleView } = usePatientViewToggle()
const [isPanelOpen, setIsPanelOpen] = useState(false)
const [selectedPatient, setSelectedPatient] = useState<PatientViewModel | undefined>(undefined)
const [searchQuery, setSearchQuery] = useState('')
const [openedPatientId, setOpenedPatientId] = useState<string | null>(null)
const [showAllPatients, setShowAllPatients] = useState<boolean>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem(STORAGE_KEY_SHOW_ALL_PATIENTS)
if (stored === 'true') {
return true
}
if (stored === 'false') {
return false
}
}
return false
})

const [isPrinting, setIsPrinting] = useState(false)

const handleShowAllPatientsChange = (checked: boolean) => {
setShowAllPatients(() => {
if (typeof window !== 'undefined') {
localStorage.setItem(STORAGE_KEY_SHOW_ALL_PATIENTS, String(checked))
}
return checked
})
}

const allPatientStates: PatientState[] = [
PatientState.Admitted,
PatientState.Discharged,
PatientState.Dead,
PatientState.Wait,
]

const patientStates = showAllPatients ? allPatientStates : (acceptedStates ?? [PatientState.Admitted])

const { data: patientsData, refetch } = usePaginatedGraphQLQuery<GetPatientsQuery, GetPatientsQuery['patients'][0], { rootLocationIds?: string[], states?: PatientState[] }>({
queryKey: ['GetPatients'],
queryKey: ['GetPatients', { rootLocationIds: effectiveRootLocationIds, states: patientStates }],
document: GetPatientsDocument,
baseVariables: {
rootLocationIds: selectedRootLocationIds && selectedRootLocationIds.length > 0 ? selectedRootLocationIds : undefined,
states: acceptedStates
rootLocationIds: effectiveRootLocationIds && effectiveRootLocationIds.length > 0 ? effectiveRootLocationIds : undefined,
states: patientStates
},
pageSize: 50,
extractItems: (result) => result.patients,
Expand Down Expand Up @@ -278,6 +312,13 @@ export const PatientList = forwardRef<PatientListRef, PatientListProps>(({ initi
/>
</div>
<div className="flex flex-col sm:flex-row items-center gap-4 w-full sm:w-auto sm:ml-auto lg:pr-4">
<div className="flex items-center gap-2">
<Checkbox
checked={showAllPatients}
onCheckedChange={handleShowAllPatientsChange}
/>
<span className="text-sm text-description whitespace-nowrap">{translation('showAllPatients') || 'Show all patients'}</span>
</div>
{viewType === 'table' && (
<Tooltip tooltip="Print" position="top">
<Button
Expand Down
18 changes: 17 additions & 1 deletion web/components/tasks/TaskCardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,22 @@ const getPriorityColor = (priority: string | null | undefined): string => {
}
}

const getPriorityCheckboxColor = (priority: string | null | undefined): string => {
if (!priority) return ''
switch (priority) {
case 'P1':
return 'border-green-500 text-green-500 checked:bg-green-500'
case 'P2':
return 'border-blue-500 text-blue-500 checked:bg-blue-500'
case 'P3':
return 'border-orange-500 text-orange-500 checked:bg-orange-500'
case 'P4':
return 'border-red-500 text-red-500 checked:bg-red-500'
default:
return ''
}
}

const toDate = (date: Date | string | null | undefined): Date | undefined => {
if (!date) return undefined
if (date instanceof Date) return date
Expand Down Expand Up @@ -233,7 +249,7 @@ export const TaskCardView = ({ task, onToggleDone: _onToggleDone, onClick, showA
<Checkbox
checked={displayDone}
onCheckedChange={handleToggleDone}
className="rounded-full mt-0.5 shrink-0"
className={clsx('rounded-full mt-0.5 shrink-0', getPriorityCheckboxColor(task.priority || (task as FlexibleTask).priority))}
/>
</div>
<div className={clsx('flex-1 min-w-0 overflow-hidden', { 'pb-16': showPatient, 'pb-12': !showPatient })}>
Expand Down
19 changes: 18 additions & 1 deletion web/components/tasks/TaskDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { ErrorDialog } from '@/components/ErrorDialog'
import { useAtomicMutation } from '@/hooks/useAtomicMutation'
import { fetcher } from '@/api/gql/fetcher'
import { UserInfoPopup } from '@/components/UserInfoPopup'
import clsx from 'clsx'

interface TaskDetailViewProps {
taskId: string | null,
Expand All @@ -57,6 +58,22 @@ interface TaskDetailViewProps {
initialPatientId?: string,
}

const getPriorityCheckboxColor = (priority: TaskPriority | null | undefined): string => {
if (!priority) return ''
switch (priority) {
case 'P1':
return 'border-green-500 text-green-500 checked:bg-green-500'
case 'P2':
return 'border-blue-500 text-blue-500 checked:bg-blue-500'
case 'P3':
return 'border-orange-500 text-orange-500 checked:bg-orange-500'
case 'P4':
return 'border-red-500 text-red-500 checked:bg-red-500'
default:
return ''
}
}

export const TaskDetailView = ({ taskId, onClose, onSuccess, initialPatientId }: TaskDetailViewProps) => {
const translation = useTasksTranslation()
const queryClient = useQueryClient()
Expand Down Expand Up @@ -455,7 +472,7 @@ export const TaskDetailView = ({ taskId, onClose, onSuccess, initialPatientId }:
reopenTask({ id: taskId })
}
}}
className="rounded-full scale-125"
className={clsx('rounded-full scale-125', getPriorityCheckboxColor(formData.priority as TaskPriority | null | undefined))}
/>
)}
<div className="flex-1">
Expand Down
18 changes: 17 additions & 1 deletion web/components/tasks/TaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ const getPriorityDotColor = (priority: string | null | undefined): string => {
}
}

const getPriorityCheckboxColor = (priority: string | null | undefined): string => {
if (!priority) return ''
switch (priority) {
case 'P1':
return 'border-green-500 text-green-500 checked:bg-green-500'
case 'P2':
return 'border-blue-500 text-blue-500 checked:bg-blue-500'
case 'P3':
return 'border-orange-500 text-orange-500 checked:bg-orange-500'
case 'P4':
return 'border-red-500 text-red-500 checked:bg-red-500'
default:
return ''
}
}

const STORAGE_KEY_SHOW_DONE = 'task-show-done'

export const TaskList = forwardRef<TaskListRef, TaskListProps>(({ tasks: initialTasks, onRefetch, showAssignee = false, initialTaskId, onInitialTaskOpened, headerActions }, ref) => {
Expand Down Expand Up @@ -446,7 +462,7 @@ export const TaskList = forwardRef<TaskListRef, TaskListProps>(({ tasks: initial
reopenTask({ id: task.id })
}
}}
className={clsx('rounded-full')}
className={clsx('rounded-full', getPriorityCheckboxColor(task.priority))}
/>
</div>
)
Expand Down
3 changes: 3 additions & 0 deletions web/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ export type TasksTranslationEntries = {
'shiftHandover': string,
'shiftHandoverDescription': string,
'showAllTasks': string,
'showAllPatients': string,
'showDone': string,
'showTeamTasks': string,
'sPropertySubjectType': (values: { subject: string }) => string,
Expand Down Expand Up @@ -528,6 +529,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
'shiftHandover': `Schichtübergabe`,
'shiftHandoverDescription': `Wählen Sie einen Benutzer aus, um alle Ihnen zugewiesenen offenen Aufgaben zu übertragen.`,
'showAllTasks': `Alle Aufgaben anzeigen`,
'showAllPatients': `Alle Patienten anzeigen`,
'showDone': `Erledigte anzeigen`,
'showTeamTasks': `Team-Aufgaben anzeigen`,
'sPropertySubjectType': ({ subject }): string => {
Expand Down Expand Up @@ -889,6 +891,7 @@ export const tasksTranslation: Translation<TasksTranslationLocales, Partial<Task
'shiftHandover': `Shift Handover`,
'shiftHandoverDescription': `Select a user to transfer all open tasks assigned to you.`,
'showAllTasks': `Show All Tasks`,
'showAllPatients': `Show all patients`,
'showDone': `Show done`,
'showTeamTasks': `Show Team Tasks`,
'sPropertySubjectType': ({ subject }): string => {
Expand Down
1 change: 1 addition & 0 deletions web/locales/de-DE.arb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@
"chooseTheme": "Design wählen",
"done": "Fertig",
"showDone": "Erledigte anzeigen",
"showAllPatients": "Alle Patienten anzeigen",
"profile": "Profil",
"patientData": "Daten",
"clickToAdd": "Klicken um hinzuzufügen",
Expand Down
1 change: 1 addition & 0 deletions web/locales/en-US.arb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@
"chooseTheme": "Choose Theme",
"done": "Done",
"showDone": "Show done",
"showAllPatients": "Show all patients",
"patientData": "Data",
"clickToAdd": "Click to add",
"rClickToAdd": "Click to add {name}!",
Expand Down
6 changes: 2 additions & 4 deletions web/pages/location/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { TaskList, type TaskViewModel } from '@/components/tasks/TaskList'
import { useGetLocationNodeQuery, useGetPatientsQuery, useGetTasksQuery, type LocationType } from '@/api/gql/generated'
import { useMemo, useState } from 'react'
import { useRouter } from 'next/router'
import { useTasksContext } from '@/hooks/useTasksContext'
import { LocationChips } from '@/components/patients/LocationChips'
import { LOCATION_PATH_SEPARATOR } from '@/utils/location'

Expand All @@ -28,7 +27,6 @@ const getKindStyles = (kind: string) => {
const LocationPage: NextPage = () => {
const translation = useTasksTranslation()
const router = useRouter()
const { selectedRootLocationIds } = useTasksContext()
const id = Array.isArray(router.query['id']) ? router.query['id'][0] : router.query['id']
const [showAllTasks, setShowAllTasks] = useState(false)

Expand All @@ -43,7 +41,7 @@ const LocationPage: NextPage = () => {
const isTeamLocation = locationData?.locationNode?.kind === 'TEAM'

const { data: patientsData, refetch: refetchPatients, isLoading: isLoadingPatients } = useGetPatientsQuery(
{ locationId: id, rootLocationIds: selectedRootLocationIds && selectedRootLocationIds.length > 0 ? selectedRootLocationIds : undefined },
{ rootLocationIds: id ? [id] : undefined },
{
enabled: !!id && !isTeamLocation,
refetchOnWindowFocus: true,
Expand Down Expand Up @@ -201,7 +199,7 @@ const LocationPage: NextPage = () => {
{!isLoading && !isError && (
<TabView>
<Tab label={translation('patients')}>
<PatientList />
<PatientList rootLocationIds={id ? [id] : undefined} />
</Tab>
<Tab label={translation('tasks')}>
<TaskList
Expand Down
Loading