Skip to content

Commit

Permalink
Merge pull request #35 from EyeSeeTea/fix/some-more-fixes
Browse files Browse the repository at this point in the history
some more fixes
  • Loading branch information
bhavananarayanan authored Nov 7, 2024
2 parents ba2f3f2 + 0718f5a commit 1a5dc6e
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 39 deletions.
10 changes: 8 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-10-16T14:36:22.158Z\n"
"PO-Revision-Date: 2024-10-16T14:36:22.158Z\n"
"POT-Creation-Date: 2024-11-03T18:35:32.151Z\n"
"PO-Revision-Date: 2024-11-03T18:35:32.151Z\n"

msgid "Low"
msgstr ""
Expand Down Expand Up @@ -87,9 +87,15 @@ msgstr ""
msgid "Edit Action Plan"
msgstr ""

msgid "Event completed"
msgstr ""

msgid "Edit Details"
msgstr ""

msgid "Complete Event"
msgstr ""

msgid "Notes"
msgstr ""

Expand Down
2 changes: 2 additions & 0 deletions src/CompositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { GetConfigurationsUseCase } from "./domain/usecases/GetConfigurationsUse
import { ConfigurationsRepository } from "./domain/repositories/ConfigurationsRepository";
import { ConfigurationsD2Repository } from "./data/repositories/ConfigurationsD2Repository";
import { ConfigurationsTestRepository } from "./data/repositories/test/ConfigurationsTestRepository";
import { CompleteEventTrackerUseCase } from "./domain/usecases/CompleteEventTrackerUseCase";

export type CompositionRoot = ReturnType<typeof getCompositionRoot>;

