Skip to content

Commit

Permalink
condition template selection page (#119)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
fzhao99 and pre-commit-ci[bot] authored Nov 8, 2024
1 parent e527aed commit c10bcfe
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 7 deletions.
4 changes: 2 additions & 2 deletions query-connector/src/app/query/components/CustomizeQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,10 @@ const CustomizeQuery: React.FC<CustomizeQueryProps> = ({
</div>
<LoadingView loading={!useCaseQueryResponse} />
<h1 className="page-title margin-bottom-05-important">Customize query</h1>
<h2 className="page-explainer margin-x-0-important">
<h2 className="page-explainer margin-y-0-important">
Query: {demoQueryValToLabelMap[queryType]}
</h2>
<h3 className="margin-x-0-important font-sans-sm text-light padding-bottom-0 padding-top-05">
<h3 className="margin-y-0-important font-sans-sm text-light padding-bottom-0 padding-top-05">
{countLabs} labs found, {countMedications} medications found,{" "}
{countConditions} conditions found.
</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Backlink: React.FC<BacklinkProps> = ({ onClick, label }) => {
<a
href="#"
onClick={onClick}
className="back-link unchanged-color-on-visit"
className="back-link unchanged-color-on-visit text-no-underline"
aria-label="Back arrow indicating ability to navigate back a page if clicked"
>
<Icon.ArrowBack aria-label="Arrow point left indicating return to previous step" />{" "}
Expand Down
4 changes: 2 additions & 2 deletions query-connector/src/app/query/designSystem/SiteAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const piiDisclaimer = (
);

type SiteAlertProps = {
page: Mode;
page?: Mode;
};

const PageModeToSiteAlertMap: { [page in Mode]?: React.ReactNode } = {
Expand All @@ -49,7 +49,7 @@ const PageModeToSiteAlertMap: { [page in Mode]?: React.ReactNode } = {
const SiteAlert: React.FC<SiteAlertProps> = ({ page }) => {
return (
<Alert type="info" headingLevel="h4" slim className="custom-alert">
{PageModeToSiteAlertMap[page]}{" "}
{page ? PageModeToSiteAlertMap[page] : piiDisclaimer}
</Alert>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
background: url("../../../../styles/assets/search.svg") center / contain
no-repeat;
background-position: 12px 12px;
float: none;
}
Original file line number Diff line number Diff line change
@@ -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<CategoryNameToConditionOptionMap | undefined>
>;
};
/**
* 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<ConditionColumnDisplayProps> = ({
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 (
<div className="grid-container ">
<div className="grid-row grid-gap">
{colsToDisplay.map((colsToDisplay, i) => {
return (
<div
className={classNames(styles.displayCol, "grid-col")}
key={`col-${i}`}
>
{colsToDisplay.map(([category, arr]) => {
const handleConditionSelection = (conditionId: string) => {
toggleFetchedConditionSelection(category, conditionId);
};
return (
<div key={category}>
<h3 className={styles.categoryHeading}>{category}</h3>
{Object.entries(arr).map(
([conditionId, conditionNameAndInclude]) => {
return (
<ConditionOption
key={conditionId}
conditionId={conditionId}
conditionName={conditionNameAndInclude.name}
handleConditionSelection={handleConditionSelection}
/>
);
},
)}
</div>
);
})}
</div>
);
})}
</div>
</div>
);
};

export default ConditionColumnDisplay;
Original file line number Diff line number Diff line change
@@ -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<ConditionOptionProps> = ({
conditionId,
conditionName,
handleConditionSelection,
}) => {
return (
<div className={styles.categoryOption}>
<Checkbox
onClick={() => {
handleConditionSelection(conditionId);
}}
id={conditionId}
label={formatDiseaseDisplay(conditionName)}
/>
</div>
);
};

export default ConditionOption;
Original file line number Diff line number Diff line change
@@ -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;
}
121 changes: 121 additions & 0 deletions query-connector/src/app/queryBuilding/buildFromTemplates/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string>();
const [searchFilter, setSearchFilter] = useState<string>();
const [fetchedConditions, setFetchedConditions] =
useState<CategoryNameToConditionOptionMap>();

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 (
<>
<SiteAlert />
<div className="main-container__wide">
<Backlink
onClick={() => {
router.push("/queryBuilding");
}}
label={"Back to My queries"}
/>
<h1 className={styles.queryTitle}>Custom query</h1>
<Label htmlFor="queryNameInput" className="margin-top-0-important">
Query name
</Label>
<TextInput
id="queryNameInput"
name="queryNameInput"
type="text"
className="maxw-mobile"
onChange={(event) => {
setQueryName(event.target.value);
}}
/>

<div
className={classNames(
"bg-gray-5 margin-top-4 ",
styles.queryTemplateContainer,
)}
>
<div className="display-flex flex-justify flex-align-end margin-bottom-3 width-full">
<h2 className="margin-y-0-important">Select condition(s)</h2>
<Button
className="margin-0"
type={"button"}
disabled={!noTemplateSelected}
>
Create query
</Button>
</div>
<div className={classNames(styles.querySelectionForm, "radius-lg")}>
<SearchField
id="conditionTemplateSearch"
placeholder="Search conditions"
className={classNames(
"maxw-mobile margin-x-auto margin-top-0 margin-bottom-4",
)}
onChange={(e) => {
e.preventDefault();
setSearchFilter(e.target.value);
}}
/>

{fetchedConditions && (
<ConditionColumnDisplay
fetchedConditions={fetchedConditions}
searchFilter={searchFilter}
setFetchedConditions={setFetchedConditions}
/>
)}
</div>
</div>
</div>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const EmptyQueriesDisplay: React.FC = () => {
</h2>

<Button
// onClick={() => router.push(`/queryBuilding/buildFromTemplates`)}
onClick={() => router.push(`/queryBuilding/buildFromTemplates`)}
className={styles.createQueryButton}
type={"button"}
>
Expand Down
2 changes: 1 addition & 1 deletion query-connector/src/styles/custom-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ h1 {
margin-bottom: 0 !important;
}

.margin-x-0-important {
.margin-y-0-important {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
Expand Down

0 comments on commit c10bcfe

Please sign in to comment.