Skip to content

Commit 5fdb6c8

Browse files
Asam237elhmn
authored andcommitted
Merge pull request #176 from osscameroon/post_alert
Post alert
2 parents 47af2c5 + ac6769e commit 5fdb6c8

24 files changed

+1864
-27324
lines changed

frontend/package-lock.json

+256-26,895
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@
55
"dependencies": {
66
"@emotion/react": "^11.10.4",
77
"@emotion/styled": "^11.10.4",
8+
"@mui/x-date-pickers": "^5.0.9",
89
"@types/nprogress": "^0.2.0",
910
"@types/react-helmet": "^6.1.5",
1011
"@types/react-paginate": "^7.1.1",
1112
"@types/react-router-config": "^5.0.6",
1213
"axios": "^0.22.0",
1314
"eslint": "^7.11.0",
15+
"formik": "^2.2.9",
1416
"i18next": "^21.9.0",
1517
"i18next-browser-languagedetector": "^6.1.5",
1618
"i18next-http-backend": "^1.4.1",
1719
"install": "^0.13.0",
1820
"leaflet": "^1.7.1",
21+
"moment": "^2.29.4",
1922
"notistack": "^2.0.8",
2023
"npm": "^8.19.2",
2124
"nprogress": "^0.2.0",
@@ -34,7 +37,8 @@
3437
"react-search-field": "^2.0.1",
3538
"react-search-input": "^0.11.3",
3639
"recoil": "^0.7.2",
37-
"recoil-persist": "^4.2.0"
40+
"recoil-persist": "^4.2.0",
41+
"yup": "^0.32.11"
3842
},
3943
"scripts": {
4044
"start": "craco start",
@@ -75,5 +79,8 @@
7579
"typescript": "^4.1.2",
7680
"web-vitals": "^1.0.1"
7781
},
78-
"proxy": "http://localhost:8000"
82+
"proxy": "http://localhost:8000",
83+
"resolutions": {
84+
"react-error-overlay": "6.0.9"
85+
}
7986
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import { useTranslation } from "react-i18next";
3+
import {
4+
Box,
5+
Button,
6+
CircularProgress,
7+
Dialog,
8+
DialogActions,
9+
DialogContent,
10+
DialogTitle,
11+
Grid,
12+
Typography
13+
} from "@mui/material";
14+
import { Form, Formik } from "formik";
15+
import * as Yup from "yup";
16+
import { FormikDatePickerField } from "../../form/FormikDatePickerField";
17+
import moment from "moment";
18+
import { useRecoilValue } from "recoil";
19+
import { CityService } from "../../services/api";
20+
import { FormikSelectField } from "../../form/FormikSelectField";
21+
import { CityType, DistrictType, PostAlertPayload, RegionType } from "../../types";
22+
import axios, { AxiosError } from "axios";
23+
import { globalUrls } from "../../services/api/urls";
24+
import { useSnackbar } from "notistack";
25+
26+
interface CreateReportModalProps {
27+
open: boolean,
28+
onClose: any
29+
}
30+
31+
function capitalizeFirstLetter(text: string) {
32+
return text.charAt(0).toUpperCase() + text.slice(1);
33+
}
34+
35+
interface FormState {
36+
region_id: number | null,
37+
city_id: number | null,
38+
}
39+
40+
const CreateReportModal = (props: CreateReportModalProps) => {
41+
const { t, i18n: { language } } = useTranslation();
42+
moment.locale(language);
43+
const { open, onClose } = props;
44+
const submitBtnRef = useRef(null);
45+
const [loading, setLoading] = useState(false);
46+
const { enqueueSnackbar } = useSnackbar();
47+
48+
const [formState, setFormState] = useState<FormState>({
49+
region_id: null,
50+
city_id: null
51+
});
52+
53+
const [cities, setCities] = useState<CityType[]>([]);
54+
const [districts, setDistricts] = useState<DistrictType[]>([]);
55+
56+
const regionResponse = useRecoilValue(CityService.getRegions);
57+
const citiesResponse = useRecoilValue(CityService.getCities);
58+
const districtResponse = useRecoilValue(CityService.getDistricts);
59+
60+
61+
const regions: RegionType[] = regionResponse?.data ?? [];
62+
63+
useEffect(() => {
64+
setCities((citiesResponse?.data ?? []).filter((item: CityType) => item.region_id == formState.region_id));
65+
setDistricts((districtResponse?.data ?? []).filter((item: DistrictType) => item.city_id == formState.city_id));
66+
}, [formState]);
67+
68+
69+
const validations = {
70+
type: Yup.string().required(t("global_field_require")),
71+
date: Yup.string().required(t("global_field_require")),
72+
region_id: Yup.string().required(t("global_field_require")),
73+
city_id: Yup.string().required(t("global_field_require")),
74+
district_id: Yup.string().required(t("global_field_require")),
75+
}
76+
77+
const initialValue = {
78+
type: "electricity",
79+
date: "",
80+
region_id: "",
81+
city_id: "",
82+
district_id: "",
83+
};
84+
85+
86+
const handleSubmit = (payload: PostAlertPayload) => {
87+
setLoading(true)
88+
axios.post(globalUrls.POST_ALERT, payload)
89+
.then((response) => {
90+
enqueueSnackbar(t("add_alert_success_message"), {
91+
variant: "success",
92+
});
93+
axios.get(globalUrls.GET_ALERTS)
94+
onClose();
95+
})
96+
.catch((error: AxiosError) => {
97+
enqueueSnackbar(error.message, {
98+
variant: "error",
99+
});
100+
})
101+
.finally(() => setLoading(false))
102+
}
103+
104+
return (
105+
<Dialog
106+
open={open}
107+
onClose={() => onClose()}
108+
maxWidth={"sm"}
109+
fullWidth
110+
>
111+
<DialogTitle
112+
>
113+
<Typography
114+
variant={"h3"}
115+
>
116+
{t('create_report_modal_title')}
117+
</Typography>
118+
</DialogTitle>
119+
<DialogContent>
120+
<Box sx={{ pt: 2 }}>
121+
<Formik
122+
initialValues={initialValue}
123+
validationSchema={Yup.object().shape(validations)}
124+
onSubmit={(values: any, formikHelpers) => {
125+
const payload: PostAlertPayload = {
126+
city_id: values.city_id,
127+
district_id: values.district_id,
128+
date: values.date.split(" ")[0],
129+
begin_time: values.date.split(" ")[1],
130+
region_id: values.region_id,
131+
type: values.type
132+
}
133+
handleSubmit(payload);
134+
}}>
135+
{({
136+
setFieldTouched,
137+
handleSubmit,
138+
setFieldValue,
139+
errors
140+
}) =>
141+
<Form onSubmit={handleSubmit} className={'form'}>
142+
<Grid container spacing={2}>
143+
<FormikSelectField
144+
xs={12}
145+
variant={'outlined'}
146+
label={t('report_field_type')}
147+
name={'type'}
148+
option={
149+
[
150+
{
151+
label: t("label_alert_type_electricity"),
152+
value: "electricity"
153+
},
154+
{
155+
label: t("label_alert_type_internet"),
156+
value: "internet"
157+
},
158+
{
159+
label: t("label_alert_type_water"),
160+
value: "water"
161+
},
162+
]
163+
}
164+
/>
165+
<FormikDatePickerField
166+
value={moment(new Date()).format("YYYY-MM-DD HH:mm")}
167+
xs={12}
168+
variant={'outlined'}
169+
label={t('report_field_date')}
170+
name={'date'}
171+
onChange={(date) => {
172+
setFieldValue("date", date);
173+
}}
174+
/>
175+
<FormikSelectField
176+
xs={12}
177+
variant={'outlined'}
178+
label={t('report_field_region')}
179+
name={'region_id'}
180+
onChange={(value: any) => {
181+
setFormState({ ...formState, region_id: value })
182+
localStorage.setItem("myRegionName", value.toString());
183+
}}
184+
option={
185+
regions.map((region, index) => {
186+
return {
187+
label: capitalizeFirstLetter(region.name ?? ""),
188+
value: region?.id ?? 0
189+
}
190+
})
191+
}
192+
/>
193+
<FormikSelectField
194+
xs={12}
195+
variant={'outlined'}
196+
label={t('report_field_city')}
197+
name={'city_id'}
198+
onChange={(value: any) => setFormState({ ...formState, city_id: value })}
199+
option={
200+
cities.map((city, index) => {
201+
return {
202+
label: capitalizeFirstLetter(city.name ?? ""),
203+
value: city.id ?? 0
204+
}
205+
})
206+
}
207+
/>
208+
<FormikSelectField
209+
xs={12}
210+
variant={'outlined'}
211+
label={t('report_field_district')}
212+
name={'district_id'}
213+
option={
214+
districts.map((district: DistrictType, index) => {
215+
return {
216+
label: capitalizeFirstLetter(district?.name ?? ""),
217+
value: district?.id ?? 0
218+
}
219+
})
220+
}
221+
/>
222+
<Grid item xs={12}>
223+
<Button
224+
fullWidth
225+
ref={submitBtnRef}
226+
type={"submit"}
227+
onClick={(e) => {
228+
Object.keys(initialValue)
229+
.forEach(field => {
230+
setFieldTouched(field, true);
231+
});
232+
}}
233+
sx={{ display: "none" }}
234+
/>
235+
</Grid>
236+
</Grid>
237+
</Form>
238+
}
239+
</Formik>
240+
</Box>
241+
242+
</DialogContent>
243+
<DialogActions>
244+
<Button
245+
variant={"text"}
246+
color={"primary"}
247+
onClick={() => onClose()}
248+
>
249+
{t('global_label_cancel')}
250+
</Button>
251+
<Button
252+
disabled={loading}
253+
variant={"contained"}
254+
color={"primary"}
255+
onClick={() => {
256+
//@ts-ignore
257+
submitBtnRef.current?.click()
258+
}}
259+
>
260+
{
261+
loading ? <CircularProgress /> : t('global_label_save')
262+
}
263+
</Button>
264+
</DialogActions>
265+
</Dialog>
266+
)
267+
}
268+
269+
export default CreateReportModal

