Skip to content

Commit

Permalink
feat: edit existing surveys
Browse files Browse the repository at this point in the history
  • Loading branch information
9sneha-n committed Oct 20, 2023

Verified

This commit was signed with the committer’s verified signature.
RodrigoCMoraes RodrigoCMoraes
1 parent 0a536a6 commit c2aa35e
Showing 18 changed files with 219 additions and 68 deletions.
10 changes: 2 additions & 8 deletions i18n/en.pot
Original file line number Diff line number Diff line change
@@ -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: 2023-10-19T19:00:24.155Z\n"
"PO-Revision-Date: 2023-10-19T19:00:24.155Z\n"
"POT-Creation-Date: 2023-10-20T08:18:28.658Z\n"
"PO-Revision-Date: 2023-10-20T08:18:28.658Z\n"

msgid "WHO privacy policy"
msgstr ""
@@ -23,12 +23,6 @@ msgstr ""
msgid "Search"
msgstr ""

msgid "Importing data and applying validation rules"
msgstr ""

msgid "This might take several minutes, do not refresh the page or press back."
msgstr ""

msgid "Cancel"
msgstr ""

2 changes: 2 additions & 0 deletions src/CompositionRoot.ts
Original file line number Diff line number Diff line change
@@ -22,6 +22,7 @@ import { SurveyD2Repository } from "./data/repositories/SurveyFormD2Repository";
import { SurveyTestRepository } from "./data/repositories/testRepositories/SurveyFormTestRepository";
import { SaveFormDataUseCase } from "./domain/usecases/SaveFormDataUseCase";
import { GetAllSurveysUseCase } from "./domain/usecases/GetAllSurveysUseCase";
import { GetPopulatedSurveyUseCase } from "./domain/usecases/GetPopulatedSurveyUseCase";

export type CompositionRoot = ReturnType<typeof getCompositionRoot>;

