Skip to content

Commit

Permalink
Dual Valueset for Medication Request Instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
amjithtitus09 committed Jan 15, 2025
1 parent 31aac73 commit 0835c63
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 32 deletions.
1 change: 1 addition & 0 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1638,6 +1638,7 @@
"priority": "Priority",
"prn_prescription": "PRN Prescription",
"prn_prescriptions": "PRN Prescriptions",
"prn_reason": "PRN Reason",
"procedure_suggestions": "Procedure Suggestions",
"procedures_select_placeholder": "Select procedures to add details",
"professional_info": "Professional Information",
Expand Down
183 changes: 183 additions & 0 deletions src/components/Questionnaire/DualValueSetSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { useQuery } from "@tanstack/react-query";
import { X } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";

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

import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";

import routes from "@/Utils/request/api";
import query from "@/Utils/request/query";
import { Code, ValueSetSystem } from "@/types/questionnaire/code";

type TabIndex = 0 | 1;

interface DualValueSetSelectProps {
systems: [ValueSetSystem, ValueSetSystem];
values?: [Code | null, Code | null];
onSelect: (index: TabIndex, value: Code | null) => void;
placeholders?: [string, string];
disabled?: boolean;
count?: number;
searchPostFix?: string;
labels: [string, string];
}

export function DualValueSetSelect({
systems,
values = [null, null],
onSelect,
placeholders = ["Search...", "Search..."],
disabled,
count = 10,
searchPostFix = "",
labels,
}: DualValueSetSelectProps) {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [activeTab, setActiveTab] = useState<TabIndex>(0);
const [search, setSearch] = useState("");

const searchQuery = useQuery({
queryKey: ["valueset", systems[activeTab], "expand", count, search],
queryFn: query.debounced(routes.valueset.expand, {
pathParams: { system: systems[activeTab] },
body: { count, search: search + searchPostFix },
}),
});

useEffect(() => {
if (open) {
setSearch("");
}
}, [open, activeTab]);

const handleRemove = (index: TabIndex, e: React.MouseEvent) => {
e.stopPropagation();
onSelect(index, null);
};

const handleSelect = (value: Code) => {
onSelect(activeTab, {
code: value.code,
display: value.display || "",
system: value.system || "",
});

if (activeTab === 0) {
setActiveTab(1);
} else {
setOpen(false);
}
};

const hasValues = values.some((v) => v !== null);

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild disabled={disabled}>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-start h-auto min-h-10 py-2",
"text-left",
!hasValues && "text-gray-400",
)}
>
{!hasValues ? (
<span>{placeholders[0]}</span>
) : (
<div className="flex flex-col gap-1 w-full">
{values.map(
(value, index) =>
value && (
<div
key={value.code}
className="flex items-center justify-between bg-gray-100 rounded px-2 py-1 min-w-0 cursor-pointer hover:bg-gray-200"
onClick={(e) => {
e.stopPropagation();
setActiveTab(index as TabIndex);
setOpen(true);
}}
>
<span className="text-sm truncate flex-1 mr-2">
{value.display}
</span>
<button
onClick={(e) => handleRemove(index as TabIndex, e)}
className="hover:text-gray-700 text-gray-500 shrink-0"
>
<X className="h-4 w-4" />
</button>
</div>
),
)}
</div>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Tabs
value={activeTab.toString()}
onValueChange={(value) => setActiveTab(Number(value) as TabIndex)}
>
<TabsList className="w-full">
<TabsTrigger value="0" className="flex-1">
{labels[0]}
</TabsTrigger>
<TabsTrigger value="1" className="flex-1">
{labels[1]}
</TabsTrigger>
</TabsList>
<Command filter={() => 1}>
<CommandInput
placeholder={placeholders[activeTab]}
className="outline-none border-none ring-0 shadow-none"
onValueChange={setSearch}
/>
<CommandList>
<CommandEmpty>
{search.length < 3
? t("min_char_length_error", { min_length: 3 })
: searchQuery.isFetching
? t("searching")
: t("no_results_found")}
</CommandEmpty>
<CommandGroup>
{searchQuery.data?.results.map((option) => (
<CommandItem
key={option.code}
value={option.code}
onSelect={() => handleSelect(option)}
className={cn(
values[activeTab]?.code === option.code &&
"bg-primary/10 text-primary font-medium",
)}
>
<span>{option.display}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</Tabs>
</PopoverContent>
</Popover>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
} from "@/components/ui/select";

import { ComboboxQuantityInput } from "@/components/Common/ComboboxQuantityInput";
import { DualValueSetSelect } from "@/components/Questionnaire/DualValueSetSelect";
import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect";

import useBreakpoints from "@/hooks/useBreakpoints";
Expand Down Expand Up @@ -169,7 +170,7 @@ export function MedicationRequestQuestion({
})}
>
{/* Header - Only show on desktop */}
<div className="hidden lg:grid grid-cols-[280px,180px,170px,160px,300px,230px,180px,250px,180px,160px,48px] bg-gray-50 border-b text-sm font-medium text-gray-500">
<div className="hidden lg:grid grid-cols-[280px,180px,170px,160px,300px,180px,250px,180px,160px,48px] bg-gray-50 border-b text-sm font-medium text-gray-500">
<div className="font-semibold text-gray-600 p-3 border-r">
{t("medicine")}
</div>
Expand All @@ -185,9 +186,6 @@ export function MedicationRequestQuestion({
<div className="font-semibold text-gray-600 p-3 border-r">
{t("instructions")}
</div>
<div className="font-semibold text-gray-600 p-3 border-r">
{t("additional_instructions")}
</div>
<div className="font-semibold text-gray-600 p-3 border-r">
{t("route")}
</div>
Expand Down Expand Up @@ -449,7 +447,7 @@ const MedicationRequestGridRow: React.FC<MedicationRequestGridRowProps> = ({
};

return (
<div className="grid grid-cols-1 lg:grid-cols-[280px,180px,170px,160px,300px,230px,180px,250px,180px,160px,48px] border-b hover:bg-gray-50/50">
<div className="grid grid-cols-1 lg:grid-cols-[280px,180px,170px,160px,300px,180px,250px,180px,160px,48px] border-b hover:bg-gray-50/50">
{/* Medicine Name */}
<div className="lg:p-4 lg:px-2 lg:py-1 flex items-center justify-between lg:justify-start lg:col-span-1 lg:border-r font-medium overflow-hidden text-sm">
<span className="break-words line-clamp-2 hidden lg:block">
Expand Down Expand Up @@ -651,33 +649,47 @@ const MedicationRequestGridRow: React.FC<MedicationRequestGridRowProps> = ({
<Label className="mb-1.5 block text-sm lg:hidden">
{t("instructions")}
</Label>
<ValueSetSelect
system="system-as-needed-reason"
value={dosageInstruction?.as_needed_for}
onSelect={(reason) =>
handleUpdateDosageInstruction({ as_needed_for: reason })
}
placeholder={t("select_prn_reason")}
disabled={disabled || !dosageInstruction?.as_needed_boolean}
wrapTextForSmallScreen={true}
/>
</div>
{/* Additional Instructions */}
<div className="lg:px-2 lg:py-1 lg:border-r overflow-hidden">
<Label className="mb-1.5 block text-sm lg:hidden">
{t("additional_instructions")}
</Label>
<ValueSetSelect
system="system-additional-instruction"
value={dosageInstruction?.additional_instruction?.[0]}
onSelect={(instruction) =>
handleUpdateDosageInstruction({
additional_instruction: [instruction],
})
}
placeholder={t("select_additional_instructions")}
disabled={disabled}
/>
{dosageInstruction?.as_needed_boolean ? (
<DualValueSetSelect
systems={[
"system-as-needed-reason",
"system-additional-instruction",
]}
values={[
dosageInstruction?.as_needed_for || null,
dosageInstruction?.additional_instruction?.[0] || null,
]}
onSelect={(index, value) => {
if (index === 0) {
handleUpdateDosageInstruction({
as_needed_for: value || undefined,
});
} else {
handleUpdateDosageInstruction({
additional_instruction: value ? [value] : undefined,
});
}
}}
labels={[t("prn_reason"), t("additional_instructions")]}
placeholders={[
t("select_prn_reason"),
t("select_additional_instructions"),
]}
disabled={disabled}
/>
) : (
<ValueSetSelect
system="system-additional-instruction"
value={dosageInstruction?.additional_instruction?.[0]}
onSelect={(instruction) =>
handleUpdateDosageInstruction({
additional_instruction: instruction ? [instruction] : undefined,
})
}
placeholder={t("select_additional_instructions")}
disabled={disabled}
/>
)}
</div>
{/* Route */}
<div className="lg:px-2 lg:py-1 lg:border-r overflow-hidden">
Expand Down

0 comments on commit 0835c63

Please sign in to comment.