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 }) => (