frontend/src/components/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export {default as LoadingScreen} from './LoadingScreen'
22
export {default as Page} from './Page/Page'
33
export {default as Header} from './Header/Header'
4+
export {default as CreateReportModal} from './CreateReport/CreateReport'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// @flow
2+
import * as React from 'react';
3+
import {BaseTextFieldProps, TextField} from "@mui/material";
4+
import {FormikFieldWrapper, FormikFieldWrapperProps} from "./FormikFieldWrapper";
5+
import {useField} from "formik";
6+
import {LocalizationProvider} from "@mui/x-date-pickers";
7+
import moment from "moment";
8+
import {AdapterMoment} from '@mui/x-date-pickers/AdapterMoment';
9+
import {DateTimePicker} from '@mui/x-date-pickers/DateTimePicker';
10+
import {useTranslation} from "react-i18next";
11+
12+
interface SelectOption {
13+
label: string | null,
14+
value: string
15+
}
16+
17+
interface FormikDatePickerFieldProps extends Partial<BaseTextFieldProps>, FormikFieldWrapperProps {
18+
name: string;
19+
onChange?: (value: any) => void;
20+
maxDate?: Date
21+
}
22+
23+
export const FormikDatePickerField = (props: FormikDatePickerFieldProps) => {
24+
const {xs, md, lg, gridClassName, ...rest} = props;
25+
const {i18n: {language}} = useTranslation();
26+
const [field, meta,] = useField({
27+
name: props.name,
28+
});
29+
30+
const [value, setValue] = React.useState(props.value);
31+
32+
return (
33+
<FormikFieldWrapper
34+
xs={xs}
35+
md={md}
36+
lg={lg}
37+
gridClassName={gridClassName}
38+
>
39+
<LocalizationProvider
40+
dateAdapter={AdapterMoment}
41+
adapterLocale={language}
42+
>
43+
<DateTimePicker
44+
label={props.label}
45+
value={value}
46+
minDate={moment(new Date()).subtract(1, 'day').toDate()}
47+
onChange={(newValue) => {
48+
setValue(newValue);
49+
if (props.onChange)
50+
props.onChange(moment(newValue).format("YYYY-MM-DD HH:mm"));
51+
}}
52+
maxDate={props.maxDate}
53+
renderInput={
54+
(params) => <TextField
55+
{...params}
56+
fullWidth
57+
variant={props.variant}
58+
error={!!(meta.touched && meta.error)}
59+
helperText={meta.touched && meta.error}
60+
/>
61+
}
62+
63+
/>
64+
</LocalizationProvider>
65+
66+
</FormikFieldWrapper>
67+
);
68+
};

0 commit comments

Comments
 (0)