Skip to content

Commit

Permalink
feat: add AddPatientModal
Browse files Browse the repository at this point in the history
* feat: add AddPatientModal.tsx

* feat: allow adding a patient without bed assignment

* refactor: fix formatting

* chore: merge main and fix errors
  • Loading branch information
DasProffi authored Aug 14, 2023
1 parent 9c3ed34 commit 28e6e9a
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 58 deletions.
5 changes: 4 additions & 1 deletion lib/components/user_input/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ type SelectProps<T> = {
isHidingCurrentValue?: boolean,
hintText?: string,
showDisabledOptions?: boolean,
className?: string
className?: string,
isDisabled?: boolean
};

/**
Expand All @@ -32,6 +33,7 @@ export const Select = <T, >({
isHidingCurrentValue = true,
hintText = '',
showDisabledOptions = true,
isDisabled,
className
}: SelectProps<T>) => {
// Notice: for more complex types this check here might need an additional compare method
Expand All @@ -51,6 +53,7 @@ export const Select = <T, >({
<>
<Menu.Button
className={tx('inline-flex w-full justify-between items-center rounded-t-lg border-2 px-4 py-2 hover:bg-gray-100 font-medium', { 'rounded-b-lg': !open })}
disabled={isDisabled}
>
<span>{options.find(option => option.value === value)?.label ?? hintText}</span>
{open ? <ChevronUp/> : <ChevronDown/>}
Expand Down
100 changes: 100 additions & 0 deletions tasks/components/AddPatientModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { tw, tx } from '@helpwave/common/twind'
import type { Languages } from '@helpwave/common/hooks/useLanguage'
import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation'
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
import type { ConfirmDialogProps } from '@helpwave/common/components/modals/ConfirmDialog'
import { ConfirmDialog } from '@helpwave/common/components/modals/ConfirmDialog'
import type { RoomBedDropDownIDs } from './RoomBedDropDown'
import { RoomBedDropDown } from './RoomBedDropDown'
import React, { useState } from 'react'
import { Span } from '@helpwave/common/components/Span'
import { Input } from '@helpwave/common/components/user_input/Input'
import { noop } from '@helpwave/common/util/noop'
import { emptyPatient, useAssignBedMutation, usePatientCreateMutation } from '../mutations/patient_mutations'

type AddPatientModalTranslation = {
addPatient: string,
name: string,
minimumLength: (characters: number) => string,
noBedSelected: string
}

const defaultAddPatientModalTranslation: Record<Languages, AddPatientModalTranslation> = {
en: {
addPatient: 'Add Patient',
name: 'Name',
minimumLength: (characters: number) => `The Name must be at least ${characters} characters long`,
noBedSelected: 'No bed selected, the patient won\'t be assigned directly'
},
de: {
addPatient: 'Patient Hinzufügen',
name: 'Name',
minimumLength: (characters: number) => `Der Name muss mindestens ${characters} characters lang sein`,
noBedSelected: 'Kein Bett ausgewählt, der Patient wird nicht direkt zugeordnet'
}
}

export type AddPatientModalProps = ConfirmDialogProps & {
wardID: string
}

/**
* A Modal for adding a Patient
*/
export const AddPatientModal = ({
language,
wardID,
title,
onConfirm = noop,
...modalProps
}: PropsWithLanguage<AddPatientModalTranslation, AddPatientModalProps>) => {
const translation = useTranslation(language, defaultAddPatientModalTranslation)
const [dropdownID, setDropdownID] = useState<RoomBedDropDownIDs>({})
const [patientName, setPatientName] = useState<string>('')
const [touched, setTouched] = useState<boolean>(false)
const assignBedMutation = useAssignBedMutation()
const createPatientMutation = usePatientCreateMutation(patient => {
if (dropdownID.bedID) {
assignBedMutation.mutate({ id: dropdownID.bedID, patientID: patient.id })
}
})

const minimumNameLength = 4
const trimmedPatientName = patientName.trim()
const validPatientName = trimmedPatientName.length >= minimumNameLength
const validRoomAndBed = dropdownID.roomID && dropdownID.bedID
const isShowingError = touched && !validPatientName

return (
<ConfirmDialog
title={title ?? translation.addPatient}
onConfirm={() => {
onConfirm()
createPatientMutation.mutate({ ...emptyPatient, name: trimmedPatientName })
}}
buttonOverwrites={[{}, {}, { disabled: !validPatientName }]}
{...modalProps}
>
<div className={tw('flex flex-col gap-y-4 min-w-[300px]')}>
<div className={tw('flex flex-col gap-y-1')}>
<Span type="labelMedium">{translation.name}</Span>
<Input
value={patientName}
onChange={text => {
setTouched(true)
setPatientName(text)
}}
/>
{isShowingError && <Span type="formError">{translation.minimumLength(minimumNameLength)}</Span>}
</div>
<RoomBedDropDown
initialRoomAndBed={dropdownID}
wardID={wardID}
onChange={roomBedDropDownIDs => setDropdownID(roomBedDropDownIDs)}
isClearable={true}
/>
<Span className={tx({ 'text-hw-warn-400': !validRoomAndBed, 'text-transparent': validRoomAndBed })}>{translation.noBedSelected}</Span>
</div>
</ConfirmDialog>
)
}
126 changes: 84 additions & 42 deletions tasks/components/RoomBedDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { tw, tx } from '@helpwave/common/twind'
import type { Languages } from '@helpwave/common/hooks/useLanguage'
import type { PropsWithLanguage } from '@helpwave/common/hooks/useTranslation'
import { useTranslation } from '@helpwave/common/hooks/useTranslation'
import { useAssignBedMutation, usePatientAssignmentByWardQuery } from '../mutations/patient_mutations'
import { usePatientAssignmentByWardQuery } from '../mutations/patient_mutations'
import { useEffect, useRef, useState } from 'react'
import { Undo2 } from 'lucide-react'
import { Undo2, X } from 'lucide-react'
import { Select } from '@helpwave/common/components/user_input/Select'
import { Span } from '@helpwave/common/components/Span'
import { noop } from '@helpwave/common/util/noop'
Expand All @@ -14,6 +14,8 @@ import { Button } from '@helpwave/common/components/Button'
type RoomBedDropDownTranslation = {
saved: string,
unsaved: string,
valid: string,
invalid: string,
room: string,
bed: string,
revert: string,
Expand All @@ -24,14 +26,19 @@ const defaultRoomBedDropDownTranslation: Record<Languages, RoomBedDropDownTransl
en: {
saved: 'saved',
unsaved: 'not saved',
valid: 'valid',
invalid: 'invalid',
room: 'Room',
bed: 'Bed',
revert: 'Revert',
submitting: 'Submitting'
submitting: 'Submitting',

},
de: {
saved: 'gespeichert',
unsaved: 'nicht gespeichert',
valid: 'okay',
invalid: 'nicht okay',
room: 'Raum',
bed: 'Bett',
revert: 'Rückgängig',
Expand All @@ -40,15 +47,22 @@ const defaultRoomBedDropDownTranslation: Record<Languages, RoomBedDropDownTransl
}

export type RoomBedDropDownIDs = {
roomID : string,
bedID?: string,
patientID: string
/**
* undefined value here means select a room and bed
*/
roomID?: string,
bedID?: string
}

export type RoomBedDropDownProps = {
initialRoomAndBed: RoomBedDropDownIDs,
wardID: string,
onChange?: (roomBedDropDownIDs:RoomBedDropDownIDs) => void
/**
* Only triggers on valid input
*/
onChange?: (roomBedDropDownIDs:RoomBedDropDownIDs) => void,
isSubmitting?: boolean,
isClearable?: boolean
}

/**
Expand All @@ -58,6 +72,8 @@ export const RoomBedDropDown = ({
language,
initialRoomAndBed,
wardID,
isSubmitting = false,
isClearable = false,
onChange = noop
}: PropsWithLanguage<RoomBedDropDownTranslation, RoomBedDropDownProps>) => {
const translation = useTranslation(language, defaultRoomBedDropDownTranslation)
Expand All @@ -66,12 +82,7 @@ export const RoomBedDropDown = ({
const ref = useRef<HTMLDivElement>(null)
const currentRoom = data?.find(value => value.id === currentSelection.roomID)
const [touched, setTouched] = useState(false)
const [submitting, setSubmitting] = useState(false)

const assignBedMutation = useAssignBedMutation(() => {
onChange(currentSelection)
setSubmitting(false)
})
const isCreating = !initialRoomAndBed.roomID

useEffect(() => {
setCurrentSelection(initialRoomAndBed)
Expand All @@ -95,51 +106,82 @@ export const RoomBedDropDown = ({
/>
))

const bedSelect = (currentRoom && (
const bedSelect = (
<Select
className={tw('min-w-[150px]')}
value={currentSelection.bedID}
options={currentRoom.beds.map(value => ({ value: value.id, label: value.name, disabled: !!value.patient }))}
isDisabled={!currentSelection.roomID}
options={(currentRoom?.beds ?? []).map(value => ({ value: value.id, label: value.name, disabled: !!value.patient }))}
onChange={value => {
setCurrentSelection({
const newSelection = {
...currentSelection,
bedID: value
})
}
setCurrentSelection(newSelection)
setTouched(true)
setSubmitting(true)
assignBedMutation.mutate({ id: value, patientID: initialRoomAndBed.patientID })
onChange(newSelection)
}}
/>
))

)
const isShowingClear = isClearable && !isSubmitting && touched
const isShowingRevert = touched && hasChanges && !isSubmitting && !isCreating && !isClearable
const changesAndSaveRow = (
<div className={tw('flex flex-row justify-between items-center')}>
{touched && hasChanges && !submitting ? (
<Button
onClick={() => {
if (hasChanges) {
setCurrentSelection({ ...initialRoomAndBed })
<div>
{isShowingRevert && (
<Button
onClick={() => {
if (hasChanges) {
setCurrentSelection({ ...initialRoomAndBed })
setTouched(false)
}
}}
variant="tertiary"
disabled={!hasChanges}
>
<div className={tw('flex flex-row gap-x-2 items-center')}>
{translation.revert}
<Undo2 size={16}/>
</div>
</Button>
)}
{isShowingClear && (
<Button
onClick={() => {
setCurrentSelection({
bedID: undefined,
roomID: undefined
})
setTouched(false)
}
}}
variant="tertiary"
disabled={!hasChanges}
>
<div className={tw('flex flex-row gap-x-2 items-center')}>
{translation.revert}
<Undo2 size={16}/>
</div>
</Button>
) : <div></div>}
{touched && !submitting && (
onChange({})
}}
variant="tertiary"
color="negative"
>
<div className={tw('flex flex-row gap-x-2 items-center')}>
{translation.revert}
<X size={16}/>
</div>
</Button>
)}
</div>
{touched && !isSubmitting && !isCreating && (
<Span className={tx({
'!text-hw-negative-400': hasChanges,
'!text-hw-positive-400': !hasChanges
})}>
{hasChanges ? translation.unsaved : translation.saved}
</Span>
)}
{submitting && (
{touched && !isSubmitting && isCreating && (
<Span className={tx({
'!text-hw-positive-400': currentSelection.roomID && currentSelection.bedID,
'!text-hw-negative-400': !(currentSelection.roomID && currentSelection.bedID)
})}>
{currentSelection.roomID && currentSelection.bedID ? translation.valid : translation.invalid}
</Span>
)}
{isSubmitting && (
<Span>
{`${translation.submitting}...`}
</Span>
Expand Down Expand Up @@ -171,7 +213,7 @@ export const RoomBedDropDown = ({
</div>
)

const heightLayout = (data && currentRoom && (
const heightLayout = (
<div className={tw('flex flex-col')}>
<table className={tw('border-spacing-y-2 border-separate')}>
<thead/>
Expand All @@ -192,13 +234,13 @@ export const RoomBedDropDown = ({
</table>
{changesAndSaveRow}
</div>
))
)

return (
<div ref={ref}>
<LoadingAndErrorComponent
isLoading={isLoading}
hasError={isError || !data || !currentRoom}
hasError={isError || !data}
>
{ref.current?.offsetWidth && ref.current.offsetWidth > 300 ? widthLayout : heightLayout}
</LoadingAndErrorComponent>
Expand Down
2 changes: 1 addition & 1 deletion tasks/components/cards/DragCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const DragCard = ({
}: DragCardProps) => {
// For now fully equal to a normal card but, that might change later
return (
<Card className={tx(className, 'border-3', {
<Card className={tx(className, 'border-2', {
'hover:border-hw-primary-800 cursor-pointer': !cardDragProperties.isDragging && !cardDragProperties.isOver, // default
'border-hw-primary-700': isSelected,
'cursor-grabbing': cardDragProperties.isDragging,
Expand Down
Loading

0 comments on commit 28e6e9a

Please sign in to comment.