Skip to content

Commit

Permalink
feat: add drag and drop to RoomOverview and PatientList
Browse files Browse the repository at this point in the history
  • Loading branch information
DasProffi authored Aug 14, 2023
1 parent a43a2e1 commit 9c3ed34
Show file tree
Hide file tree
Showing 15 changed files with 460 additions and 163 deletions.
8 changes: 6 additions & 2 deletions lib/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { tx } from '../twind'
import type { MouseEventHandler, PropsWithChildren } from 'react'
import type { Class } from '@twind/core'
import { noop } from '../util/noop'

export type CardProps = {
isSelected?: boolean,
Expand All @@ -14,12 +15,15 @@ export type CardProps = {
export const Card = ({
children,
isSelected = false,
onTileClick = () => undefined,
onTileClick = noop,
className = '',
}: PropsWithChildren<CardProps>) => {
return (
<div onClick={onTileClick}
className={tx('cursor-pointer rounded-md py-2 px-4 border border-2 hover:border-hw-primary-700 w-full', { 'border-hw-primary-700': isSelected }, className)}>
className={tx('rounded-md py-2 px-4 border border-2 w-full bg-white hover:border-hw-primary-800 cursor-pointer', {
'border-hw-primary-700': isSelected,
}, className)}
>
{children}
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion tasks/components/KanbanColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Languages } from '@helpwave/common/hooks/useLanguage'
import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation'
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
import { useDroppable } from '@dnd-kit/core'
import { Sortable } from './Sortable'
import { Sortable } from './dnd-kit/Sortable'
import {
SortableContext,
verticalListSortingStrategy
Expand Down
91 changes: 58 additions & 33 deletions tasks/components/RoomOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { useContext, useEffect, useRef, useState } from 'react'
import { WardOverviewContext } from '../pages/ward/[uuid]'
import type { PatientDTO } from '../mutations/patient_mutations'
import { emptyPatient } from '../mutations/patient_mutations'
import { Droppable } from './dnd-kit/Droppable'
import { Draggable } from './dnd-kit/Draggable'
import { DragCard } from './cards/DragCard'

export type RoomOverviewProps = {
room: RoomOverviewDTO
Expand All @@ -21,11 +24,12 @@ export const RoomOverview = ({ room }: RoomOverviewProps) => {
const ref = useRef<HTMLDivElement>(null)
const [columns, setColumns] = useState(3)

const setSelectedBed = (room: RoomOverviewDTO, bed: BedMinimalDTO, patient: PatientDTO|undefined) =>
const setSelectedBed = (room: RoomOverviewDTO, bed: BedMinimalDTO, patientID?: string, patient?: PatientDTO) =>
context.updateContext({
...context.state,
roomID: room.id,
bedID: bed.id,
patientID,
patient
})

Expand All @@ -46,39 +50,60 @@ export const RoomOverview = ({ room }: RoomOverviewProps) => {
<div className={tw(`grid grid-cols-${columns} gap-4`)}>
{room.beds.map(bed => bed.patient && bed.patient?.id ?
(
<PatientCard
key={bed.id}
bedName={bed.name}
patientName={bed.patient.name}
doneTasks={bed.patient.tasksDone}
inProgressTasks={bed.patient.tasksInProgress}
unscheduledTasks={bed.patient.tasksUnscheduled}
onTileClick={(event) => {
event.stopPropagation()
if (bed.patient) {
// LINTER: `bed.patient.id` gets evaluated as undefined without this if
setSelectedBed(room, bed, {
...emptyPatient,
id: bed.patient.id
})
}
}}
isSelected={selectedBedID === bed.id}
/>
<Droppable id={bed.id} key={bed.id} data={{ bed, room }}>
{({ isOver }) => bed.patient && bed.patient?.id && (
<DragCard
className={tw('!p-0 !border-0')}
>
<Draggable id={bed.patient.id + 'roomOverview'} data={{ bed }}>
{() => bed.patient && bed.patient?.id && (
<PatientCard
bedName={bed.name}
patientName={bed.patient.name}
doneTasks={bed.patient.tasksDone}
inProgressTasks={bed.patient.tasksInProgress}
unscheduledTasks={bed.patient.tasksUnscheduled}
onTileClick={(event) => {
event.stopPropagation()
if (bed.patient) {
// LINTER: `bed.patient.id` gets evaluated as undefined without this if
setSelectedBed(room, bed, bed.patient.id)
}
}}
isSelected={selectedBedID === bed.id}
cardDragProperties={{ isOver, isDangerous: true }}
/>
)}
</Draggable>
</DragCard>
)}
</Droppable>
) : (
<BedCard
key={bed.id}
bedName={bed.name}
// TODO move patient creation to here
onTileClick={(event) => {
event.stopPropagation()
setSelectedBed(room, bed, {
...emptyPatient,
id: bed.patient?.id ?? '',
name: `Patient ${room?.beds.findIndex(bedOfRoom => bedOfRoom.id === bed.id) ?? 1}`
})
}}
isSelected={selectedBedID === bed.id}/>
// Maybe also wrap inside the drag and drop later
<Droppable key={bed.id} id={bed.id} data={{ bed, room }}>
{({ isOver, active }) => (!isOver ? (
<BedCard
bedName={bed.name}
// TODO move patient creation to here
onTileClick={(event) => {
event.stopPropagation()
setSelectedBed(room, bed, undefined, {
...emptyPatient,
id: '',
name: `Patient ${room?.beds.findIndex(bedOfRoom => bedOfRoom.id === bed.id) ?? 1}`
})
}}
isSelected={selectedBedID === bed.id}
/>
) : (
<PatientCard
bedName={bed.name}
patientName={active?.data.current?.bed?.patient?.name ?? active?.data.current?.name}
cardDragProperties={{ isOver }}
/>
)
)}
</Droppable>
)
)}
</div>
Expand Down
16 changes: 9 additions & 7 deletions tasks/components/cards/BedCard.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { tw } from '@helpwave/common/twind'
import type { CardProps } from '@helpwave/common/components/Card'
import { Card } from '@helpwave/common/components/Card'
import { tw, tx } from '@helpwave/common/twind'
import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation'
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
import { Plus } from 'lucide-react'
import { Span } from '@helpwave/common/components/Span'
import type { Languages } from '@helpwave/common/hooks/useLanguage'
import type { DragCardProps } from './DragCard'
import { DragCard } from './DragCard'

type BedCardTranslation = {
nobody: string
Expand All @@ -20,7 +20,7 @@ const defaultBedCardTranslation: Record<Languages, BedCardTranslation> = {
}
}

export type BedCardProps = CardProps & {
export type BedCardProps = DragCardProps & {
bedName: string
}

Expand All @@ -33,20 +33,22 @@ export const BedCard = ({
language,
bedName,
onTileClick,
isSelected
isSelected,
className,
...restCardProps
}: PropsWithLanguage<BedCardTranslation, BedCardProps>) => {
const translation = useTranslation(language, defaultBedCardTranslation)
return (
(
<Card onTileClick={onTileClick} isSelected={isSelected} className={tw('min-h-[148px] flex flex-col')}>
<DragCard onTileClick={onTileClick} isSelected={isSelected} className={tx('min-h-[148px] flex flex-col', className)} {...restCardProps}>
<div className={tw('flex flex-row justify-between')}>
<Span type="subsubsectionTitle">{bedName}</Span>
<Span>{translation.nobody}</Span>
</div>
<div className={tw('flex flex-1 justify-center items-center')}>
<Plus/>
</div>
</Card>
</DragCard>
)
)
}
40 changes: 40 additions & 0 deletions tasks/components/cards/DragCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { tx } from '@helpwave/common/twind'
import type { PropsWithChildren } from 'react'
import type { CardProps } from '@helpwave/common/components/Card'
import { Card } from '@helpwave/common/components/Card'

export type CardDragProperties = {
isDragging?: boolean,
isOver?: boolean,
isDangerous?: boolean,
isInvalid?: boolean
}

export type DragCardProps = PropsWithChildren<CardProps & {
cardDragProperties?: CardDragProperties
}>

/**
* A Card to use when items are dragged for uniform design
*/
export const DragCard = ({
children,
cardDragProperties = {},
isSelected,
className,
...cardProps
}: DragCardProps) => {
// For now fully equal to a normal card but, that might change later
return (
<Card className={tx(className, 'border-3', {
'hover:border-hw-primary-800 cursor-pointer': !cardDragProperties.isDragging && !cardDragProperties.isOver, // default
'border-hw-primary-700': isSelected,
'cursor-grabbing': cardDragProperties.isDragging,
'border-hw-warn-400 border-dashed': cardDragProperties.isOver && cardDragProperties.isDangerous,
'border-hw-primary-700 border-dashed': cardDragProperties.isOver && !cardDragProperties.isDangerous && !cardDragProperties.isInvalid,
'border-hw-negative-400': cardDragProperties.isOver && cardDragProperties.isInvalid,
})} {...cardProps}>
{children}
</Card>
)
}
17 changes: 9 additions & 8 deletions tasks/components/cards/PatientCard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { tw } from '@helpwave/common/twind'
import type { CardProps } from '@helpwave/common/components/Card'
import { Card } from '@helpwave/common/components/Card'
import { PillLabelsColumn } from '../pill/PillLabelsColumn'
import { Span } from '@helpwave/common/components/Span'
import type { DragCardProps } from './DragCard'
import { DragCard } from './DragCard'

export type PatientCardProps = CardProps & {
export type PatientCardProps = DragCardProps & {
bedName: string,
patientName: string,
unscheduledTasks: number,
inProgressTasks: number,
doneTasks: number
unscheduledTasks?: number,
inProgressTasks?: number,
doneTasks?: number
}

/**
Expand All @@ -23,9 +23,10 @@ export const PatientCard = ({
doneTasks,
isSelected,
onTileClick,
...restCardProps
}: PatientCardProps) => {
return (
<Card isSelected={isSelected} onTileClick={onTileClick}>
<DragCard isSelected={isSelected} onTileClick={onTileClick} {...restCardProps}>
<div className={tw('flex flex-row justify-between')}>
<Span className={tw('whitespace-nowrap')} type="subsubsectionTitle">{bedName}</Span>
<Span className={tw('ml-2 truncate')}>{patientName}</Span>
Expand All @@ -37,6 +38,6 @@ export const PatientCard = ({
unscheduledCount={unscheduledTasks}
/>
</div>
</Card>
</DragCard>
)
}
39 changes: 39 additions & 0 deletions tasks/components/dnd-kit/Draggable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Active, Over } from '@dnd-kit/core'
import { useDraggable } from '@dnd-kit/core'
import type { Data } from '@dnd-kit/core/dist/store/types'

export type DraggableBuilderProps = {
isDragging: boolean,
active: Active | null,
over: Over | null
}

export type DraggableProps = {
children: ((draggableBuilderProps: DraggableBuilderProps) => React.ReactNode | undefined),
id: string,
data?: Data
}

/**
* A Component for the dnd kit draggable
*/
export const Draggable = ({
children,
id,
data
}: DraggableProps) => {
const { attributes, listeners, setNodeRef, transform, ...draggableBuilderProps } = useDraggable({
id,
data
})

const style = {
transform: `translate3d(${transform?.x ?? 0}px, ${transform?.y ?? 0}px, 0)`
}

return (
<div ref={setNodeRef} {...style} {...listeners} {...attributes}>
{children(draggableBuilderProps)}
</div>
)
}
32 changes: 32 additions & 0 deletions tasks/components/dnd-kit/Droppable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Active, Over } from '@dnd-kit/core'
import { useDroppable } from '@dnd-kit/core'
import type { Data } from '@dnd-kit/core/dist/store/types'

export type DroppableBuilderProps = {
isOver: boolean,
active: Active | null,
over: Over | null
}

export type DroppableProps = {
children: ((droppableBuilderProps: DroppableBuilderProps) => React.ReactNode | undefined),
id: string,
data?: Data
}

/**
* A Component for the dnd kit droppable
*/
export const Droppable = ({
children,
id,
data
}: DroppableProps) => {
const { setNodeRef, ...droppableBuilderProps } = useDroppable({ id, data })

return (
<div ref={setNodeRef}>
{children(droppableBuilderProps)}
</div>
)
}
File renamed without changes.
Loading

0 comments on commit 9c3ed34

Please sign in to comment.