Skip to content

Commit

Permalink
Basic allergies (#32)
Browse files Browse the repository at this point in the history
# Basic allergies

## ♻️ Current situation & Problem
Introduces allergies tab to the patient. Temporarily, it doesn't contain
`code` selection. That would be a separate PR.

### 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 20, 2024
1 parent 8e8f57f commit 13dab0e
Show file tree
Hide file tree
Showing 8 changed files with 433 additions and 0 deletions.
92 changes: 92 additions & 0 deletions app/(dashboard)/patients/[id]/Allergies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// 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 { createColumnHelper } from '@tanstack/table-core'
import { Plus } from 'lucide-react'
import { useMemo } from 'react'
import { AllergyFormDialog } from '@/app/(dashboard)/patients/[id]/AllergyForm'
import { AllergyMenu } from '@/app/(dashboard)/patients/[id]/AllergyMenu'
import { createAllergy } from '@/app/(dashboard)/patients/actions'
import {
stringifyIntoleranceCriticality,
stringifyIntoleranceType,
} from '@/modules/firebase/models/medication'
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'
import { type AllergiesData, type Allergy } from '../utils'

interface AllergiesProps extends AllergiesData {}

const columnHelper = createColumnHelper<Allergy>()

export const Allergies = ({
allergyIntolerances,
userId,
resourceType,
}: AllergiesProps) => {
const createDialog = useOpenState()

const columns = useMemo(
() => [
columnHelper.accessor('type', {
header: 'Type',
cell: (props) => stringifyIntoleranceType(props.getValue()),
}),
columnHelper.accessor('criticality', {
header: 'Criticality',
cell: (props) => stringifyIntoleranceCriticality(props.getValue()),
}),
columnHelper.display({
id: 'actions',
cell: (props) => (
<AllergyMenu
allergy={props.row.original}
userId={userId}
resourceType={resourceType}
/>
),
}),
],
[resourceType, userId],
)

return (
<>
<AllergyFormDialog
onSubmit={async (data) => {
await createAllergy({
userId,
resourceType,
...data,
})
createDialog.close()
}}
open={createDialog.isOpen}
onOpenChange={createDialog.setIsOpen}
/>
<DataTable
columns={columns}
data={allergyIntolerances}
header={
<>
<Button
size="sm"
variant="secondary"
className="ml-auto"
onClick={createDialog.open}
>
<Plus />
Add allergy
</Button>
</>
}
/>
</>
)
}
128 changes: 128 additions & 0 deletions app/(dashboard)/patients/[id]/AllergyForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// 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 { type ComponentProps } from 'react'
import { z } from 'zod'
import { type Allergy } from '@/app/(dashboard)/patients/utils'
import {
FHIRAllergyIntoleranceCriticality,
FHIRAllergyIntoleranceType,
stringifyIntoleranceCriticality,
stringifyIntoleranceType,
} from '@/modules/firebase/models/medication'
import { Button } from '@/packages/design-system/src/components/Button'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/packages/design-system/src/components/Dialog'
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 allergyFormSchema = z.object({
type: z.nativeEnum(FHIRAllergyIntoleranceType),
criticality: z.nativeEnum(FHIRAllergyIntoleranceCriticality),
})

export type AllergyFormSchema = z.infer<typeof allergyFormSchema>

interface AllergyFormProps {
allergy?: Allergy
onSubmit: (data: AllergyFormSchema) => Promise<void>
}

export const AllergyForm = ({ allergy, onSubmit }: AllergyFormProps) => {
const isEdit = !!allergy
const form = useForm({
formSchema: allergyFormSchema,
defaultValues: {
type: allergy?.type ?? FHIRAllergyIntoleranceType.allergy,
criticality:
allergy?.criticality ?? FHIRAllergyIntoleranceCriticality.high,
},
})

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={field.onChange} {...field}>
<SelectTrigger>
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
{Object.values(FHIRAllergyIntoleranceType).map((type) => (
<SelectItem key={type} value={type}>
{stringifyIntoleranceType(type)}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
<Field
control={form.control}
name="criticality"
label="Criticality"
render={({ field }) => (
<Select onValueChange={field.onChange} {...field}>
<SelectTrigger>
<SelectValue placeholder="Criticality" />
</SelectTrigger>
<SelectContent>
{Object.values(FHIRAllergyIntoleranceCriticality).map(
(criticality) => (
<SelectItem key={criticality} value={criticality}>
{stringifyIntoleranceCriticality(criticality)}
</SelectItem>
),
)}
</SelectContent>
</Select>
)}
/>
<Button type="submit" isPending={form.formState.isSubmitting}>
{isEdit ? 'Edit' : 'Create'} allergy
</Button>
</form>
)
}

type AllergyFormDialogProps = AllergyFormProps &
Pick<ComponentProps<typeof Dialog>, 'open' | 'onOpenChange'>

export const AllergyFormDialog = ({
open,
onOpenChange,
allergy,
...props
}: AllergyFormDialogProps) => (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{allergy ? 'Edit' : 'Create'} allergy</DialogTitle>
</DialogHeader>
<AllergyForm {...props} allergy={allergy} />
</DialogContent>
</Dialog>
)
79 changes: 79 additions & 0 deletions app/(dashboard)/patients/[id]/AllergyMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// 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 { AllergyFormDialog } from '@/app/(dashboard)/patients/[id]/AllergyForm'
import {
deleteAllergy,
updateAllergy,
} from '@/app/(dashboard)/patients/actions'
import { type Allergy } 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 AllergyMenuProps {
userId: string
resourceType: ResourceType
allergy: Allergy
}

export const AllergyMenu = ({
userId,
resourceType,
allergy,
}: AllergyMenuProps) => {
const deleteConfirm = useOpenState()
const editAllergy = useOpenState()

const handleDelete = async () => {
await deleteAllergy({
userId,
resourceType,
allergyIntoleranceId: allergy.id,
})
deleteConfirm.close()
}

return (
<>
<AllergyFormDialog
onSubmit={async (data) => {
await updateAllergy({
userId,
resourceType,
allergyIntoleranceId: allergy.id,
...data,
})
editAllergy.close()
}}
open={editAllergy.isOpen}
onOpenChange={editAllergy.setIsOpen}
allergy={allergy}
/>
<ConfirmDeleteDialog
open={deleteConfirm.isOpen}
onOpenChange={deleteConfirm.setIsOpen}
entityName="observation"
onDelete={handleDelete}
/>
<RowDropdownMenu>
<DropdownMenuItem onClick={editAllergy.open}>
<Pencil />
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={deleteConfirm.open}>
<Trash />
Delete
</DropdownMenuItem>
</RowDropdownMenu>
</>
)
}
9 changes: 9 additions & 0 deletions app/(dashboard)/patients/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type PatientFormSchema,
} from '@/app/(dashboard)/patients/PatientForm'
import {
getAllergiesData,
getFormProps,
getLabsData,
getMedicationsData,
Expand All @@ -39,6 +40,7 @@ import {
} from '@/packages/design-system/src/components/Tabs'
import { getUserName } from '@/packages/design-system/src/modules/auth/user'
import { PageTitle } from '@/packages/design-system/src/molecules/DashboardLayout'
import { Allergies } from './Allergies'
import { GenerateHealthSummary } from './GenerateHealthSummary'
import { Labs } from './Labs'
import { DashboardLayout } from '../../DashboardLayout'
Expand Down Expand Up @@ -72,6 +74,7 @@ interface PatientPageProps {
enum Tab {
information = 'information',
medications = 'medications',
allergies = 'allergies',
labs = 'labs',
}

Expand Down Expand Up @@ -179,6 +182,9 @@ const PatientPage = async ({ params }: PatientPageProps) => {
<TabsTrigger value={Tab.medications} className="grow">
Medications
</TabsTrigger>
<TabsTrigger value={Tab.allergies} className="grow">
Allergies
</TabsTrigger>
<TabsTrigger value={Tab.labs} className="grow">
Labs
</TabsTrigger>
Expand All @@ -200,6 +206,9 @@ const PatientPage = async ({ params }: PatientPageProps) => {
}}
/>
</TabsContent>
<TabsContent value={Tab.allergies}>
<Allergies {...await getAllergiesData({ userId, resourceType })} />
</TabsContent>
<TabsContent value={Tab.labs}>
<Labs {...await getLabsData({ userId, resourceType })} />
</TabsContent>
Expand Down
Loading

0 comments on commit 13dab0e

Please sign in to comment.