Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post alert #176

Merged
merged 6 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27,151 changes: 256 additions & 26,895 deletions frontend/package-lock.json

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
"dependencies": {
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@mui/x-date-pickers": "^5.0.9",
"@types/nprogress": "^0.2.0",
"@types/react-helmet": "^6.1.5",
"@types/react-paginate": "^7.1.1",
"@types/react-router-config": "^5.0.6",
"axios": "^0.22.0",
"eslint": "^7.11.0",
"formik": "^2.2.9",
"i18next": "^21.9.0",
"i18next-browser-languagedetector": "^6.1.5",
"i18next-http-backend": "^1.4.1",
"install": "^0.13.0",
"leaflet": "^1.7.1",
"moment": "^2.29.4",
"notistack": "^2.0.8",
"npm": "^8.19.2",
"nprogress": "^0.2.0",
Expand All @@ -34,7 +37,8 @@
"react-search-field": "^2.0.1",
"react-search-input": "^0.11.3",
"recoil": "^0.7.2",
"recoil-persist": "^4.2.0"
"recoil-persist": "^4.2.0",
"yup": "^0.32.11"
},
"scripts": {
"start": "craco start",
Expand Down Expand Up @@ -75,5 +79,8 @@
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
"proxy": "http://localhost:8000"
"proxy": "http://localhost:8000",
"resolutions": {
"react-error-overlay": "6.0.9"
}
}
269 changes: 269 additions & 0 deletions frontend/src/components/CreateReport/CreateReport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from "react-i18next";
import {
Box,
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
Typography
} from "@mui/material";
import { Form, Formik } from "formik";
import * as Yup from "yup";
import { FormikDatePickerField } from "../../form/FormikDatePickerField";
import moment from "moment";
import { useRecoilValue } from "recoil";
import { CityService } from "../../services/api";
import { FormikSelectField } from "../../form/FormikSelectField";
import { CityType, DistrictType, PostAlertPayload, RegionType } from "../../types";
import axios, { AxiosError } from "axios";
import { globalUrls } from "../../services/api/urls";
import { useSnackbar } from "notistack";

interface CreateReportModalProps {
open: boolean,
onClose: any
}

function capitalizeFirstLetter(text: string) {
return text.charAt(0).toUpperCase() + text.slice(1);
}

interface FormState {
region_id: number | null,
city_id: number | null,
}

