diff --git a/package.json b/package.json index 25b6254e..258b6c67 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@emotion/react": "11.11.4", "@emotion/styled": "11.11.5", "@eyeseetea/d2-api": "1.14.0", - "@eyeseetea/d2-ui-components": "2.7.0", + "@eyeseetea/d2-ui-components": "v2.9.0-beta.2", "@eyeseetea/feedback-component": "0.0.3", "@material-ui/core": "4.12.4", "@material-ui/icons": "4.11.3", diff --git a/src/webapp/components/form-page/FormPage.tsx b/src/webapp/components/form-page/FormPage.tsx new file mode 100644 index 00000000..17a22e43 --- /dev/null +++ b/src/webapp/components/form-page/FormPage.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import styled from "styled-components"; + +import i18n from "../../../utils/i18n"; +import { Button } from "../button/Button"; +import { Separator } from "../separator/Separator"; + +interface FormPageProps { + title?: string; + subtitle?: string; + saveLabel?: string; + cancelLabel?: string; + children: React.ReactNode; + onSave: () => void; + onCancel: () => void; +} + +export const FormPage: React.FC = React.memo( + ({ title, subtitle = "", saveLabel = "", cancelLabel = "", children, onSave, onCancel }) => { + return ( + +
+ + {title ? {title} : null} + {subtitle ? {subtitle} : null} + + {i18n.t("Indicates required")} +
+ {children} +
+ + + + + +
+
+ ); + } +); + +const StyledFormPage = styled.div` + width: 100%; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; +`; + +const Content = styled.div` + width: 100%; +`; + +const Footer = styled.div``; + +const ButtonsFooter = styled.div` + margin-block-start: 48px; + display: flex; + gap: 48px; +`; + +const TitleContainer = styled.div` + display: flex; + gap: 4px; +`; + +const Title = styled.span` + color: ${props => props.theme.palette.common.grey700}; + font-size: 0.875rem; + font-weight: 700; +`; + +const Subtitle = styled.span` + color: ${props => props.theme.palette.common.grey700}; + font-size: 0.875rem; + font-weight: 400; +`; + +const RequiredText = styled.span` + color: ${props => props.theme.palette.common.black}; + font-size: 0.875rem; + font-weight: 700; + &::before { + content: "*"; + color: ${props => props.theme.palette.common.red}; + margin-inline-end: 4px; + } +`; diff --git a/src/webapp/components/form-section/FormSection.tsx b/src/webapp/components/form-section/FormSection.tsx new file mode 100644 index 00000000..ccd5b11e --- /dev/null +++ b/src/webapp/components/form-section/FormSection.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import styled from "styled-components"; +import { IconInfo24 } from "@dhis2/ui"; + +import { Separator } from "../separator/Separator"; +import { IconButton } from "../icon-button/IconButton"; + +interface FormSectionProps { + title: string; + children: React.ReactNode; + hasSeparator?: boolean; + onClickInfo?: () => void; + direction?: "row" | "column"; +} + +export const FormSection: React.FC = React.memo( + ({ title, hasSeparator = false, children, onClickInfo, direction = "row" }) => { + return ( + + {hasSeparator && } + + + {title} + {onClickInfo && } onClick={onClickInfo} />} + + {children} + + + ); + } +); + +const FormSectionContainer = styled.div` + width: 100%; + margin-block-end: 24px; +`; + +const Container = styled.div<{ direction: string }>` + display: flex; + flex-direction: ${props => props.direction}; + width: 100%; + gap: ${props => (props.direction === "row" ? "48px" : "24px")}; + align-items: ${props => (props.direction === "row" ? "center" : "flex-start")}; +`; + +const TitleContainer = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const FormContainer = styled.div` + width: 100%; +`; + +const RequiredText = styled.span` + color: ${props => props.theme.palette.common.black}; + font-size: 0.875rem; + font-weight: 700; + white-space: nowrap; + &::after { + content: "*"; + color: ${props => props.theme.palette.common.red}; + margin-inline-start: 4px; + } +`; diff --git a/src/webapp/components/icon-button/IconButton.tsx b/src/webapp/components/icon-button/IconButton.tsx new file mode 100644 index 00000000..160985cb --- /dev/null +++ b/src/webapp/components/icon-button/IconButton.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { IconButton as MUIIconButton } from "@material-ui/core"; +import styled from "styled-components"; + +interface IconButtonProps { + icon: React.ReactNode; + disabled?: boolean; + onClick: () => void; +} + +export const IconButton: React.FC = React.memo( + ({ icon, disabled = false, onClick }) => { + return ( + + {icon} + + ); + } +); + +const StyledIconButton = styled(MUIIconButton)` + color: ${props => props.theme.palette.icon.color}; + padding: 0; +`; diff --git a/src/webapp/components/layout/main-content/MainContent.tsx b/src/webapp/components/layout/main-content/MainContent.tsx index 892a03a5..19f391a2 100644 --- a/src/webapp/components/layout/main-content/MainContent.tsx +++ b/src/webapp/components/layout/main-content/MainContent.tsx @@ -59,7 +59,7 @@ const SubTitle = styled.span` `; const PageContent = styled.div` - margin-block-start: 64px; + margin-block-start: 48px; `; const Main = styled.main` diff --git a/src/webapp/components/notice-box/NoticeBox.tsx b/src/webapp/components/notice-box/NoticeBox.tsx index af7987b2..621ed9fb 100644 --- a/src/webapp/components/notice-box/NoticeBox.tsx +++ b/src/webapp/components/notice-box/NoticeBox.tsx @@ -22,7 +22,6 @@ export const NoticeBox: React.FC = React.memo(({ title, children const Container = styled.div` display: flex; flex-direction: column; - width: 100%; padding-inline: 16px; padding-block: 12px; gap: 8px; @@ -44,6 +43,7 @@ const TitleContainer = styled.div` `; const Content = styled.div` + margin-inline-start: 32px; font-size: 0.875rem; font-weight: 400; color: ${props => props.theme.palette.text.primary}; diff --git a/src/webapp/components/section/Section.tsx b/src/webapp/components/section/Section.tsx new file mode 100644 index 00000000..5da0311b --- /dev/null +++ b/src/webapp/components/section/Section.tsx @@ -0,0 +1,87 @@ +import React from "react"; +import styled from "styled-components"; + +import i18n from "../../../utils/i18n"; +import { Separator } from "../separator/Separator"; + +interface SectionProps { + title?: string; + lastUpdated?: string; + children: React.ReactNode; + headerButtom?: React.ReactNode; + hasSeparator?: boolean; + titleVariant?: "primary" | "secondary"; +} + +export const Section: React.FC = React.memo( + ({ + title = "", + lastUpdated = "", + headerButtom, + hasSeparator = false, + children, + titleVariant = "primary", + }) => { + return ( + + {hasSeparator && } +
+ + {title ? {title} : null} + {lastUpdated ? ( + + {i18n.t("Last updated: ")} + {lastUpdated} + + ) : null} + + {headerButtom ?
{headerButtom}
: null} +
+ {children} +
+ ); + } +); + +const SectionContainer = styled.section<{ $hasSeparator?: boolean }>` + width: 100%; + margin-block-end: ${props => (props.$hasSeparator ? "0" : "24px")}; +`; + +const Header = styled.div` + display: flex; + justify-content: space-between; +`; + +const TitleContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const Title = styled.span<{ $titleVariant: string }>` + color: ${props => + props.$titleVariant === "secondary" + ? props.theme.palette.common.grey800 + : props.theme.palette.common.black}; + font-size: 1.25rem; + font-weight: 400; +`; + +const LastUpdatedContainer = styled.div` + display: flex; + gap: 4px; + color: ${props => props.theme.palette.common.grey600}; + font-size: 0.875rem; + font-weight: 400; + margin-block-start: 12px; +`; + +const LastUpdatedTitle = styled.span` + color: ${props => props.theme.palette.common.grey600}; + font-size: 0.875rem; + font-weight: 700; +`; + +const Content = styled.div` + margin-block-start: 20px; +`; diff --git a/src/webapp/components/separator/Separator.tsx b/src/webapp/components/separator/Separator.tsx new file mode 100644 index 00000000..f8f685e5 --- /dev/null +++ b/src/webapp/components/separator/Separator.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import styled from "styled-components"; + +interface SeparatorProps { + margin?: string; +} + +export const Separator: React.FC = React.memo(({ margin = "" }) => { + return ; +}); + +const StyledSeparator = styled.hr<{ margin?: string }>` + border: none; + height: 1px; + background-color: ${props => props.theme.palette.common.grey4}; + margin-block: ${props => props.margin || "24px"}; +`; diff --git a/src/webapp/pages/app/__tests__/App.spec.tsx b/src/webapp/pages/app/__tests__/App.spec.tsx index 4ca8b7b3..56ba2733 100644 --- a/src/webapp/pages/app/__tests__/App.spec.tsx +++ b/src/webapp/pages/app/__tests__/App.spec.tsx @@ -4,9 +4,8 @@ import App from "../App"; import { getTestContext } from "../../../../utils/tests"; import { Provider } from "@dhis2/app-runtime"; -// TODO: fix describe("App", () => { - it.skip("renders the feedback component", async () => { + it("renders the feedback component", async () => { const view = getView(); expect(await view.findByText("Send feedback")).toBeInTheDocument(); diff --git a/src/webapp/pages/app/__tests__/__snapshots__/App.spec.tsx.snap b/src/webapp/pages/app/__tests__/__snapshots__/App.spec.tsx.snap deleted file mode 100644 index 6451f564..00000000 --- a/src/webapp/pages/app/__tests__/__snapshots__/App.spec.tsx.snap +++ /dev/null @@ -1,93 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`App > navigates to page 1`] = ` - -
-
- -
-
-
- -
- Detail page -
-
-

