Skip to content

Commit

Permalink
Refactor Encounter Actions into a Reusable Component
Browse files Browse the repository at this point in the history
- Create new `EncounterActions` component to centralize encounter-related actions
- Support both dropdown and standalone layouts
- Integrate with existing components like PatientInfoCard and QuickAccess
- Simplify action handling and improve code reusability
  • Loading branch information
bodhish committed Mar 7, 2025
1 parent 2ef181d commit 93be23b
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 108 deletions.
202 changes: 202 additions & 0 deletions src/components/Encounter/EncounterActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Link } from "raviger";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";

import { cn } from "@/lib/utils";

import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button, buttonVariants } from "@/components/ui/button";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";

import { PLUGIN_Component } from "@/PluginEngine";
import routes from "@/Utils/request/api";
import mutate from "@/Utils/request/mutate";
import { Encounter, completedEncounterStatus } from "@/types/emr/encounter";

interface EncounterActionsProps {
encounter: Encounter;
variant?: "default" | "outline" | "ghost";
size?: "default" | "sm" | "lg" | "icon";
disableButtons?: boolean;
className?: string;
layout?: "dropdown" | "standalone";
}

export default function EncounterActions({
encounter,
variant = "outline",
size = "default",
disableButtons = false,
className,
layout = "standalone",
}: EncounterActionsProps) {
const { t } = useTranslation();
const queryClient = useQueryClient();

const { mutate: updateEncounter } = useMutation({
mutationFn: mutate(routes.encounter.update, {
pathParams: { id: encounter.id },
}),
onSuccess: () => {
toast.success(t("encounter_marked_as_complete"));
queryClient.invalidateQueries({ queryKey: ["encounter", encounter.id] });
},
onError: () => {
toast.error(t("error_updating_encounter"));
},
});

const handleMarkAsComplete = () => {
updateEncounter({
...encounter,
status: "completed",
organizations: encounter.organizations.map((org) => org.id),
patient: encounter.patient.id,
encounter_class: encounter.encounter_class,
period: encounter.period,
hospitalization: encounter.hospitalization,
priority: encounter.priority,
external_identifier: encounter.external_identifier,
facility: encounter.facility.id,
});
};

if (completedEncounterStatus.includes(encounter.status) || disableButtons) {
return null;
}

const ActionItems = () => {
if (layout === "dropdown") {
return (
<>
<DropdownMenuItem asChild>
<Link
href={`/facility/${encounter.facility.id}/patient/${encounter.patient.id}/encounter/${encounter.id}/treatment_summary`}
>
{t("treatment_summary")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`/facility/${encounter.facility.id}/patient/${encounter.patient.id}/encounter/${encounter.id}/files/discharge_summary`}
>
{t("discharge_summary")}
</Link>
</DropdownMenuItem>
<AlertDialog>
<AlertDialogTrigger asChild>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
{t("mark_as_complete")}
</DropdownMenuItem>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("mark_as_complete")}</AlertDialogTitle>
<AlertDialogDescription>
{t("mark_encounter_as_complete_confirmation")}
</AlertDialogDescription>
</AlertDialogHeader>

<PLUGIN_Component
__name="PatientInfoCardMarkAsComplete"
encounter={encounter}
/>

<AlertDialogFooter>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
<AlertDialogAction
className={cn(buttonVariants({ variant: "primary" }))}
onClick={handleMarkAsComplete}
data-cy="mark-encounter-as-complete"
>
{t("mark_as_complete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}

return (
<div className={cn("space-y-2", className)}>
<Button
variant={variant}
size={size}
className="w-full justify-start"
asChild
>
<Link
href={`/facility/${encounter.facility.id}/patient/${encounter.patient.id}/encounter/${encounter.id}/treatment_summary`}
>
{t("treatment_summary")}
</Link>
</Button>
<Button
variant={variant}
size={size}
className="w-full justify-start"
asChild
>
<Link
href={`/facility/${encounter.facility.id}/patient/${encounter.patient.id}/encounter/${encounter.id}/files/discharge_summary`}
>
{t("discharge_summary")}
</Link>
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant={variant}
size={size}
className="w-full justify-start"
>
{t("mark_as_complete")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("mark_as_complete")}</AlertDialogTitle>
<AlertDialogDescription>
{t("mark_encounter_as_complete_confirmation")}
</AlertDialogDescription>
</AlertDialogHeader>

<PLUGIN_Component
__name="PatientInfoCardMarkAsComplete"
encounter={encounter}
/>

<AlertDialogFooter>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
<AlertDialogAction
className={cn(buttonVariants({ variant: "primary" }))}
onClick={handleMarkAsComplete}
data-cy="mark-encounter-as-complete"
>
{t("mark_as_complete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<PLUGIN_Component
__name="PatientInfoCardActions"
encounter={encounter}
/>
</div>
);
};

return ActionItems();
}
10 changes: 10 additions & 0 deletions src/components/Facility/ConsultationDetails/QuickAccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CareIcon from "@/CAREUI/icons/CareIcon";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";

import EncounterActions from "@/components/Encounter/EncounterActions";
import LinkDepartmentsSheet from "@/components/Patient/LinkDepartmentsSheet";

import useQuestionnaireOptions from "@/hooks/useQuestionnaireOptions";
Expand Down Expand Up @@ -44,6 +45,15 @@ export default function QuickAccess({ encounter }: QuickAccessProps) {
</section>
)}

{/* Encounter Actions */}
<section>
<h3 className="text-lg font-medium text-gray-950 mb-1">
{t("actions")}
</h3>

<EncounterActions encounter={encounter} />
</section>

{/* Departments and Teams */}
<section>
<div className="items-center justify-between mb-3">
Expand Down
123 changes: 15 additions & 108 deletions src/components/Patient/PatientInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import {
BedSingle,
Building,
Expand All @@ -10,29 +9,16 @@ import {
} from "lucide-react";
import { Link } from "raviger";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";

import { cn } from "@/lib/utils";

import CareIcon from "@/CAREUI/icons/CareIcon";

import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Badge } from "@/components/ui/badge";
import { Button, buttonVariants } from "@/components/ui/button";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
Expand All @@ -43,14 +29,13 @@ import {
} from "@/components/ui/popover";

import { Avatar } from "@/components/Common/Avatar";
import EncounterActions from "@/components/Encounter/EncounterActions";
import { LocationSheet } from "@/components/Location/LocationSheet";
import { LocationTree } from "@/components/Location/LocationTree";
import LinkDepartmentsSheet from "@/components/Patient/LinkDepartmentsSheet";

import { PLUGIN_Component } from "@/PluginEngine";
import dayjs from "@/Utils/dayjs";
import routes from "@/Utils/request/api";
import mutate from "@/Utils/request/mutate";
import { formatDateTime, formatPatientAge } from "@/Utils/utils";
import {
Encounter,
Expand All @@ -70,35 +55,6 @@ export interface PatientInfoCardProps {
export default function PatientInfoCard(props: PatientInfoCardProps) {
const { patient, encounter, disableButtons = false } = props;
const { t } = useTranslation();
const queryClient = useQueryClient();

const { mutate: updateEncounter } = useMutation({
mutationFn: mutate(routes.encounter.update, {
pathParams: { id: encounter.id },
}),
onSuccess: () => {
toast.success(t("encounter_marked_as_complete"));
queryClient.invalidateQueries({ queryKey: ["encounter", encounter.id] });
},
onError: () => {
toast.error(t("error_updating_encounter"));
},
});

const handleMarkAsComplete = () => {
updateEncounter({
...encounter,
status: "completed",
organizations: encounter.organizations.map((org) => org.id),
patient: encounter.patient.id,
encounter_class: encounter.encounter_class,
period: encounter.period,
hospitalization: encounter.hospitalization,
priority: encounter.priority,
external_identifier: encounter.external_identifier,
facility: encounter.facility.id,
});
};

return (
<>
Expand Down Expand Up @@ -475,71 +431,22 @@ export default function PatientInfoCard(props: PatientInfoCardProps) {
className="flex w-full flex-col gap-3 lg:w-auto 2xl:flex-row"
data-cy="update-encounter-button"
>
<AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="primary">
{t("update")}
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t("actions")}</DropdownMenuLabel>
<DropdownMenuItem>
<Link
href={`/facility/${encounter.facility.id}/patient/${patient.id}/encounter/${encounter.id}/treatment_summary`}
className="cursor-pointer text-gray-800"
>
{t("treatment_summary")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`/facility/${encounter.facility.id}/patient/${patient.id}/encounter/${encounter.id}/files/discharge_summary`}
className="cursor-pointer text-gray-800"
>
{t("discharge_summary")}
</Link>
</DropdownMenuItem>
<AlertDialogTrigger asChild>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
{t("mark_as_complete")}
</DropdownMenuItem>
</AlertDialogTrigger>
<PLUGIN_Component
__name="PatientInfoCardActions"
encounter={encounter}
/>
</DropdownMenuContent>
</DropdownMenu>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{t("mark_as_complete")}
</AlertDialogTitle>
<AlertDialogDescription>
{t("mark_encounter_as_complete_confirmation")}
</AlertDialogDescription>
</AlertDialogHeader>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="primary">
{t("update")}
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t("actions")}</DropdownMenuLabel>
<EncounterActions encounter={encounter} layout="dropdown" />
<PLUGIN_Component
__name="PatientInfoCardMarkAsComplete"
__name="PatientInfoCardActions"
encounter={encounter}
/>

<AlertDialogFooter>
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>

<AlertDialogAction
className={cn(buttonVariants({ variant: "primary" }))}
onClick={handleMarkAsComplete}
data-cy="mark-encounter-as-complete"
>
{t("mark_as_complete")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</div>
Expand Down

0 comments on commit 93be23b

Please sign in to comment.