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
22 changes: 20 additions & 2 deletions web/components/patients/PatientDataEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo, useEffect } from 'react'
import { useState, useMemo, useEffect, useCallback } from 'react'
import type { FormFieldDataHandling } from '@helpwave/hightide'
import { FormProvider, Input, DateTimeInput, Select, SelectOption, Textarea, Checkbox, Button, ConfirmDialog, LoadingContainer, useCreateForm, FormField, Visibility, useFormObserverKey, IconButton } from '@helpwave/hightide'
import { CenteredLoadingLogo } from '@/components/CenteredLoadingLogo'
Expand All @@ -22,6 +22,8 @@ import {
} from '@/data'
import { useTasksContext } from '@/hooks/useTasksContext'
import { ErrorDialog } from '@/components/ErrorDialog'
import { useCreateDraftDirty } from '@/hooks/useCreateDraftDirty'
import { serializePatientCreateDraft } from '@/utils/createDraftSnapshots'

type PatientFormValues = Omit<CreatePatientInput, 'clinicId' | 'teamIds' | 'positionId'> & {
clinic: NonNullable<GetPatientQuery['patient']>['clinic'] | null,
Expand All @@ -34,6 +36,7 @@ interface PatientDataEditorProps {
initialCreateData?: Partial<CreatePatientInput>,
onSuccess?: () => void,
onClose?: () => void,
onCreateDraftDirtyChange?: (dirty: boolean) => void,
}

const getDefaultBirthdate = () => {
Expand All @@ -59,6 +62,7 @@ export const PatientDataEditor = ({
initialCreateData = {},
onSuccess,
onClose,
onCreateDraftDirtyChange,
}: PatientDataEditorProps) => {
const translation = useTasksTranslation()
const { selectedLocationId, selectedRootLocationIds, rootLocations } = useTasksContext()
Expand Down Expand Up @@ -207,6 +211,18 @@ export const PatientDataEditor = ({

const { store, update: updateForm } = form

const serializePatientDraft = useCallback(
(values: PatientFormValues) => serializePatientCreateDraft(values),
[]
)

useCreateDraftDirty({
enabled: !isEditMode && onCreateDraftDirtyChange != null,
store,
serialize: serializePatientDraft,
onDirtyChange: onCreateDraftDirtyChange,
})

useEffect(() => {
if (patientData) {
const patient = patientData
Expand Down Expand Up @@ -548,12 +564,13 @@ export const PatientDataEditor = ({
</FormField>

{isEditMode && patientId && patientData && (
<div className="pt-6 mt-6 border-t border-divider flex justify-end gap-2">
<div className="pt-6 mt-6 border-t border-divider flex w-full min-w-0 flex-col gap-2 sm:flex-row sm:flex-wrap sm:justify-end sm:items-stretch">
{patientData.state !== PatientState.Dead && (
<Button
onClick={() => setIsMarkDeadDialogOpen(true)}
color="negative"
coloringStyle="outline"
className="w-full min-w-0 max-w-full whitespace-normal text-center leading-snug h-auto min-h-11 py-2.5 sm:w-auto sm:shrink"
>
{translation('markPatientDead')}
</Button>
Expand All @@ -562,6 +579,7 @@ export const PatientDataEditor = ({
onClick={() => setIsDeleteDialogOpen(true)}
color="negative"
coloringStyle="outline"
className="w-full min-w-0 max-w-full whitespace-normal text-center leading-snug h-auto min-h-11 py-2.5 sm:w-auto sm:shrink"
>
{translation('deletePatient') ?? 'Delete Patient'}
</Button>
Expand Down
3 changes: 3 additions & 0 deletions web/components/patients/PatientDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface PatientDetailViewProps {
onSuccess: () => void,
initialCreateData?: Partial<CreatePatientInput>,
onOpenSystemSuggestion?: (suggestion: SystemSuggestion, patientName: string) => void,
onCreateDraftDirtyChange?: (dirty: boolean) => void,
}

export const PatientDetailView = ({
Expand All @@ -61,6 +62,7 @@ export const PatientDetailView = ({
onSuccess,
initialCreateData = {},
onOpenSystemSuggestion,
onCreateDraftDirtyChange,
}: PatientDetailViewProps) => {
const translation = useTasksTranslation()

Expand Down Expand Up @@ -240,6 +242,7 @@ export const PatientDetailView = ({
initialCreateData={initialCreateData}
onSuccess={onSuccess}
onClose={onClose}
onCreateDraftDirtyChange={isEditMode ? undefined : onCreateDraftDirtyChange}
/>
</TabPanel>

Expand Down
41 changes: 32 additions & 9 deletions web/components/patients/PatientTasksView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useMemo, useEffect, useCallback } from 'react'
import { Button, Drawer, ExpandableContent, ExpandableHeader, ExpandableRoot } from '@helpwave/hightide'
import { Button, ConfirmDialog, Drawer, ExpandableContent, ExpandableHeader, ExpandableRoot } from '@helpwave/hightide'
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
import { CheckCircle2, ChevronDown, Circle, Combine, PlusIcon } from 'lucide-react'
import { TaskCardView } from '@/components/tasks/TaskCardView'
Expand Down Expand Up @@ -34,13 +34,32 @@ export const PatientTasksView = ({
const translation = useTasksTranslation()
const [taskId, setTaskId] = useState<string | null>(null)
const [isCreatingTask, setIsCreatingTask] = useState(false)
const [isCreateTaskDraftDirty, setIsCreateTaskDraftDirty] = useState(false)
const [isDiscardTaskCreateOpen, setIsDiscardTaskCreateOpen] = useState(false)
const [loadPresetOpen, setLoadPresetOpen] = useState(false)
const [optimisticTaskUpdates, setOptimisticTaskUpdates] = useState<Map<string, boolean>>(new Map())

const [completeTask] = useCompleteTask()
const [reopenTask] = useReopenTask()
const initialPatientName = `${patientData?.patient?.firstname ?? ''} ${patientData?.patient?.lastname ?? ''}`.trim()

const isTaskCreateDrawer = isCreatingTask && !taskId

const closeTaskDrawer = useCallback(() => {
setTaskId(null)
setIsCreatingTask(false)
setIsCreateTaskDraftDirty(false)
setIsDiscardTaskCreateOpen(false)
}, [])

const requestCloseTaskDrawer = useCallback(() => {
if (isTaskCreateDrawer && isCreateTaskDraftDirty) {
setIsDiscardTaskCreateOpen(true)
return
}
closeTaskDrawer()
}, [isTaskCreateDrawer, isCreateTaskDraftDirty, closeTaskDrawer])

const apiTasksWithOptimistic = useMemo(() => {
const baseTasks = patientData?.patient?.tasks || []
return baseTasks.map(task => {
Expand Down Expand Up @@ -177,10 +196,7 @@ export const PatientTasksView = ({
/>
<Drawer
isOpen={!!taskId || isCreatingTask}
onClose={() => {
setTaskId(null)
setIsCreatingTask(false)
}}
onClose={requestCloseTaskDrawer}
alignment="right"
titleElement={taskId ? translation('editTask') : translation('createTask')}
description={undefined}
Expand All @@ -192,12 +208,19 @@ export const PatientTasksView = ({
onListSync={() => {
onSuccess?.()
}}
onClose={() => {
setTaskId(null)
setIsCreatingTask(false)
}}
onClose={requestCloseTaskDrawer}
onCreateDraftDirtyChange={isTaskCreateDrawer ? setIsCreateTaskDraftDirty : undefined}
/>
</Drawer>
<ConfirmDialog
isOpen={isDiscardTaskCreateOpen}
onCancel={() => setIsDiscardTaskCreateOpen(false)}
onConfirm={closeTaskDrawer}
titleElement={translation('discardDraftTitle')}
description={translation('discardDraftMessage')}
confirmType="negative"
buttonOverwrites={[{}, {}, { text: translation('discard') }]}
/>
</>
)
}
28 changes: 26 additions & 2 deletions web/components/tables/PatientList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useMemo, useState, forwardRef, useImperativeHandle, useEffect, useCallback, useRef, type ReactNode } from 'react'
import { useMutation } from '@apollo/client/react'
import type { IdentifierFilterValue, FilterListItem, FilterListPopUpBuilderProps } from '@helpwave/hightide'
import { Chip, FillerCell, HelpwaveLogo, LoadingContainer, SearchBar, ProgressIndicator, Tooltip, Drawer, TableProvider, TableDisplay, TableColumnSwitcher, IconButton, useLocale, FilterList, SortingList, Button, ExpansionIcon, Visibility } from '@helpwave/hightide'
import { Chip, ConfirmDialog, FillerCell, HelpwaveLogo, LoadingContainer, SearchBar, ProgressIndicator, Tooltip, Drawer, TableProvider, TableDisplay, TableColumnSwitcher, IconButton, useLocale, FilterList, SortingList, Button, ExpansionIcon, Visibility } from '@helpwave/hightide'
import clsx from 'clsx'
import { LayoutGrid, PlusIcon, Table2 } from 'lucide-react'
import type { LocationType } from '@/api/gql/generated'
Expand Down Expand Up @@ -219,6 +219,8 @@ export const PatientList = forwardRef<PatientListRef, PatientListProps>(({ initi
])

const [isSaveViewDialogOpen, setIsSaveViewDialogOpen] = useState(false)
const [isCreatePatientDraftDirty, setIsCreatePatientDraftDirty] = useState(false)
const [isDiscardPatientCreateOpen, setIsDiscardPatientCreateOpen] = useState(false)

const [suggestionModalOpen, setSuggestionModalOpen] = useState(false)
const [suggestionModalSuggestion, setSuggestionModalSuggestion] = useState<SystemSuggestion | null>(null)
Expand Down Expand Up @@ -482,10 +484,22 @@ export const PatientList = forwardRef<PatientListRef, PatientListProps>(({ initi
setIsPanelOpen(true)
}, [])

const handleClose = () => {
const isPatientCreateMode = !selectedPatient && !openedPatientId

const performPatientDrawerClose = useCallback(() => {
setIsPanelOpen(false)
setSelectedPatient(undefined)
setOpenedPatientId(null)
setIsCreatePatientDraftDirty(false)
setIsDiscardPatientCreateOpen(false)
}, [])

const handleClose = () => {
if (isPanelOpen && isPatientCreateMode && isCreatePatientDraftDirty) {
setIsDiscardPatientCreateOpen(true)
return
}
performPatientDrawerClose()
}

const patientPropertyColumns = useMemo<ColumnDef<PatientViewModel>[]>(
Expand Down Expand Up @@ -1081,8 +1095,18 @@ export const PatientList = forwardRef<PatientListRef, PatientListProps>(({ initi
onPatientUpdated?.()
}}
onOpenSystemSuggestion={openSuggestionModal}
onCreateDraftDirtyChange={isPatientCreateMode ? setIsCreatePatientDraftDirty : undefined}
/>
</Drawer>
<ConfirmDialog
isOpen={isDiscardPatientCreateOpen}
onCancel={() => setIsDiscardPatientCreateOpen(false)}
onConfirm={performPatientDrawerClose}
titleElement={translation('discardDraftTitle')}
description={translation('discardDraftMessage')}
confirmType="negative"
buttonOverwrites={[{}, {}, { text: translation('discard') }]}
/>
<SystemSuggestionModal
isOpen={suggestionModalOpen}
onClose={closeSuggestionModal}
Expand Down
30 changes: 28 additions & 2 deletions web/components/tables/TaskList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ export const TaskList = forwardRef<TaskListRef, TaskListProps>(({ tasks: initial
const [isHandoverDialogOpen, setIsHandoverDialogOpen] = useState(false)
const [selectedUserId, setSelectedUserId] = useState<string | null>(null)
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false)
const [isCreateTaskDraftDirty, setIsCreateTaskDraftDirty] = useState(false)
const [isDiscardTaskCreateOpen, setIsDiscardTaskCreateOpen] = useState(false)
const isOpeningConfirmDialogRef = useRef(false)
const [isShowFilters, setIsShowFilters] = useState(false)
const [isShowSorting, setIsShowSorting] = useState(false)
Expand Down Expand Up @@ -834,6 +836,20 @@ export const TaskList = forwardRef<TaskListRef, TaskListProps>(({ tasks: initial
const hasOpenDrawer = taskDialogState.isOpen || selectedPatientId != null
const hasFilterPanelOpen = isShowFilters || isShowSorting

const closeTaskDrawer = useCallback(() => {
setTaskDialogState({ isOpen: false })
setIsCreateTaskDraftDirty(false)
setIsDiscardTaskCreateOpen(false)
}, [])

const requestCloseTaskDrawer = useCallback(() => {
if (taskDialogState.isOpen && !taskDialogState.taskId && isCreateTaskDraftDirty) {
setIsDiscardTaskCreateOpen(true)
return
}
closeTaskDrawer()
}, [taskDialogState.isOpen, taskDialogState.taskId, isCreateTaskDraftDirty, closeTaskDrawer])

useEffect(() => {
if (typeof document === 'undefined') return
if (isMobileIOS && hasOpenDrawer) {
Expand Down Expand Up @@ -1017,14 +1033,24 @@ export const TaskList = forwardRef<TaskListRef, TaskListProps>(({ tasks: initial
titleElement={taskDialogState.taskId ? translation('editTask') : translation('createTask')}
description={undefined}
isOpen={taskDialogState.isOpen}
onClose={() => setTaskDialogState({ isOpen: false })}
onClose={requestCloseTaskDrawer}
>
<TaskDetailView
taskId={taskDialogState.taskId ?? null}
onClose={() => setTaskDialogState({ isOpen: false })}
onClose={requestCloseTaskDrawer}
onListSync={onRefetch}
onCreateDraftDirtyChange={taskDialogState.isOpen && !taskDialogState.taskId ? setIsCreateTaskDraftDirty : undefined}
/>
</Drawer>
<ConfirmDialog
isOpen={isDiscardTaskCreateOpen}
onCancel={() => setIsDiscardTaskCreateOpen(false)}
onConfirm={closeTaskDrawer}
titleElement={translation('discardDraftTitle')}
description={translation('discardDraftMessage')}
confirmType="negative"
buttonOverwrites={[{}, {}, { text: translation('discard') }]}
/>
<Drawer
alignment="right"
titleElement={translation('editPatient')}
Expand Down
18 changes: 17 additions & 1 deletion web/components/tasks/TaskDataEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState, useMemo, useRef } from 'react'
import { useEffect, useState, useMemo, useRef, useCallback } from 'react'
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
import type { CreateTaskInput, UpdateTaskInput, TaskPriority } from '@/api/gql/generated'
import { PatientState } from '@/api/gql/generated'
Expand Down Expand Up @@ -31,6 +31,8 @@ import { AvatarStatusComponent } from '@/components/AvatarStatusComponent'
import { UserInfoPopup } from '@/components/UserInfoPopup'
import { localToUTCWithSameTime, PatientDetailView } from '@/components/patients/PatientDetailView'
import { ErrorDialog } from '@/components/ErrorDialog'
import { useCreateDraftDirty } from '@/hooks/useCreateDraftDirty'
import { serializeTaskCreateDraft } from '@/utils/createDraftSnapshots'
import clsx from 'clsx'
import { PriorityUtils } from '@/utils/priority'

Expand Down Expand Up @@ -62,6 +64,7 @@ interface TaskDataEditorProps {
initialPatientName?: string,
onListSync?: () => void,
onClose?: () => void,
onCreateDraftDirtyChange?: (dirty: boolean) => void,
presetRowEditor?: PresetRowEditorConfig | null,
}

Expand All @@ -71,6 +74,7 @@ export const TaskDataEditor = ({
initialPatientName,
onListSync,
onClose,
onCreateDraftDirtyChange,
presetRowEditor,
}: TaskDataEditorProps) => {
const translation = useTasksTranslation()
Expand Down Expand Up @@ -203,6 +207,18 @@ export const TaskDataEditor = ({

const { update: updateForm } = form

const serializeTaskDraft = useCallback(
(values: TaskFormValues) => serializeTaskCreateDraft(values),
[]
)

useCreateDraftDirty({
enabled: !isEditMode && !isPresetRowMode && onCreateDraftDirtyChange != null,
store: form.store,
serialize: serializeTaskDraft,
onDirtyChange: onCreateDraftDirtyChange,
})

useEffect(() => {
if (!isPresetRowMode || !presetRowEditor) return
updateForm(prev => ({
Expand Down
4 changes: 3 additions & 1 deletion web/components/tasks/TaskDetailView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ interface TaskDetailViewProps {
onListSync?: () => void,
initialPatientId?: string,
initialPatientName?: string,
onCreateDraftDirtyChange?: (dirty: boolean) => void,
}

export const TaskDetailView = ({ taskId, onClose, onListSync, initialPatientId, initialPatientName }: TaskDetailViewProps) => {
export const TaskDetailView = ({ taskId, onClose, onListSync, initialPatientId, initialPatientName, onCreateDraftDirtyChange }: TaskDetailViewProps) => {
const translation = useTasksTranslation()

const isEditMode = !!taskId
Expand Down Expand Up @@ -99,6 +100,7 @@ export const TaskDetailView = ({ taskId, onClose, onListSync, initialPatientId,
initialPatientName={initialPatientName}
onListSync={onListSync}
onClose={onClose}
onCreateDraftDirtyChange={isEditMode ? undefined : onCreateDraftDirtyChange}
/>
</TabPanel>
<TabPanel
Expand Down
Loading
Loading