Skip to content

Commit

Permalink
Labs actions (#29)
Browse files Browse the repository at this point in the history
# Labs actions

## ♻️ Current situation & Problem
As a clinician, I want to modify labs values.

### Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md).
  • Loading branch information
arkadiuszbachorski authored Aug 19, 2024
1 parent 056798b commit 6c63e93
Show file tree
Hide file tree
Showing 8 changed files with 484 additions and 29 deletions.
175 changes: 175 additions & 0 deletions app/(dashboard)/patients/[id]/LabForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//
// This source file is part of the Stanford Biodesign Digital Health ENGAGE-HF open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//
'use client'
import { Dialog } from '@radix-ui/react-dialog'
import { type ComponentProps } from 'react'
import { z } from 'zod'
import {
getObservationTypeUnits,
getUnitOfObservationType,
} from '@/app/(dashboard)/patients/clientUtils'
import { type Observation } from '@/app/(dashboard)/patients/utils'
import { ObservationType } from '@/modules/firebase/utils'
import { Button } from '@/packages/design-system/src/components/Button'
import { DatePicker } from '@/packages/design-system/src/components/DatePicker'
import {
DialogContent,
DialogHeader,
DialogTitle,
} from '@/packages/design-system/src/components/Dialog'
import { Input } from '@/packages/design-system/src/components/Input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/packages/design-system/src/components/Select'
import { Field } from '@/packages/design-system/src/forms/Field'
import { useForm } from '@/packages/design-system/src/forms/useForm'

export const labFormSchema = z.object({
type: z.nativeEnum(ObservationType),
effectiveDateTime: z.date(),
unit: z.string(),
value: z.number(),
})

export type LabFormSchema = z.infer<typeof labFormSchema>

interface LabFormProps {
observation?: Observation
onSubmit: (data: LabFormSchema) => Promise<void>
}

export const LabForm = ({ observation, onSubmit }: LabFormProps) => {
const isEdit = !!observation
const defaultType = observation?.type ?? ObservationType.potassium
const form = useForm({
formSchema: labFormSchema,
defaultValues: {
type: defaultType,
effectiveDateTime:
observation?.effectiveDateTime ?
new Date(observation.effectiveDateTime)
: new Date(),
unit: observation?.unit ?? getUnitOfObservationType(defaultType).unit,
value: observation?.value,
},
})

const [formType, formUnit] = form.watch(['type', 'unit'])
const units = getObservationTypeUnits(formType)

const handleSubmit = form.handleSubmit(async (data) => {
await onSubmit(data)
})

return (
<form onSubmit={handleSubmit}>
<Field
control={form.control}
name="type"
label="Type"
render={({ field }) => (
<Select
onValueChange={(type) => {
field.onChange(type)
form.setValue(
'unit',
getUnitOfObservationType(type as ObservationType, formUnit)
.unit,
)
}}
{...field}
>
<SelectTrigger>
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
{Object.values(ObservationType).map((type) => (
<SelectItem key={type} value={type}>
{type}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
<Field
control={form.control}
name="unit"
label="Unit"
render={({ field }) => (
<Select {...field} key={formType}>
<SelectTrigger>
<SelectValue placeholder="Unit" />
</SelectTrigger>
<SelectContent>
{units.map((unit) => (
<SelectItem key={unit.unit} value={unit.unit}>
{unit.unit}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
<Field
control={form.control}
name="value"
label="Value"
render={({ field }) => (
<Input
{...field}
type="number"
onChange={(event) =>
field.onChange(event.currentTarget.valueAsNumber)
}
/>
)}
/>
<Field
control={form.control}
name="effectiveDateTime"
label="Date"
render={({ field }) => (
<DatePicker
mode="single"
selected={field.value}
onSelect={(date) => field.onChange(date)}
defaultMonth={field.value}
toYear={new Date().getFullYear()}
/>
)}
/>
<Button type="submit" isPending={form.formState.isSubmitting}>
{isEdit ? 'Edit' : 'Create'} observation
</Button>
</form>
)
}

type LabFormDialogProps = LabFormProps &
Pick<ComponentProps<typeof Dialog>, 'open' | 'onOpenChange'>

export const LabFormDialog = ({
open,
onOpenChange,
observation,
...props
}: LabFormDialogProps) => (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{observation ? 'Edit' : 'Create'} observation</DialogTitle>
</DialogHeader>
<LabForm {...props} observation={observation} />
</DialogContent>
</Dialog>
)
80 changes: 80 additions & 0 deletions app/(dashboard)/patients/[id]/LabMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// This source file is part of the Stanford Biodesign Digital Health ENGAGE-HF open-source project
//
// SPDX-FileCopyrightText: 2023 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//
'use client'
import { Pencil, Trash } from 'lucide-react'
import { LabFormDialog } from '@/app/(dashboard)/patients/[id]/LabForm'
import {
deleteObservation,
updateObservation,
} from '@/app/(dashboard)/patients/actions'
import { type Observation } from '@/app/(dashboard)/patients/utils'
import { type ResourceType } from '@/modules/firebase/utils'
import { RowDropdownMenu } from '@/packages/design-system/src/components/DataTable'
import { DropdownMenuItem } from '@/packages/design-system/src/components/DropdownMenu'
import { ConfirmDeleteDialog } from '@/packages/design-system/src/molecules/ConfirmDeleteDialog'
import { useOpenState } from '@/packages/design-system/src/utils/useOpenState'

interface LabMenuProps {
userId: string
resourceType: ResourceType
observation: Observation
}

export const LabMenu = ({
userId,
resourceType,
observation,
}: LabMenuProps) => {
const deleteConfirm = useOpenState()
const editObservation = useOpenState()

const handleDelete = async () => {
await deleteObservation({
userId,
resourceType,
observationId: observation.id,
observationType: observation.type,
})
deleteConfirm.close()
}

return (
<>
<LabFormDialog
onSubmit={async (data) => {
await updateObservation({
userId,
resourceType,
observationId: observation.id,
...data,
})
editObservation.close()
}}
open={editObservation.isOpen}
onOpenChange={editObservation.setIsOpen}
observation={observation}
/>
<ConfirmDeleteDialog
open={deleteConfirm.isOpen}
onOpenChange={deleteConfirm.setIsOpen}
entityName="observation"
onDelete={handleDelete}
/>
<RowDropdownMenu>
<DropdownMenuItem onClick={editObservation.open}>
<Pencil />
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={deleteConfirm.open}>
<Trash />
Delete
</DropdownMenuItem>
</RowDropdownMenu>
</>
)
}
103 changes: 78 additions & 25 deletions app/(dashboard)/patients/[id]/Labs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,88 @@
//
'use client'
import { createColumnHelper } from '@tanstack/table-core'
import { type LabsData } from '@/app/(dashboard)/patients/utils'
import { Plus } from 'lucide-react'
import { useMemo } from 'react'
import { LabFormDialog } from '@/app/(dashboard)/patients/[id]/LabForm'
import { LabMenu } from '@/app/(dashboard)/patients/[id]/LabMenu'
import { createObservation } from '@/app/(dashboard)/patients/actions'
import type { LabsData, Observation } from '@/app/(dashboard)/patients/utils'
import { Button } from '@/packages/design-system/src/components/Button'
import { DataTable } from '@/packages/design-system/src/components/DataTable'
import { useOpenState } from '@/packages/design-system/src/utils/useOpenState'

interface LabsProps extends LabsData {}

type Observation = LabsData['observations'][number]

const columnHelper = createColumnHelper<Observation>()
const columns = [
columnHelper.accessor('effectiveDateTime', {
header: 'Date',
cell: (props) => {
const value = props.getValue()
const date = value ? new Date(value) : undefined
return date?.toLocaleDateString() ?? ''
},
}),
columnHelper.accessor('type', {
header: 'Type',
}),
columnHelper.accessor('value', {
header: 'Value',
cell: (props) => {
const observation = props.row.original
return `${observation.value} ${observation.unit}`
},
}),
]

export const Labs = ({ observations }: LabsProps) => {
return <DataTable columns={columns} data={observations} />
export const Labs = ({ observations, userId, resourceType }: LabsProps) => {
const createDialog = useOpenState()

const columns = useMemo(
() => [
columnHelper.accessor('effectiveDateTime', {
header: 'Date',
cell: (props) => {
const value = props.getValue()
const date = value ? new Date(value) : undefined
return date?.toLocaleDateString() ?? ''
},
}),
columnHelper.accessor('type', {
header: 'Type',
}),
columnHelper.accessor('value', {
header: 'Value',
cell: (props) => {
const observation = props.row.original
return `${observation.value} ${observation.unit}`
},
}),
columnHelper.display({
id: 'actions',
cell: (props) => (
<LabMenu
observation={props.row.original}
userId={userId}
resourceType={resourceType}
/>
),
}),
],
[resourceType, userId],
)

return (
<>
<LabFormDialog
onSubmit={async (data) => {
await createObservation({
userId,
resourceType,
...data,
})
createDialog.close()
}}
open={createDialog.isOpen}
onOpenChange={createDialog.setIsOpen}
/>
<DataTable
columns={columns}
data={observations}
header={
<>
<Button
size="sm"
variant="secondary"
className="ml-auto"
onClick={createDialog.open}
>
<Plus />
Add observation
</Button>
</>
}
/>
</>
)
}
Loading

0 comments on commit 6c63e93

Please sign in to comment.