- Hello John -

-
-
-
- -
-
- -`; diff --git a/src/webapp/pages/app/themes/dhis2.theme.ts b/src/webapp/pages/app/themes/dhis2.theme.ts index 7b08cf5f..0d17c3c5 100644 --- a/src/webapp/pages/app/themes/dhis2.theme.ts +++ b/src/webapp/pages/app/themes/dhis2.theme.ts @@ -314,8 +314,8 @@ export const muiTheme = createTheme({ color: colors.grey400, borderColor: colors.grey300, backgroundColor: colors.grey100, - fontWeight: 400, "& .MuiButton-label": { + fontWeight: 400, color: colors.grey900, }, "& .MuiButton-startIcon": { @@ -337,8 +337,8 @@ export const muiTheme = createTheme({ color: colors.grey400, borderColor: colors.grey400, backgroundColor: colors.grey100, - fontWeight: 400, "& .MuiButton-label": { + fontWeight: 400, color: colors.grey900, }, "& .MuiButton-startIcon": { diff --git a/src/webapp/pages/create-event/CreateEventPage.tsx b/src/webapp/pages/create-event/CreateEventPage.tsx index bb7b42c7..07b576d0 100644 --- a/src/webapp/pages/create-event/CreateEventPage.tsx +++ b/src/webapp/pages/create-event/CreateEventPage.tsx @@ -2,11 +2,27 @@ import React from "react"; import { Layout } from "../../components/layout/Layout"; import i18n from "../../../utils/i18n"; +import { FormPage } from "../../components/form-page/FormPage"; +import { FormSection } from "../../components/form-section/FormSection"; +import { useHistory } from "react-router-dom"; export const CreateEventPage: React.FC = React.memo(() => { + const history = useHistory(); + + const goBack = () => { + history.goBack(); + }; + return ( - CreateEventPage + {}}> + +
test
+
+ +
test
+
+
); }); diff --git a/src/webapp/pages/dashboard/Components.tsx b/src/webapp/pages/dashboard/Components.tsx index 84afcdf7..9d985e35 100644 --- a/src/webapp/pages/dashboard/Components.tsx +++ b/src/webapp/pages/dashboard/Components.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import styled from "styled-components"; +import { IconEdit24 } from "@dhis2/ui"; import { Button } from "../../components/button/Button"; import { AddNewOption } from "../../components/add-new-option/AddNewOption"; @@ -174,8 +175,24 @@ export const Components: React.FC = React.memo(() => { /> - - Risk assessment incomplete + +
+ No plan has been created for this incident. + +
diff --git a/src/webapp/pages/dashboard/DashboardPage.tsx b/src/webapp/pages/dashboard/DashboardPage.tsx index 01b8b4ed..a4394631 100644 --- a/src/webapp/pages/dashboard/DashboardPage.tsx +++ b/src/webapp/pages/dashboard/DashboardPage.tsx @@ -1,13 +1,20 @@ import React from "react"; -import { Layout } from "../../components/layout/Layout"; import i18n from "../../../utils/i18n"; +import { Layout } from "../../components/layout/Layout"; +import { Section } from "../../components/section/Section"; import { Components } from "./Components"; export const DashboardPage: React.FC = React.memo(() => { return ( - + {/* */} +
Respond, alert, watch content
+
+ All public health events content +
+
7-1-7 performance content
+
Performance overview content
); }); diff --git a/src/webapp/pages/event-tracker/EventTrackerPage.tsx b/src/webapp/pages/event-tracker/EventTrackerPage.tsx index 8ac88681..cf54bad0 100644 --- a/src/webapp/pages/event-tracker/EventTrackerPage.tsx +++ b/src/webapp/pages/event-tracker/EventTrackerPage.tsx @@ -1,8 +1,45 @@ import React from "react"; +import { IconEdit24 } from "@dhis2/ui"; -import { Layout } from "../../components/layout/Layout"; import i18n from "../../../utils/i18n"; +import { Layout } from "../../components/layout/Layout"; +import { Section } from "../../components/section/Section"; +import { Button } from "../../components/button/Button"; +import { RiskAssessmentSection } from "./RiskAssessmentSection"; export const EventTrackerPage: React.FC = React.memo(() => { - return EventTrackerPage; + return ( + +
} + > + {i18n.t("Edit Details")} + + } + hasSeparator + > + Event details +
+
+ Districts affected content +
+ +
+ Overview content +
+
Cases content
+
+ 7-1-7 performance content +
+
+ ); }); diff --git a/src/webapp/pages/event-tracker/RiskAssessmentSection.tsx b/src/webapp/pages/event-tracker/RiskAssessmentSection.tsx new file mode 100644 index 00000000..0a16112f --- /dev/null +++ b/src/webapp/pages/event-tracker/RiskAssessmentSection.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { IconEdit24 } from "@dhis2/ui"; + +import i18n from "../../../utils/i18n"; +import { Section } from "../../components/section/Section"; +import { Button } from "../../components/button/Button"; +import { NoticeBox } from "../../components/notice-box/NoticeBox"; + +export const RiskAssessmentSection: React.FC = React.memo(() => { + return ( +
} + > + {i18n.t("Create Risk Assessment")} + + } + hasSeparator + > + +
{i18n.t("Risks associated with this event have not yet been assessed.")}
+
+
+ ); +}); diff --git a/src/webapp/pages/incident-action-plan/IncidentActionPlanPage.tsx b/src/webapp/pages/incident-action-plan/IncidentActionPlanPage.tsx index 6e658acb..906ac431 100644 --- a/src/webapp/pages/incident-action-plan/IncidentActionPlanPage.tsx +++ b/src/webapp/pages/incident-action-plan/IncidentActionPlanPage.tsx @@ -1,7 +1,10 @@ import React from "react"; +import { IconEdit24 } from "@dhis2/ui"; -import { Layout } from "../../components/layout/Layout"; import i18n from "../../../utils/i18n"; +import { Layout } from "../../components/layout/Layout"; +import { Section } from "../../components/section/Section"; +import { Button } from "../../components/button/Button"; export const IncidentActionPlanPage: React.FC = React.memo(() => { return ( @@ -9,7 +12,58 @@ export const IncidentActionPlanPage: React.FC = React.memo(() => { title={i18n.t("Incident Action Plan")} subtitle={i18n.t("Cholera in NW Province, June 2023")} > - IncidentActionPlanPage{" "} +
IAP details
+
} + > + {i18n.t("Edit Response Actions")} + + } + > + Response actions content +
+
} + > + {i18n.t("Edit Action Plan")} + + } + > + Action plan content +
+
} + > + {i18n.t("Edit Team")} + + } + > + Team content +
); }); diff --git a/yarn.lock b/yarn.lock index 7780d253..2710f263 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3575,10 +3575,10 @@ react "^16.12.0" yargs "^14.0.0" -"@eyeseetea/d2-ui-components@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@eyeseetea/d2-ui-components/-/d2-ui-components-2.7.0.tgz#5a09f5212d4da6ce4fa510978d2535a0f6a4de57" - integrity sha512-g/nl0PFQCFjcn9V/YSIwbCrQJOtTa5ngkKGBvY85Cba17PzFAwqjD5D2JQY5cBAc0NxSkIyH4QucqaSnX5/11g== +"@eyeseetea/d2-ui-components@v2.9.0-beta.2": + version "2.9.0-beta.2" + resolved "https://registry.yarnpkg.com/@eyeseetea/d2-ui-components/-/d2-ui-components-2.9.0-beta.2.tgz#7df5ea659ed1d487d78301f8e0c1f735dcb9f6e0" + integrity sha512-Sc6itN7pUB38OzcHMfc4JcML+8g9WnTQfNpwHhFg7vuyF15VzcfYQ+vq0JpNl3epNkABfdP9QVvrtf9IO9ORjQ== dependencies: "@date-io/core" "1.3.6" "@date-io/moment" "1.0.2"