diff --git a/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionColumnDisplay.tsx b/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionColumnDisplay.tsx new file mode 100644 index 000000000..68740c678 --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionColumnDisplay.tsx @@ -0,0 +1,113 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { + CategoryNameToConditionDetailsMap, + filterSearchByCategoryAndCondition, +} from "../utils"; +import styles from "./buildfromTemplate.module.scss"; +import ConditionOption from "./ConditionOption"; +import classNames from "classnames"; + +type ConditionColumnDisplayProps = { + fetchedConditions: CategoryNameToConditionDetailsMap; + searchFilter: string | undefined; + setFetchedConditions: Dispatch< + SetStateAction + >; +}; +/** + * Column display component for the query building page + * @param root0 - params + * @param root0.fetchedConditions - conditions queried from backend to display + * @param root0.searchFilter - filter grabbed from search field to filter fetched + * components against + * @param root0.setFetchedConditions + * @returns Conditions split out into two columns that will filter themselves + * at both the category and condition levels if a valid search filter is applied. + */ +export const ConditionColumnDisplay: React.FC = ({ + fetchedConditions, + searchFilter, + setFetchedConditions, +}) => { + const [conditionsToDisplay, setConditionsToDisplay] = + useState(fetchedConditions); + + useEffect(() => { + if (searchFilter === "") { + setConditionsToDisplay(fetchedConditions); + } + if (searchFilter) { + const filteredDisplay = filterSearchByCategoryAndCondition( + searchFilter, + fetchedConditions, + ); + setConditionsToDisplay(filteredDisplay); + } + }, [searchFilter]); + + function toggleFetchedConditionSelection( + category: string, + conditionId: string, + ) { + const prevFetch = structuredClone(fetchedConditions); + const prevValues = prevFetch[category][conditionId]; + prevFetch[category][conditionId] = { + name: prevValues.name, + include: !prevValues.include, + }; + setFetchedConditions(prevFetch); + } + + const columnOneEntries = Object.entries(conditionsToDisplay).filter( + (_, i) => i % 2 === 0, + ); + const columnTwoEntries = Object.entries(conditionsToDisplay).filter( + (_, i) => i % 2 === 1, + ); + + const colsToDisplay = [ + columnOneEntries, + columnTwoEntries, + // alphabetize by category + ].map((arr) => arr.sort((a, b) => (a[0] > b[0] ? 1 : -1))); + + return ( +
+
+ {colsToDisplay.map((colsToDisplay, i) => { + return ( +
+ {colsToDisplay.map(([category, arr]) => { + const handleConditionSelection = (conditionId: string) => { + toggleFetchedConditionSelection(category, conditionId); + }; + return ( +
+

{category}

+ {Object.entries(arr).map( + ([conditionId, conditionNameAndInclude]) => { + return ( + + ); + }, + )} +
+ ); + })} +
+ ); + })} +
+
+ ); +}; + +export default ConditionColumnDisplay; diff --git a/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionOption.tsx b/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionOption.tsx new file mode 100644 index 000000000..57c75b4eb --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionOption.tsx @@ -0,0 +1,38 @@ +import Checkbox from "../../query/designSystem/checkbox/Checkbox"; +import { ConditionDetails, formatDiseaseDisplay } from "../utils"; + +type ConditionOptionProps = { + conditionId: string; + conditionNameAndInclude: ConditionDetails; + handleConditionSelection: (conditionId: string) => void; +}; + +/** + * Display component for a condition on the query building page + * @param root0 - params + * @param root0.conditionIdToTemplateMap - the ID: Condition name map that needs to + * be displayed + * @param root0.conditionId + * @param root0.conditionNameAndInclude + * @param root0.handleConditionSelection + * @returns A component for display to redner on the query building page + */ +const ConditionOption: React.FC = ({ + conditionId, + conditionNameAndInclude, + handleConditionSelection, +}) => { + return ( +
+ { + handleConditionSelection(conditionId); + }} + id={conditionId} + label={formatDiseaseDisplay(conditionNameAndInclude.name)} + /> +
+ ); +}; + +export default ConditionOption; diff --git a/query-connector/src/app/queryBuilding/buildFromTemplates/buildfromTemplate.module.scss b/query-connector/src/app/queryBuilding/buildFromTemplates/buildfromTemplate.module.scss new file mode 100644 index 000000000..c9ae7c798 --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/buildfromTemplate.module.scss @@ -0,0 +1,46 @@ +@use "../../../styles/variables" as *; + +.queryTemplateContainer { + padding: 3.5rem 5rem; + border-radius: 4px; +} + +.querySelectionForm { + background-color: #fff; + padding: 2rem; +} + +.querySelectionFormSearch { + border: 1px solid #919191; + border-radius: 4px; + height: 1.5rem; + padding: 0.75rem; +} + +.querySelectionFormHeader { + width: 50rem; +} + +#conditionTemplateSearch:first-child { + flex: 1; +} + +.categoryHeading { + text-transform: uppercase; + color: $gray-500; + font-weight: 700; + font-size: 1rem; + margin-bottom: 1rem; +} + +.categoryHeading:not(first-of-type) { + margin-top: 2rem; +} + +.displayCol:first-of-type { + border-right: 1px solid $base-lighter; +} + +.displayCol:nth-of-type(2) { + padding-left: 2rem; +} diff --git a/query-connector/src/app/queryBuilding/buildFromTemplates/page.tsx b/query-connector/src/app/queryBuilding/buildFromTemplates/page.tsx new file mode 100644 index 000000000..a69e936dd --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/page.tsx @@ -0,0 +1,122 @@ +"use client"; + +import Backlink from "@/app/query/components/backLink/Backlink"; +import styles from "./buildfromTemplate.module.scss"; +import { useRouter } from "next/navigation"; +import { Button, Label, TextInput } from "@trussworks/react-uswds"; +import { useEffect, useState } from "react"; +import classNames from "classnames"; +import { getConditionsData } from "@/app/database-service"; +import { + CategoryNameToConditionDetailsMap, + mapFetchedDataToFrontendStructure, +} from "../utils"; +import ConditionColumnDisplay from "./ConditionColumnDisplay"; +import SearchField from "@/app/query/designSystem/searchField/SearchField"; + +/** + * The query building page + * @returns the component for the query building page + */ +export default function QueryTemplateSelection() { + const router = useRouter(); + const [queryName, setQueryName] = useState(); + const [searchFilter, setSearchFilter] = useState(); + const [fetchedConditions, setFetchedConditions] = + useState(); + + useEffect(() => { + let isSubscribed = true; + + async function fetchConditionsAndUpdateState() { + const { conditionCategoryToIdNameArrayMap } = await getConditionsData(); + + if (isSubscribed) { + setFetchedConditions( + mapFetchedDataToFrontendStructure(conditionCategoryToIdNameArrayMap), + ); + } + } + + fetchConditionsAndUpdateState().catch(console.error); + + return () => { + isSubscribed = false; + }; + }, []); + + const noTemplateSelected = + fetchedConditions && + Object.values(Object.values(fetchedConditions)) + .map((arr) => Object.values(arr).flatMap((e) => e.include)) + .flatMap((e) => e.some(Boolean)) + .some(Boolean); + + return ( +
+ { + router.push("/queryBuilding"); + }} + label={"Back to My Queries"} + /> +

Custom query

+ + { + setQueryName(event.target.value); + }} + /> + +
+
+

Select condition(s)

+ +
+
+ { + e.preventDefault(); + setSearchFilter(e.target.value); + }} + /> + + {fetchedConditions && ( + + )} +
+
+
+ ); +}