Expand Down Expand Up @@ -106,6 +107,7 @@ function getCompositionRoot(repositories: Repositories) {
repositories.configurationsRepository,
repositories.teamMemberRepository
),
complete: new CompleteEventTrackerUseCase(repositories),
},
incidentActionPlan: {
get: new GetIncidentActionByIdUseCase(repositories),
Expand Down
43 changes: 40 additions & 3 deletions src/data/repositories/DiseaseOutbreakEventD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { D2Api } from "../../types/d2-api";
import { DiseaseOutbreakEventRepository } from "../../domain/repositories/DiseaseOutbreakEventRepository";
import { apiToFuture, FutureData } from "../api-futures";
import { DiseaseOutbreakEventBaseAttrs } from "../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent";
import { Id, ConfigLabel } from "../../domain/entities/Ref";
import { Id } from "../../domain/entities/Ref";
import {
mapDiseaseOutbreakEventToTrackedEntityAttributes,
mapTrackedEntityAttributesToDiseaseOutbreak,
Expand All @@ -13,6 +13,7 @@ import { getProgramTEAsMetadata } from "./utils/MetadataHelper";
import { assertOrError } from "./utils/AssertOrError";
import { Future } from "../../domain/entities/generic/Future";
import { getAllTrackedEntitiesAsync } from "./utils/getAllTrackedEntities";
import { D2TrackerEnrollment } from "@eyeseetea/d2-api/api/trackerEnrollments";

export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRepository {
constructor(private api: D2Api) {}
Expand Down Expand Up @@ -84,8 +85,44 @@ export class DiseaseOutbreakEventD2Repository implements DiseaseOutbreakEventRep
);
}

getConfigStrings(): FutureData<ConfigLabel[]> {
throw new Error("Method not implemented.");
complete(id: Id): FutureData<void> {
return apiToFuture(
this.api.tracker.enrollments.get({
fields: {
enrollment: true,
enrolledAt: true,
occurredAt: true,
},
trackedEntity: id,
enrolledBefore: new Date().toISOString(),
program: RTSL_ZEBRA_PROGRAM_ID,
orgUnit: RTSL_ZEBRA_ORG_UNIT_ID,
})
).flatMap(enrollmentResponse => {
const currentEnrollment = enrollmentResponse.instances[0];
const currentEnrollmentId = currentEnrollment?.enrollment;
if (!currentEnrollment || !currentEnrollmentId) {
return Future.error(new Error(`Enrollment not found for Event Tracker`));
}

const enrollment: D2TrackerEnrollment = {
...currentEnrollment,
orgUnit: RTSL_ZEBRA_ORG_UNIT_ID,
program: RTSL_ZEBRA_PROGRAM_ID,
trackedEntity: id,
status: "COMPLETED",
};

return apiToFuture(
this.api.tracker.post({ importStrategy: "UPDATE" }, { enrollments: [enrollment] })
).flatMap(response => {
if (response.status !== "OK") {
return Future.error(
new Error(`Error completing disease outbreak event : ${response.message}`)
);
} else return Future.success(undefined);
});
});
}

//TO DO : Implement delete/archive after requirement confirmation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { DiseaseOutbreakEventRepository } from "../../../domain/repositories/Dis
import { FutureData } from "../../api-futures";

export class DiseaseOutbreakEventTestRepository implements DiseaseOutbreakEventRepository {
complete(_id: Id): FutureData<void> {
return Future.success(undefined);
}
get(id: Id): FutureData<DiseaseOutbreakEventBaseAttrs> {
return Future.success({
id: id,
Expand Down
4 changes: 2 additions & 2 deletions src/domain/repositories/DiseaseOutbreakEventRepository.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FutureData } from "../../data/api-futures";
import { DiseaseOutbreakEventBaseAttrs } from "../entities/disease-outbreak-event/DiseaseOutbreakEvent";
import { ConfigLabel, Id } from "../entities/Ref";
import { Id } from "../entities/Ref";

export interface DiseaseOutbreakEventRepository {
get(id: Id): FutureData<DiseaseOutbreakEventBaseAttrs>;
getAll(): FutureData<DiseaseOutbreakEventBaseAttrs[]>;
save(diseaseOutbreak: DiseaseOutbreakEventBaseAttrs): FutureData<Id>;
getConfigStrings(): FutureData<ConfigLabel[]>;
complete(id: Id): FutureData<void>;
}
15 changes: 15 additions & 0 deletions src/domain/usecases/CompleteEventTrackerUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FutureData } from "../../data/api-futures";
import { Id } from "../entities/Ref";
import { DiseaseOutbreakEventRepository } from "../repositories/DiseaseOutbreakEventRepository";

export class CompleteEventTrackerUseCase {
constructor(
private options: {
diseaseOutbreakEventRepository: DiseaseOutbreakEventRepository;
}
) {}

public execute(id: Id): FutureData<void> {
return this.options.diseaseOutbreakEventRepository.complete(id);
}
}
18 changes: 3 additions & 15 deletions src/domain/usecases/GetDiseaseOutbreakByIdUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { OrgUnitRepository } from "../repositories/OrgUnitRepository";
import { RiskAssessmentRepository } from "../repositories/RiskAssessmentRepository";
import { RoleRepository } from "../repositories/RoleRepository";
import { TeamMemberRepository } from "../repositories/TeamMemberRepository";
import { getIncidentAction } from "./utils/incident-action/GetIncidentActionById";
import { getIncidentManagementTeamById } from "./utils/incident-management-team/GetIncidentManagementTeamById";
import { getAll } from "./utils/risk-assessment/GetRiskAssessmentById";

export class GetDiseaseOutbreakByIdUseCase {
Expand Down Expand Up @@ -62,18 +60,8 @@ export class GetDiseaseOutbreakByIdUseCase {
this.options.riskAssessmentRepository,
configurations
),
incidentAction: getIncidentAction(
id,
this.options.incidentActionRepository,
configurations
),
incidentManagementTeam: getIncidentManagementTeamById(
id,
this.options,
configurations
),
roles: this.options.roleRepository.getAll(),
}).flatMap(({ riskAssessment, incidentAction, incidentManagementTeam, roles }) => {
}).flatMap(({ riskAssessment, roles }) => {
return this.options.incidentManagementTeamRepository
.getIncidentManagementTeamMember(incidentManagerName, id, roles)
.flatMap(incidentManager => {
Expand All @@ -86,8 +74,8 @@ export class GetDiseaseOutbreakByIdUseCase {
notificationSource: notificationSource,
incidentManager: incidentManager,
riskAssessment: riskAssessment,
incidentActionPlan: incidentAction,
incidentManagementTeam: incidentManagementTeam,
incidentActionPlan: undefined, //IAP is fetched on menu click. It is not needed here.
incidentManagementTeam: undefined, //IMT is fetched on menu click. It is not needed here.
});
return Future.success(diseaseOutbreakEvent);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import { Box, Button, Typography } from "@material-ui/core";
import { UserCard } from "../../user-selector/UserCard";
import { RouteName, useRoutes } from "../../../hooks/useRoutes";
import { EditOutlined } from "@material-ui/icons";
import { CheckOutlined } from "@material-ui/icons";
import { Loader } from "../../loader/Loader";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import { FormSummaryData } from "../../../pages/event-tracker/useDiseaseOutbreakEvent";
import { Maybe } from "../../../../utils/ts-utils";
import { FormType } from "../../../pages/form-page/FormPage";
import { Id } from "../../../../domain/entities/Ref";
import { useAppContext } from "../../../contexts/app-context";

export type FormSummaryProps = {
export type EventTrackerFormSummaryProps = {
id: Id;
formType: FormType;
formSummary: Maybe<FormSummaryData>;
Expand All @@ -22,7 +24,8 @@ export type FormSummaryProps = {

const ROW_COUNT = 3;

export const FormSummary: React.FC<FormSummaryProps> = React.memo(props => {
export const EventTrackerFormSummary: React.FC<EventTrackerFormSummaryProps> = React.memo(props => {
const { compositionRoot } = useAppContext();
const { id, formType, formSummary, summaryError } = props;
const { goTo } = useRoutes();
const snackbar = useSnackbar();
Expand All @@ -38,6 +41,18 @@ export const FormSummary: React.FC<FormSummaryProps> = React.memo(props => {
goTo(RouteName.EDIT_FORM, { formType: formType, id: id });
}, [formType, goTo, id]);

const onCompleteClick = useCallback(() => {
compositionRoot.diseaseOutbreakEvent.complete.execute(id).run(
() => {
snackbar.success(i18n.t("Event completed"));
},
err => {
snackbar.error(i18n.t(`Failed to complete event: ${err.message}`));
console.error(err);
}
);
}, [compositionRoot, id, snackbar]);

const editButton = (
<Button
variant="outlined"
Expand All @@ -49,6 +64,17 @@ export const FormSummary: React.FC<FormSummaryProps> = React.memo(props => {
</Button>
);

const completeButton = (
<Button
variant="outlined"
color="secondary"
onClick={onCompleteClick}
startIcon={<CheckOutlined />}
>
{i18n.t("Complete Event")}
</Button>
);

const getSummaryColumn = useCallback((index: number, label: string, value: string) => {
return (
<Typography key={index}>
Expand All @@ -66,6 +92,7 @@ export const FormSummary: React.FC<FormSummaryProps> = React.memo(props => {
title={formSummary.subTitle}
hasSeparator={true}
headerButton={editButton}
secondaryHeaderButton={completeButton}
titleVariant="secondary"
>
<SummaryContainer>
Expand Down
12 changes: 10 additions & 2 deletions src/webapp/components/section/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type SectionProps = {
lastUpdated?: string;
children: React.ReactNode;
headerButton?: React.ReactNode;
secondaryHeaderButton?: React.ReactNode;
hasSeparator?: boolean;
titleVariant?: "primary" | "secondary";
};
Expand All @@ -18,6 +19,7 @@ export const Section: React.FC<SectionProps> = React.memo(
title = "",
lastUpdated = "",
headerButton,
secondaryHeaderButton,
hasSeparator = false,
children,
titleVariant = "primary",
Expand All @@ -40,15 +42,21 @@ export const Section: React.FC<SectionProps> = React.memo(
) : null}
</TitleContainer>

{headerButton ? <div>{headerButton}</div> : null}
<ButtonContainer>
{headerButton ? <div>{headerButton}</div> : null}
{secondaryHeaderButton ? <div>{secondaryHeaderButton}</div> : null}
</ButtonContainer>
</Header>

<Content>{children}</Content>
</SectionContainer>
);
}
);

const ButtonContainer = styled.div`
display: flex;
gap: 5px;
`;
const SectionContainer = styled.section<{ $hasSeparator?: boolean }>`
width: 100%;
margin-block-end: ${props => (props.$hasSeparator ? "0" : "24px")};
Expand Down
43 changes: 37 additions & 6 deletions src/webapp/components/table/BasicTable.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import React from "react";
import { Table, TableBody, TableCell, TableHead, TableRow } from "@material-ui/core";
import React, { useCallback, useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TableSortLabel,
} from "@material-ui/core";
import styled from "styled-components";
import { Maybe } from "../../../utils/ts-utils";
import i18n from "../../../utils/i18n";
import { Option } from "../utils/option";
import { Cell } from "./Cell";
import _c from "../../../domain/entities/generic/Collection";

const noop = () => {};

Expand Down Expand Up @@ -34,18 +42,41 @@ interface BasicTableProps {
rows: TableRowType[];
onChange?: (cell: Maybe<string>, rowIndex: number, column: TableColumn["value"]) => void;
showRowIndex?: boolean;
onOrderBy?: (direction: "asc" | "desc") => void;
}

export const BasicTable: React.FC<BasicTableProps> = React.memo(
({ columns, rows, onChange = noop, showRowIndex = false }) => {
({ columns, rows, onChange = noop, showRowIndex = false, onOrderBy }) => {
const [order, setOrder] = useState<"asc" | "desc">();

const orderBy = useCallback(() => {
const updatedOrder = order === "asc" ? "desc" : "asc";
setOrder(prevOrder => (prevOrder === "asc" ? "desc" : "asc"));
onOrderBy && onOrderBy(updatedOrder);
}, [onOrderBy, order]);

return (
<StyledTable stickyHeader>
<TableHead>
<TableRow>
{showRowIndex && <TableCell />}
{columns.map(({ value, label }) => (
<TableCell key={value}>{i18n.t(label)}</TableCell>
))}
{columns.map(({ value, label }) =>
label === "Assessment Date" ? (
<TableCell key={value} sortDirection={order}>
<TableSortLabel
direction={order}
onClick={orderBy}
active={true}
>
{label}
</TableSortLabel>
</TableCell>
) : (
<TableCell key={value} sortDirection={order}>
{i18n.t(label)}
</TableCell>
)
)}
</TableRow>
</TableHead>
<TableBody>
Expand Down
Loading

0 comments on commit 1a5dc6e

Please sign in to comment.