Skip to content

Commit

Permalink
Add create disease outbreak event form in presentation layer
Browse files Browse the repository at this point in the history
  • Loading branch information
anagperal committed Jul 9, 2024
1 parent 7581407 commit 90c5b8c
Show file tree
Hide file tree
Showing 23 changed files with 1,840 additions and 64 deletions.
31 changes: 25 additions & 6 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-07-01T02:04:44.570Z\n"
"PO-Revision-Date: 2024-07-01T02:04:44.570Z\n"
"POT-Creation-Date: 2024-07-09T06:45:31.820Z\n"
"PO-Revision-Date: 2024-07-09T06:45:31.820Z\n"

msgid "Low"
msgstr ""
Expand All @@ -32,10 +32,14 @@ msgstr ""
msgid "Within a province with more than one district affected"
msgstr ""

msgid "More than one province affected with high threat of spread locally and internationally"
msgid ""
"More than one province affected with high threat of spread locally and "
"internationally"
msgstr ""

msgid "Available within the district with support from provincial and national level"
msgid ""
"Available within the district with support from provincial and national "
"level"
msgstr ""

msgid "Available within the province with minimal support from national level"
Expand All @@ -59,18 +63,24 @@ msgstr ""
msgid "Add new option"
msgstr ""

msgid "Indicates required"
msgid "Error displaying this field in the form"
msgstr ""

msgid "Save"
msgid "Indicates required"
msgstr ""

msgid "Cancel"
msgstr ""

msgid "Save"
msgstr ""

msgid "Create Event"
msgstr ""

msgid "Status: "
msgstr ""

msgid "Close"
msgstr ""

Expand All @@ -89,6 +99,15 @@ msgstr ""
msgid "Event Tracker"
msgstr ""

msgid "Page Not Found"
msgstr ""

msgid "Create Event form cannot be loaded"
msgstr ""

msgid "An error occurred while loading Create Event form"
msgstr ""

msgid "Incident Action Plan"
msgstr ""

Expand Down
21 changes: 18 additions & 3 deletions i18n/es.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: i18next-conv\n"
"POT-Creation-Date: 2024-07-01T02:04:44.570Z\n"
"POT-Creation-Date: 2024-07-09T06:45:31.820Z\n"
"PO-Revision-Date: 2018-10-25T09:02:35.143Z\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
Expand Down Expand Up @@ -62,18 +62,24 @@ msgstr ""
msgid "Add new option"
msgstr ""

msgid "Indicates required"
msgid "Error displaying this field in the form"
msgstr ""

msgid "Save"
msgid "Indicates required"
msgstr ""

msgid "Cancel"
msgstr ""

msgid "Save"
msgstr ""

msgid "Create Event"
msgstr ""

msgid "Status: "
msgstr ""

msgid "Close"
msgstr ""

Expand All @@ -92,6 +98,15 @@ msgstr ""
msgid "Event Tracker"
msgstr ""

msgid "Page Not Found"
msgstr ""

msgid "Create Event form cannot be loaded"
msgstr ""

msgid "An error occurred while loading Create Event form"
msgstr ""

msgid "Incident Action Plan"
msgstr ""

Expand Down
4 changes: 4 additions & 0 deletions src/CompositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { UserD2Repository } from "./data/repositories/UserD2Repository";
import { UserTestRepository } from "./data/repositories/UserTestRepository";
import { UserRepository } from "./domain/repositories/UserRepository";
import { GetCurrentUserUseCase } from "./domain/usecases/GetCurrentUserUseCase";
import { GetDiseaseOutbreakEventUseCase } from "./domain/usecases/GetDiseaseOutbreakEventUseCase";
import { D2Api } from "./types/d2-api";

export type CompositionRoot = ReturnType<typeof getCompositionRoot>;
Expand All @@ -15,6 +16,9 @@ function getCompositionRoot(repositories: Repositories) {
users: {
getCurrent: new GetCurrentUserUseCase(repositories.usersRepository),
},
diseaseOutbreakEvent: {
get: new GetDiseaseOutbreakEventUseCase(),
},
};
}

Expand Down
9 changes: 9 additions & 0 deletions src/domain/usecases/GetDiseaseOutbreakEventUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { FutureData } from "../../data/api-futures";
import { DiseaseOutbreakEvent } from "../entities/DiseaseOutbreakEvent";
import { Future } from "../entities/generic/Future";

export class GetDiseaseOutbreakEventUseCase {
public execute(id: string): FutureData<DiseaseOutbreakEvent> {
return Future.success({} as DiseaseOutbreakEvent);
}
}
17 changes: 14 additions & 3 deletions src/utils/tests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { ReactNode } from "react";
import { AppContext, AppContextState } from "../webapp/contexts/app-context";
import { getTestCompositionRoot } from "../CompositionRoot";
import { createAdminUser } from "../domain/entities/__tests__/userFixtures";
import { MuiThemeProvider } from "@material-ui/core";
import { ThemeProvider } from "styled-components";
import OldMuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import muiThemeLegacy from "../webapp/pages/app/themes/dhis2-legacy.theme";
import { muiTheme } from "../webapp/pages/app/themes/dhis2.theme";