const CreateReportModal = (props: CreateReportModalProps) => {
const { t, i18n: { language } } = useTranslation();
moment.locale(language);
const { open, onClose } = props;
const submitBtnRef = useRef(null);
const [loading, setLoading] = useState(false);
const { enqueueSnackbar } = useSnackbar();

const [formState, setFormState] = useState<FormState>({
region_id: null,
city_id: null
});

const [cities, setCities] = useState<CityType[]>([]);
const [districts, setDistricts] = useState<DistrictType[]>([]);

const regionResponse = useRecoilValue(CityService.getRegions);
const citiesResponse = useRecoilValue(CityService.getCities);
const districtResponse = useRecoilValue(CityService.getDistricts);


const regions: RegionType[] = regionResponse?.data ?? [];

useEffect(() => {
setCities((citiesResponse?.data ?? []).filter((item: CityType) => item.region_id == formState.region_id));
setDistricts((districtResponse?.data ?? []).filter((item: DistrictType) => item.city_id == formState.city_id));
}, [formState]);


const validations = {
type: Yup.string().required(t("global_field_require")),
date: Yup.string().required(t("global_field_require")),
region_id: Yup.string().required(t("global_field_require")),
city_id: Yup.string().required(t("global_field_require")),
district_id: Yup.string().required(t("global_field_require")),
}

const initialValue = {
type: "electricity",
date: "",
region_id: "",
city_id: "",
district_id: "",
};


const handleSubmit = (payload: PostAlertPayload) => {
setLoading(true)
axios.post(globalUrls.POST_ALERT, payload)
.then((response) => {
enqueueSnackbar(t("add_alert_success_message"), {
variant: "success",
});
axios.get(globalUrls.GET_ALERTS)
onClose();
})
.catch((error: AxiosError) => {
enqueueSnackbar(error.message, {
variant: "error",
});
})
.finally(() => setLoading(false))
}

return (
<Dialog
open={open}
onClose={() => onClose()}
maxWidth={"sm"}
fullWidth
>
<DialogTitle
>
<Typography
variant={"h3"}
>
{t('create_report_modal_title')}
</Typography>
</DialogTitle>
<DialogContent>
<Box sx={{ pt: 2 }}>
<Formik
initialValues={initialValue}
validationSchema={Yup.object().shape(validations)}
onSubmit={(values: any, formikHelpers) => {
const payload: PostAlertPayload = {
city_id: values.city_id,
district_id: values.district_id,
date: values.date.split(" ")[0],
begin_time: values.date.split(" ")[1],
region_id: values.region_id,
type: values.type
}
handleSubmit(payload);
}}>
{({
setFieldTouched,
handleSubmit,
setFieldValue,
errors
}) =>
<Form onSubmit={handleSubmit} className={'form'}>
<Grid container spacing={2}>
<FormikSelectField
xs={12}
variant={'outlined'}
label={t('report_field_type')}
name={'type'}
option={
[
{
label: t("label_alert_type_electricity"),
value: "electricity"
},
{
label: t("label_alert_type_internet"),
value: "internet"
},
{
label: t("label_alert_type_water"),
value: "water"
},
]
}
/>
<FormikDatePickerField
value={moment(new Date()).format("YYYY-MM-DD HH:mm")}
xs={12}
variant={'outlined'}
label={t('report_field_date')}
name={'date'}
onChange={(date) => {
setFieldValue("date", date);
}}
/>
<FormikSelectField
xs={12}
variant={'outlined'}
label={t('report_field_region')}
name={'region_id'}
onChange={(value: any) => {
setFormState({ ...formState, region_id: value })
localStorage.setItem("myRegionName", value.toString());
}}
option={
regions.map((region, index) => {
return {
label: capitalizeFirstLetter(region.name ?? ""),
value: region?.id ?? 0
}
})
}
/>
<FormikSelectField
xs={12}
variant={'outlined'}
label={t('report_field_city')}
name={'city_id'}
onChange={(value: any) => setFormState({ ...formState, city_id: value })}
option={
cities.map((city, index) => {
return {
label: capitalizeFirstLetter(city.name ?? ""),
value: city.id ?? 0
}
})
}
/>
<FormikSelectField
xs={12}
variant={'outlined'}
label={t('report_field_district')}
name={'district_id'}
option={
districts.map((district: DistrictType, index) => {
return {
label: capitalizeFirstLetter(district?.name ?? ""),
value: district?.id ?? 0
}
})
}
/>
<Grid item xs={12}>
<Button
fullWidth
ref={submitBtnRef}
type={"submit"}
onClick={(e) => {
Object.keys(initialValue)
.forEach(field => {
setFieldTouched(field, true);
});
}}
sx={{ display: "none" }}
/>
</Grid>
</Grid>
</Form>
}
</Formik>
</Box>

</DialogContent>
<DialogActions>
<Button
variant={"text"}
color={"primary"}
onClick={() => onClose()}
>
{t('global_label_cancel')}
</Button>
<Button
disabled={loading}
variant={"contained"}
color={"primary"}
onClick={() => {
//@ts-ignore
submitBtnRef.current?.click()
}}
>
{
loading ? <CircularProgress /> : t('global_label_save')
}
</Button>
</DialogActions>
</Dialog>
)
}

export default CreateReportModal
1 change: 1 addition & 0 deletions frontend/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export {default as LoadingScreen} from './LoadingScreen'
export {default as Page} from './Page/Page'
export {default as Header} from './Header/Header'
export {default as CreateReportModal} from './CreateReport/CreateReport'
68 changes: 68 additions & 0 deletions frontend/src/form/FormikDatePickerField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// @flow
import * as React from 'react';
import {BaseTextFieldProps, TextField} from "@mui/material";
import {FormikFieldWrapper, FormikFieldWrapperProps} from "./FormikFieldWrapper";
import {useField} from "formik";
import {LocalizationProvider} from "@mui/x-date-pickers";
import moment from "moment";
import {AdapterMoment} from '@mui/x-date-pickers/AdapterMoment';
import {DateTimePicker} from '@mui/x-date-pickers/DateTimePicker';
import {useTranslation} from "react-i18next";

interface SelectOption {
label: string | null,
value: string
}

interface FormikDatePickerFieldProps extends Partial<BaseTextFieldProps>, FormikFieldWrapperProps {
name: string;
onChange?: (value: any) => void;
maxDate?: Date
}

export const FormikDatePickerField = (props: FormikDatePickerFieldProps) => {
const {xs, md, lg, gridClassName, ...rest} = props;
const {i18n: {language}} = useTranslation();
const [field, meta,] = useField({
name: props.name,
});

const [value, setValue] = React.useState(props.value);

return (
<FormikFieldWrapper
xs={xs}
md={md}
lg={lg}
gridClassName={gridClassName}
>
<LocalizationProvider
dateAdapter={AdapterMoment}
adapterLocale={language}
>
<DateTimePicker
label={props.label}
value={value}
minDate={moment(new Date()).subtract(1, 'day').toDate()}
onChange={(newValue) => {
setValue(newValue);
if (props.onChange)
props.onChange(moment(newValue).format("YYYY-MM-DD HH:mm"));
}}
maxDate={props.maxDate}
renderInput={
(params) => <TextField
{...params}
fullWidth
variant={props.variant}
error={!!(meta.touched && meta.error)}
helperText={meta.touched && meta.error}
/>
}

/>
</LocalizationProvider>

</FormikFieldWrapper>
);
};
Loading