Skip to content

Commit

Permalink
customize query multiple accordion implementation pt 1 (#2564)
Browse files Browse the repository at this point in the history
* cleanup and setup

* customize query implementation pt 1

* [pre-commit.ci] auto fixes from pre-commit hooks

* add in beginning of new file

* add in new file

---------

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 Sep 17, 2024
1 parent a5b4169 commit 2d2c5ee
Show file tree
Hide file tree
Showing 17 changed files with 431 additions and 36 deletions.
2 changes: 2 additions & 0 deletions containers/tefca-viewer/src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,5 @@ export interface ValueSet {
medications: ValueSetItem[];
conditions: ValueSetItem[];
}

export type ValueSetType = keyof ValueSet;
8 changes: 4 additions & 4 deletions containers/tefca-viewer/src/app/database-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const filterValueSets = async (
) => {
// Assign clinical code type based on desired filter
// Mapping is established in TCR, so follow that convention
let valuesetFilters;
let valuesetFilters: string[];
if (type == "labs") {
valuesetFilters = ["ostc", "lotc", "lrtc"];
} else if (type == "medications") {
Expand All @@ -81,10 +81,10 @@ export const filterValueSets = async (
};

/**
* Helper function that transforms a set of database rows into a list of
* ValueSet item structs for display on the CustomizeQuery page.
* Helper function that transforms and groups a set of database rows into a list of
* ValueSet items grouped by author and code_system for display on the CustomizeQuery page.
* @param rows The rows returned from the DB.
* @returns A list of ValueSetItems constructed from the DB rows.
* @returns A list of ValueSetItems grouped by author and system.
*/
export const mapQueryRowsToValueSetItems = async (rows: QueryResultRow[]) => {
const vsItems = rows.map((r) => {
Expand Down
8 changes: 7 additions & 1 deletion containers/tefca-viewer/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Button,
} from "@trussworks/react-uswds";
import { useRouter } from "next/navigation";
import Image from "next/image";

/**
* The landing page for the TEFCA Viewer.
Expand Down Expand Up @@ -33,7 +34,12 @@ export default function LandingPage() {
TEFCA, giving you access to more complete and timely data.
</h2>
</div>
<img src="/tefca-viewer/tefca-graphic.svg" />
<Image
alt="Graphic illustrating what TEFCA is"
src="/tefca-viewer/tefca-graphic.svg"
width={250}
height={300}
/>
</div>
</div>
<div className="home">
Expand Down
10 changes: 4 additions & 6 deletions containers/tefca-viewer/src/app/query-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,9 @@ async function patientQuery(
// Check for errors
if (response.status !== 200) {
console.error(
`Patient search failed. Status: ${
response.status
} \n Body: ${response.text} \n Headers: ${JSON.stringify(
response.headers.raw(),
)}`,
`Patient search failed. Status: ${response.status} \n Body: ${
response.text
} \n Headers: ${JSON.stringify(response.headers.raw())}`,
);
}
queryResponse = await parseFhirSearch(response, queryResponse);
Expand Down Expand Up @@ -274,7 +272,7 @@ export async function createBundle(
entry: [],
};

Object.entries(queryResponse).forEach(([key, resources]) => {
Object.entries(queryResponse).forEach(([_, resources]) => {
if (Array.isArray(resources)) {
resources.forEach((resource) => {
bundle.entry?.push({ resource });
Expand Down
17 changes: 6 additions & 11 deletions containers/tefca-viewer/src/app/query/components/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
} from "@trussworks/react-uswds";
import { AccordionItemProps } from "@trussworks/react-uswds/lib/components/Accordion/Accordion";

type AccordionProps = {
title: string;
export type AccordionProps = {
title: string | React.ReactNode;
content: string | React.ReactNode;
expanded?: boolean;
id: string;
Expand Down Expand Up @@ -41,18 +41,13 @@ const Accordion: React.FC<AccordionProps> = ({
containerClassName,
accordionClassName,
}) => {
const accordionItem: AccordionItemProps = {
title,
content,
id,
expanded,
headingLevel,
handleToggle,
};
const accordionItem: AccordionItemProps[] = [
{ title, content, id, expanded, headingLevel, handleToggle },
];
return (
<div className={containerClassName}>
<TrussAccordion
items={[accordionItem]}
items={accordionItem}
multiselectable={true}
className={accordionClassName}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { filterValueSets } from "@/app/database-service";
import { UseCaseQueryResponse } from "@/app/query-service";
import LoadingView from "./LoadingView";
import { showRedirectConfirmation } from "./RedirectionToast";
import "./customizeQuery.css";

interface CustomizeQueryProps {
useCaseQueryResponse: UseCaseQueryResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Demographics: React.FC<DemographicsProps> = ({ patient }) => {

return (
<div>
{demographicData.map((item, index) => (
{demographicData.map((item) => (
<DataDisplay item={item} key={item.title} />
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const MultiplePatientSearchResults: React.FC<
</tbody>
</Table>
<h3>Not seeing what you are looking for?</h3>
<a href="#" onClick={() => goBack()}>
<a href="#" className="back-link" onClick={() => goBack()}>
Return to patient search
</a>
</div>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Icon } from "@trussworks/react-uswds";
import styles from "./customizeQuery.module.css";
import { GroupedValueSet } from "./customizeQueryUtils";

type CustomizeQueryAccordionBodyProps = {
group: GroupedValueSet;
toggleInclude: (groupIndex: string, itemIndex: number) => void;
groupIndex: string;
};

/**
* Styling component to render the body table for the customize query components
* @param param0 - props for rendering
* @param param0.group - Matched concept associated with the query that
* contains valuesets to filter query on
* @param param0.toggleInclude - Listener event to handle a valueset inclusion/
* exclusion check
* @param param0.groupIndex - Index corresponding to group
* @returns JSX Fragment for the accordion body
*/
const CustomizeQueryAccordionBody: React.FC<
CustomizeQueryAccordionBodyProps
> = ({ group, toggleInclude, groupIndex }) => {
return (
<div className={`${styles.customizeQueryAccordion__body}`}>
<div className={`${styles.customizeQueryGridContainer}`}>
<div className={`${styles.customizeQueryGridHeader} margin-top-10`}>
<div className={`${styles.accordionTableHeader}`}>Include</div>
<div className={`${styles.accordionTableHeader}`}>Code</div>
<div className={`${styles.accordionTableHeader}`}>Display</div>
</div>
<div className="display-flex flex-column">
{group.items.map((item, index) => (
<div
className={`${styles.customizeQueryGridRow} ${styles.customizeQueryStripedRow}`}
key={item.code}
>
<div
className={`margin-4 ${styles.customizeQueryCheckbox} ${styles.customizeQueryCheckbox} ${styles.hideCheckboxLabel}`}
onClick={(e) => {
e.stopPropagation();
toggleInclude(groupIndex, index);
}}
>
{item.include && (
<Icon.Check
className="usa-icon"
style={{ backgroundColor: "white" }}
size={4}
color="#005EA2"
/>
)}
</div>
<div>{item.code}</div>
<div>{item.display}</div>
</div>
))}
</div>
</div>
</div>
);
};

export default CustomizeQueryAccordionBody;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Icon } from "@trussworks/react-uswds";
import styles from "./customizeQuery.module.css";
import { GroupedValueSet } from "./customizeQueryUtils";

type CustomizeQueryAccordionProps = {
selectedCount: number;
handleSelectAllChange: (groupIndex: string, checked: boolean) => void;
groupIndex: string;
group: GroupedValueSet;
};

/**
* Rendering component for customize query header
* @param param0 - props for rendering
* @param param0.selectedCount - stateful tally of the number of selected valuesets
* @param param0.handleSelectAllChange
* Listner function to include all valuesets when checkbox is selected
* @param param0.groupIndex - index corresponding to group
* @param param0.group - matched concept containing all rendered valuesets
* @returns A component that renders the customization query body
*/
const CustomizeQueryAccordionHeader: React.FC<CustomizeQueryAccordionProps> = ({
selectedCount,
handleSelectAllChange,
groupIndex,
group,
}) => {
return (
<div className="accordion-header display-flex flex-no-wrap flex-align-start customize-query-header">
<div
id="select-all"
className={`hide-checkbox-label ${styles.customizeQueryCheckbox}`}
onClick={(e) => {
e.stopPropagation();
handleSelectAllChange(
groupIndex,
selectedCount !== group.items.length,
);
}}
>
{selectedCount === group.items.length && (
<Icon.Check
className="usa-icon bg-base-lightest"
size={4}
color="#565C65"
/>
)}
{selectedCount > 0 && selectedCount < group.items.length && (
<Icon.Remove
className="usa-icon bg-base-lightest"
size={4}
color="#565C65"
/>
)}
</div>
<div>
{`${group.valueSetName}`}

<span className="accordion-subtitle margin-top-2">
<strong>Author:</strong> {group.author}{" "}
<strong style={{ marginLeft: "20px" }}>System:</strong> {group.system}
</span>
</div>
<span className="margin-left-auto">{`${selectedCount} selected`}</span>
</div>
);
};

export default CustomizeQueryAccordionHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { ValueSetType } from "@/app/constants";
import styles from "./customizeQuery.module.css";

type CustomizeQueryNavProps = {
activeTab: string;
handleTabChange: (tabName: ValueSetType) => void;
handleSelectAllForTab: (checked: boolean) => void;
};

/**
* Nav component for customize query page
* @param param0 - props for rendering
* @param param0.handleTabChange - listener event for tab selection
* @param param0.activeTab - currently active tab
* @param param0.handleSelectAllForTab - Listener function to grab all the
* returned labs when the select all button is hit
* @returns Nav component for the customize query page
*/
const CustomizeQueryNav: React.FC<CustomizeQueryNavProps> = ({
handleTabChange,
activeTab,
handleSelectAllForTab,
}) => {
return (
<>
<nav className={`${styles.usaNav} ${styles.customizeQueryNav}`}>
<ul className="usa-sidenav">
<li className={`usa-sidenav_item`}>
<a
href="#labs"
className={`${
activeTab === "labs" ? `${styles.currentTab}` : ""
}`}
onClick={() => handleTabChange("labs")}
>
Labs
</a>
</li>
<li className={`usa-sidenav_item`}>
<a
className={`${
activeTab === "medications" ? `${styles.currentTab}` : ""
}`}
href="#medications"
onClick={() => handleTabChange("medications")}
>
Medications
</a>
</li>
<li className={`usa-sidenav_item`}>
<a
className={`${
activeTab === "conditions" ? `${styles.currentTab}` : ""
}`}
href="#conditions"
onClick={() => handleTabChange("conditions")}
>
Conditions
</a>
</li>
</ul>
</nav>

<ul className="usa-nav__primary usa-accordion"></ul>
<hr className="custom-hr"></hr>
<a
href="#"
type="button"
className="include-all-link"
onClick={(e) => {
e.preventDefault();
handleSelectAllForTab(true);
}}
>
Include all {activeTab}
</a>
</>
);
};

export default CustomizeQueryNav;
Loading

0 comments on commit 2d2c5ee

Please sign in to comment.