export function getTestContext() {
const context: AppContextState = {
Expand All @@ -18,8 +23,14 @@ export function getReactComponent(children: ReactNode): RenderResult {
const context = getTestContext();

return render(
<AppContext.Provider value={context}>
<SnackbarProvider>{children}</SnackbarProvider>
</AppContext.Provider>
<MuiThemeProvider theme={muiTheme}>
<ThemeProvider theme={muiTheme}>
<OldMuiThemeProvider muiTheme={muiThemeLegacy}>
<AppContext.Provider value={context}>
<SnackbarProvider>{children}</SnackbarProvider>
</AppContext.Provider>
</OldMuiThemeProvider>
</ThemeProvider>
</MuiThemeProvider>
);
}
164 changes: 164 additions & 0 deletions src/webapp/components/form/FieldWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React, { useEffect, useState } from "react";

import i18n from "../../../utils/i18n";
import { TextInput } from "../text-input/TextInput";
import { MemberSelector } from "../member-selector/MemberSelector";
import { FormField, FormFieldState } from "./FormFieldState";
import { useDebounce } from "../../hooks/useDebounce";
import { MultipleSelector } from "../selector/MultipleSelector";
import { Selector } from "../selector/Selector";
import { RadioButtonsGroup } from "../radio-buttons-group/RadioButtonsGroup";
import { TextArea } from "../text-input/TextArea";
import { DatePicker } from "../date-picker/DatePicker";
import { Checkbox } from "../checkbox/Checkbox";

export type FieldWidgetProps = {
onChange: (updatedField: FormFieldState) => void;
field: FormFieldState;
disabled?: boolean;
};

export const FieldWidget: React.FC<FieldWidgetProps> = React.memo(props => {
const { field, onChange, disabled = false } = props;
const [textFieldValue, setTextFieldValue] = useState<string>(
field.type === "text" ? field.value : ""
);
const debouncedTextFieldValue = useDebounce(textFieldValue);

useEffect(() => {
if (debouncedTextFieldValue !== field.value && field.type === "text") {
onChange(FormField.updateState(field, debouncedTextFieldValue));
}
}, [debouncedTextFieldValue, field, onChange]);

switch (field.type) {
case "select": {
return field.multiple && field.options && Array.isArray(field.value) ? (
<MultipleSelector
id={field.id}
placeholder={field.placeholder}
label={field.label}
selected={field.value}
onChange={newValue => onChange(FormField.updateState(field, newValue))}
options={field.options}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
) : !field.multiple && field.options && typeof field.value === "string" ? (
<Selector
id={field.id}
placeholder={field.placeholder}
label={field.label}
selected={field.value}
onChange={newValue => onChange(FormField.updateState(field, newValue))}
options={field.options}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
) : (
<span>{i18n.t("Error displaying this field in the form")}</span>
);
}

case "radio": {
return field.options && typeof field.value === "string" ? (
<RadioButtonsGroup
id={field.id}
label={field.label}
selected={field.value}
onChange={event => onChange(FormField.updateState(field, event.target.value))}
options={field.options}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
) : (
<span>{i18n.t("Error displaying this field in the form")}</span>
);
}

case "text":
return field.multiline ? (
<TextArea
id={field.id}
label={field.label}
value={textFieldValue}
onChange={newValue => setTextFieldValue(newValue)}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
) : (
<TextInput
id={field.id}
label={field.label}
value={textFieldValue}
onChange={event => setTextFieldValue(event.target.value)}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
);

case "date":
return (
<DatePicker
id={field.id}
label={field.label}
value={field.value}
onChange={newValue => onChange(FormField.updateState(field, newValue))}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
);

case "boolean": {
return (
<Checkbox
id={field.id}
label={field.label}
checked={field.value}
onChange={newValue => onChange(FormField.updateState(field, newValue))}
helperText={field.helperText}
disabled={disabled}
/>
);
}

case "member": {
return (
<MemberSelector
id={field.id}
placeholder={field.placeholder}
label={field.label}
selected={field.value}
onChange={newValue => onChange(FormField.updateState(field, newValue))}
options={field.options}
helperText={field.helperText}
errorText={field.errors ? field.errors.join("/n") : ""}
error={field.errors && field.errors.length > 0}
required={field.required && field.showIsRequired}
disabled={disabled}
/>
);
}

default:
return <span>{i18n.t("Error displaying this field in the form")}</span>;
}
});
51 changes: 51 additions & 0 deletions src/webapp/components/form/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from "react";

import { useForm } from "./useForm";
import { FormState } from "./FormState";
import { FormLayout } from "./FormLayout";
import { FormSection } from "./FormSection";
import { FormFieldState } from "./FormFieldState";
import { Layout } from "../layout/Layout";

export type FormProps = {
formState: FormState;
onFormChange: (newFormState: FormState, updatedField: FormFieldState) => void;
onSave: () => void;
onCancel?: () => void;
};

export const Form: React.FC<FormProps> = React.memo(props => {
const { formState, onFormChange, onSave, onCancel } = props;

const { formLocalState, handleUpdateFormField } = useForm(formState, onFormChange);

return (
<Layout title={formLocalState.title} subtitle={formLocalState.subtitle} hideSideBarOptions>
<FormLayout
title={formLocalState.titleDescripton}
subtitle={formLocalState.subtitleDescripton}
onSave={onSave}
onCancel={onCancel}
saveLabel={formLocalState.saveButtonLabel}
disableSave={!formLocalState.isValid}
>
{formLocalState.sections.map(section => {
if (!section.isVisible) return null;

return (
<FormSection
key={section.id}
title={section.title}
hasSeparator
required={section.required}
direction={section.direction}
subsections={section.subsections}
fields={section.fields}
onUpdateField={handleUpdateFormField}
/>
);
})}
</FormLayout>
</Layout>
);
});
Loading

0 comments on commit 90c5b8c

Please sign in to comment.