import { useEffect, useState, type ReactNode } from "react";
import { Check, Search, X } from "lucide-react";
import { Badge } from "../ui/badge";
import { EmptyState } from "../ui/empty-state";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { cn } from "../../lib/utils";
import { IValueSet, useValueSetServiceContext } from "../../services/ValueSetService";
import { codedAnswerLabel } from "../../utils/codedAnswerLabel";
import { LANGUAGE_OPTIONS, type Demographics } from "./demographics";
import { ENROLLED_OPTIONS, enrolledChoice, enrolledFromChoice } from "../../utils/tribalAffiliation";
// Read, searched and sorted by its label; the item itself (system, code, display) is what is chosen and stored.
const sortByDisplay = (items: IValueSet[]): IValueSet[] => [...items].sort((a, b) => codedAnswerLabel(a).localeCompare(codedAnswerLabel(b)));
const ChipGroup = ({ items, onRemove, disabled }: { items: IValueSet[]; onRemove: (code: string) => void; disabled: boolean }) =>
items.length ? (
{items.map((item) => (
{codedAnswerLabel(item)}
))}
) : null;
type PickerProps = {
id: string;
options: IValueSet[];
selected: IValueSet[];
onSelect: (item: IValueSet) => void;
placeholder: string;
disabled: boolean;
};
const SearchableMultiSelect = ({ id, options, selected, onSelect, placeholder, disabled }: PickerProps) => {
const [search, setSearch] = useState("");
const [resetKey, setResetKey] = useState(0);
const filtered = search.trim()
? options.filter((option) => codedAnswerLabel(option).toLowerCase().includes(search.toLowerCase()))
: options;
return (
);
};
const toggle = (item: IValueSet, list: IValueSet[]): IValueSet[] =>
list.some((entry) => entry.code === item.code) ? list.filter((entry) => entry.code !== item.code) : [...list, item];
type OptionLists = {
races: IValueSet[];
detailedRaces: IValueSet[];
ethnicities: IValueSet[];
detailedEthnicities: IValueSet[];
tribes: IValueSet[];
};
const NO_OPTIONS: OptionLists = { races: [], detailedRaces: [], ethnicities: [], detailedEthnicities: [], tribes: [] };
/** Languages, race, ethnicity and tribal affiliation, edited inside the You section's form. */
export const DemographicsEditor = ({
value,
onChange,
disabled,
afterLanguages,
}: {
value: Demographics;
onChange: (next: Demographics) => void;
disabled: boolean;
/** A control of the caller's that the form places straight after Languages. */
afterLanguages?: ReactNode;
}) => {
const { fetchValueSet, valueSetUrls } = useValueSetServiceContext();
const [options, setOptions] = useState(NO_OPTIONS);
const [optionsFailed, setOptionsFailed] = useState(false);
useEffect(() => {
let current = true;
Promise.all([
fetchValueSet(valueSetUrls.ombRaceCategory),
fetchValueSet(valueSetUrls.detailedRace),
fetchValueSet(valueSetUrls.ombEthnicityCategory),
fetchValueSet(valueSetUrls.detailedEthnicity),
fetchValueSet(valueSetUrls.tribalEntityUS),
])
.then(([races, detailedRaces, ethnicities, detailedEthnicities, tribes]) => {
if (!current) return;
setOptions({
races: sortByDisplay(races),
detailedRaces: sortByDisplay(detailedRaces),
ethnicities: sortByDisplay(ethnicities),
detailedEthnicities: sortByDisplay(detailedEthnicities),
tribes: sortByDisplay(tribes),
});
})
.catch(() => {
if (current) setOptionsFailed(true);
});
return () => {
current = false;
};
}, [fetchValueSet, valueSetUrls]);
const picker = (
key: keyof Omit,
label: string,
list: IValueSet[],
placeholder: string,
onRemove?: (next: IValueSet[]) => Partial,
single = false,
) => (
onChange({ ...value, [key]: single ? toggle(item, value[key]).slice(-1) : toggle(item, value[key]) })}
/>
{
const next = value[key].filter((item) => item.code !== code);
onChange({ ...value, [key]: next, ...(onRemove?.(next) ?? {}) });
}}
/>
);
return (
<>
{optionsFailed && (
We could not load the race, ethnicity and tribe lists just now.
)}
{picker("languages", "Languages you speak", LANGUAGE_OPTIONS, "Select language")}
{afterLanguages}
{picker("races", "Race", options.races, "Select race", (next) => (next.length ? {} : { detailedRaces: [] }))}
{value.races.length > 0 && picker("detailedRaces", "More detail, if you want to give it", options.detailedRaces, "Select detail")}
{picker("ethnicities", "Ethnicity", options.ethnicities, "Select ethnicity", (next) => (next.length ? {} : { detailedEthnicities: [] }), true)}
{value.ethnicities.length > 0 &&
picker("detailedEthnicities", "More detail, if you want to give it", options.detailedEthnicities, "Select detail")}
{picker("tribes", "Tribal affiliation", options.tribes, "Select tribal affiliation", (next) => (next.length ? {} : { enrolled: undefined }), true)}
>
);
};
export default DemographicsEditor;