diff --git a/src/webapp/components/form/FormFieldsState.ts b/src/webapp/components/form/FormFieldsState.ts index 3d34d4d4..a3878d36 100644 --- a/src/webapp/components/form/FormFieldsState.ts +++ b/src/webapp/components/form/FormFieldsState.ts @@ -1,6 +1,6 @@ import { Maybe } from "../../../utils/ts-utils"; import { validateFieldRequired, validateFieldRequiredWithNotApplicable } from "./validations"; -import { UserOption } from "../user-selector/UserSelector"; +import { User } from "../user-selector/UserSelector"; import { Option } from "../utils/option"; import { ValidationError, ValidationErrorKey } from "../../../domain/entities/ValidationError"; import { FormSectionState } from "./FormSectionsState"; @@ -52,7 +52,7 @@ export type FormDateFieldState = FormFieldStateBase & { export type FormAvatarFieldState = FormFieldStateBase> & { type: "user"; - options: UserOption[]; + options: User[]; }; export type FormFieldState = diff --git a/src/webapp/components/form/form-summary/FormSummary.tsx b/src/webapp/components/form/form-summary/FormSummary.tsx new file mode 100644 index 00000000..07f70bd0 --- /dev/null +++ b/src/webapp/components/form/form-summary/FormSummary.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import styled from "styled-components"; +import i18n from "@eyeseetea/d2-ui-components/locales"; +import { Id } from "../../../../domain/entities/Ref"; +import { Section } from "../../section/Section"; +import { Box, Button, Typography } from "@material-ui/core"; +import { useFormSummary } from "./useFormSummary"; +import { UserCard } from "../../user-selector/UserCard"; +import { RouteName, useRoutes } from "../../../hooks/useRoutes"; +import { EditOutlined } from "@material-ui/icons"; +import { Loader } from "../../loader/Loader"; + +export type FormSummaryProps = { + id: Id; +}; + +export const FormSummary: React.FC = React.memo(props => { + const { id } = props; + const { formSummary } = useFormSummary(id); + const { goTo } = useRoutes(); + + const editButton = ( + + ); + + return formSummary ? ( + <> +
+ + + {formSummary.summary.map((labelWithValue, index) => + index < 3 ? ( + + + {labelWithValue.label}: + {" "} + {labelWithValue.value} + + ) : null + )} + + + {formSummary.summary.map((labelWithValue, index) => + index < 3 ? null : ( + + + + {labelWithValue.label}: + {" "} + {labelWithValue.value} + + + ) + )} + + + {formSummary.incidentManager && ( + + )} + + +
+ + ) : ( + + ); +}); + +const SummaryContainer = styled.div` + display: flex; + width: max-content; + align-items: center; + margin-top: 0rem; +`; + +const SummaryColumn = styled.div` + flex: 1; + padding-right: 1rem; + color: ${props => props.theme.palette.text.hint}; +`; diff --git a/src/webapp/components/form/form-summary/useFormSummary.ts b/src/webapp/components/form/form-summary/useFormSummary.ts new file mode 100644 index 00000000..b52ca725 --- /dev/null +++ b/src/webapp/components/form/form-summary/useFormSummary.ts @@ -0,0 +1,83 @@ +import { useEffect, useState } from "react"; +import { useAppContext } from "../../../contexts/app-context"; +import { Id } from "../../../../domain/entities/Ref"; +import { DiseaseOutbreakEvent } from "../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent"; +import { User } from "../../user-selector/UserSelector"; +import { mapTeamMemberToUser } from "../../../pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState"; +import { Maybe } from "../../../../utils/ts-utils"; + +type LabelWithValue = { + label: string; + value: string; +}; + +type FormSummary = { + subTitle: string; + summary: LabelWithValue[]; + + incidentManager: Maybe; +}; +export function useFormSummary(id: Id) { + const { compositionRoot } = useAppContext(); + const [formSummary, setFormSummary] = useState(); + + useEffect(() => { + compositionRoot.diseaseOutbreakEvent.get.execute(id).run( + diseaseOutbreakEvent => { + setFormSummary(mapDiseaseOutbreakEventToFormSummary(diseaseOutbreakEvent)); + }, + () => {} + ); + }, [compositionRoot.diseaseOutbreakEvent.get, id]); + + const mapDiseaseOutbreakEventToFormSummary = ( + diseaseOutbreakEvent: DiseaseOutbreakEvent + ): FormSummary => { + return { + subTitle: diseaseOutbreakEvent.name, + summary: [ + { + label: "Last updated", + value: `${diseaseOutbreakEvent.lastUpdated.toLocaleDateString()} ${diseaseOutbreakEvent.lastUpdated.toLocaleTimeString()}`, + }, + { + label: diseaseOutbreakEvent.dataSource === "EBS" ? "Event type" : "Disease", + value: + diseaseOutbreakEvent.dataSource === "EBS" + ? diseaseOutbreakEvent.hazardType ?? "" + : diseaseOutbreakEvent.suspectedDisease?.name ?? "", + }, + { + label: "Event ID", + value: diseaseOutbreakEvent.id, + }, + { + label: "Emergence date", + value: diseaseOutbreakEvent.emerged.date.toLocaleString("default", { + month: "long", + year: "numeric", + }), + }, + { + label: "Detection date", + value: diseaseOutbreakEvent.detected.date.toLocaleString("default", { + month: "long", + year: "numeric", + }), + }, + { + label: "Notification date", + value: diseaseOutbreakEvent.notified.date.toLocaleString("default", { + month: "long", + year: "numeric", + }), + }, + ], + incidentManager: diseaseOutbreakEvent.incidentManager + ? mapTeamMemberToUser(diseaseOutbreakEvent.incidentManager) + : undefined, + }; + }; + + return { formSummary }; +} diff --git a/src/webapp/components/user-selector/UserCard.tsx b/src/webapp/components/user-selector/UserCard.tsx new file mode 100644 index 00000000..3b70d134 --- /dev/null +++ b/src/webapp/components/user-selector/UserCard.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { User } from "./UserSelector"; +import { AvatarCard } from "../avatar-card/AvatarCard"; +import styled from "styled-components"; +import { Link } from "@material-ui/core"; +import i18n from "@eyeseetea/d2-ui-components/locales"; + +type UserCardProps = { + selectedUser: User; +}; +export const UserCard: React.FC = React.memo(props => { + const { selectedUser } = props; + return ( + + + + + {selectedUser?.label} + + {selectedUser?.workPosition && {selectedUser?.workPosition}} + + + + {selectedUser?.phone} + + + {selectedUser?.email} + + + +
+ {i18n.t("Status: ", { nsSeparator: false })} + + {selectedUser?.status && {selectedUser?.status}} +
+
+
+
+ ); +}); +const AvatarContainer = styled.div` + display: flex; + flex-direction: column; + gap: 12px; + flex: 2; + max-width: 400px; + flex-basis: 0; + @media (max-width: 800px) { + align-self: center; + } +`; + +const Container = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +const Content = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +const TextBold = styled.span` + color: ${props => props.theme.palette.common.black}; + font-size: 0.875rem; + font-weight: 700; +`; + +const Text = styled.span` + color: ${props => props.theme.palette.common.black}; + font-size: 0.875rem; + font-weight: 400; +`; + +const StyledLink = styled(Link)` + &.MuiTypography-colorPrimary { + font-size: 0.875rem; + font-weight: 400; + text-decoration: underline; + color: ${props => props.theme.palette.common.black}; + } +`; diff --git a/src/webapp/components/user-selector/UserSelector.tsx b/src/webapp/components/user-selector/UserSelector.tsx index d50a561c..d2c3db9c 100644 --- a/src/webapp/components/user-selector/UserSelector.tsx +++ b/src/webapp/components/user-selector/UserSelector.tsx @@ -1,12 +1,9 @@ import React, { useMemo } from "react"; import styled from "styled-components"; -import { Link } from "@material-ui/core"; - -import i18n from "../../../utils/i18n"; import { Selector } from "../selector/Selector"; -import { AvatarCard } from "../avatar-card/AvatarCard"; +import { UserCard } from "./UserCard"; -export type UserOption = { +export type User = { value: string; label: string; disabled?: boolean; @@ -22,7 +19,7 @@ type UserSelectorProps = { id: string; selected?: string; onChange: (value: string) => void; - options: UserOption[]; + options: User[]; label?: string; placeholder?: string; disabled?: boolean; @@ -69,35 +66,7 @@ export const UserSelector: React.FC = React.memo(props => { /> - {selectedUser && ( - - - - - {selectedUser?.label} - - {selectedUser?.workPosition && ( - {selectedUser?.workPosition} - )} - - - - {selectedUser?.phone} - - - {selectedUser?.email} - - - -
- {i18n.t("Status: ", { nsSeparator: false })} - - {selectedUser?.status && {selectedUser?.status}} -
-
-
-
- )} + {selectedUser && } ); }); @@ -113,18 +82,6 @@ const ComponentContainer = styled.div` } `; -const AvatarContainer = styled.div` - display: flex; - flex-direction: column; - gap: 12px; - flex: 2; - max-width: 400px; - flex-basis: 0; - @media (max-width: 800px) { - align-self: center; - } -`; - const SelectorContainer = styled.div` margin-block-start: 24px; flex: 1; @@ -135,36 +92,3 @@ const SelectorContainer = styled.div` width: 100%; } `; - -const Container = styled.div` - display: flex; - flex-direction: column; - gap: 12px; -`; - -const Content = styled.div` - display: flex; - flex-direction: column; - gap: 4px; -`; - -const TextBold = styled.span` - color: ${props => props.theme.palette.common.black}; - font-size: 0.875rem; - font-weight: 700; -`; - -const Text = styled.span` - color: ${props => props.theme.palette.common.black}; - font-size: 0.875rem; - font-weight: 400; -`; - -const StyledLink = styled(Link)` - &.MuiTypography-colorPrimary { - font-size: 0.875rem; - font-weight: 400; - text-decoration: underline; - color: ${props => props.theme.palette.common.black}; - } -`; diff --git a/src/webapp/hooks/useRoutes.ts b/src/webapp/hooks/useRoutes.ts index e232b2b6..c832c81f 100644 --- a/src/webapp/hooks/useRoutes.ts +++ b/src/webapp/hooks/useRoutes.ts @@ -21,7 +21,7 @@ const formType = `:formType(${join(formTypes, "|")})` as const; export const routes: Record = { [RouteName.CREATE_FORM]: `/create/${formType}`, [RouteName.EDIT_FORM]: `/edit/${formType}/:id`, - [RouteName.EVENT_TRACKER]: "/event-tracker", + [RouteName.EVENT_TRACKER]: "/event-tracker/:id", [RouteName.IM_TEAM_BUILDER]: "/incident-management-team-builder", [RouteName.INCIDENT_ACTION_PLAN]: "/incident-action-plan", [RouteName.RESOURCES]: "/resources", diff --git a/src/webapp/pages/event-tracker/EventTrackerPage.tsx b/src/webapp/pages/event-tracker/EventTrackerPage.tsx index 318e447b..8fa8ecf0 100644 --- a/src/webapp/pages/event-tracker/EventTrackerPage.tsx +++ b/src/webapp/pages/event-tracker/EventTrackerPage.tsx @@ -1,10 +1,18 @@ import React from "react"; - import i18n from "../../../utils/i18n"; import { Layout } from "../../components/layout/Layout"; +import { useParams } from "react-router-dom"; +import { FormSummary } from "../../components/form/form-summary/FormSummary"; // TODO: Add every section here export const EventTrackerPage: React.FC = React.memo(() => { - return ; + const { id } = useParams<{ + id: string; + }>(); + return ( + + + + ); }); diff --git a/src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts b/src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts index 7d94e664..931d1630 100644 --- a/src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts +++ b/src/webapp/pages/form-page/disease-outbreak-event/utils/mapEntityToInitialFormState.ts @@ -1,8 +1,9 @@ import { DiseaseOutbreakEventWithOptions } from "../../../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEventWithOptions"; +import { TeamMember } from "../../../../../domain/entities/incident-management-team/TeamMember"; import { Option } from "../../../../../domain/entities/Ref"; import { getFieldIdFromIdsDictionary } from "../../../../components/form/FormFieldsState"; import { FormState } from "../../../../components/form/FormState"; -import { UserOption } from "../../../../components/user-selector/UserSelector"; +import { User } from "../../../../components/user-selector/UserSelector"; import { Option as PresentationOption } from "../../../../components/utils/option"; export const diseaseOutbreakEventFieldIds = { @@ -37,6 +38,19 @@ export const diseaseOutbreakEventFieldIds = { notes: "notes", } as const; +export function mapTeamMemberToUser(teamMember: TeamMember): User { + return { + value: teamMember.username, + label: teamMember.name, + workPosition: teamMember.role?.name || "", + phone: teamMember.phone || "", + email: teamMember.email || "", + status: teamMember.status || "", + src: teamMember.photo?.toString(), + alt: teamMember.photo ? `Photo of ${teamMember.name}` : undefined, + }; +} + // TODO: Thinking for the future about generate this FormState by iterating over Object.Keys(diseaseOutbreakEvent) export function mapEntityToInitialFormState( diseaseOutbreakEventWithOptions: DiseaseOutbreakEventWithOptions @@ -52,16 +66,7 @@ export function mapEntityToInitialFormState( incidentStatus, } = options; - const teamMemberOptions: UserOption[] = teamMembers.map(tm => ({ - value: tm.username, - label: tm.name, - workPosition: tm.role?.name || "", - phone: tm.phone || "", - email: tm.email || "", - status: tm.status || "", - src: tm.photo?.toString(), - alt: tm.photo ? `Photo of ${tm.name}` : undefined, - })); + const teamMemberOptions: User[] = teamMembers.map(tm => mapTeamMemberToUser(tm)); const dataSourcesOptions: PresentationOption[] = mapToPresentationOptions(dataSources); const hazardTypesOptions: PresentationOption[] = mapToPresentationOptions(hazardTypes);