diff --git a/services/app-api/forms/wp.json b/services/app-api/forms/wp.json index c74f77a03..e65ce82c8 100644 --- a/services/app-api/forms/wp.json +++ b/services/app-api/forms/wp.json @@ -4,7 +4,7 @@ "basePath": "/wp", "version": "WP_2023-08-21", "entities": { - "": { "required": true } + "targetPopulation": { "required": true } }, "routes": [ { @@ -23,20 +23,91 @@ { "name": "Transition Benchmarks", "path": "/wp/transition-benchmarks", - "pageType": "standard", + "pageType": "modalDrawer", + "entityType": "targetPopulation", "verbiage": { "intro": { - "exportSectionHeader": "", - "section": "", - "subsection": "", + "subsection": "Transition Benchmark Projections", "info": [ { - "type": "html", - "content": "" + "type": "span", + "content": "Provide the projected number of transitions for target populations during each quarter. This number includes institutional residents who are discharged from an institution to a qualified residence during the reporting period, enroll in MFP, and being using Medicaid home and community-based services (HCBS)." } ] }, - "exportSectionHeader": "" + "dashboardTitle": "Report projected number of transitions for each target population", + "addEntityButtonText": "Add other target population", + "editEntityButtonText": "Edit name", + "addEditModalAddTitle": "Add other target population", + "addEditModalEditTitle": "Edit other target population", + "deleteEntityButtonAltText": "Delete other target population", + "deleteModalTitle": "Are you sure you want to delete this target population?", + "deleteModalConfirmButtonText": "Yes, delete population", + "deleteModalWarning": "Are you sure you want to proceed? You will lose all information entered for this population in the Work Plan. The population will remain in previously submitted Semi-Annual Reports if applicable.", + "entityUnfinishedMessage": "Complete the remaining indicators for this access measure by entering details.", + "enterEntityDetailsButtonText": "Edit", + "reviewPdfHint": "To view Transition Benchmark Totals by target population and by quarter, click Review PDF and it will open a summary in a new tab.", + "drawerTitle": "Report transition benchmarks for ", + "drawerInfo": [ + { + "type": "span", + "content": "Please provide the projected number of transitions for [entity_name] during each quarter. This number includes institutional residents who are discharged from an institution to a qualified residence during the reporting period, enroll in MFP, and begin using Medicaid HCBS." + }, + { + "type": "p", + "content": "Complete all fields and select the Save & close button to save this section." + } + ] + }, + "modalForm": { + "id": "tb-modal", + "fields": [ + { + "id": "transitionBenchmarks_targetPopulationName", + "type": "text", + "validation": "text", + "props": { + "label": "Target population name", + "hint": "Specify an \"other\" target population applicable to your MFP demonstration project. Individuals reported under this population must align throughout MFP. (e.g., HIV/AIDS, brain injury)" + } + } + ] + }, + "drawerForm": { + "id": "tb-drawer", + "fields": [ + { + "id": "transitionBenchmarks_applicableToMfpDemonstration", + "type": "radio", + "validation": "radio", + "props": { + "label": "Is this target population applicable to your MFP demonstration?", + "hint": "Enter 0 for quarters with no projected transitions. Enter N/A for quarters you do not expect to report.", + "choices": [ + { + "id": "2UObIwERkSKEGVUU1g8E1v", + "label": "No" + }, + { + "id": "2UObIuHjl15upf6tLcgcWd", + "label": "Yes", + "children": [ + { + "id": "quarterlyProjections_1", + "type": "number", + "nested": true, + "parentFieldName": "transitionBenchmarks_applicableToMfpDemonstration", + "parentOptionId": "2UObIuHjl15upf6tLcgcWd", + "props": { + "label": "2023 Q4" + } + } + ] + } + ] + } + } + ] } }, { diff --git a/services/ui-src/src/assets/icons/icon_add.png b/services/ui-src/src/assets/icons/icon_add.png new file mode 100644 index 000000000..7465cbb3f Binary files /dev/null and b/services/ui-src/src/assets/icons/icon_add.png differ diff --git a/services/ui-src/src/components/app/AppRoutes.tsx b/services/ui-src/src/components/app/AppRoutes.tsx index 00bfc0355..59179b87c 100644 --- a/services/ui-src/src/components/app/AppRoutes.tsx +++ b/services/ui-src/src/components/app/AppRoutes.tsx @@ -14,7 +14,7 @@ import { // utils import { ScrollToTopComponent, useUserStore } from "utils"; // types -import { ReportType, ReportRoute } from "types"; +import { ReportRoute, ReportType } from "types"; export const AppRoutes = () => { const { userIsAdmin } = useUserStore().user ?? {}; @@ -51,6 +51,7 @@ export const AppRoutes = () => { pageType: "standard", }, ]; + // LaunchDarkly const wpReport = useFlags()?.wpReport; const sarReport = useFlags().sarReport; diff --git a/services/ui-src/src/components/drawers/Drawer.tsx b/services/ui-src/src/components/drawers/Drawer.tsx index 0d88f3d91..17fac1c7d 100644 --- a/services/ui-src/src/components/drawers/Drawer.tsx +++ b/services/ui-src/src/components/drawers/Drawer.tsx @@ -18,11 +18,13 @@ import { makeMediaQueryClasses, parseCustomHtml } from "utils"; export const Drawer = ({ verbiage, drawerDisclosure, + selectedEntity, children, ...props }: Props) => { const mqClasses = makeMediaQueryClasses(); const { isOpen, onClose } = drawerDisclosure; + return ( )} - {verbiage.drawerTitle} + + {verbiage.drawerTitle + selectedEntity} + {verbiage.drawerInfo && ( {parseCustomHtml(verbiage.drawerInfo)} @@ -71,6 +75,7 @@ interface Props { isOpen: boolean; onClose: Function; }; + selectedEntity?: string; [key: string]: any; } @@ -127,9 +132,12 @@ const sx = { }, infoTextBox: { marginTop: "2rem", - "p, span": { - color: "palette.gray", - fontSize: "16px", + fontSize: "md", + fontWeight: "light", + color: "palette.gray", + paddingBottom: "0", + p: { + marginTop: "1rem", }, a: { color: "palette.primary", diff --git a/services/ui-src/src/components/drawers/ReportDrawer.tsx b/services/ui-src/src/components/drawers/ReportDrawer.tsx index 1d503b74f..d37b896f8 100644 --- a/services/ui-src/src/components/drawers/ReportDrawer.tsx +++ b/services/ui-src/src/components/drawers/ReportDrawer.tsx @@ -36,6 +36,7 @@ export const ReportDrawer = ({ verbiage={verbiage} drawerDisclosure={drawerDisclosure} entityType={entityType} + selectedEntity={selectedEntity?.name} {...props} > {formFieldsExist ? ( diff --git a/services/ui-src/src/components/index.ts b/services/ui-src/src/components/index.ts index c4958add9..8191f9163 100644 --- a/services/ui-src/src/components/index.ts +++ b/services/ui-src/src/components/index.ts @@ -72,10 +72,11 @@ export { ReportPageWrapper } from "./reports/ReportPageWrapper"; export { ReportContext } from "./reports/ReportProvider"; export { DrawerReportPage } from "./reports/DrawerReportPage"; export { ModalDrawerReportPage } from "./reports/ModalDrawerReportPage"; - // statusing export { StatusTable } from "./statusing/StatusTable"; // tables +export { EntityRow } from "./tables/EntityRow"; +export { EntityStatusIcon } from "./tables/EntityStatusIcon"; export { Table } from "./tables/Table"; // widgets export { SpreadsheetWidget } from "./widgets/SpreadsheetWidget"; diff --git a/services/ui-src/src/components/menus/Sidebar.tsx b/services/ui-src/src/components/menus/Sidebar.tsx index 0aedf9355..98de35dbd 100644 --- a/services/ui-src/src/components/menus/Sidebar.tsx +++ b/services/ui-src/src/components/menus/Sidebar.tsx @@ -70,6 +70,15 @@ export const Sidebar = ({ isHidden }: SidebarProps) => { section={{ name: "placeholder", path: "/standard" }} level={1} /> + {/* Temporary Page Navigation */} + diff --git a/services/ui-src/src/components/reports/ModalDrawerReportPage.test.tsx b/services/ui-src/src/components/reports/ModalDrawerReportPage.test.tsx index 8556f6d5d..29665ae9d 100644 --- a/services/ui-src/src/components/reports/ModalDrawerReportPage.test.tsx +++ b/services/ui-src/src/components/reports/ModalDrawerReportPage.test.tsx @@ -23,7 +23,7 @@ const { addEntityButtonText, editEntityButtonText, enterEntityDetailsButtonText, - deleteModalConfirmButtonText, + // deleteModalConfirmButtonText, } = mockModalDrawerReportPageJson.verbiage; const modalDrawerReportPageComponentWithEntities = ( @@ -50,28 +50,18 @@ describe("Test ModalDrawerReportPage with entities", () => { await userEvent.click(addEntityButton); expect(screen.getByRole("dialog")).toBeVisible(); - const editButton = screen.getByText(editEntityButtonText); + const editButton = screen.getAllByText(editEntityButtonText)[0]; await userEvent.click(editButton); const closeButton = screen.getByText("Close"); await userEvent.click(closeButton); }); - test("ModalDrawerReportPage opens the delete modal on remove click", async () => { - const addEntityButton = screen.getByText(addEntityButtonText); - const removeButton = screen.getByTestId("delete-entity-button"); - await userEvent.click(removeButton); - // click delete in modal - const deleteButton = screen.getByText(deleteModalConfirmButtonText); - await userEvent.click(deleteButton); - - // verify that the field is removed - const inputBoxLabelAfterRemove = screen.queryAllByTestId("test-label"); - expect(inputBoxLabelAfterRemove).toHaveLength(0); - expect(addEntityButton).toBeVisible(); - }); + // TODO: test delete modal + functionality test("ModalDrawerReportPage opens the drawer on enter-details click", async () => { - const enterDetailsButton = screen.getByText(enterEntityDetailsButtonText); + const enterDetailsButton = screen.getAllByText( + enterEntityDetailsButtonText + )[0]; await userEvent.click(enterDetailsButton); expect(screen.getByRole("dialog")).toBeVisible(); }); diff --git a/services/ui-src/src/components/reports/ModalDrawerReportPage.tsx b/services/ui-src/src/components/reports/ModalDrawerReportPage.tsx index bf25ca1b6..61fbb9cf8 100644 --- a/services/ui-src/src/components/reports/ModalDrawerReportPage.tsx +++ b/services/ui-src/src/components/reports/ModalDrawerReportPage.tsx @@ -1,22 +1,36 @@ import { useState } from "react"; // components -import { Box, Button, Heading, useDisclosure } from "@chakra-ui/react"; +import { + Box, + Button, + Heading, + Image, + Text, + useDisclosure, +} from "@chakra-ui/react"; import { AddEditEntityModal, DeleteEntityModal, + EntityRow, + ReportDrawer, ReportPageFooter, ReportPageIntro, + Table, } from "components"; - +// assets +import addIcon from "assets/icons/icon_add.png"; +import searchIcon from "assets/icons/icon_search_blue.png"; // types import { EntityShape, EntityType, ModalDrawerReportPageShape } from "types"; -import { EntityCard } from "components/cards/EntityCard"; -import { ReportDrawer } from "components/drawers/ReportDrawer"; +// utils +import { parseCustomHtml } from "utils"; import { getFormattedEntityData } from "utils/reports/entities"; export const ModalDrawerReportPage = ({ route, validateOnRender }: Props) => { const { entityType, verbiage, modalForm, drawerForm: drawerFormJson } = route; + // const reportFieldDataEntities = report?.fieldData[entityType] || []; + const submitting = false; const [selectedEntity, setSelectedEntity] = useState( undefined @@ -24,8 +38,29 @@ export const ModalDrawerReportPage = ({ route, validateOnRender }: Props) => { const entities = [ { - id: "123", - name: "mock-entity", + id: "0", + name: "Older adults", + isOtherEntity: false, + }, + { + id: "1", + name: "Individuals with physical disabilities (PD)", + isOtherEntity: false, + }, + { + id: "2", + name: "Individuals with intellectual and developmental disabilities (I/DD)", + isOtherEntity: false, + }, + { + id: "3", + name: "Individuals with mental health and substance abuse disorders (MH/SUD)", + isOtherEntity: false, + }, + { + id: "4", + name: "Other: {entity}", + isOtherEntity: true, }, ]; @@ -86,38 +121,54 @@ export const ModalDrawerReportPage = ({ route, validateOnRender }: Props) => { drawerOnCloseHandler(); }; - const dashTitle = `${verbiage.dashboardTitle}${ - verbiage.countEntitiesInTitle ? ` ${entities.length}` : "" - }`; + const tableHeaders = { + headRow: ["", "", ""], + }; return ( {verbiage.intro && } - - {entities.length !== 0 && ( - - {dashTitle} - - )} - {entities.map((entity: EntityShape, entityIndex: number) => ( - - ))} + + {verbiage.dashboardTitle} + + + + {/* TODO: real entities */} + {entities.map((entity: EntityShape) => ( + + ))} +
+ +
+ + {parseCustomHtml(verbiage.reviewPdfHint)} + + +
+ {/* MODALS */} { onClose: closeDeleteEntityModal, }} /> + {/* DRAWER */} { validateOnRender={validateOnRender} data-testid="report-drawer" /> - {entities.length > 1 && ( - - )}
@@ -172,18 +216,18 @@ interface Props { } const sx = { + buttonIcons: { + height: "1rem", + }, dashboardTitle: { - marginBottom: "1.25rem", - fontSize: "md", + paddingBottom: "0", fontWeight: "bold", color: "palette.gray_medium", }, - topAddEntityButton: { + addEntityButton: { marginTop: "1.5rem", marginBottom: "2rem", }, - bottomAddEntityButton: { - marginTop: "2rem", - marginBottom: "0", - }, + table: {}, + reviewPdfButton: { marginTop: "1.5rem", marginBottom: "2rem" }, }; diff --git a/services/ui-src/src/components/reports/ReportPageWrapper.tsx b/services/ui-src/src/components/reports/ReportPageWrapper.tsx index 1a2fbc5c2..dc406685f 100644 --- a/services/ui-src/src/components/reports/ReportPageWrapper.tsx +++ b/services/ui-src/src/components/reports/ReportPageWrapper.tsx @@ -9,7 +9,11 @@ import { StandardReportPage, } from "components"; import { useLocation } from "react-router-dom"; -import { PageTypes } from "types"; +import { + ModalDrawerReportPageShape, + PageTypes, + StandardReportPageShape, +} from "types"; // utils import { mockDrawerReportPageJson, @@ -34,17 +38,26 @@ export const ReportPageWrapper = () => { return mockStandardReportPageJson; } }; + // these should be built off the form template, which comes from the report. const renderPageSection = (route: PageTypes) => { switch (route) { case PageTypes.DRAWER: return ; case PageTypes.MODAL_DRAWER: - return ; + return ( + + ); case PageTypes.REVIEW_SUBMIT: return ; default: - return ; + return ( + + ); } }; @@ -54,7 +67,7 @@ export const ReportPageWrapper = () => { <> - {renderPageSection(PageTypes.STANDARD)} + {renderPageSection(PageTypes.MODAL_DRAWER)} diff --git a/services/ui-src/src/components/tables/EntityRow.tsx b/services/ui-src/src/components/tables/EntityRow.tsx new file mode 100644 index 000000000..c5e988f04 --- /dev/null +++ b/services/ui-src/src/components/tables/EntityRow.tsx @@ -0,0 +1,124 @@ +// components +import { Box, Button, Image, Td, Tr } from "@chakra-ui/react"; +import { EntityStatusIcon } from "components"; +// types +import { AnyObject, EntityShape } from "types"; +// utils +import { useUserStore } from "utils"; +// assets +import deleteIcon from "assets/icons/icon_cancel_x_circle.png"; + +export const EntityRow = ({ + entity, + verbiage, + openAddEditEntityModal, + openDeleteEntityModal, + openDrawer, +}: Props) => { + const { name, isOtherEntity } = entity; + const { userIsEndUser } = useUserStore().user ?? {}; + + return ( + + + + + {name} + + + {isOtherEntity && ( + + )} + + {isOtherEntity && ( + + )} + + + + ); +}; + +interface Props { + entity: EntityShape; + verbiage: AnyObject; + openAddEditEntityModal: Function; + openDeleteEntityModal: Function; + openDrawer: Function; + [key: string]: any; +} + +const sx = { + content: { + verticalAlign: "middle", + paddingLeft: "1.5rem", + td: { + borderColor: "palette.gray_light", + paddingRight: 0, + }, + }, + statusIcon: { + maxWidth: "fit-content", + }, + errorText: { + color: "palette.error_dark", + fontSize: "0.75rem", + marginBottom: "0.75rem", + }, + entityName: { + maxWidth: "18.75rem", + fontSize: "md", + fontWeight: "bold", + }, + actionContainer: { + alignItems: "center", + display: "flex", + }, + editNameButton: { + paddingRight: "2.5rem", + fontWeight: "normal", + textDecoration: "underline", + color: "palette.primary", + }, + editEntityButton: { + padding: 0, + fontWeight: "bold", + width: "6.5rem", + marginLeft: "8.25rem", + }, + editOtherEntityButton: { + padding: 0, + fontWeight: "bold", + width: "6.5rem", + }, + deleteButton: { + height: "1.875rem", + width: "1.875rem", + minWidth: "1.875rem", + padding: 0, + marginLeft: "1rem", + background: "white", + "&:hover, &:hover:disabled": { + background: "white", + }, + }, +}; diff --git a/services/ui-src/src/components/tables/EntityStatusIcon.tsx b/services/ui-src/src/components/tables/EntityStatusIcon.tsx new file mode 100644 index 000000000..cdd05b2d8 --- /dev/null +++ b/services/ui-src/src/components/tables/EntityStatusIcon.tsx @@ -0,0 +1,99 @@ +// components +import { Box, Image, Text } from "@chakra-ui/react"; +// utils +import { EntityShape } from "types"; +// assets +import unfinishedIcon from "assets/icons/icon_error_circle_bright.png"; +import unfinishedIconDark from "assets/icons/icon_error_circle.png"; +import successIcon from "assets/icons/icon_check_circle.png"; +import successIconDark from "assets/icons/icon_check_circle_dark.png"; +import { useContext, useMemo } from "react"; +import { ReportContext } from "components/reports/ReportProvider"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const EntityStatusIcon = ({ entity, isPdf }: Props) => { + const { report } = useContext(ReportContext); + + const entityComplete = useMemo(() => { + return false; + }, [report]); + + return ( + + {entityComplete ? ( + <> + {isPdf + {isPdf && ( + + Complete + + )} + + ) : ( + <> + {isPdf + {isPdf && ( + + Error + + )} + + )} + + ); +}; + +interface Props { + /** + * Entity to show status for + */ + entity: EntityShape; + /** + * Whether or not icon is appearing on PDF page (used for styling) + */ + isPdf?: boolean; + [key: string]: any; +} + +const sx = { + container: { + display: "flex", + flexDirection: "column", + alignItems: "center", + }, + successText: { + color: "palette.success_darker", + fontSize: "0.667rem", + }, + errorText: { + color: "palette.error_darker", + fontSize: "0.667rem", + }, + containerPdf: { + display: "flex", + flexDirection: "column", + alignItems: "center", + }, + statusIcon: { + marginLeft: "0rem", + img: { + maxWidth: "fit-content", + }, + }, + statusIconPdf: { + marginLeft: "0rem", + img: { + maxWidth: "fit-content", + }, + }, +}; diff --git a/services/ui-src/src/types/reports.ts b/services/ui-src/src/types/reports.ts index 000fcfcb6..7497b8336 100644 --- a/services/ui-src/src/types/reports.ts +++ b/services/ui-src/src/types/reports.ts @@ -84,7 +84,6 @@ export interface ReportRouteWithoutForm extends ReportRouteBase { export interface DrawerReportPageVerbiage extends ReportPageVerbiage { dashboardTitle: string; - countEntitiesInTitle?: boolean; drawerTitle: string; drawerInfo?: CustomHtmlElement[]; missingEntityMessage?: CustomHtmlElement[]; @@ -96,14 +95,14 @@ export interface ModalDrawerReportPageVerbiage editEntityButtonText: string; addEditModalAddTitle: string; addEditModalEditTitle: string; - addEditModalMessage: string; deleteEntityButtonAltText: string; deleteModalTitle: string; deleteModalConfirmButtonText: string; deleteModalWarning: string; entityUnfinishedMessage: string; enterEntityDetailsButtonText: string; - editEntityDetailsButtonText: string; + reviewPdfHint: string; + drawerTitle: string; } /** diff --git a/services/ui-src/src/utils/other/renderHtml.ts b/services/ui-src/src/utils/other/renderHtml.ts new file mode 100644 index 000000000..3c1a19d3e --- /dev/null +++ b/services/ui-src/src/utils/other/renderHtml.ts @@ -0,0 +1,7 @@ +import React from "react"; + +// render '<' special character +export const renderHtml = (rawHTML: string) => + React.createElement("span", { + dangerouslySetInnerHTML: { __html: rawHTML }, + }); diff --git a/services/ui-src/src/utils/testing/mockForm.tsx b/services/ui-src/src/utils/testing/mockForm.tsx index bfa65ce2d..bbd008ff8 100644 --- a/services/ui-src/src/utils/testing/mockForm.tsx +++ b/services/ui-src/src/utils/testing/mockForm.tsx @@ -194,8 +194,8 @@ export const mockModalDrawerReportPageVerbiage = { deleteModalWarning: "Mock delete modal warning", entityUnfinishedMessage: "Mock entity unfinished messsage", enterEntityDetailsButtonText: "Mock enter entity details button text", - editEntityDetailsButtonText: "Mock edit entity details button text", drawerTitle: "Mock drawer title", + reviewPdfHint: "Mock review PDF hint", drawerNoFormMessage: "Mock no form fields here", };