diff --git a/query-connector/src/app/query/components/CustomizeQuery.tsx b/query-connector/src/app/query/components/CustomizeQuery.tsx index 9a98ad641..e52ba704f 100644 --- a/query-connector/src/app/query/components/CustomizeQuery.tsx +++ b/query-connector/src/app/query/components/CustomizeQuery.tsx @@ -200,10 +200,10 @@ const CustomizeQuery: React.FC = ({

Customize query

-

+

Query: {demoQueryValToLabelMap[queryType]}

-

+

{countLabs} labs found, {countMedications} medications found,{" "} {countConditions} conditions found.

diff --git a/query-connector/src/app/query/components/backLink/Backlink.tsx b/query-connector/src/app/query/components/backLink/Backlink.tsx index 51d798b30..573e7ce67 100644 --- a/query-connector/src/app/query/components/backLink/Backlink.tsx +++ b/query-connector/src/app/query/components/backLink/Backlink.tsx @@ -17,7 +17,7 @@ const Backlink: React.FC = ({ onClick, label }) => { {" "} diff --git a/query-connector/src/app/query/designSystem/SiteAlert.tsx b/query-connector/src/app/query/designSystem/SiteAlert.tsx index 4fa810e01..50c71bfa4 100644 --- a/query-connector/src/app/query/designSystem/SiteAlert.tsx +++ b/query-connector/src/app/query/designSystem/SiteAlert.tsx @@ -29,7 +29,7 @@ const piiDisclaimer = ( ); type SiteAlertProps = { - page: Mode; + page?: Mode; }; const PageModeToSiteAlertMap: { [page in Mode]?: React.ReactNode } = { @@ -49,7 +49,7 @@ const PageModeToSiteAlertMap: { [page in Mode]?: React.ReactNode } = { const SiteAlert: React.FC = ({ page }) => { return ( - {PageModeToSiteAlertMap[page]}{" "} + {page ? PageModeToSiteAlertMap[page] : piiDisclaimer} ); }; diff --git a/query-connector/src/app/query/designSystem/searchField/searchField.module.scss b/query-connector/src/app/query/designSystem/searchField/searchField.module.scss index 80ff54df2..556e5348f 100644 --- a/query-connector/src/app/query/designSystem/searchField/searchField.module.scss +++ b/query-connector/src/app/query/designSystem/searchField/searchField.module.scss @@ -9,4 +9,5 @@ background: url("../../../../styles/assets/search.svg") center / contain no-repeat; background-position: 12px 12px; + float: none; } 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..782925ece --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionColumnDisplay.tsx @@ -0,0 +1,114 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { + CategoryNameToConditionOptionMap, + filterSearchByCategoryAndCondition, +} from "../utils"; +import styles from "./buildfromTemplate.module.scss"; +import ConditionOption from "./ConditionOption"; +import classNames from "classnames"; + +type ConditionColumnDisplayProps = { + fetchedConditions: CategoryNameToConditionOptionMap; + 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 - state function that updates the include / + * exclude of the queryset + * @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..6721d67d4 --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/ConditionOption.tsx @@ -0,0 +1,37 @@ +import Checkbox from "../../query/designSystem/checkbox/Checkbox"; +import { formatDiseaseDisplay } from "../utils"; +import styles from "./buildfromTemplate.module.scss"; +type ConditionOptionProps = { + conditionId: string; + conditionName: string; + handleConditionSelection: (conditionId: string) => void; +}; + +/** + * Display component for a condition on the query building page + * @param root0 - params + * @param root0.conditionId - ID of the condition to reference + * @param root0.conditionName - name of condition to display + * @param root0.handleConditionSelection - listner function for checkbox + * selection + * @returns A component for display to redner on the query building page + */ +const ConditionOption: React.FC = ({ + conditionId, + conditionName, + handleConditionSelection, +}) => { + return ( +
+ { + handleConditionSelection(conditionId); + }} + id={conditionId} + label={formatDiseaseDisplay(conditionName)} + /> +
+ ); +}; + +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..025183df9 --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/buildfromTemplate.module.scss @@ -0,0 +1,45 @@ +@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; +} + +#conditionTemplateSearch:first-child { + flex: 1; +} + +.categoryHeading { + text-transform: uppercase; + color: $gray-500; + font-weight: 700; + font-size: 1rem; + margin-bottom: 1rem; + margin-top: 0; +} + +.displayCol:first-of-type { + border-right: 1px solid $base-lighter; +} + +.displayCol:nth-of-type(2) { + padding-left: 2rem; +} +.categoryOption { + margin: 2rem 0; +} +.categoryOption:last-of-type { + margin-bottom: 3rem; +} 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..e4c64911b --- /dev/null +++ b/query-connector/src/app/queryBuilding/buildFromTemplates/page.tsx @@ -0,0 +1,121 @@ +"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 { + CategoryNameToConditionOptionMap, + mapFetchedDataToFrontendStructure, +} from "../utils"; +import ConditionColumnDisplay from "./ConditionColumnDisplay"; +import SearchField from "@/app/query/designSystem/searchField/SearchField"; +import SiteAlert from "@/app/query/designSystem/SiteAlert"; + +/** + * 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 { categoryToConditionArrayMap } = await getConditionsData(); + + if (isSubscribed) { + setFetchedConditions( + mapFetchedDataToFrontendStructure(categoryToConditionArrayMap), + ); + } + } + + 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 && ( + + )} +
+
+
+ + ); +} diff --git a/query-connector/src/app/queryBuilding/emptyState/EmptyQueriesDisplay.tsx b/query-connector/src/app/queryBuilding/emptyState/EmptyQueriesDisplay.tsx index 28fde1c51..42ce3aba6 100644 --- a/query-connector/src/app/queryBuilding/emptyState/EmptyQueriesDisplay.tsx +++ b/query-connector/src/app/queryBuilding/emptyState/EmptyQueriesDisplay.tsx @@ -31,7 +31,7 @@ export const EmptyQueriesDisplay: React.FC = () => {