Skip to content

Commit

Permalink
feat: Event tracker summary form
Browse files Browse the repository at this point in the history
  • Loading branch information
9sneha-n committed Aug 21, 2024
1 parent 6e96266 commit 0198930
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 96 deletions.
4 changes: 2 additions & 2 deletions src/webapp/components/form/FormFieldsState.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -52,7 +52,7 @@ export type FormDateFieldState = FormFieldStateBase<Date | null> & {

export type FormAvatarFieldState = FormFieldStateBase<Maybe<string>> & {
type: "user";
options: UserOption[];
options: User[];
};

export type FormFieldState =
Expand Down
94 changes: 94 additions & 0 deletions src/webapp/components/form/form-summary/FormSummary.tsx
Original file line number Diff line number Diff line change
@@ -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<FormSummaryProps> = React.memo(props => {
const { id } = props;
const { formSummary } = useFormSummary(id);
const { goTo } = useRoutes();

const editButton = (
<Button
variant="outlined"
color="secondary"
onClick={() => {
goTo(RouteName.EDIT_FORM, { formType: "disease-outbreak-event", id });
}}
startIcon={<EditOutlined />}
>
{i18n.t("Edit Details")}
</Button>
);

return formSummary ? (
<>
<Section
title={formSummary.subTitle}
hasSeparator={true}
headerButtom={editButton}
titleVariant="secondary"
>
<SummaryContainer>
<SummaryColumn>
{formSummary.summary.map((labelWithValue, index) =>
index < 3 ? (
<Typography key={index}>
<Box fontWeight="bold" display="inline">
{labelWithValue.label}:
</Box>{" "}
{labelWithValue.value}
</Typography>
) : null
)}
</SummaryColumn>
<SummaryColumn>
{formSummary.summary.map((labelWithValue, index) =>
index < 3 ? null : (
<SummaryColumn key={index}>
<Typography>
<Box fontWeight="bold" display="inline">
{labelWithValue.label}:
</Box>{" "}
{labelWithValue.value}
</Typography>
</SummaryColumn>
)
)}
</SummaryColumn>
<SummaryColumn>
{formSummary.incidentManager && (
<UserCard selectedUser={formSummary.incidentManager} />
)}
</SummaryColumn>
</SummaryContainer>
</Section>
</>
) : (
<Loader />
);
});

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};
`;
83 changes: 83 additions & 0 deletions src/webapp/components/form/form-summary/useFormSummary.ts
Original file line number Diff line number Diff line change
@@ -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<User>;
};
export function useFormSummary(id: Id) {
const { compositionRoot } = useAppContext();
const [formSummary, setFormSummary] = useState<FormSummary>();

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 };
}
84 changes: 84 additions & 0 deletions src/webapp/components/user-selector/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -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<UserCardProps> = React.memo(props => {
const { selectedUser } = props;
return (
<AvatarContainer>
<AvatarCard avatarSize="medium" alt={selectedUser?.alt} src={selectedUser?.src}>
<Container>
<Content>
<TextBold>{selectedUser?.label}</TextBold>

{selectedUser?.workPosition && <Text>{selectedUser?.workPosition}</Text>}
</Content>

<Content>
<Text>{selectedUser?.phone}</Text>

<StyledLink href={`mailto:${selectedUser?.email}`}>
{selectedUser?.email}
</StyledLink>
</Content>

<div>
<TextBold>{i18n.t("Status: ", { nsSeparator: false })}</TextBold>

{selectedUser?.status && <Text>{selectedUser?.status}</Text>}
</div>
</Container>
</AvatarCard>
</AvatarContainer>
);
});
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};
}
`;
84 changes: 4 additions & 80 deletions src/webapp/components/user-selector/UserSelector.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,7 +19,7 @@ type UserSelectorProps = {
id: string;
selected?: string;
onChange: (value: string) => void;
options: UserOption[];
options: User[];
label?: string;
placeholder?: string;
disabled?: boolean;
Expand Down Expand Up @@ -69,35 +66,7 @@ export const UserSelector: React.FC<UserSelectorProps> = React.memo(props => {
/>
</SelectorContainer>

{selectedUser && (
<AvatarContainer>
<AvatarCard avatarSize="medium" alt={selectedUser?.alt} src={selectedUser?.src}>
<Container>
<Content>
<TextBold>{selectedUser?.label}</TextBold>

{selectedUser?.workPosition && (
<Text>{selectedUser?.workPosition}</Text>
)}
</Content>

<Content>
<Text>{selectedUser?.phone}</Text>

<StyledLink href={`mailto:${selectedUser?.email}`}>
{selectedUser?.email}
</StyledLink>
</Content>

<div>
<TextBold>{i18n.t("Status: ", { nsSeparator: false })}</TextBold>

{selectedUser?.status && <Text>{selectedUser?.status}</Text>}
</div>
</Container>
</AvatarCard>
</AvatarContainer>
)}
{selectedUser && <UserCard selectedUser={selectedUser} />}
</ComponentContainer>
);
});
Expand All @@ -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;
Expand All @@ -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};
}
`;
Loading

0 comments on commit 0198930

Please sign in to comment.