diff --git a/services/app-api/forms/cmit.ts b/services/app-api/forms/cmit.ts index 8e901cfa..70ea0d6b 100644 --- a/services/app-api/forms/cmit.ts +++ b/services/app-api/forms/cmit.ts @@ -68,4 +68,34 @@ export const CMIT_LIST: CMIT[] = [ dataSource: DataSource.Hybrid, options: "", }, + { + cmit: 20, + name: "LTSS-6: Admission to a Facility from the Community", + uid: "20", + measureSteward: "CMS", + measureSpecification: [MeasureSpecification.CMS], + deliverySystem: [DeliverySystem.FFS, DeliverySystem.MLTSS], + dataSource: DataSource.Administrative, + options: "", + }, + { + cmit: 968, + name: "LTSS-7: Minimizing Facility Length of Stay", + uid: "968", + measureSteward: "CMS", + measureSpecification: [MeasureSpecification.CMS], + deliverySystem: [DeliverySystem.FFS, DeliverySystem.MLTSS], + dataSource: DataSource.Administrative, + options: "", + }, + { + cmit: 414, + name: "LTSS-8: Successful Transition after Long-Term Facility Stay", + uid: "414", + measureSteward: "CMS", + measureSpecification: [MeasureSpecification.CMS], + deliverySystem: [DeliverySystem.FFS, DeliverySystem.MLTSS], + dataSource: DataSource.Administrative, + options: "", + }, ]; diff --git a/services/app-api/forms/qm.ts b/services/app-api/forms/qm.ts index 5737fbac..56df7352 100644 --- a/services/app-api/forms/qm.ts +++ b/services/app-api/forms/qm.ts @@ -413,21 +413,273 @@ export const qmReportTemplate: ReportTemplate = { title: "LTSS-6: Admission to a Facility from the Community", type: PageType.Measure, sidebar: false, - elements: [], + elements: [ + { + type: ElementType.ButtonLink, + label: "Return to Required Measures Dashboard", + to: "req-measure-result", + }, + { + type: ElementType.Header, + text: "{measureName}", + }, + { + type: ElementType.Accordion, + label: "Instructions", + value: + "[Optional instructional content that could support the user in completing this page]", + }, + { + type: ElementType.SubHeader, + text: "Measure Details", + }, + { + type: ElementType.Radio, + label: "Were the reported measure results audited or validated?", + value: [ + { label: "No, I am reporting on this measure", value: "no" }, + { + label: "Yes, CMS is reporting on my behalf", + value: "yes", + checkedChildren: [ + { + type: ElementType.Textbox, + label: + "What is the name of the agency of entity that audited or validated the report?", + }, + ], + }, + ], + }, + { + type: ElementType.Radio, + label: + "Did you deviate from the [reportYear] Technical Specifications?", + value: [ + { label: "No", value: "no" }, + { + label: "Yes", + value: "yes", + checkedChildren: [ + { + type: ElementType.Textbox, + label: "Please explain the deviation.", + }, + ], + }, + ], + }, + { + type: ElementType.Radio, + label: "Do you want CMS to calculate this measure on your behalf?", + value: [ + { label: "No", value: "no" }, + { + label: "Yes", + value: "yes", + }, + ], + }, + { + type: ElementType.Radio, + label: "Which delivery systems were used to report the LTSS measure?", + value: [ + { label: "Managed Care", value: "managed-care" }, + { label: "Free-For-Service", value: "fee-for-service" }, + { label: "Both", value: "both" }, + ], + }, + { + type: ElementType.SubHeader, + text: "Quality Measures", + }, + { + type: ElementType.QualityMeasureTable, + measureDisplay: "quality", + }, + ], }, [MeasureTemplateName["LTSS-7"]]: { id: "LTSS-7", title: "LTSS-7: Minimizing Facility Length of Stay", type: PageType.Measure, sidebar: false, - elements: [], + elements: [ + { + type: ElementType.ButtonLink, + label: "Return to Required Measures Dashboard", + to: "req-measure-result", + }, + { + type: ElementType.Header, + text: "{measureName}", + }, + { + type: ElementType.Accordion, + label: "Instructions", + value: + "[Optional instructional content that could support the user in completing this page]", + }, + { + type: ElementType.SubHeader, + text: "Measure Details", + }, + { + type: ElementType.Radio, + label: "Were the reported measure results audited or validated?", + value: [ + { label: "No, I am reporting on this measure", value: "no" }, + { + label: "Yes, CMS is reporting on my behalf", + value: "yes", + checkedChildren: [ + { + type: ElementType.Textbox, + label: + "What is the name of the agency of entity that audited or validated the report?", + }, + ], + }, + ], + }, + { + type: ElementType.Radio, + label: + "Did you deviate from the [reportYear] Technical Specifications?", + value: [ + { label: "No", value: "no" }, + { + label: "Yes", + value: "yes", + checkedChildren: [ + { + type: ElementType.Textbox, + label: "Please explain the deviation.", + }, + ], + }, + ], + }, + { + type: ElementType.Radio, + label: "Do you want CMS to calculate this measure on your behalf?", + value: [ + { label: "No", value: "no" }, + { + label: "Yes", + value: "yes", + }, + ], + }, + { + type: ElementType.Radio, + label: "Which delivery systems were used to report the LTSS measure?", + value: [ + { label: "Managed Care", value: "managed-care" }, + { label: "Free-For-Service", value: "fee-for-service" }, + { label: "Both", value: "both" }, + ], + }, + { + type: ElementType.SubHeader, + text: "Quality Measures", + }, + { + type: ElementType.QualityMeasureTable, + measureDisplay: "quality", + }, + ], }, [MeasureTemplateName["LTSS-8"]]: { id: "LTSS-8", title: "LTSS-8: Successful Transition after Long-Term Facility Stay", type: PageType.Measure, sidebar: false, - elements: [], + elements: [ + { + type: ElementType.ButtonLink, + label: "Return to Required Measures Dashboard", + to: "req-measure-result", + }, + { + type: ElementType.Header, + text: "{measureName}", + }, + { + type: ElementType.Accordion, + label: "Instructions", + value: + "[Optional instructional content that could support the user in completing this page]", + }, + { + type: ElementType.SubHeader, + text: "Measure Details", + }, + { + type: ElementType.Radio, + label: "Were the reported measure results audited or validated?", + value: [ + { label: "No, I am reporting on this measure", value: "no" }, + { + label: "Yes, CMS is reporting on my behalf", + value: "yes", + checkedChildren: [ + { + type: ElementType.Textbox, + label: + "What is the name of the agency of entity that audited or validated the report?", + }, + ], + }, + ], + }, + { + type: ElementType.Radio, + label: + "Did you deviate from the [reportYear] Technical Specifications?", + value: [ + { label: "No", value: "no" }, + { + label: "Yes", + value: "yes", + checkedChildren: [ + { + type: ElementType.Textbox, + label: "Please explain the deviation.", + }, + ], + }, + ], + }, + { + type: ElementType.Radio, + label: "Do you want CMS to calculate this measure on your behalf?", + value: [ + { label: "No", value: "no" }, + { + label: "Yes", + value: "yes", + }, + ], + }, + { + type: ElementType.Radio, + label: "Which delivery systems were used to report the LTSS measure?", + value: [ + { label: "Managed Care", value: "managed-care" }, + { label: "Free-For-Service", value: "fee-for-service" }, + { label: "Both", value: "both" }, + ], + }, + { + type: ElementType.SubHeader, + text: "Quality Measures", + }, + { + type: ElementType.QualityMeasureTable, + measureDisplay: "quality", + }, + ], }, //optional [MeasureTemplateName["FASI-1"]]: { diff --git a/services/ui-src/src/cmit.ts b/services/ui-src/src/cmit.ts index f53512c5..88b493af 100644 --- a/services/ui-src/src/cmit.ts +++ b/services/ui-src/src/cmit.ts @@ -64,4 +64,34 @@ export const CMIT_LIST: CMIT[] = [ dataSource: DataSource.Hybrid, options: "", }, + { + cmit: 20, + name: "LTSS-6: Admission to a Facility from the Community", + uid: "20", + measureSteward: "CMS", + measureSpecification: [MeasureSpecification.CMS], + deliverySystem: [DeliverySystem.FFS, DeliverySystem.MLTSS], + dataSource: DataSource.Administrative, + options: "", + }, + { + cmit: 968, + name: "LTSS-7: Minimizing Facility Length of Stay", + uid: "968", + measureSteward: "CMS", + measureSpecification: [MeasureSpecification.CMS], + deliverySystem: [DeliverySystem.FFS, DeliverySystem.MLTSS], + dataSource: DataSource.Administrative, + options: "", + }, + { + cmit: 414, + name: "LTSS-8: Successful Transition after Long-Term Facility Stay", + uid: "414", + measureSteward: "CMS", + measureSpecification: [MeasureSpecification.CMS], + deliverySystem: [DeliverySystem.FFS, DeliverySystem.MLTSS], + dataSource: DataSource.Administrative, + options: "", + }, ]; diff --git a/services/ui-src/src/components/fields/DateField.tsx b/services/ui-src/src/components/fields/DateField.tsx index f11ca954..89b8fb7c 100644 --- a/services/ui-src/src/components/fields/DateField.tsx +++ b/services/ui-src/src/components/fields/DateField.tsx @@ -48,6 +48,12 @@ export const DateField = (props: PageElementProps) => { value={displayValue} hint={parsedHint} errorMessage={errorMessage} + /* + * Typescript hack. CmsdsDateField won't recognize + * passthrough props until @cmsgov/design-system^9.0.0 + * TODO: Unhack, to just `disabled={props.disabled}` + */ + {...{ disabled: props.disabled }} /> ); diff --git a/services/ui-src/src/components/pages/Dashboard/DashboardPage.test.tsx b/services/ui-src/src/components/pages/Dashboard/DashboardPage.test.tsx index 13920d72..32c9ccba 100644 --- a/services/ui-src/src/components/pages/Dashboard/DashboardPage.test.tsx +++ b/services/ui-src/src/components/pages/Dashboard/DashboardPage.test.tsx @@ -1,9 +1,14 @@ import { render, screen, waitFor } from "@testing-library/react"; import { DashboardPage } from "components"; -import { RouterWrappedComponent, mockUseStore } from "utils/testing/setupJest"; +import { + RouterWrappedComponent, + mockUseReadOnlyUserStore, + mockUseStore, +} from "utils/testing/setupJest"; import { useStore } from "utils"; import { getReportsForState } from "utils/api/requestMethods/report"; import { Report } from "types"; +import dashboardVerbiage from "verbiage/pages/dashboard"; window.HTMLElement.prototype.scrollIntoView = jest.fn(); @@ -46,7 +51,7 @@ const dashboardComponent = ( ); -describe("", () => { +describe("DashboardPage with state user", () => { beforeEach(() => jest.clearAllMocks()); it("should render an empty state when there are no reports", async () => { @@ -95,3 +100,22 @@ describe("", () => { expect(cellContent("Edited by")).toBe("Mock User"); }); }); + +describe("DashboardPage with Read only user", () => { + beforeEach(() => { + mockedUseStore.mockReturnValue(mockUseReadOnlyUserStore); + }); + it("should not render the Start Report button when user is read only", async () => { + (getReportsForState as jest.Mock).mockResolvedValueOnce([]); + + render(dashboardComponent); + await waitFor(() => { + expect(getReportsForState).toHaveBeenCalled(); + }); + + const startReportButton = screen.queryByText( + dashboardVerbiage.body.link.callToActionText + ); + expect(startReportButton).not.toBeInTheDocument(); + }); +}); diff --git a/services/ui-src/src/components/pages/Dashboard/DashboardPage.tsx b/services/ui-src/src/components/pages/Dashboard/DashboardPage.tsx index f9497167..d20ae899 100644 --- a/services/ui-src/src/components/pages/Dashboard/DashboardPage.tsx +++ b/services/ui-src/src/components/pages/Dashboard/DashboardPage.tsx @@ -25,7 +25,7 @@ import arrowLeftIcon from "assets/icons/arrows/icon_arrow_left_blue.png"; import { getReportsForState } from "utils/api/requestMethods/report"; export const DashboardPage = () => { - const { userIsAdmin } = useStore().user ?? {}; + const { userIsAdmin, userIsEndUser } = useStore().user ?? {}; const { reportType, state } = useParams(); const [isLoading, setIsLoading] = useState(true); const [reports, setReports] = useState([]); @@ -87,11 +87,13 @@ export const DashboardPage = () => { {!isLoading && } {!reports?.length && {body.empty}} - - - + {userIsEndUser && ( + + + + )} { diff --git a/services/ui-src/src/components/report/Page.test.tsx b/services/ui-src/src/components/report/Page.test.tsx index 76454dea..640d96ca 100644 --- a/services/ui-src/src/components/report/Page.test.tsx +++ b/services/ui-src/src/components/report/Page.test.tsx @@ -1,9 +1,12 @@ +import { + mockUseReadOnlyUserStore, + mockUseStore, +} from "utils/testing/setupJest"; import { useNavigate, useParams } from "react-router-dom"; import userEvent from "@testing-library/user-event"; import { render, screen } from "@testing-library/react"; import { ElementType, PageElement } from "types/report"; import { useStore } from "utils"; -import { mockUseStore } from "utils/testing/setupJest"; import { Page } from "./Page"; jest.mock("react-router-dom", () => ({ @@ -92,7 +95,27 @@ const elements: PageElement[] = [ }, ]; -describe("Page Component", () => { +const textFieldElement: PageElement[] = [ + { + type: ElementType.Textbox, + label: "labeled", + }, + { + type: ElementType.Radio, + label: "radio button", + value: [{ label: "radio choice 1", value: "1", checkedChildren: [] }], + }, +]; + +const dateFieldElement: PageElement[] = [ + { + type: ElementType.Date, + label: "date label", + helperText: "can you read this?", + }, +]; + +describe("Page Component with state user", () => { test.each(elements)("Renders all element types: %p", (element) => { const { container } = render(); expect(container).not.toBeEmptyDOMElement(); @@ -132,3 +155,21 @@ describe("Page Component", () => { expect(container).not.toBeEmptyDOMElement(); }); }); + +describe("Page Component with read only user", () => { + beforeEach(() => { + mockedUseStore.mockReturnValue(mockUseReadOnlyUserStore); + }); + test("text field and radio button should be disabled", () => { + render(); + const textField = screen.getByRole("textbox"); + const radioButton = screen.getByLabelText("radio choice 1"); + expect(textField).toBeDisabled(); + expect(radioButton).toBeDisabled(); + }); + test("date field should be disabled", () => { + render(); + const dateField = screen.getByRole("textbox"); + expect(dateField).toBeDisabled(); + }); +}); diff --git a/services/ui-src/src/components/report/Page.tsx b/services/ui-src/src/components/report/Page.tsx index 11a8cc26..e0691778 100644 --- a/services/ui-src/src/components/report/Page.tsx +++ b/services/ui-src/src/components/report/Page.tsx @@ -11,12 +11,14 @@ import { MeasureTableElement } from "./MeasureTable"; import { QualityMeasureTableElement } from "./QualityMeasureTable"; import { StatusTableElement } from "./StatusTable"; import { TextField, DateField, RadioField } from "components"; +import { useStore } from "utils"; interface Props { elements: PageElement[]; } export const Page = ({ elements }: Props) => { + const { userIsEndUser } = useStore().user || {}; const renderElement = (element: PageElement) => { const elementType = element.type; switch (elementType) { @@ -55,6 +57,7 @@ export const Page = ({ elements }: Props) => { formkey={buildFormKey(index)} key={index} element={element} + disabled={!userIsEndUser} /> ); }); diff --git a/services/ui-src/src/components/report/StatusTable.test.tsx b/services/ui-src/src/components/report/StatusTable.test.tsx index 72b49833..2819306a 100644 --- a/services/ui-src/src/components/report/StatusTable.test.tsx +++ b/services/ui-src/src/components/report/StatusTable.test.tsx @@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event"; import { StatusTableElement } from "./StatusTable"; import { MemoryRouter } from "react-router-dom"; import { useStore } from "utils"; +import { mockUseReadOnlyUserStore } from "utils/testing/setupJest"; jest.mock("utils", () => ({ useStore: jest.fn(), @@ -25,16 +26,18 @@ const report = { ], }; -describe("StatusTableElement", () => { +const mockPageMap = new Map(); +mockPageMap.set("root", 0); +mockPageMap.set("1", 1); +mockPageMap.set("2", 2); + +const mockedUseStore = useStore as jest.MockedFunction; + +describe("StatusTable with state user", () => { beforeEach(() => { jest.clearAllMocks(); - const mockPageMap = new Map(); - mockPageMap.set("root", 0); - mockPageMap.set("1", 1); - mockPageMap.set("2", 2); - - (useStore as unknown as jest.Mock).mockReturnValue({ + mockedUseStore.mockReturnValue({ pageMap: mockPageMap, report: report, }); @@ -97,3 +100,23 @@ describe("StatusTableElement", () => { expect(container.firstChild).toBeNull(); }); }); + +describe("StatusPage with Read only user", () => { + beforeEach(() => { + mockedUseStore.mockReturnValue({ + ...mockUseReadOnlyUserStore, + pageMap: mockPageMap, + report: report, + }); + }); + it("should not render the Submit QMS Report button when user is read only", async () => { + render( + + + + ); + + const submitButton = screen.queryByText("Submit QMS Report"); + expect(submitButton).not.toBeInTheDocument(); + }); +}); diff --git a/services/ui-src/src/components/report/StatusTable.tsx b/services/ui-src/src/components/report/StatusTable.tsx index 73a514b0..b051d763 100644 --- a/services/ui-src/src/components/report/StatusTable.tsx +++ b/services/ui-src/src/components/report/StatusTable.tsx @@ -19,7 +19,7 @@ import { TableStatusIcon } from "components/tables/TableStatusIcon"; import { reportBasePath } from "utils/other/routing"; export const StatusTableElement = () => { - const { pageMap, report } = useStore(); + const { pageMap, report, user } = useStore(); const { reportType, state, reportId } = useParams(); if (!pageMap) { @@ -95,12 +95,14 @@ export const StatusTableElement = () => { > Review PDF - + {user?.userIsEndUser && ( + + )} ); diff --git a/services/ui-src/src/utils/testing/setupJest.tsx b/services/ui-src/src/utils/testing/setupJest.tsx index d846b825..92e9bdce 100644 --- a/services/ui-src/src/utils/testing/setupJest.tsx +++ b/services/ui-src/src/utils/testing/setupJest.tsx @@ -250,6 +250,12 @@ export const mockUseAdminStore: HcbsUserState & AdminBannerState = { ...mockBannerStore, }; +export const mockUseReadOnlyUserStore: HcbsUserState & AdminBannerState = { + ...mockHelpDeskUserStore, + ...mockBannerStore, + ...mockReportStore, +}; + // ROUTER export const RouterWrappedComponent: React.FC = ({ children }) => (