LFFE-10 complete excel export of charts and solve problem of reloading and add refresh chart data and some other actions

This commit is contained in:
2023-11-14 17:29:10 +03:30
parent f48672274d
commit c9326730d8
13 changed files with 236 additions and 83 deletions

View File

@@ -25,6 +25,7 @@
"date-fns-jalali": "^2.13.0-0",
"dayjs": "^1.11.9",
"eslint": "8.36.0",
"file-saver": "^2.0.5",
"formik": "^2.2.9",
"fs-extra": "^11.1.1",
"image-resize": "^1.3.2",

View File

@@ -2,42 +2,108 @@ import {Accordion, AccordionDetails, AccordionSummary, Box, Button, Stack, Typog
import FileUploadIcon from '@mui/icons-material/FileUpload';
import AssignmentIcon from '@mui/icons-material/Assignment';
import {useTranslations} from "next-intl";
import ProvinceFilter from "@/components/dashboard/reports/Filter/ProvinceFilter";
import YearFilter from "@/components/dashboard/reports/Filter/YearFilter";
import {useState} from "react";
import {useFormik} from "formik";
import {GET_EXPORT} from "@/core/data/apiRoutes";
import moment from "jalali-moment";
import FileSaver from 'file-saver';
import useRequest from "@/lib/app/hooks/useRequest";
const ExcelExport = () => {
const t = useTranslations();
const requestServer = useRequest()
const filterItem = [
{type: "province", type_fa: "استان", multiple: false},
{type: "year", type_fa: "سال", multiple: false}
];
const filter_by_province = filterItem.find((item) => item.type === "province");
const filter_by_year = filterItem.find((item) => item.type === "year");
const [filteredText, setFilteredText] = useState({province: "", year: ""});
const formik = useFormik({
initialValues: {
...(filter_by_year && {
date: filter_by_year.multiple ? [] : `${moment().format('jYYYY')}`
}),
...(filter_by_province && {
province_id: filter_by_province.multiple ? [] : ""
}),
from_date: "",
to_date: ""
}, onSubmit: (values, {setSubmitting}) => {
const province = values.province_id;
const fields = [
values.from_date ? {key: "from_date", value: values.from_date} : null,
values.to_date ? {key: "to_date", value: values.to_date} : null,
...(Array.isArray(province)
? province.length !== 0
? province.map((item, index) => ({key: `province_id[${index}]`, value: item}))
: []
: province
? [{key: "province_id", value: province}]
: []
),
].filter(Boolean);
const queryString = fields.length !== 0
? `?${fields.map((option) => `${option.key}=${option.value}`).join('&')}`
: '';
requestServer(`${GET_EXPORT}${queryString}`, 'GET', {auth: true, requestOptions: {responseType: 'blob'}})
.then((response) => {
const filename = `گزارش درخواست های وام ناوگان تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
FileSaver.saveAs(response.data, filename);
}).catch(() => {
}).finally(() => {
setSubmitting(false);
});
},
});
return (
<Accordion elevation={0} sx={{
width: "95%",
mx: "auto",
mb: 2,
borderBottom: 2,
borderBottomColor: "divider",
backgroundColor: "#f1f1f1"
}}>
<AccordionSummary>
<Box sx={{width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between"}}>
<Box sx={{display: "flex", alignItems: "center", gap: 0.5}}>
<AssignmentIcon sx={{color: "primary.main"}}/>
<Typography sx={{fontWeight: 500, color: "primary.main"}}>
{t("filters.excel_export")}
</Typography>
<Stack sx={{width: "95%", mx: "auto"}}>
<Accordion elevation={0} sx={{
width: "100%",
mb: 2,
borderBottom: 2,
borderBottomColor: "divider",
backgroundColor: "#f1f1f1"
}}>
<AccordionSummary>
<Box sx={{width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between"}}>
<Box sx={{display: "flex", alignItems: "center", gap: 0.5}}>
<AssignmentIcon sx={{color: "primary.main"}}/>
<Typography sx={{fontWeight: 500, color: "primary.main"}}>
{t("filters.excel_export")}
</Typography>
</Box>
<FileUploadIcon sx={{color: "primary.main"}}/>
</Box>
<FileUploadIcon sx={{color: "primary.main"}}/>
</Box>
</AccordionSummary>
<AccordionDetails
sx={{display: "flex", justifyContent: "space-between", alignItems: "center", mx: "auto",}}>
<Stack sx={{flexDirection: {xs: "column", sm: "row"}, alignItems: "center", flexWrap: 'wrap'}}>
</Stack>
<Stack>
<Button variant="contained" sx={{backgroundColor: "success.main"}}>
{t("filters.export")}
</Button>
</Stack>
</AccordionDetails>
</Accordion>
</AccordionSummary>
<AccordionDetails
sx={{display: "flex", justifyContent: "space-between", alignItems: "center", mx: "auto",}}>
<Stack sx={{flexDirection: {xs: "column", sm: "row"}, alignItems: "center", flexWrap: 'wrap'}}>
{filter_by_province ?
<ProvinceFilter formik={formik} multiple={filter_by_province.multiple}
setFilteredText={setFilteredText}/> : ""
}
{filter_by_year ?
<YearFilter formik={formik} multiple={filter_by_year.multiple}
setFilteredText={setFilteredText}/> : ""
}
</Stack>
<Stack>
<Button onClick={formik.handleSubmit} variant="contained"
disabled={formik.isSubmitting}
sx={{backgroundColor: "success.main"}}
>
{t("filters.export")}
</Button>
</Stack>
</AccordionDetails>
</Accordion>
</Stack>
)
};

View File

@@ -1,6 +1,17 @@
import {Box, FormControl, InputLabel, MenuItem, OutlinedInput, Select, Typography} from "@mui/material";
import {
Box,
FormControl,
IconButton,
InputAdornment,
InputLabel,
MenuItem,
OutlinedInput,
Select,
Typography
} from "@mui/material";
import {useTranslations} from "next-intl";
import useProvince from "@/lib/app/hooks/useProvince";
import ClearIcon from '@mui/icons-material/Clear';
const provinceList = [
{name: "قزوین", id: 1},
@@ -32,6 +43,9 @@ const ProvinceFilter = ({formik, multiple, setFilteredText}) => {
}));
}
};
const needAdorment = multiple
? formik.values.province_id.length === 0 :
formik.values.province_id === "";
return (
<FormControl sx={{width: 300, mx: 1}}>
<InputLabel size="small">{t("filters.choose_province")}</InputLabel>
@@ -48,7 +62,7 @@ const ProvinceFilter = ({formik, multiple, setFilteredText}) => {
renderValue={multiple ? (selected) => (
<Box sx={{
display: 'flex', gap: 0.7, overflow: "hidden",
width: '100%',
width: '100%', pr: 4
}}>
{selected.map((value) => {
const selectedProvince = provinceList.find(province => province.id === value);
@@ -60,6 +74,19 @@ const ProvinceFilter = ({formik, multiple, setFilteredText}) => {
})}
</Box>
) : undefined}
endAdornment={
!needAdorment ?
<InputAdornment position="end" sx={{mr: 2}}>
<IconButton
onClick={() => multiple
? formik.setFieldValue('province_id', [])
: formik.setFieldValue('province_id', "")
}
size="small">
<ClearIcon/>
</IconButton>
</InputAdornment> : null
}
>
{isLoadingProvinceList ? (
<MenuItem>

View File

@@ -1,20 +1,11 @@
import {Box, Chip, FormControl, InputLabel, MenuItem, OutlinedInput, Select} from "@mui/material";
import moment from "jalali-moment";
import {useTranslations} from "next-intl";
import {periodOfYear} from "@/core/utils/yearPeriodFinder";
const currentShamsiYear = moment().format('jYYYY');
const shamsiYearList = Array.from({length: currentShamsiYear - 1399}, (_, index) => (currentShamsiYear - index).toString());
const periodOfYear = (year) => {
const isLeapYear = moment.jIsLeapYear(year);
const firstDayGeorgian = moment.from(`${year}/01/01`, 'fa').format('YYYY-MM-DD');
const lastDayGeorgian = moment.from(`${year}/12/${isLeapYear ? '30' : '29'}`, 'fa').format('YYYY-MM-DD');
return {
from: firstDayGeorgian,
to: lastDayGeorgian
}
}
const YearFilter = ({formik, multiple, setFilteredText}) => {
const t = useTranslations();
const yearChange = (event) => {

View File

@@ -1,4 +1,14 @@
import {Accordion, AccordionDetails, AccordionSummary, Box, Button, Stack, Typography} from "@mui/material";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Button,
CircularProgress,
IconButton,
Stack,
Typography
} from "@mui/material";
import {useFormik} from "formik";
import ProvinceFilter from "../Filter/ProvinceFilter";
import YearFilter from "../Filter/YearFilter";
@@ -6,26 +16,35 @@ import FilterAltIcon from '@mui/icons-material/FilterAlt';
import EqualizerIcon from '@mui/icons-material/Equalizer';
import {useTranslations} from "next-intl";
import TroubleshootIcon from '@mui/icons-material/Troubleshoot';
import {useState} from "react";
import CachedIcon from '@mui/icons-material/Cached';
import {useEffect, useState} from "react";
import moment from "jalali-moment";
import {periodOfYear} from "@/core/utils/yearPeriodFinder";
const Filter = ({title, filterItem, setFilterOption}) => {
CachedIcon
const Filter = ({title, filterItem, setFilterOption, isLoadingReportList, mutate}) => {
const t = useTranslations();
const filter_by_province = filterItem.find((item) => item.type === "province");
const filter_by_year = filterItem.find((item) => item.type === "year");
const [expanded, setExpanded] = useState(false);
const [filteredText, setFilteredText] = useState({province: "", year: ""});
const [stepValue, setStepValue] = useState({
...(filter_by_year && {
date: filter_by_year.multiple ? [] : `${moment().format('jYYYY')}`
}),
...(filter_by_province && {
province_id: filter_by_province.multiple ? [] : ""
}),
from_date: periodOfYear(moment().format('jYYYY')).from,
to_date: periodOfYear(moment().format('jYYYY')).to
})
const formik = useFormik({
initialValues: {
...(filter_by_year && {
date: filter_by_year.multiple ? [] : ""
}),
...(filter_by_province && {
province_id: filter_by_province.multiple ? [] : ""
}),
from_date: "",
to_date: ""
}, onSubmit: (values, {setSubmitting}) => {
enableReinitialize: true,
initialValues: stepValue, onSubmit: (values, {setSubmitting}) => {
setStepValue(values)
const province = values.province_id;
const fields = [
values.from_date ? {key: "from_date", value: values.from_date} : null,
@@ -39,28 +58,54 @@ const Filter = ({title, filterItem, setFilterOption}) => {
: []
),
].filter(Boolean);
console.log("fields", fields)
setFilterOption(fields)
setSubmitting(false)
},
});
useEffect(() => {
const province = formik.values.province_id;
const fields = [
formik.values.from_date ? {key: "from_date", value: formik.values.from_date} : null,
formik.values.to_date ? {key: "to_date", value: formik.values.to_date} : null,
...(Array.isArray(province)
? province.length !== 0
? province.map((item, index) => ({key: `province_id[${index}]`, value: item}))
: []
: province
? [{key: "province_id", value: province}]
: []
),
].filter(Boolean);
setFilterOption(fields)
}, []);
return (
<Accordion elevation={0} sx={{
<Accordion elevation={0} expanded={expanded} onChange={() => {
}} sx={{
mb: 2,
borderBottom: 2,
borderBottomColor: "divider",
backgroundColor: "#f1f1f1"
backgroundColor: "#f1f1f1",
}}>
<AccordionSummary>
<AccordionSummary sx={{cursor: "unset !important"}}>
<Box sx={{width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between"}}>
<Box sx={{display: "flex", alignItems: "center", gap: 0.5}}>
<EqualizerIcon sx={{color: "primary.main"}}/>
<Typography sx={{fontWeight: 500, color: "primary.main"}}>دریافت گزارش تکمیلی</Typography>
<Typography sx={{fontWeight: 500, color: "primary.main"}}>{title}</Typography>
<Typography variant="caption" sx={{fontWeight: 500}} color="error">
{filteredText.province} {filteredText.year}
</Typography>
</Box>
<FilterAltIcon sx={{color: "primary.main"}}/>
<Box>
<IconButton onClick={() => mutate()}
size="small">
<CachedIcon sx={{color: "success.main"}}/>
</IconButton>
<IconButton onClick={() => setExpanded((prevExpanded) => !prevExpanded)}
size="small">
<FilterAltIcon sx={{color: "success.main"}}/>
</IconButton>
</Box>
</Box>
</AccordionSummary>
<AccordionDetails sx={{display: "flex", justifyContent: "space-between", alignItems: "center"}}>
@@ -76,12 +121,13 @@ const Filter = ({title, filterItem, setFilterOption}) => {
</Stack>
<Stack>
<Button onClick={formik.handleSubmit} variant="contained"
disabled={formik.isSubmitting || !formik.dirty}
startIcon={<TroubleshootIcon/>}
disabled={isLoadingReportList || !formik.dirty}
startIcon={isLoadingReportList ?
<CircularProgress size={20} color="inherit"/> :
<TroubleshootIcon/>}
>{t("filters.filter")}</Button>
</Stack>
</AccordionDetails>
</Accordion>
)
};

View File

@@ -1,6 +1,5 @@
import {Grid, Paper} from "@mui/material";
import {useTranslations} from "next-intl";
import Filter from "@/components/dashboard/reports/Filter";
import RadialBarSemiCircularChart from "@/components/dashboard/reports/LoanDetails/RadialBarSemiCircularChart";
import RadialBarStrokedChart from "@/components/dashboard/reports/LoanDetails/RadialBarStrokedChart";
@@ -9,12 +8,14 @@ const LoanDetails = () => {
const t = useTranslations();
return (
<Paper elevation={2} sx={{width: "95%", mx: "auto", my: 2, pb: 2, background: "#f7f7f7e6"}}>
<Filter title={t("reports.loan_details")}
filterItem={[
{type: "province", type_fa: "استان", multiple: false},
{type: "year", type_fa: "سال", multiple: false}
]}
/>
{/*<Filter title={t("reports.loan_details")}*/}
{/* filterItem={[*/}
{/* {type: "province", type_fa: "استان", multiple: false},*/}
{/* {type: "year", type_fa: "سال", multiple: false}*/}
{/* ]}*/}
{/* setFilterOption={setFilterOption}*/}
{/* isLoadingReportList={isLoadingReportList}*/}
{/*/>*/}
<Grid container sx={{justifyContent: {xs: "center", md: "space-between"}}}>
<Grid item xs={12} sm={10} md={5} sx={{aspectRatio: "1/0.75"}}>
<RadialBarSemiCircularChart data={20} label={t("reports.cost_of_loan_refer_to_bank")}/>

View File

@@ -8,8 +8,7 @@ import {GET_LOAN_DISTRIBUTION} from "@/core/data/apiRoutes";
const LoanDistribution = () => {
const t = useTranslations();
const {errorReportList, isLoadingReportList, reportList, setFilterOption} = useChart(GET_LOAN_DISTRIBUTION);
const {errorReportList, isLoadingReportList, reportList, setFilterOption, mutate} = useChart(GET_LOAN_DISTRIBUTION);
return (
<Paper elevation={2} sx={{width: "95%", mx: "auto", my: 2, pb: 2, background: "#f7f7f7e6"}}>
<Filter title={t("reports.loan_distribution")}
@@ -18,6 +17,8 @@ const LoanDistribution = () => {
{type: "year", type_fa: "سال", multiple: false}
]}
setFilterOption={setFilterOption}
isLoadingReportList={isLoadingReportList}
mutate={mutate}
/>
<Grid container sx={{justifyContent: {xs: "center", md: "space-between"}}}>
{isLoadingReportList ? (

View File

@@ -7,7 +7,7 @@ import {GET_PROVINCE_PROGRESS} from "@/core/data/apiRoutes";
const LoanProgress = () => {
const t = useTranslations();
const {errorReportList, isLoadingReportList, reportList, setFilterOption} = useChart(GET_PROVINCE_PROGRESS);
const {errorReportList, isLoadingReportList, reportList, setFilterOption, mutate} = useChart(GET_PROVINCE_PROGRESS);
return (
<Paper elevation={2} sx={{width: "95%", mx: "auto", my: 2, pb: 2, background: "#f7f7f7e6"}}>
<Filter title={t("reports.loan_progress")}
@@ -15,6 +15,8 @@ const LoanProgress = () => {
{type: "year", type_fa: "سال", multiple: false}
]}
setFilterOption={setFilterOption}
isLoadingReportList={isLoadingReportList}
mutate={mutate}
/>
<Grid container>
<Grid item xs={12} sx={{aspectRatio: {xs: "1/1", sm: "1/1", md: "1/0.4", xl: "1/0.3"}}}>

View File

@@ -199,7 +199,7 @@ export const GET_PERMISSIONS_LIST =
//role management
// reports
export const GET_EXPORTS = BASE_URL + "/dashboard/exports/navgan"
export const GET_EXPORT = BASE_URL + "/dashboard/exports/navgan"
export const GET_PROVINCE_PROGRESS = BASE_URL + "/dashboard/reports/navgan/province_progress"
export const GET_LOAN_DISTRIBUTION = BASE_URL + "/dashboard/reports/navgan/loan_distribution"
// reports

View File

@@ -0,0 +1,11 @@
import moment from "jalali-moment";
export const periodOfYear = (year) => {
const isLeapYear = moment.jIsLeapYear(year);
const firstDayGeorgian = moment.from(`${year}/01/01`, 'fa').format('YYYY-MM-DD');
const lastDayGeorgian = moment.from(`${year}/12/${isLeapYear ? '30' : '29'}`, 'fa').format('YYYY-MM-DD');
return {
from: firstDayGeorgian,
to: lastDayGeorgian
}
}

View File

@@ -8,7 +8,6 @@ const useChart = (chart_url) => {
const queryString = filterOption.length !== 0
? `?${filterOption.map((option) => `${option.key}=${option.value}`).join('&')}`
: '';
console.log("check query", queryString)
//swr config
const fetcher = (...args) => {
return requestServer(args, 'get').then(({data}) => {
@@ -17,17 +16,23 @@ const useChart = (chart_url) => {
})
};
const {data, isLoading} = useSWR(`${chart_url}${queryString}`, fetcher, {
const {
data,
isValidating,
mutate
} = useSWR(filterOption.length !== 0 ? `${chart_url}${queryString}` : "", fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
revalidateOnReconnect: false,
keepPreviousData: true
});
return {
reportList: data,
isLoadingReportList: isLoading,
isLoadingReportList: isValidating,
errorReportList: !data,
setFilterOption: setFilterOption
setFilterOption: setFilterOption,
mutate: mutate
}
};

View File

@@ -16,7 +16,8 @@ const useProvince = () => {
const {data, isLoading} = useSWR(GET_PROVINCE_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
revalidateOnReconnect: false,
keepPreviousData: true
});
return {

View File

@@ -16,7 +16,8 @@ const useRole = () => {
const {data, isLoading} = useSWR(GET_ROLE_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
revalidateOnReconnect: false,
keepPreviousData: true
});
return {