Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experience/16759/code mapping results page #17436

Merged
merged 9 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"scripts": {
"postinstall": "scripts/postinstall.sh",
"dev": "vite",
"dev:disable:overlays": "DISABLE_OVERLAYS=1 vite",
"preview": "vite preview --mode preview",
"preview:csp": "yarn run preview --mode csp",
"preview:test": "yarn run preview --mode test",
Expand Down
86 changes: 29 additions & 57 deletions frontend-react/src/components/CodeMapping/CodeMappingForm.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
import { Button, ButtonGroup, FileInput } from "@trussworks/react-uswds";
import { FormEventHandler, MouseEventHandler, type PropsWithChildren, useCallback } from "react";
import CodeMappingResults from "./CodeMappingResults";
import { FormEventHandler, MouseEventHandler, useCallback } from "react";
import site from "../../content/site.json";
import useCodeMappingFormSubmit from "../../hooks/api/UseCodeMappingFormSubmit/UseCodeMappingFormSubmit";
import Spinner from "../Spinner";
import { USLink } from "../USLink";

export type CodeMappingFormProps = PropsWithChildren;
interface CodeMappingFormProps {
onSubmitHandler: FormEventHandler<HTMLFormElement>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rename to onSubmit to follow event handler property names on dom elements

setFileName: (fileName: string) => void;
}

const CodeMappingForm = (props: CodeMappingFormProps) => {
const { data, isPending, mutate } = useCodeMappingFormSubmit();
/**
* TODO: Implement submit handler
*/
const onSubmitHandler = useCallback<FormEventHandler<HTMLFormElement>>(
(ev) => {
ev.preventDefault();
mutate();
return false;
},
[mutate],
);
const CodeMappingForm = ({ onSubmitHandler, setFileName }: CodeMappingFormProps) => {
const onBackHandler = useCallback<MouseEventHandler>((_ev) => {
window.history.back();
}, []);
const onCancelHandler = useCallback<MouseEventHandler>((_ev) => {
// Don't have a proper mechanism to cancel in-flight requests so refresh page
window.location.reload();
}, []);

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (!files || files.length === 0) return;

// Take the first file name
setFileName(files[0].name);
};

return (
<>
Expand All @@ -40,41 +31,22 @@ const CodeMappingForm = (props: CodeMappingFormProps) => {
<a href={site.assets.codeMapTemplate.path}>our template</a> to format your result and organism codes to
LOINC or SNOMED. Note: Local codes cannot be automatically mapped.
</p>
{data && <CodeMappingResults />}
{isPending && (
<>
<Spinner />
<p className="text-center">
Checking your file for any unmapped codes that will <br /> prevent data from being reported
successfully
</p>
<Button type={"button"} outline onClick={onCancelHandler}>
Cancel

<form onSubmit={onSubmitHandler}>
<label className="usa-label" htmlFor="file-input-specific">
Upload CSV file
</label>
<span className="usa-hint" id="file-input-specific-hint">
Make sure your file has a .csv extension
</span>
<FileInput id={""} name={"file"} className="maxw-full" accept=".csv" onChange={handleFileChange} />
<ButtonGroup className="margin-top-5">
<Button type={"button"} outline onClick={onBackHandler}>
Back
</Button>
</>
)}
{!data && !isPending && (
<form onSubmit={onSubmitHandler}>
<label className="usa-label" htmlFor="file-input-specific">
Upload CSV file
</label>
<span className="usa-hint" id="file-input-specific-hint">
Make sure your file has a .csv extension
</span>
<FileInput id={""} name={"file"} className="maxw-full" accept=".csv" />
<ButtonGroup className="margin-top-5">
<Button type={"button"} outline onClick={onBackHandler}>
Back
</Button>
<Button type={"submit"}>Submit</Button>
</ButtonGroup>
</form>
)}
{props.children}
<p className="margin-top-9">
Questions or feedback? Please email{" "}
<USLink href="mailto:[email protected]">[email protected]</USLink>
</p>
<Button type={"submit"}>Submit</Button>
</ButtonGroup>
</form>
</>
);
};
Expand Down
63 changes: 57 additions & 6 deletions frontend-react/src/components/CodeMapping/CodeMappingResults.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,61 @@
import type { PropsWithChildren } from "react";
import { Button } from "@trussworks/react-uswds";
import { CodeMapData } from "../../hooks/api/UseCodeMappingFormSubmit/UseCodeMappingFormSubmit";
import { Alert, Table } from "../../shared";

export type CodeMappingResultsProps = PropsWithChildren;
interface CodeMappingResultsProps {
fileName: string;
data: CodeMapData[];
initialStepHandler: () => void;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe rename to onReset, if that's what is practically happening on button usage


/**
* TODO: Implement result page
*/
const CodeMappingResults = (props: CodeMappingResultsProps) => <>TODO {props.children}</>;
const CodeMappingResults = ({ fileName, data, initialStepHandler }: CodeMappingResultsProps) => {
const unmappedData = data.filter((item: CodeMapData) => item.mapped === "N");
const areCodesMapped = unmappedData.length === 0;
const rowData = unmappedData.map((dataRow) => [
{
columnKey: "Code",
columnHeader: "Code",
content: dataRow["test code"],
},
{
columnKey: "Name",
columnHeader: "Name",
content: dataRow["test description"],
},
{
columnKey: "Coding system",
columnHeader: "Coding system",
content: dataRow["coding system"],
},
]);
return (
<>
<h2 className="margin-bottom-0">
<span className="text-normal font-body-md text-base margin-bottom-0">
<p>File Name</p>
<p>{fileName}</p>
</span>
</h2>
<div className="margin-top-2">
{areCodesMapped ? (
<Alert type={"success"} heading={"All codes are mapped"}></Alert>
) : (
<Alert type={"error"} heading={"Your file contains unmapped codes "}>
Review unmapped codes for any user error, such as a typo. If the unmapped codes are accurate,
download the table and send the file to your onboarding engineer or [email protected]. Our
team will support any remaining mapping needed.
Copy link
Collaborator

@jillian-hammer jillian-hammer Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing hyperlinks:

  • "download the table" should be linked to download the CSV. ok if this is just a placeholder link as a reminder to implement it once the CSV functionality is incorporated.
  • "[email protected]" should be hyperlinked to mailto:[email protected]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked via Slack: we have a discrete ticket for downloading the CSV and will tackle this then.

</Alert>
)}
</div>
<h3>Unmapped codes</h3>
<div className="padding-top-2 padding-bottom-4 padding-x-3 bg-gray-5 margin-bottom-4">
<Table gray borderless rowData={rowData} />
</div>
<Button type="button" onClick={initialStepHandler}>
Test another file
</Button>
</>
);
};

export default CodeMappingResults;
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import { useMutation } from "@tanstack/react-query";

/**
* TODO: Implement hook
*/
export interface CodeMapData {
"test code": string;
"test description": string;
"coding system": string;
mapped: string;
}

const useCodeMappingFormSubmit = () => {
const fn = async () => {
// Fake request until implementation
// Simulate network request
await new Promise((resolve) => setTimeout(resolve, 2500));

// Return sample JSON
return [
{
"test code": "97097-0",
"test description":
"SARS-CoV-2 (COVID-19) Ag [Presence] in Upper respiratory specimen by Rapid immunoassay",
"coding system": "LOINC",
mapped: "Y",
},
{
"test code": "80382-5",
"test description":
"Influenza virus A Ag [Presence] in Upper respiratory specimen by Rapid immunoassay",
"coding system": "LOINC",
mapped: "Y",
},
{
"test code": "12345",
"test description": "Flu B",
"coding system": "LOCAL",
mapped: "N",
},
];
};
return useMutation({
mutationFn: fn,
});

return useMutation({ mutationFn: fn });
};

export default useCodeMappingFormSubmit;
74 changes: 68 additions & 6 deletions frontend-react/src/pages/onboarding/CodeMappingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,76 @@
import type { PropsWithChildren } from "react";
import { Button, GridContainer } from "@trussworks/react-uswds";
import { FormEventHandler, MouseEventHandler, useCallback, useState } from "react";
import { Helmet } from "react-helmet-async";
import CodeMappingForm from "../../components/CodeMapping/CodeMappingForm";
import CodeMappingResults from "../../components/CodeMapping/CodeMappingResults";
import Spinner from "../../components/Spinner";
import { USLink } from "../../components/USLink";
import useCodeMappingFormSubmit from "../../hooks/api/UseCodeMappingFormSubmit/UseCodeMappingFormSubmit";

export type CodeMappingPageProps = PropsWithChildren;
enum CodeMappingSteps {
StepOne = "CodeMapFileSelect",
StepTwo = "CodeMapResult",
}

const CodeMappingPage = (props: CodeMappingPageProps) => {
const CodeMappingPage = () => {
const { data, isPending, mutate } = useCodeMappingFormSubmit();
const [currentCodeMapStep, setCurrentCodeMapStep] = useState<CodeMappingSteps>(CodeMappingSteps.StepOne);
const [fileName, setFileName] = useState("");
const onCancelHandler = useCallback<MouseEventHandler>((_ev) => {
// Don't have a proper mechanism to cancel in-flight requests so refresh page
window.location.reload();
}, []);
const initialStepHandler = () => {
setCurrentCodeMapStep(CodeMappingSteps.StepOne);
};
const onSubmitHandler = useCallback<FormEventHandler<HTMLFormElement>>(
(ev) => {
ev.preventDefault();
mutate();
setCurrentCodeMapStep(CodeMappingSteps.StepTwo);
return false;
},
[mutate],
);
return (
<>
<h1>Code mapping tool</h1>
<CodeMappingForm />
{props.children}
<Helmet>
<title>Code mapping tool - ReportStream</title>
</Helmet>

<GridContainer>
<h1>Code mapping tool</h1>
{isPending ? (
<>
<Spinner />
<p className="text-center">
Checking your file for any unmapped codes that will <br /> prevent data from being reported
successfully
</p>
<Button type={"button"} outline onClick={onCancelHandler}>
Cancel
</Button>
</>
) : (
<>
{currentCodeMapStep === CodeMappingSteps.StepOne && (
<CodeMappingForm onSubmitHandler={onSubmitHandler} setFileName={setFileName} />
)}
{currentCodeMapStep === CodeMappingSteps.StepTwo && (
<CodeMappingResults
fileName={fileName}
data={data ?? []}
initialStepHandler={initialStepHandler}
/>
)}
</>
)}

<p className="margin-top-9">
Questions or feedback? Please email{" "}
<USLink href="mailto:[email protected]">[email protected]</USLink>
</p>
</GridContainer>
</>
);
};
Expand Down
8 changes: 8 additions & 0 deletions frontend-react/src/shared/Table/Table.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@
font-size: 16px;
margin: 0;
}

.gray-table {
background-color: color("gray-5");

&.usa-table td {
background-color: color("gray-5");
}
}
}

&__StickyHeader {
Expand Down
Loading
Loading