@@ -49,6 +50,7 @@ function getCompositionRoot(repositories: Repositories) {
},
surveys: {
getForm: new GetSurveyUseCase(repositories.surveyFormRepository),
getPopulatedForm: new GetPopulatedSurveyUseCase(repositories.surveyFormRepository),
saveFormData: new SaveFormDataUseCase(repositories.surveyFormRepository),
getSurveys: new GetAllSurveysUseCase(repositories.surveyFormRepository),
},
18 changes: 15 additions & 3 deletions src/data/repositories/SurveyFormD2Repository.ts
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ const SURVEY_COMPLETED_DATAELEMENT_ID = "KuGRIx3I16f";
export class SurveyD2Repository implements SurveyRepository {
constructor(private api: D2Api) {}

getForm(programId: Id): FutureData<Questionnaire> {
getForm(programId: Id, event: D2TrackerEvent | undefined): FutureData<Questionnaire> {
return apiToFuture(
this.api.request<EventProgramMetadata>({
method: "get",
@@ -47,7 +47,8 @@ export class SurveyD2Repository implements SurveyRepository {
const questions: Question[] = this.mapProgramDataElementToQuestions(
section.dataElements,
resp.dataElements,
resp.options
resp.options,
event
);

return {
@@ -65,7 +66,8 @@ export class SurveyD2Repository implements SurveyRepository {
questions: this.mapProgramDataElementToQuestions(
programDataElements,
resp.dataElements,
resp.options
resp.options,
event
),
isVisible: true,
},
@@ -258,4 +260,14 @@ export class SurveyD2Repository implements SurveyRepository {
return Future.success(surveys);
});
}

getSurveyById(eventId: string): FutureData<D2TrackerEvent> {
return apiToFuture(
this.api.tracker.events.getById(eventId, {
fields: { $all: true },
})
).flatMap(resp => {
return Future.success(resp);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { D2TrackerEvent } from "@eyeseetea/d2-api/api/trackerEvents";
import { ImportStrategy, TrackerEventsPostRequest } from "../../../domain/entities/EventProgram";
import { Future } from "../../../domain/entities/generic/Future";
import { Questionnaire } from "../../../domain/entities/Questionnaire";
@@ -37,4 +38,7 @@ export class SurveyTestRepository implements SurveyRepository {
console.debug(programId, orgUnitId);
throw new Error("Method not implemented.");
}
getSurveyById(eventId: string): FutureData<D2TrackerEvent> {
throw new Error("Method not implemented." + eventId);
}
}
4 changes: 3 additions & 1 deletion src/domain/repositories/SurveyRepository.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { D2TrackerEvent } from "@eyeseetea/d2-api/api/trackerEvents";
import { FutureData } from "../../data/api-futures";
import { ImportStrategy, TrackerEventsPostRequest } from "../entities/EventProgram";
import { Questionnaire } from "../entities/Questionnaire";
import { Id } from "../entities/Ref";
import { Survey } from "../entities/Survey";

export interface SurveyRepository {
getForm(programId: Id): FutureData<Questionnaire>;
getForm(programId: Id, event: D2TrackerEvent | undefined): FutureData<Questionnaire>;
saveFormData(events: TrackerEventsPostRequest, action: ImportStrategy): FutureData<void>;
getSurveys(programId: Id, orgUnitId: Id): FutureData<Survey[]>;
getSurveyById(eventId: string): FutureData<D2TrackerEvent>;
}
27 changes: 27 additions & 0 deletions src/domain/usecases/GetPopulatedSurveyUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Id } from "@eyeseetea/d2-api";
import { FutureData } from "../../data/api-futures";
import { PPS_SURVEY_FORM_ID } from "../../data/repositories/SurveyFormD2Repository";
import { Future } from "../entities/generic/Future";
import { Questionnaire } from "../entities/Questionnaire";
import { SURVEY_FORM_TYPES } from "../entities/Survey";

import { SurveyRepository } from "../repositories/SurveyRepository";

export class GetPopulatedSurveyUseCase {
constructor(private surveyReporsitory: SurveyRepository) {}

public execute(eventId: Id, surveyType: SURVEY_FORM_TYPES): FutureData<Questionnaire> {
let programId = "";
switch (surveyType) {
case "PPSSurveyForm":
programId = PPS_SURVEY_FORM_ID;
break;
default:
return Future.error(new Error("Unknown survey type"));
}

return this.surveyReporsitory.getSurveyById(eventId).flatMap(event => {
return this.surveyReporsitory.getForm(programId, event);
});
}
}
2 changes: 1 addition & 1 deletion src/domain/usecases/GetSurveyUseCase.ts
Original file line number Diff line number Diff line change
@@ -19,6 +19,6 @@ export class GetSurveyUseCase {
return Future.error(new Error("Unknown survey type"));
}

return this.surveyReporsitory.getForm(programId);
return this.surveyReporsitory.getForm(programId, undefined);
}
}
63 changes: 34 additions & 29 deletions src/domain/usecases/SaveFormDataUseCase.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Id } from "@eyeseetea/d2-api";
import { DataValue, Id } from "@eyeseetea/d2-api";
import { D2TrackerEvent } from "@eyeseetea/d2-api/api/trackerEvents";
import { FutureData } from "../../data/api-futures";
import { Future } from "../entities/generic/Future";
@@ -14,7 +14,8 @@ export class SaveFormDataUseCase {
public execute(
surveyType: SURVEY_FORM_TYPES,
questionnaire: Questionnaire,
orgUnitId: Id
orgUnitId: Id,
eventId: string | undefined = undefined
): FutureData<void> {
let programId = "";
switch (surveyType) {
@@ -25,17 +26,22 @@ export class SaveFormDataUseCase {
return Future.error(new Error("Unknown survey type"));
}

const event = this.mapQuestionnaireToEvent(questionnaire, orgUnitId, programId);

return this.surveyReporsitory.saveFormData({ events: [event] }, "CREATE_AND_UPDATE");
return this.mapQuestionnaireToEvent(questionnaire, orgUnitId, programId, eventId).flatMap(
event => {
return this.surveyReporsitory.saveFormData(
{ events: [event] },
"CREATE_AND_UPDATE"
);
}
);
}

private mapQuestionnaireToEvent(
questionnaire: Questionnaire,
orgUnitId: string,
programId: Id
// eventId: string | undefined = undefined
): D2TrackerEvent {
programId: Id,
eventId: string | undefined = undefined
): FutureData<D2TrackerEvent> {
const questions = questionnaire.sections.flatMap(section => section.questions);

const dataValues = _(
@@ -58,27 +64,26 @@ export class SaveFormDataUseCase {
.compact()
.value();

// if (eventId) {
// return this.surveyReporsitory.getEventById(eventId).flatMap(event => {
// const updatedEvent: D2TrackerEvent = {
// ...event,
// status: eventStatus,
// dataValues: dataValues as DataValue[],
// };
// return Future.success({ event: updatedEvent, confidential, message });
// });
// } else {
const event: D2TrackerEvent = {
event: "",
orgUnit: orgUnitId,
program: programId,
status: "ACTIVE",
occurredAt: new Date().toISOString().split("T")?.at(0) || "",
//@ts-ignore
dataValues: dataValues,
};
return event;
if (eventId) {
return this.surveyReporsitory.getSurveyById(eventId).flatMap(event => {
const updatedEvent: D2TrackerEvent = {
...event,

// }
dataValues: dataValues as DataValue[],
};
return Future.success(updatedEvent);
});
} else {
const event: D2TrackerEvent = {
event: "",
orgUnit: orgUnitId,
program: programId,
status: "ACTIVE",
occurredAt: new Date().toISOString().split("T")?.at(0) || "",
//@ts-ignore
dataValues: dataValues,
};
return Future.success(event);
}
}
}
54 changes: 54 additions & 0 deletions src/webapp/components/action-menu-button/ActionMenuButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { IconButton, Menu, MenuItem } from "@material-ui/core";
import MoreVert from "@material-ui/icons/MoreVert";
import * as React from "react";

interface ActionMenuProps {
options: string[];
optionClickHandler: { option: string; handler: () => void }[];
}

export const ActionMenuButton: React.FC<ActionMenuProps> = ({ options, optionClickHandler }) => {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};

const menuItemClick = (option: string) => {
optionClickHandler.find(optionClick => optionClick.option === option)?.handler();
handleClose();
};

return (
<div>
<IconButton
aria-label="more"
id="long-button"
aria-controls={open ? "long-menu" : undefined}
aria-expanded={open ? "true" : undefined}
aria-haspopup="true"
onClick={handleClick}
>
<MoreVert />
</IconButton>
<Menu
id="long-menu"
MenuListProps={{
"aria-labelledby": "long-button",
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
>
{options.map(option => (
<MenuItem key={option} onClick={() => menuItemClick(option)}>
{option}
</MenuItem>
))}
</Menu>
</div>
);
};
26 changes: 23 additions & 3 deletions src/webapp/components/survey-list/SurveyList.tsx
Original file line number Diff line number Diff line change
@@ -12,17 +12,28 @@ import {
Backdrop,
CircularProgress,
} from "@material-ui/core";
import { NavLink } from "react-router-dom";
import { NavLink, useHistory } from "react-router-dom";

import styled from "styled-components";
import { Id } from "../../../domain/entities/Ref";
import { useSurveys } from "../../hooks/useSurveys";
import { palette } from "../../pages/app/themes/dhis2.theme";
import { ActionMenuButton } from "../action-menu-button/ActionMenuButton";

import { CustomCard } from "../custom-card/CustomCard";
import { StyledLoaderContainer } from "../survey/SurveyForm";

export const SurveyList: React.FC = () => {
const surveyType = "PPSSurveyForm"; //TO DO: Get from Props.
const { surveys, loading } = useSurveys(surveyType);
const history = useHistory();

const editSurvey = (surveyId: Id) => {
history.push({
pathname: `/survey/${surveyId}`,
state: { surveyId: surveyId },
});
};

return (
<ContentWrapper>
@@ -86,13 +97,22 @@ export const SurveyList: React.FC = () => {
{surveys.map(survey => (
<TableRow key={survey.id}>
<TableCell>
{survey.startDate?.toUTCString() || ""}
{survey.startDate?.toDateString() || ""}
</TableCell>
<TableCell>{survey.status}</TableCell>
<TableCell>{survey.surveyType}</TableCell>
<TableCell>{survey.assignedOrgUnit.name}</TableCell>
<TableCell style={{ opacity: 0.5 }}>
<Button>Edit</Button>
<ActionMenuButton
options={["Edit", "Assign Country"]}
optionClickHandler={[
{
option: "Edit",
handler: () =>
editSurvey(survey.id),
},
]}
/>
</TableCell>
</TableRow>
))}
1 change: 1 addition & 0 deletions src/webapp/components/survey-questions/QuestionWidget.tsx
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ export const QuestionWidget: React.FC<QuestionWidgetProps> = React.memo(props =>
case "date":
return (
<DatePickerWidget
name={question.id}
value={question.value}
onChange={value => onChange(update(question, value))}
disabled={disabled}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DatePicker } from "@eyeseetea/d2-ui-components";
import React from "react";
// @ts-ignore
import { Maybe } from "../../../../types/utils";
import { BaseWidgetProps } from "./BaseWidget";
import { DatePicker } from "material-ui";

export interface DatePickerWidgetProps extends BaseWidgetProps<Date> {
value: Maybe<Date>;
name: string;
}

const DatePickerWidget: React.FC<DatePickerWidgetProps> = props => {
@@ -23,9 +24,11 @@ const DatePickerWidget: React.FC<DatePickerWidgetProps> = props => {
);
return (
<DatePicker
key={props.name}
name={props.name}
value={stateValue}
disabled={props.disabled}
onChange={(_e, newValue) => notifyChange(newValue)}
onChange={newValue => notifyChange(newValue)}
/>
);
};
Loading

0 comments on commit c2aa35e

Please sign in to comment.