Merge branch 'release/v.1.22.3'
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
NEXT_PUBLIC_API_NAME = "Loan Facilities Dashboard"
|
||||
NEXT_PUBLIC_API_VERSION = "1.18.2"
|
||||
NEXT_PUBLIC_API_VERSION = "1.22.3"
|
||||
NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa"
|
||||
NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl"
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@mui/icons-material": "^5.11.16",
|
||||
"@mui/material": "^5.12.0",
|
||||
"@mui/x-data-grid": "^6.18.1",
|
||||
"@mui/x-date-pickers": "^6.9.2",
|
||||
"apexcharts": "^3.44.0",
|
||||
"axios": "^1.4.0",
|
||||
@@ -29,6 +30,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",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"login": "ورود",
|
||||
"login_expert": "ورود کارشناس",
|
||||
"expert": "کارشناس",
|
||||
"last_updated_at": "اخرین بروز رسانی",
|
||||
"last_updated_at": "آخرین بروزرسانی",
|
||||
"filter_mode": "حالت فیلتر",
|
||||
"date_picker_label": "فیلتر بر اساس تاریخ",
|
||||
"lessThan": "کوچکتر از",
|
||||
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"sidebar": {
|
||||
"dashboard": "داشبورد",
|
||||
"reports": "گزارشات",
|
||||
"passenger-office-chief": "اداره مسافر",
|
||||
"machinery-expert": "ماشین آلات",
|
||||
"passenger-boss": "رئیس مسافر ",
|
||||
@@ -56,7 +57,8 @@
|
||||
"expert-management": "مدیریت کارشناسان",
|
||||
"user-management": "مدیریت کاربر",
|
||||
"role-management": "مدیریت نقش ها",
|
||||
"edit-profile": "ویرایش پروفایل"
|
||||
"edit-profile": "ویرایش پروفایل",
|
||||
"loan-history": "کارتابل نظارت"
|
||||
},
|
||||
"secondary": {
|
||||
"passenger-office": "توزیع درخواست",
|
||||
@@ -99,6 +101,7 @@
|
||||
},
|
||||
"Dashboard": {
|
||||
"dashboard_page": "داشبورد",
|
||||
"reports": "گزارشات",
|
||||
"passenger_boss_page": "رئیس مسافر",
|
||||
"transportation_assistance": "معاونت حمل و نقل",
|
||||
"change_password": "تغییر رمز عبور",
|
||||
@@ -113,7 +116,8 @@
|
||||
"role_management_page": "مدیریت نقش ها",
|
||||
"edit_profile": "ویرایش پروفایل",
|
||||
"expert_management": "مدیریت کارشناسان",
|
||||
"user_management_page": "مدیریت کاربر"
|
||||
"user_management_page": "مدیریت کاربر",
|
||||
"loan_history": "کارتابل نظارت"
|
||||
},
|
||||
"MuiDatePicker": {
|
||||
"date_picker_birthday": "تاریخ"
|
||||
@@ -168,7 +172,8 @@
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "ارجاع به کارشناس ماشین آلات",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf"
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"excel_report": "گزارش اکسل"
|
||||
},
|
||||
"CommercialChief": {
|
||||
"name": "نام",
|
||||
@@ -193,7 +198,8 @@
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "تایید نهایی و ارجاع به بانک",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf"
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"excel_report": "گزارش اکسل"
|
||||
},
|
||||
"PassengerBoss": {
|
||||
"name": "نام",
|
||||
@@ -211,7 +217,8 @@
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"print_final_credit_amount": "چاپ صورت جلسه تعیین مبلغ نهایی",
|
||||
"max_amount": "حداکثر مبلغ تصویب شده {value} میلیون ریال می باشد"
|
||||
"max_amount": "حداکثر مبلغ تصویب شده {value} میلیون ریال می باشد",
|
||||
"excel_report": "گزارش اکسل"
|
||||
},
|
||||
"RefahiProvinceManager": {
|
||||
"name": "نام",
|
||||
@@ -282,7 +289,8 @@
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "ارجاع به مدیر کل استانی",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf"
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"excel_report": "گزارش اکسل"
|
||||
},
|
||||
"InspectorExpert": {
|
||||
"name": "نام",
|
||||
@@ -311,7 +319,8 @@
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"print_report": "چاپ گزارش کارشناسی ماشین آلات",
|
||||
"max_amount": "حداکثر مبلغ پیشنهادی {value} میلیون ریال می باشد"
|
||||
"max_amount": "حداکثر مبلغ پیشنهادی {value} میلیون ریال می باشد",
|
||||
"excel_report": "گزارش اکسل"
|
||||
},
|
||||
"UserManagement": {
|
||||
"name": "نام",
|
||||
@@ -499,5 +508,43 @@
|
||||
"button-cancel": "انصراف",
|
||||
"button-add": "ثبت",
|
||||
"loading_permissions_list": "درحال دریافت لیست دسترسی ها"
|
||||
},
|
||||
"reports": {
|
||||
"loan_progress": "درصد پیشرفت تسویه وام",
|
||||
"loan_distribution": "درصد توزیع وام ها در هر مرحله",
|
||||
"filter_as": "فیلتر های اعمال شده",
|
||||
"loan_details": "جزئیات وام ها",
|
||||
"cost_of_loan_refer_to_bank": "مبلغ وام های معرفی به بانک",
|
||||
"number_of_loan_refer_to_bank": "تعداد وام های معرفی به بانک",
|
||||
"cost_of_payed_loan": "مبلغ وام های پرداخت شده",
|
||||
"number_of_payed_loan": "تعداد وام های پرداخت شده",
|
||||
"loading_fetching_reports": "درحال دریافت گزارشات",
|
||||
"error_fetching_reports": "خطا در دریافت گزارشات"
|
||||
},
|
||||
"filters": {
|
||||
"choose_province": "انتخاب استان",
|
||||
"choose_year": "انتخاب سال",
|
||||
"provinces": "استان های",
|
||||
"province": "استان",
|
||||
"year": "سال",
|
||||
"years": "سال های",
|
||||
"text_field_loading_provinces_list": "درحال دریافت استان ها",
|
||||
"text_field_error_fetching_provinces": "خطا در دریافت استان ها",
|
||||
"filter_btn": "فیلتر",
|
||||
"filter": "اعمال فیلتر",
|
||||
"excel_export": "خروجی به اکسل",
|
||||
"export": "دریافت",
|
||||
"update_again": "بروزرسانی مجدد"
|
||||
},
|
||||
"LoanHistory": {
|
||||
"history": "تاریخچه",
|
||||
"Table_history_error": "خطا در دریافت اطلاعات",
|
||||
"history_report": "فایل ضمیمه",
|
||||
"Table_head_name": "نام",
|
||||
"Table_head_date": "تاریخ",
|
||||
"Table_head_position": "سمت",
|
||||
"Table_head_action": "عملیات",
|
||||
"Table_head_file": "فایل پیوست",
|
||||
"empty_history_detail": "سابقه ای برای این وام وجود ندارد"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import {Button, DialogActions, DialogContent,} from "@mui/material"
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useFormik} from "formik";
|
||||
import TableContent from "@/components/dashboard/loan-history/Form/HistoryForm/TableContent";
|
||||
|
||||
const HistoryContent = ({mutate, setOpenConfirmDialog, rowId}) => {
|
||||
const t = useTranslations();
|
||||
|
||||
const formik = useFormik({});
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<TableContent rowId={rowId}/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenConfirmDialog(false)} variant="outlined" color="secondary"
|
||||
disabled={formik.isSubmitting} autoFocus>
|
||||
{t("ConfirmDialog.button-cancel")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
export default HistoryContent
|
||||
@@ -0,0 +1,39 @@
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
|
||||
const PrintHistory = ({attachment}) => {
|
||||
const t = useTranslations();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
|
||||
const clickHandler = (attachment) => {
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = attachment;
|
||||
anchor.setAttribute('download', '');
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ?
|
||||
<CircularProgress size={18} color="inherit"/> :
|
||||
<DescriptionIcon/>}
|
||||
onClick={() => clickHandler(attachment)}
|
||||
>
|
||||
{t("LoanHistory.history_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintHistory
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Box,
|
||||
LinearProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {GET_HISTORY_DETAIL} from "@/core/data/apiRoutes";
|
||||
import moment from "jalali-moment";
|
||||
import PrintHistory from "@/components/dashboard/loan-history/Form/HistoryForm/PrintHistory";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
const TableContent = ({rowId}) => {
|
||||
const t = useTranslations();
|
||||
const requestServer = useRequest({auth: true, notification: false})
|
||||
const [historyDetails, setHistoryDetails] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
requestServer(`${GET_HISTORY_DETAIL}/${rowId}`, 'get').then((response) => {
|
||||
setIsLoading(false);
|
||||
setHistoryDetails(response.data.data);
|
||||
}).catch(() => {
|
||||
setError(true);
|
||||
})
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{error ? (<Typography sx={{
|
||||
padding: 2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}>{t("LoanHistory.Table_history_error")}</Typography>) :
|
||||
(isLoading ? (<LinearProgress sx={{width: "100%"}}/>) : (
|
||||
historyDetails.latest_histories.length === 0 ? (
|
||||
<Typography
|
||||
sx={{
|
||||
padding: 2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
variant={'h6'}>{t("LoanHistory.empty_history_detail")}</Typography>
|
||||
) : (
|
||||
<Paper elevation={0} sx={{
|
||||
width: '100%', overflow: 'hidden',
|
||||
'*::-webkit-scrollbar': {
|
||||
width: '0.5em'
|
||||
},
|
||||
'*::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: 'primary.main',
|
||||
}
|
||||
}}>
|
||||
<TableContainer sx={{maxHeight: 600}}>
|
||||
<Table stickyHeader sx={{minWidth: 650}}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{t("LoanHistory.Table_head_name")}</TableCell>
|
||||
<TableCell>{t("LoanHistory.Table_head_date")}</TableCell>
|
||||
<TableCell>{t("LoanHistory.Table_head_position")}</TableCell>
|
||||
<TableCell>{t("LoanHistory.Table_head_action")}</TableCell>
|
||||
<TableCell>{t("LoanHistory.Table_head_file")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{
|
||||
historyDetails.latest_histories.map((latest_history, index) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={index}
|
||||
sx={{
|
||||
'&:last-child td, &:last-child th': {border: 0}
|
||||
}}
|
||||
>
|
||||
<TableCell component="th" scope="row">
|
||||
{latest_history.expert.name}
|
||||
</TableCell>
|
||||
<TableCell>{moment(latest_history.created_at).locale("fa").format("HH:mm | yyyy/MM/DD")}</TableCell>
|
||||
<TableCell>{latest_history.expert.position}</TableCell>
|
||||
<TableCell>{latest_history.previous_state_name}</TableCell>
|
||||
<TableCell>
|
||||
{latest_history.attachment ? (
|
||||
<PrintHistory attachment={latest_history.attachment}/>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
)
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
|
||||
)
|
||||
}
|
||||
export default TableContent
|
||||
@@ -0,0 +1,31 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useState} from "react";
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import HistoryContent from "@/components/dashboard/loan-history/Form/HistoryForm/HistoryContent";
|
||||
|
||||
const History = ({rowId, mutate}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={t("LoanHistory.history")}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenConfirmDialog(true)
|
||||
}}
|
||||
>
|
||||
<HistoryIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog scroll={'paper'} fullWidth maxWidth='lg' open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
<DialogTitle>{t("LoanHistory.history")}</DialogTitle>
|
||||
<HistoryContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
export default History;
|
||||
16
src/components/dashboard/loan-history/TableRowActions.jsx
Normal file
16
src/components/dashboard/loan-history/TableRowActions.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import {Box} from "@mui/material";
|
||||
import History from "@/components/dashboard/loan-history/Form/HistoryForm";
|
||||
|
||||
const TableRow = ({row, mutate}) => {
|
||||
return (
|
||||
<Box sx={{display: "flex", flexWrap: "nowrap", gap: "8px"}}>
|
||||
<History
|
||||
rowId={row.getValue("id")}
|
||||
vehicle_type={row.original.vehicle_type}
|
||||
mutate={mutate}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableRow;
|
||||
183
src/components/dashboard/loan-history/index.jsx
Normal file
183
src/components/dashboard/loan-history/index.jsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import {Box, Typography} from "@mui/material";
|
||||
import {useMemo} from "react";
|
||||
import {GET_LOAN_HISTORY} from "@/core/data/apiRoutes";
|
||||
import {useTranslations} from "next-intl";
|
||||
import TableRowActions from "./TableRowActions";
|
||||
import moment from "jalali-moment";
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
|
||||
function DashboardLoanHistoryComponent() {
|
||||
const t = useTranslations();
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorFn: (row) => row.id,
|
||||
id: "id",
|
||||
header: t("PassengerOffice.id"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterFn: "equals",
|
||||
columnFilterModeOptions: [
|
||||
"equals",
|
||||
"notEquals",
|
||||
"contains",
|
||||
"lessThan",
|
||||
"greaterThan",
|
||||
"between",
|
||||
],
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.name,
|
||||
id: "name",
|
||||
header: t("MachinaryOffice.name"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterFn: "contains",
|
||||
columnFilterModeOptions: ["contains", "equals", "notEquals"],
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.national_id,
|
||||
id: "national_id",
|
||||
header: t("MachinaryOffice.national_id"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterFn: "equals",
|
||||
columnFilterModeOptions: [
|
||||
"equals",
|
||||
"notEquals",
|
||||
"contains",
|
||||
"lessThan",
|
||||
"greaterThan",
|
||||
"between",
|
||||
],
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.phone_number,
|
||||
id: "phone_number",
|
||||
header: t("MachinaryOffice.phone_number"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterFn: "equals",
|
||||
columnFilterModeOptions: [
|
||||
"equals",
|
||||
"notEquals",
|
||||
"contains",
|
||||
"lessThan",
|
||||
"greaterThan",
|
||||
"between",
|
||||
],
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) =>
|
||||
moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
id: "created_at",
|
||||
header: t("MachinaryOffice.created_at"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "date",
|
||||
filterFn: "lessThan",
|
||||
columnFilterModeOptions: ["lessThan", "greaterThan"],
|
||||
Cell: ({renderedCellValue}) => {
|
||||
return <Typography variant="body2">{renderedCellValue}</Typography>;
|
||||
},
|
||||
Header: ({column}) => <>{column.columnDef.header}</>,
|
||||
Filter: ({column}) => {
|
||||
return (
|
||||
<MuiDatePicker column={column}/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorFn: (row) =>
|
||||
moment(row.updated_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
|
||||
id: "updated_at",
|
||||
header: t("MachinaryOffice.updated_at"),
|
||||
enableColumnFilter: false,
|
||||
datatype: "numeric",
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.navgan_id,
|
||||
id: "navgan_id",
|
||||
header: t("MachinaryOffice.navgan_id"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterFn: "equals",
|
||||
columnFilterModeOptions: [
|
||||
"equals",
|
||||
"notEquals",
|
||||
"contains",
|
||||
"lessThan",
|
||||
"greaterThan",
|
||||
"between",
|
||||
],
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.vehicle_type,
|
||||
id: "vehicle_type",
|
||||
header: t("MachinaryOffice.vehicle_type"),
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterFn: "contains",
|
||||
columnFilterModeOptions: ["contains", "equals", "notEquals"],
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.state_name,
|
||||
id: "state_id",
|
||||
header: t("MachinaryOffice.state_name"),
|
||||
enableColumnFilter: false,
|
||||
datatype: "numeric",
|
||||
Cell: ({renderedCellValue}) => (
|
||||
<Typography variant="body2">{renderedCellValue}</Typography>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<Box sx={{px: 3}}>
|
||||
<DataTable
|
||||
tableUrl={GET_LOAN_HISTORY}
|
||||
columns={columns}
|
||||
selectableRow={false}
|
||||
enableCustomToolbar={false}
|
||||
enableLastUpdate={true}
|
||||
sorting={[{
|
||||
id: 'id', desc: false
|
||||
}]}
|
||||
enablePinning={true}
|
||||
enableDensityToggle={false}
|
||||
initialState={{density: 'compact'}} //compact (small) //comfortable (medium) //spacious (large)
|
||||
enableColumnFilters={true}
|
||||
enableHiding={true}
|
||||
enableFullScreenToggle={false}
|
||||
enableGlobalFilter={false}
|
||||
enableColumnResizing={false} // if you want true this you shold change renderRowActions props ** see https://www.material-react-table.com/docs/guides/row-actions
|
||||
enableRowActions={true}
|
||||
TableRowAction={TableRowActions}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardLoanHistoryComponent;
|
||||
@@ -0,0 +1,68 @@
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useMemo, useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {EXPORT_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
|
||||
import moment from "jalali-moment";
|
||||
import FileSaver from 'file-saver';
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
|
||||
const PrintExcel = ({table}) => {
|
||||
const t = useTranslations();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {columnFilters, columnFilterFns, sorting} = table.getState()
|
||||
const columns = table.getAllColumns()
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
const filters = columnFilters.map((filter) => {
|
||||
let datatype;
|
||||
for (const i in columns) {
|
||||
if (columns[i].id == filter.id) {
|
||||
datatype = columns[i].columnDef.datatype;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
|
||||
};
|
||||
});
|
||||
params.set("start", '0');
|
||||
params.set("filters", JSON.stringify(filters ?? []));
|
||||
params.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return params;
|
||||
}, [columnFilters, columnFilterFns, sorting, columns,]);
|
||||
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
requestServer(`${EXPORT_MACHINARY_OFFICE}?${filterParams}`, 'get', {
|
||||
auth: true,
|
||||
notification: false,
|
||||
requestOptions: {responseType: 'blob'}
|
||||
}).then((response) => {
|
||||
const filename = `گزارش ماشین آلات تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <DescriptionIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("MachinaryOffice.excel_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintExcel
|
||||
@@ -1,10 +1,12 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintReport from "@/components/dashboard/machinary-office/Buttons/printReport";
|
||||
import PrintExcel from "@/components/dashboard/machinary-office/Buttons/printExcel";
|
||||
|
||||
const TableToolbar = () => {
|
||||
const TableToolbar = ({table}) => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintReport/>
|
||||
<PrintExcel table={table}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useMemo, useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {EXPORT_NAVGAN_PROVINCE_MANAGER} from "@/core/data/apiRoutes";
|
||||
import moment from "jalali-moment";
|
||||
import FileSaver from 'file-saver';
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
|
||||
const PrintExcel = ({table}) => {
|
||||
const t = useTranslations();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {columnFilters, columnFilterFns, sorting} = table.getState()
|
||||
const columns = table.getAllColumns()
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
const filters = columnFilters.map((filter) => {
|
||||
let datatype;
|
||||
for (const i in columns) {
|
||||
if (columns[i].id == filter.id) {
|
||||
datatype = columns[i].columnDef.datatype;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
|
||||
};
|
||||
});
|
||||
params.set("start", '0');
|
||||
params.set("filters", JSON.stringify(filters ?? []));
|
||||
params.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return params;
|
||||
}, [columnFilters, columnFilterFns, sorting, columns,]);
|
||||
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
requestServer(`${EXPORT_NAVGAN_PROVINCE_MANAGER}?${filterParams}`, 'get', {
|
||||
auth: true,
|
||||
notification: false,
|
||||
requestOptions: {responseType: 'blob'}
|
||||
}).then((response) => {
|
||||
const filename = `گزارش مدیر کل استانی تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <DescriptionIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("NavganProvinceManager.excel_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintExcel
|
||||
@@ -0,0 +1,13 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintExcel from "@/components/dashboard/navgan-province-manager/Buttons/printExcel";
|
||||
|
||||
const TableToolbar = ({table}) => {
|
||||
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintExcel table={table}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableToolbar
|
||||
@@ -6,6 +6,7 @@ import TableRowActions from "./TableRowActions";
|
||||
import moment from "jalali-moment";
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import TableToolbar from "@/components/dashboard/navgan-province-manager/TableToolbar";
|
||||
|
||||
function DashboardNavganProvinceManagerComponent() {
|
||||
const t = useTranslations();
|
||||
@@ -162,7 +163,8 @@ function DashboardNavganProvinceManagerComponent() {
|
||||
tableUrl={GET_NAVGAN_PROVINCE_MANAGER}
|
||||
columns={columns}
|
||||
selectableRow={false}
|
||||
enableCustomToolbar={false}
|
||||
enableCustomToolbar={true}
|
||||
CustomToolbar={TableToolbar}
|
||||
enableLastUpdate={true}
|
||||
sorting={[{
|
||||
id: 'id', desc: false
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useMemo, useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {EXPORT_PASSENGER_BOSS} from "@/core/data/apiRoutes";
|
||||
import moment from "jalali-moment";
|
||||
import FileSaver from 'file-saver';
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
|
||||
const PrintExcel = ({table}) => {
|
||||
const t = useTranslations();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {columnFilters, columnFilterFns, sorting} = table.getState()
|
||||
const columns = table.getAllColumns()
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
const filters = columnFilters.map((filter) => {
|
||||
let datatype;
|
||||
for (const i in columns) {
|
||||
if (columns[i].id == filter.id) {
|
||||
datatype = columns[i].columnDef.datatype;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
|
||||
};
|
||||
});
|
||||
params.set("start", '0');
|
||||
params.set("filters", JSON.stringify(filters ?? []));
|
||||
params.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return params;
|
||||
}, [columnFilters, columnFilterFns, sorting, columns,]);
|
||||
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
requestServer(`${EXPORT_PASSENGER_BOSS}?${filterParams}`, 'get', {
|
||||
auth: true,
|
||||
notification: false,
|
||||
requestOptions: {responseType: 'blob'}
|
||||
}).then((response) => {
|
||||
const filename = `گزارش کارگروه استانی تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <DescriptionIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("PassengerBoss.excel_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintExcel
|
||||
@@ -1,10 +1,12 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintFinalCreditAmount from "@/components/dashboard/passenger-boss/Buttons/printFinalCreditAmount";
|
||||
import PrintExcel from "@/components/dashboard/passenger-boss/Buttons/printExcel";
|
||||
|
||||
const TableToolbar = () => {
|
||||
const TableToolbar = ({table}) => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintFinalCreditAmount/>
|
||||
<PrintExcel table={table}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useMemo, useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {EXPORT_PASSENGER_OFFICE} from "@/core/data/apiRoutes";
|
||||
import moment from "jalali-moment";
|
||||
import FileSaver from 'file-saver';
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
|
||||
const PrintExcel = ({table}) => {
|
||||
const t = useTranslations();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {columnFilters, columnFilterFns, sorting} = table.getState()
|
||||
const columns = table.getAllColumns()
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
const filters = columnFilters.map((filter) => {
|
||||
let datatype;
|
||||
for (const i in columns) {
|
||||
if (columns[i].id == filter.id) {
|
||||
datatype = columns[i].columnDef.datatype;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
|
||||
};
|
||||
});
|
||||
params.set("start", '0');
|
||||
params.set("filters", JSON.stringify(filters ?? []));
|
||||
params.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return params;
|
||||
}, [columnFilters, columnFilterFns, sorting, columns,]);
|
||||
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
requestServer(`${EXPORT_PASSENGER_OFFICE}?${filterParams}`, 'get', {
|
||||
auth: true,
|
||||
notification: false,
|
||||
requestOptions: {responseType: 'blob'}
|
||||
}).then((response) => {
|
||||
const filename = `گزارش اداره مسافر تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <DescriptionIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("PassengerOffice.excel_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintExcel
|
||||
13
src/components/dashboard/passenger-office/TableToolbar.jsx
Normal file
13
src/components/dashboard/passenger-office/TableToolbar.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintExcel from "@/components/dashboard/passenger-office/Buttons/printExcel";
|
||||
|
||||
const TableToolbar = ({table}) => {
|
||||
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintExcel table={table}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableToolbar
|
||||
@@ -6,6 +6,7 @@ import TableRowActions from "./TableRowActions";
|
||||
import moment from "jalali-moment";
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import TableToolbar from "@/components/dashboard/passenger-office/TableToolbar";
|
||||
|
||||
function DashboardPassengerOfficeComponent() {
|
||||
const t = useTranslations();
|
||||
@@ -161,7 +162,8 @@ function DashboardPassengerOfficeComponent() {
|
||||
tableUrl={GET_PASSENGER_OFFICE}
|
||||
columns={columns}
|
||||
selectableRow={false}
|
||||
enableCustomToolbar={false}
|
||||
enableCustomToolbar={true}
|
||||
CustomToolbar={TableToolbar}
|
||||
enableLastUpdate={true}
|
||||
sorting={[{
|
||||
id: 'id', desc: false
|
||||
|
||||
135
src/components/dashboard/reports/ExcelExport/index.jsx
Normal file
135
src/components/dashboard/reports/ExcelExport/index.jsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Stack,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
import BackupIcon from '@mui/icons-material/Backup';
|
||||
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";
|
||||
import {periodOfYear} from "@/core/utils/yearPeriodFinder";
|
||||
|
||||
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: periodOfYear(moment().format('jYYYY')).from,
|
||||
to_date: periodOfYear(moment().format('jYYYY')).to
|
||||
}, 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('&')}`
|
||||
: '';
|
||||
setSubmitting(true)
|
||||
requestServer(`${GET_EXPORT}${queryString}`, 'GET', {
|
||||
auth: true,
|
||||
notification: false,
|
||||
requestOptions: {responseType: 'blob'}
|
||||
})
|
||||
.then((response) => {
|
||||
const filename = `گزارش درخواست های وام ناوگان تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<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>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: {xs: "column", sm: "row"},
|
||||
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"}}
|
||||
startIcon={formik.isSubmitting ?
|
||||
<CircularProgress size={20} color="inherit"/> :
|
||||
<BackupIcon/>}
|
||||
>
|
||||
{t("filters.export")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
)
|
||||
};
|
||||
|
||||
export default ExcelExport;
|
||||
111
src/components/dashboard/reports/Filter/ProvinceFilter.jsx
Normal file
111
src/components/dashboard/reports/Filter/ProvinceFilter.jsx
Normal file
@@ -0,0 +1,111 @@
|
||||
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},
|
||||
{name: "تبریز", id: 2},
|
||||
{name: "مشهد", id: 3}
|
||||
]
|
||||
|
||||
const ProvinceFilter = ({formik, multiple, setFilteredText}) => {
|
||||
const {errorProvinceList, isLoadingProvinceList, provinceList} = useProvince();
|
||||
const t = useTranslations();
|
||||
const provinceChange = (event) => {
|
||||
const {target: {value}} = event;
|
||||
formik.handleChange(event);
|
||||
formik.setFieldValue('province_id', value);
|
||||
if (Array.isArray(value)) {
|
||||
const province_names = []
|
||||
value.map((item) => {
|
||||
province_names.push(provinceList.find(province => province.id === item).name)
|
||||
})
|
||||
setFilteredText(prevState => ({
|
||||
...prevState,
|
||||
province: province_names.length !== 0 ? `| ${t("filters.provinces")}: ${province_names.join(' , ')}` : ""
|
||||
}));
|
||||
} else {
|
||||
const selectedProvince = provinceList.find(province => province.id === value);
|
||||
setFilteredText(prevState => ({
|
||||
...prevState,
|
||||
province: `| ${t("filters.province")}: ${selectedProvince.name}`
|
||||
}));
|
||||
}
|
||||
};
|
||||
const needAdorment = multiple
|
||||
? formik.values.province_id.length === 0 :
|
||||
formik.values.province_id === "";
|
||||
return (
|
||||
<FormControl sx={{width: 300, mx: 1, my: {xs: 1, lg: 0}}}>
|
||||
<InputLabel size="small">{t("filters.choose_province")}</InputLabel>
|
||||
<Select
|
||||
name="province_id"
|
||||
multiple={multiple}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formik.values.province_id}
|
||||
onChange={provinceChange}
|
||||
onBlur={formik.handleBlur("province_id")}
|
||||
input={<OutlinedInput label={t("filters.choose_province")}/>}
|
||||
disabled={isLoadingProvinceList || errorProvinceList}
|
||||
renderValue={multiple ? (selected) => (
|
||||
<Box sx={{
|
||||
display: 'flex', gap: 0.7, overflow: "hidden",
|
||||
width: '100%', pr: 4
|
||||
}}>
|
||||
{selected.map((value) => {
|
||||
const selectedProvince = provinceList.find(province => province.id === value);
|
||||
return (
|
||||
<Typography key={value} variant="button">
|
||||
{selectedProvince ? selectedProvince.name : ''}
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
{t("filters.text_field_loading_provinces_list")}
|
||||
</MenuItem>
|
||||
) : errorProvinceList ? (
|
||||
<MenuItem>
|
||||
{t("filters.text_field_error_fetching_provinces")}
|
||||
</MenuItem>
|
||||
) : (
|
||||
provinceList.map((item) => (
|
||||
<MenuItem key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
};
|
||||
|
||||
export default ProvinceFilter;
|
||||
66
src/components/dashboard/reports/Filter/YearFilter.jsx
Normal file
66
src/components/dashboard/reports/Filter/YearFilter.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
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 YearFilter = ({formik, multiple, setFilteredText}) => {
|
||||
const t = useTranslations();
|
||||
const yearChange = (event) => {
|
||||
const {target: {value}} = event;
|
||||
formik.handleChange(event);
|
||||
formik.setFieldValue('date', value);
|
||||
const {from, to} = periodOfYear(value);
|
||||
formik.setFieldValue('from_date', from);
|
||||
formik.setFieldValue('to_date', to);
|
||||
if (Array.isArray(value)) {
|
||||
setFilteredText(prevState => ({
|
||||
...prevState,
|
||||
year: `| ${t("filters.years")}: ${value.join(' , ')}`
|
||||
}));
|
||||
} else {
|
||||
setFilteredText(prevState => ({
|
||||
...prevState,
|
||||
year: `| ${t("filters.year")}: ${value}`
|
||||
}));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<FormControl sx={{width: 300, mx: 1, my: {xs: 1, lg: 0}}}>
|
||||
<InputLabel size="small">{t("filters.choose_year")}</InputLabel>
|
||||
<Select
|
||||
name="date"
|
||||
multiple={multiple}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
value={formik.values.date}
|
||||
onChange={yearChange}
|
||||
onBlur={formik.handleBlur("date")}
|
||||
input={<OutlinedInput label={t("filters.choose_year")}/>}
|
||||
renderValue={multiple ? (selected) => (
|
||||
<Box sx={{display: 'flex', flexWrap: 'wrap', gap: 0.5}}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={value}
|
||||
sx={{color: "#fff", backgroundColor: "primary.dark"}}/>
|
||||
))}
|
||||
</Box>
|
||||
) : undefined}
|
||||
>
|
||||
{
|
||||
shamsiYearList.map((year) => (
|
||||
<MenuItem
|
||||
key={year}
|
||||
value={year}
|
||||
>
|
||||
{year}
|
||||
</MenuItem>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)
|
||||
};
|
||||
|
||||
export default YearFilter;
|
||||
144
src/components/dashboard/reports/Filter/index.jsx
Normal file
144
src/components/dashboard/reports/Filter/index.jsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Button,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import {useFormik} from "formik";
|
||||
import ProvinceFilter from "../Filter/ProvinceFilter";
|
||||
import YearFilter from "../Filter/YearFilter";
|
||||
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 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, 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({
|
||||
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,
|
||||
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);
|
||||
setFilterOption(fields)
|
||||
},
|
||||
});
|
||||
|
||||
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} expanded={expanded} onChange={() => {
|
||||
}} sx={{
|
||||
mb: 2,
|
||||
borderBottom: 2,
|
||||
borderBottomColor: "divider",
|
||||
backgroundColor: "#f1f1f1",
|
||||
}}>
|
||||
<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"}}>{title}</Typography>
|
||||
<Typography variant="caption" sx={{fontWeight: 500}} color="error">
|
||||
{filteredText.province} {filteredText.year}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Tooltip title={t("filters.update_again")} arrow>
|
||||
<IconButton onClick={() => mutate()}
|
||||
size="small">
|
||||
<CachedIcon color="primary"/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("filters.filter_btn")} arrow>
|
||||
<IconButton onClick={() => setExpanded((prevExpanded) => !prevExpanded)}
|
||||
size="small">
|
||||
<FilterAltIcon color="primary"/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{
|
||||
display: "flex",
|
||||
flexDirection: {xs: "column", sm: "row"},
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<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={isLoadingReportList || !formik.dirty}
|
||||
startIcon={isLoadingReportList ?
|
||||
<CircularProgress size={20} color="inherit"/> :
|
||||
<TroubleshootIcon/>}
|
||||
>{t("filters.filter")}</Button>
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)
|
||||
};
|
||||
|
||||
export default Filter;
|
||||
@@ -0,0 +1,47 @@
|
||||
import Chart from "@/core/components/Chart";
|
||||
import {calculateGradientColor} from "@/core/utils/gradientColorHandler";
|
||||
|
||||
const RadialBarChart = ({data}) => {
|
||||
const specialOption = {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -90,
|
||||
endAngle: 90,
|
||||
track: {
|
||||
background: "#e7e7e7",
|
||||
strokeWidth: '90%',
|
||||
margin: 5, // margin is in pixels
|
||||
dropShadow: {
|
||||
enabled: true,
|
||||
top: 2,
|
||||
left: 0,
|
||||
color: '#999',
|
||||
opacity: 1,
|
||||
blur: 2
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
name: {
|
||||
show: false
|
||||
},
|
||||
value: {
|
||||
offsetY: -2,
|
||||
fontSize: '22px'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
colors: calculateGradientColor(data)
|
||||
},
|
||||
};
|
||||
const series = [data]
|
||||
return (
|
||||
<Chart chartId="LoanProgressBar" type="radialBar" specialOption={specialOption} series={series}/>
|
||||
)
|
||||
};
|
||||
|
||||
export default RadialBarChart;
|
||||
@@ -0,0 +1,46 @@
|
||||
import Chart from "@/core/components/Chart";
|
||||
import {calculateGradientColor} from "@/core/utils/gradientColorHandler";
|
||||
|
||||
const RadialBarSemiCircularChart = ({data, label}) => {
|
||||
const specialOption = {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -110,
|
||||
endAngle: 110,
|
||||
track: {
|
||||
background: "#e7e7e7",
|
||||
strokeWidth: '80%',
|
||||
},
|
||||
dataLabels: {
|
||||
name: {
|
||||
fontSize: '20px',
|
||||
offsetY: 110,
|
||||
color: "#8c8c8c"
|
||||
},
|
||||
value: {
|
||||
offsetY: 50,
|
||||
fontSize: '50px',
|
||||
fontWeight: 500,
|
||||
color: "#8c8c8c",
|
||||
formatter: function (val) {
|
||||
return val + "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
colors: calculateGradientColor(data)
|
||||
},
|
||||
labels: [label],
|
||||
};
|
||||
const series = [data]
|
||||
return (
|
||||
<Chart chartId="LoanProgressBar" type="radialBar" specialOption={specialOption} series={series}/>
|
||||
)
|
||||
};
|
||||
|
||||
export default RadialBarSemiCircularChart;
|
||||
@@ -0,0 +1,51 @@
|
||||
import Chart from "@/core/components/Chart";
|
||||
import {calculateGradientColor} from "@/core/utils/gradientColorHandler";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
const RadialBarStrokedChart = ({data, label}) => {
|
||||
const t = useTranslations();
|
||||
const specialOption = {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
plotOptions: {
|
||||
radialBar: {
|
||||
startAngle: -110,
|
||||
endAngle: 110,
|
||||
track: {
|
||||
background: "#e7e7e7",
|
||||
strokeWidth: '80%',
|
||||
},
|
||||
dataLabels: {
|
||||
name: {
|
||||
fontSize: '20px',
|
||||
offsetY: 110,
|
||||
color: "#8c8c8c"
|
||||
},
|
||||
value: {
|
||||
offsetY: 50,
|
||||
fontSize: '50px',
|
||||
fontWeight: 500,
|
||||
color: "#8c8c8c",
|
||||
formatter: function (val) {
|
||||
return val + "%";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
stroke: {
|
||||
dashArray: 4
|
||||
},
|
||||
fill: {
|
||||
colors: calculateGradientColor(data)
|
||||
},
|
||||
labels: [label],
|
||||
};
|
||||
const series = [data]
|
||||
return (
|
||||
<Chart chartId="LoanProgressBar" type="radialBar" specialOption={specialOption} series={series}/>
|
||||
)
|
||||
};
|
||||
|
||||
export default RadialBarStrokedChart;
|
||||
37
src/components/dashboard/reports/LoanDetails/index.jsx
Normal file
37
src/components/dashboard/reports/LoanDetails/index.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import {Grid, Paper} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import RadialBarSemiCircularChart from "@/components/dashboard/reports/LoanDetails/RadialBarSemiCircularChart";
|
||||
import RadialBarStrokedChart from "@/components/dashboard/reports/LoanDetails/RadialBarStrokedChart";
|
||||
|
||||
|
||||
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}*/}
|
||||
{/* ]}*/}
|
||||
{/* 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")}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={10} md={5} sx={{aspectRatio: "1/0.75"}}>
|
||||
<RadialBarSemiCircularChart data={60} label={t("reports.number_of_loan_refer_to_bank")}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={10} md={5} sx={{aspectRatio: "1/0.75"}}>
|
||||
<RadialBarSemiCircularChart data={95} label={t("reports.cost_of_payed_loan")}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={10} md={5} sx={{aspectRatio: "1/0.75"}}>
|
||||
<RadialBarStrokedChart data={15} label={t("reports.number_of_payed_loan")}/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
};
|
||||
|
||||
export default LoanDetails;
|
||||
@@ -0,0 +1,34 @@
|
||||
import Chart from "@/core/components/Chart";
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import {useMediaQuery} from "@mui/material";
|
||||
|
||||
|
||||
const Pie = ({data}) => {
|
||||
const theme = useTheme();
|
||||
const upperMd = useMediaQuery((theme.breakpoints.up("md")))
|
||||
const specialOption = {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.9,
|
||||
},
|
||||
labels: data.map((item) => ` ${item.state_name}`),
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 300,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: upperMd
|
||||
}
|
||||
};
|
||||
const series = data.map((item) => +item.percentage)
|
||||
return (
|
||||
<Chart chartId="LoanDistributionPie" type="pie" specialOption={specialOption} series={series}/>
|
||||
)
|
||||
};
|
||||
|
||||
export default Pie;
|
||||
@@ -0,0 +1,41 @@
|
||||
import Chart from "@/core/components/Chart";
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import {useMediaQuery} from "@mui/material";
|
||||
|
||||
const PolarChart = ({data}) => {
|
||||
const theme = useTheme();
|
||||
const upperMd = useMediaQuery((theme.breakpoints.up("md")))
|
||||
const specialOption = {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
plotOptions: {
|
||||
polarArea: {
|
||||
rings: {
|
||||
strokeWidth: 2,
|
||||
strokeColor: '#e3e3e3',
|
||||
},
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
opacity: 0.9,
|
||||
},
|
||||
labels: data.map((item) => ` ${item.state_name}`),
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: upperMd
|
||||
}
|
||||
};
|
||||
const series = data.map((item) => +item.percentage)
|
||||
return (
|
||||
<Chart chartId="LoanDistributionPolar" type="polarArea" specialOption={specialOption} series={series}/>
|
||||
)
|
||||
};
|
||||
|
||||
export default PolarChart;
|
||||
60
src/components/dashboard/reports/LoanDistribution/index.jsx
Normal file
60
src/components/dashboard/reports/LoanDistribution/index.jsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import {Grid, Paper, Typography} from "@mui/material";
|
||||
import PolarChart from "./PolarChart";
|
||||
import Pie from "./PieChart";
|
||||
import {useTranslations} from "next-intl";
|
||||
import Filter from "@/components/dashboard/reports/Filter";
|
||||
import useChart from "@/lib/app/hooks/useChart";
|
||||
import {GET_LOAN_DISTRIBUTION} from "@/core/data/apiRoutes";
|
||||
import LoadingHardPage from "@/core/components/LoadingHardPage";
|
||||
import AnalyticsIcon from "@mui/icons-material/Analytics";
|
||||
|
||||
const LoanDistribution = () => {
|
||||
const t = useTranslations();
|
||||
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")}
|
||||
filterItem={[
|
||||
{type: "province", type_fa: "استان", multiple: true},
|
||||
{type: "year", type_fa: "سال", multiple: false}
|
||||
]}
|
||||
setFilterOption={setFilterOption}
|
||||
isLoadingReportList={isLoadingReportList}
|
||||
mutate={mutate}
|
||||
/>
|
||||
<Grid container sx={{justifyContent: {xs: "center", md: "space-between", position: "relative"}}}>
|
||||
{isLoadingReportList ? (
|
||||
<Grid item xs={12} sx={{aspectRatio: "3/0.95"}}>
|
||||
<LoadingHardPage
|
||||
icon={<AnalyticsIcon sx={{width: "inherit", height: "inherit"}}/>}
|
||||
loading={isLoadingReportList}
|
||||
label={t("reports.loading_fetching_reports")}
|
||||
width={100}
|
||||
height={100}
|
||||
sx={{position: "absolute", bgcolor: "#f7f7f7e6"}}
|
||||
/>
|
||||
</Grid>
|
||||
) : errorReportList ? (
|
||||
<Grid item xs={12}
|
||||
sx={{aspectRatio: "3/0.95", display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Typography variant="h6" sx={{
|
||||
color: "#858585",
|
||||
letterSpacing: "0.1rem"
|
||||
}}>{t("reports.error_fetching_reports")}</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
<>
|
||||
<Grid item xs={12} sm={10} md={6} xl={5} sx={{aspectRatio: "1/0.75"}}>
|
||||
<PolarChart data={reportList}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={10} md={6} xl={5} sx={{aspectRatio: "1/0.75"}}>
|
||||
<Pie data={reportList}/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
};
|
||||
|
||||
export default LoanDistribution;
|
||||
64
src/components/dashboard/reports/LoanProgress/BarChart.jsx
Normal file
64
src/components/dashboard/reports/LoanProgress/BarChart.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import {calculateGradientColor} from "@/core/utils/gradientColorHandler";
|
||||
import Chart from "@/core/components/Chart";
|
||||
|
||||
const BarChart = ({data}) => {
|
||||
const usableArray = data.map(({province_name, percentage, ...rest}) => ({
|
||||
x: province_name,
|
||||
y: +percentage,
|
||||
...rest,
|
||||
}));
|
||||
let colorArray = data.map((obj) => calculateGradientColor(obj.percentage));
|
||||
const specialOption = {
|
||||
title: {
|
||||
text: undefined,
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
distributed: true,
|
||||
horizontal: false,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
colors: colorArray,
|
||||
xaxis: {
|
||||
labels: {
|
||||
rotate: 45,
|
||||
rotateAlways: false,
|
||||
trim: false,
|
||||
style: {
|
||||
fontWeight: 600,
|
||||
fontSize: '11px',
|
||||
colors: '#7e7e7e',
|
||||
},
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
labels: {
|
||||
formatter: function (val) {
|
||||
return val + '%';
|
||||
},
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
style: {
|
||||
fontSize: '14px',
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
};
|
||||
const series = [
|
||||
{
|
||||
name: 'درصد پیشرفت',
|
||||
data: usableArray,
|
||||
},
|
||||
];
|
||||
|
||||
return <Chart chartId="LoanProgressBar" type="bar" specialOption={specialOption} series={series}/>;
|
||||
};
|
||||
|
||||
export default BarChart;
|
||||
67
src/components/dashboard/reports/LoanProgress/index.jsx
Normal file
67
src/components/dashboard/reports/LoanProgress/index.jsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import {Grid, Paper, Typography} from "@mui/material";
|
||||
import BarChart from "./BarChart";
|
||||
import Filter from "../Filter";
|
||||
import {useTranslations} from "next-intl";
|
||||
import useChart from "@/lib/app/hooks/useChart";
|
||||
import {GET_PROVINCE_PROGRESS} from "@/core/data/apiRoutes";
|
||||
import LoadingHardPage from "@/core/components/LoadingHardPage";
|
||||
import AnalyticsIcon from '@mui/icons-material/Analytics';
|
||||
|
||||
const LoanProgress = () => {
|
||||
const t = useTranslations();
|
||||
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")}
|
||||
filterItem={[
|
||||
{type: "year", type_fa: "سال", multiple: false}
|
||||
]}
|
||||
setFilterOption={setFilterOption}
|
||||
isLoadingReportList={isLoadingReportList}
|
||||
mutate={mutate}
|
||||
/>
|
||||
<Grid container>
|
||||
{isLoadingReportList ? (
|
||||
<Grid item xs={12}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
aspectRatio: {xs: "1/1", sm: "1/1", md: "1/0.4", xl: "1/0.3", position: "relative"}
|
||||
}}>
|
||||
<LoadingHardPage
|
||||
icon={<AnalyticsIcon sx={{width: "inherit", height: "inherit"}}/>}
|
||||
loading={isLoadingReportList}
|
||||
label={t("reports.loading_fetching_reports")}
|
||||
width={100}
|
||||
height={100}
|
||||
sx={{position: "absolute", bgcolor: "#f7f7f7e6"}}
|
||||
/>
|
||||
</Grid>
|
||||
) : errorReportList ? (
|
||||
<Grid item xs={12}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
aspectRatio: {xs: "1/1", sm: "1/1", md: "1/0.4", xl: "1/0.3", position: "relative"}
|
||||
}}>
|
||||
<Typography variant="h6" sx={{
|
||||
color: "#858585",
|
||||
letterSpacing: "0.1rem"
|
||||
}}>{t("reports.error_fetching_reports")}</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
<Grid item xs={12}
|
||||
sx={{
|
||||
aspectRatio: {xs: "1/1", sm: "1/1", md: "1/0.4", xl: "1/0.3", position: "relative"}
|
||||
}}>
|
||||
<BarChart data={reportList}/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
};
|
||||
|
||||
export default LoanProgress;
|
||||
17
src/components/dashboard/reports/index.jsx
Normal file
17
src/components/dashboard/reports/index.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import LoanProgress from "@/components/dashboard/reports/LoanProgress";
|
||||
import LoanDistribution from "@/components/dashboard/reports/LoanDistribution";
|
||||
import LoanDetails from "@/components/dashboard/reports/LoanDetails";
|
||||
import ExcelExport from "@/components/dashboard/reports/ExcelExport";
|
||||
|
||||
const DashboardReportsComponent = (props) => {
|
||||
return (
|
||||
<>
|
||||
<ExcelExport/>
|
||||
<LoanProgress/>
|
||||
<LoanDistribution/>
|
||||
<LoanDetails/>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default DashboardReportsComponent;
|
||||
@@ -0,0 +1,68 @@
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useMemo, useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {EXPORT_TRANSPORTATION_ASSISTANCE} from "@/core/data/apiRoutes";
|
||||
import moment from "jalali-moment";
|
||||
import FileSaver from 'file-saver';
|
||||
import DescriptionIcon from '@mui/icons-material/Description';
|
||||
|
||||
const PrintExcel = ({table}) => {
|
||||
const t = useTranslations();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {columnFilters, columnFilterFns, sorting} = table.getState()
|
||||
const columns = table.getAllColumns()
|
||||
|
||||
const filterParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
const filters = columnFilters.map((filter) => {
|
||||
let datatype;
|
||||
for (const i in columns) {
|
||||
if (columns[i].id == filter.id) {
|
||||
datatype = columns[i].columnDef.datatype;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
|
||||
};
|
||||
});
|
||||
params.set("start", '0');
|
||||
params.set("filters", JSON.stringify(filters ?? []));
|
||||
params.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return params;
|
||||
}, [columnFilters, columnFilterFns, sorting, columns,]);
|
||||
|
||||
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
requestServer(`${EXPORT_TRANSPORTATION_ASSISTANCE}?${filterParams}`, 'get', {
|
||||
auth: true,
|
||||
notification: false,
|
||||
requestOptions: {responseType: 'blob'}
|
||||
}).then((response) => {
|
||||
const filename = `گزارش معاونت حمل و نقل تاریخ_${moment().format('jYYYY_jMM_jDD')}.xlsx`;
|
||||
FileSaver.saveAs(response.data, filename);
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <DescriptionIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("TransportationAssistance.excel_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintExcel
|
||||
@@ -1,21 +1,88 @@
|
||||
import UploadSystem from "@/core/components/UploadSystem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
FormHelperText, Grid, LinearProgress,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {CONFIRM_TRANSPORTATION_ASSISTANCE} from "@/core/data/apiRoutes";
|
||||
import {useState} from "react";
|
||||
import {CONFIRM_TRANSPORTATION_ASSISTANCE, GET_TRANSPORTATION_ASSISTANCE_DETAILS} from "@/core/data/apiRoutes";
|
||||
import {useEffect, useState} from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
import CustomAccordion from "@/core/components/CustomAccordion";
|
||||
import ImageContent from "@/components/dashboard/transportation-assistance/Form/ConfirmForm/ImageContent";
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog ,handleSizeChangeClick}) => {
|
||||
const t = useTranslations();
|
||||
const [selectedImage, setSelectedImage] = useState("");
|
||||
const [fileType, setfileType] = useState(null);
|
||||
const [fileName, setfileName] = useState(null);
|
||||
const [showAddIcon, setShowAddIcon] = useState(true);
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {update_notification} = useNotification()
|
||||
const requestServer = useRequest({auth: true , notification: false})
|
||||
const [accordionStates, setAccordionStates] = useState([]);
|
||||
const [hasAccordionOpened, setHasAccordionOpened] = useState([]);
|
||||
const {update_notification} = useNotification();
|
||||
const [hasImageShown, sethasImageShown] = useState(false);
|
||||
const [imageLink, setimageLink] = useState(null);
|
||||
const [detailsList, setDetailsData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isSkeleton, setIsSkeleton] = useState(false);
|
||||
const [isButtonDisabled, setIsButtonDisabled] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
requestServer(`${GET_TRANSPORTATION_ASSISTANCE_DETAILS}/${rowId}`, 'get')
|
||||
.then((response) => {
|
||||
const booleanArray = Array.from({ length: response.data.data.attachment_files.length }, () => false);
|
||||
setAccordionStates(booleanArray);
|
||||
setHasAccordionOpened(booleanArray);
|
||||
setDetailsData(response.data)
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (detailsList && detailsList.data && detailsList.data.attachment_files && detailsList.data.attachment_files.length !== 0) {
|
||||
setIsButtonDisabled(true);
|
||||
} else {
|
||||
setIsButtonDisabled(false);
|
||||
}
|
||||
}, [detailsList]);
|
||||
|
||||
const handleAccordionChange = (index) => {
|
||||
const newAccordionStates = accordionStates.map((state, i) => i === index ? !state : false);
|
||||
setAccordionStates(newAccordionStates);
|
||||
|
||||
if (newAccordionStates[index]===false)
|
||||
{
|
||||
handleSizeChangeClick(index , false);
|
||||
sethasImageShown(false);
|
||||
setIsSkeleton(false)
|
||||
}
|
||||
else {
|
||||
handleSizeChangeClick(index , true);
|
||||
sethasImageShown(true);
|
||||
setimageLink(detailsList.data.attachment_files[index])
|
||||
setIsSkeleton(true)
|
||||
const newHasAccordionOpened = [...hasAccordionOpened];
|
||||
newHasAccordionOpened[index] = true;
|
||||
setHasAccordionOpened(newHasAccordionOpened);
|
||||
|
||||
if (newHasAccordionOpened.every(opened => opened === true)) {
|
||||
setIsButtonDisabled(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
|
||||
@@ -72,51 +139,96 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderCustomAccordions = () => {
|
||||
if (detailsList && detailsList.data && detailsList.data.attachment_files) {
|
||||
return detailsList.data.attachment_files.map((file, index) => (
|
||||
<CustomAccordion
|
||||
key={index}
|
||||
title={`${file.title}`}
|
||||
hasOpened={accordionStates[index]}
|
||||
hasAccordionOpened={hasAccordionOpened[index]}
|
||||
handleAccordionChange={() => handleAccordionChange(index)}
|
||||
accordionIndex={index}
|
||||
handleSizeChange={() => handleSizeChange(index)}
|
||||
/>
|
||||
));
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Stack spacing={2}>
|
||||
<Stack>
|
||||
<TextField
|
||||
name="description"
|
||||
multiline
|
||||
rows={8}
|
||||
label={<>
|
||||
<span>{t("ConfirmDialog.description")}</span><small>{t("ConfirmDialog.optional")}</small></>}
|
||||
value={formik.values.description}
|
||||
onChange={handleDescriptionChange}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
sx={{mt: 1}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography>{t("UploadSystem.upload_file")}{t("UploadSystem.optional")}</Typography>
|
||||
<UploadSystem
|
||||
selectedImage={selectedImage}
|
||||
handleUploadChange={handleUploadChange} // Pass the updated function directly
|
||||
setselectedImage={setSelectedImage}
|
||||
setFieldValue={formik.setFieldValue} // Remove props.setFieldValue, use formik.setFieldValue
|
||||
fieldname="confirm_img"
|
||||
fileType={fileType}
|
||||
fileName={fileName}
|
||||
imageAlt={t("app_name")}
|
||||
imageSize={[250, 150]}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
onBlur={() => formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={hasImageShown ? 6 : 12}>
|
||||
<Box sx={{ border: '1px solid #ccc' ,p:2 , mb:2 , borderRadius: '8px'}}>
|
||||
<Typography sx={{color: "primary.main" , fontWeight: 500}}>{t("ConfirmDialog.approved_amount")} {t("sidebar.machinery-expert")}</Typography>
|
||||
<Typography pt={1}>
|
||||
{isNaN((detailsList?.data?.proposed_amount) / 10)
|
||||
? ""
|
||||
: ((detailsList?.data?.proposed_amount) / 10).toLocaleString()}{" "}
|
||||
{t("ConfirmDialog.unit")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ border: '1px solid #ccc' ,p:2 , mb:2 , borderRadius: '8px'}}>
|
||||
<Typography sx={{color: "primary.main" , fontWeight: 500}}>{t("ConfirmDialog.approved_amount")} {t("secondary.passenger-boss")}</Typography>
|
||||
<Typography pt={1}>
|
||||
{isNaN((detailsList?.data?.approved_amount) / 10)
|
||||
? ""
|
||||
: ((detailsList?.data?.approved_amount) / 10).toLocaleString()}{" "}
|
||||
{t("ConfirmDialog.unit")}
|
||||
</Typography>
|
||||
</Box>
|
||||
{loading ? <LinearProgress /> : renderCustomAccordions()}
|
||||
<Stack spacing={2}>
|
||||
<Stack>
|
||||
<TextField
|
||||
name="description"
|
||||
multiline
|
||||
rows={8}
|
||||
label={<>
|
||||
<span>{t("ConfirmDialog.description")}</span><small>{t("ConfirmDialog.optional")}</small></>}
|
||||
value={formik.values.description}
|
||||
onChange={handleDescriptionChange}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
sx={{mt: 1}}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography>{t("UploadSystem.upload_file")}{t("UploadSystem.optional")}</Typography>
|
||||
<UploadSystem
|
||||
selectedImage={selectedImage}
|
||||
handleUploadChange={handleUploadChange}
|
||||
setselectedImage={setSelectedImage}
|
||||
setFieldValue={formik.setFieldValue}
|
||||
fieldname="confirm_img"
|
||||
fileType={fileType}
|
||||
fileName={fileName}
|
||||
imageAlt={t("app_name")}
|
||||
imageSize={[250, 150]}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
onBlur={() => formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Stack sx={{ display: hasImageShown ? 'flex' : 'none', alignItems: 'center', gap: 0.5 , justifyContent:'center'}}>
|
||||
<ImageContent imageLink={imageLink} isSkeleton={isSkeleton} setIsSkeleton={setIsSkeleton}/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenConfirmDialog(false)} variant="outlined" color="secondary"
|
||||
@@ -124,7 +236,9 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
{t("ConfirmDialog.button-cancel")}
|
||||
</Button>
|
||||
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
|
||||
disabled={formik.isSubmitting || !formik.isValid}>
|
||||
disabled={formik.isSubmitting || !formik.isValid ||
|
||||
isButtonDisabled }
|
||||
>
|
||||
{t("ConfirmDialog.button-confirm")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import {Box, Skeleton, Stack} from "@mui/material";
|
||||
import {useEffect} from "react";
|
||||
const ImageContent = (link) => {
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
link.setIsSkeleton(false);
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [link.isSkeleton]);
|
||||
|
||||
const isPDF = link && link.imageLink && link.imageLink.attachment && link.imageLink.attachment.endsWith('.pdf');
|
||||
return (
|
||||
<>
|
||||
<Stack sx={{width :"100%"}}>
|
||||
{link.isSkeleton && (
|
||||
<Box sx={{display:'flex' ,justifyContent:'center' , border: '1px solid #ccc' ,p:5 , height: '700px' , borderRadius: '8px' , width:'100%'}}>
|
||||
<Skeleton variant="rectangular" animation="wave" sx={{ width: '100%', height: '100%' }} />
|
||||
</Box>
|
||||
)}
|
||||
{isPDF ? (
|
||||
<embed src={link.imageLink.attachment} type="application/pdf" width="100%" height="700px" style={{ display: link.isSkeleton ? 'none' : 'block' }}/>
|
||||
) : (
|
||||
link && link.imageLink && link.imageLink.attachment && (
|
||||
<Box sx={{display: link.isSkeleton ? 'none' : 'flex' ,justifyContent:'center' , border: '1px solid #ccc' , p:5 , height: '700px' , borderRadius: '8px',}}>
|
||||
<Box component="img" src={link.imageLink.attachment} alt="Image Preview" width="auto" height="auto" sx={{maxHeight: '700px' , maxWidth:'100%'}}></Box>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
export default ImageContent;
|
||||
@@ -1,27 +1,45 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useState} from "react";
|
||||
import {useState , useEffect} from "react";
|
||||
import ConfirmContent from "./ConfirmContent";
|
||||
import CallMadeIcon from '@mui/icons-material/CallMade';
|
||||
const Confirm = ({rowId, mutate}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
const [dialogSize, setDialogSize] = useState('sm');
|
||||
|
||||
useEffect(() => {
|
||||
if (!openConfirmDialog) {
|
||||
setDialogSize('sm');
|
||||
}
|
||||
}, [openConfirmDialog]);
|
||||
|
||||
const handleSizeChangeClick = (num , state) => {
|
||||
if (state===true){
|
||||
setDialogSize('xl');
|
||||
|
||||
}
|
||||
else {
|
||||
setDialogSize('sm');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={t("ConfirmDialog.confirm")}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenConfirmDialog(true)
|
||||
setOpenConfirmDialog(true);
|
||||
}}
|
||||
>
|
||||
<CallMadeIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
PaperProps={{sx: {transition: 'all .3s', boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}} maxWidth={dialogSize}>
|
||||
<DialogTitle>{t("TransportationAssistance.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog} setDialogSize={setDialogSize} handleSizeChangeClick={handleSizeChangeClick}/>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintExcel from "@/components/dashboard/transportation-assistance/Buttons/printExcel";
|
||||
|
||||
const TableToolbar = ({table}) => {
|
||||
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintExcel table={table}/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableToolbar
|
||||
@@ -6,6 +6,7 @@ import TableRowActions from "./TableRowActions";
|
||||
import moment from "jalali-moment";
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import TableToolbar from "@/components/dashboard/transportation-assistance/TableToolbar";
|
||||
|
||||
function DashboardTransportationAssistanceComponent() {
|
||||
const t = useTranslations();
|
||||
@@ -161,7 +162,8 @@ function DashboardTransportationAssistanceComponent() {
|
||||
tableUrl={GET_TRANSPORTATION_ASSISTANCE}
|
||||
columns={columns}
|
||||
selectableRow={false}
|
||||
enableCustomToolbar={false}
|
||||
enableCustomToolbar={true}
|
||||
CustomToolbar={TableToolbar}
|
||||
enableLastUpdate={true}
|
||||
sorting={[{
|
||||
id: 'id', desc: false
|
||||
|
||||
@@ -51,6 +51,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
if (hasSubItems) {
|
||||
dispatch({type: "COLLAPSE_MENU", key: item.key});
|
||||
}
|
||||
handleDrawerToggle()
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
|
||||
@@ -43,6 +43,9 @@ const SidebarListSubItem = ({item, handleDrawerToggle}) => {
|
||||
to={{
|
||||
pathname: subitem.route,
|
||||
}}
|
||||
onClick={() => {
|
||||
handleDrawerToggle()
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
}}
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import useLanguage from "@/lib/app/hooks/useLanguage";
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import WidgetsIcon from '@mui/icons-material/Widgets';
|
||||
|
||||
const widgetIconSvgString = ReactDOMServer.renderToString(<WidgetsIcon/>);
|
||||
|
||||
const ApexChart = dynamic(() => import("react-apexcharts"), {ssr: false});
|
||||
|
||||
const Chart = ({type, series, specialOption}) => {
|
||||
const Chart = ({chartId, type, series, specialOption}) => {
|
||||
const {languageApp, languageList} = useLanguage();
|
||||
const chartLang = languageList.find((item) => item.key == languageApp).chartLocalization
|
||||
const options = {
|
||||
chart: {
|
||||
id: chartId,
|
||||
locales: [chartLang],
|
||||
defaultLocale: languageApp,
|
||||
fontFamily: languageList[0].fontFamily
|
||||
fontFamily: languageList[0].fontFamily,
|
||||
toolbar: {
|
||||
tools: {
|
||||
zoomIn: false,
|
||||
zoomOut: false,
|
||||
download: widgetIconSvgString,
|
||||
}
|
||||
}
|
||||
},
|
||||
...(specialOption ? specialOption : {})
|
||||
}
|
||||
|
||||
72
src/core/components/CustomAccordion.jsx
Normal file
72
src/core/components/CustomAccordion.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Box,
|
||||
Typography,
|
||||
Grid, ButtonGroup, Button, CircularProgress
|
||||
} from "@mui/material";
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import VerifiedIcon from '@mui/icons-material/Verified';
|
||||
import Check from "@mui/icons-material/Check";
|
||||
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
|
||||
import VisibilityRoundedIcon from "@mui/icons-material/VisibilityRounded";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useState} from "react";
|
||||
|
||||
const CustomAccordion = ({ title,
|
||||
hasOpened,
|
||||
hasAccordionOpened,
|
||||
handleAccordionChange,
|
||||
accordionIndex,
|
||||
handleSizeChange }) => {
|
||||
const t = useTranslations();
|
||||
const [downloadStatus, setDownloadStatus] = useState('initial');
|
||||
const handleDownloadClick = (setStatus) => {
|
||||
|
||||
console.log(setStatus)
|
||||
setStatus('downloading');
|
||||
setTimeout(() => {
|
||||
setStatus('completed');
|
||||
setTimeout(() => {
|
||||
setStatus('initial');
|
||||
}, 2500);
|
||||
}, 2000);
|
||||
// Perform the actual download logic here
|
||||
};
|
||||
return(
|
||||
<Accordion elevation={0} expanded={hasOpened || false} onChange={() => handleAccordionChange(accordionIndex)} sx={{
|
||||
mb: 2,
|
||||
boxShadow: 1,
|
||||
backgroundColor: "#f7f7f7"
|
||||
}}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
|
||||
<Typography sx={{ fontWeight: 500, color: "primary.main" }}>{title}</Typography>
|
||||
{hasAccordionOpened && <VerifiedIcon style={{ color: "green" }} />}
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||
<Grid container direction="column" alignItems="center">
|
||||
<Grid item>
|
||||
<ButtonGroup variant="contained" aria-label="outlined primary button group">
|
||||
<Button onClick={() => handleDownloadClick(setDownloadStatus)}>
|
||||
{downloadStatus === 'completed' ? (
|
||||
<Check sx={{ color: 'white' }} fontSize="small" />
|
||||
) : downloadStatus === 'downloading' ? (
|
||||
<CircularProgress sx={{ color: 'white' }} size={18} />
|
||||
) : (
|
||||
<FileDownloadOutlinedIcon sx={{ color: 'white' }} fontSize="small" />
|
||||
)}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomAccordion;
|
||||
@@ -114,6 +114,7 @@ function DataTable(props) {
|
||||
renderTopToolbarCustomActions={({table}) => (<>
|
||||
{props.enableCustomToolbar /* send condition */ ?
|
||||
<props.CustomToolbar
|
||||
table={table}
|
||||
mutate={mutate}/> /* send component */ : <span></span>}
|
||||
</>)}
|
||||
renderBottomToolbarCustomActions={({table}) => (<>
|
||||
|
||||
38
src/core/components/DownloadSection.jsx
Normal file
38
src/core/components/DownloadSection.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
|
||||
import Check from '@mui/icons-material/Check';
|
||||
import {CircularProgress} from "@mui/material"
|
||||
const DownloadSection = ({ downloadStatus, onDownloadClick }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
width: '30',
|
||||
height: '30',
|
||||
backgroundColor: downloadStatus === 'completed' ? '#0070f3' : 'white',
|
||||
border: `1px solid ${downloadStatus === 'completed' ? '#0070f3' : 'gray'}`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
transition: 'background 0.2s, box-shadow 0.2s',
|
||||
boxShadow: downloadStatus === 'completed' ? '0 0 5px rgba(0, 112, 243, 0.5)' : 'none',
|
||||
padding:'5px'
|
||||
}}
|
||||
onClick={onDownloadClick}
|
||||
>
|
||||
{downloadStatus === 'completed' ? (
|
||||
<>
|
||||
<Check sx={{ color: 'white' }} fontSize="medium" />
|
||||
</>
|
||||
) : downloadStatus === 'downloading' ? (
|
||||
<CircularProgress size={25} />
|
||||
) : (
|
||||
<>
|
||||
<FileDownloadOutlinedIcon sx={{ color: '#0070f3' }} fontSize="medium" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadSection;
|
||||
@@ -21,6 +21,9 @@ export const CONFIRM_PASSENGER_OFFICE =
|
||||
|
||||
export const REJECT_PASSENGER_OFFICE =
|
||||
BASE_URL + "/dashboard/passenger_office_chief/reject";
|
||||
|
||||
export const EXPORT_PASSENGER_OFFICE =
|
||||
BASE_URL + "/dashboard/passenger_office_chief/export"
|
||||
//passenger office
|
||||
|
||||
//passenger boss
|
||||
@@ -32,17 +35,23 @@ export const CONFIRM_PASSENGER_BOSS =
|
||||
|
||||
export const REJECT_PASSENGER_BOSS =
|
||||
BASE_URL + "/dashboard/province_working_group/reject";
|
||||
export const EXPORT_PASSENGER_BOSS =
|
||||
BASE_URL + "/dashboard/province_working_group/export";
|
||||
//passenger boss
|
||||
|
||||
//transportation assistance
|
||||
export const GET_TRANSPORTATION_ASSISTANCE =
|
||||
BASE_URL + "/dashboard/transportation_assistant/show";
|
||||
|
||||
export const GET_TRANSPORTATION_ASSISTANCE_DETAILS =
|
||||
BASE_URL + "/dashboard/transportation_assistant";
|
||||
export const CONFIRM_TRANSPORTATION_ASSISTANCE =
|
||||
BASE_URL + "/dashboard/transportation_assistant/confirm";
|
||||
|
||||
export const REJECT_TRANSPORTATION_ASSISTANCE =
|
||||
BASE_URL + "/dashboard/transportation_assistant/reject";
|
||||
export const EXPORT_TRANSPORTATION_ASSISTANCE =
|
||||
BASE_URL + "/dashboard/transportation_assistant/export";
|
||||
//transportation assistance
|
||||
|
||||
//machinary office
|
||||
@@ -54,6 +63,8 @@ export const CONFIRM_MACHINARY_OFFICE =
|
||||
|
||||
export const REJECT_MACHINARY_OFFICE =
|
||||
BASE_URL + "/dashboard/machinery_expert/reject";
|
||||
export const EXPORT_MACHINARY_OFFICE =
|
||||
BASE_URL + "/dashboard/machinery_expert/export";
|
||||
//machinary office
|
||||
|
||||
//commercial chief
|
||||
@@ -76,6 +87,8 @@ export const CONFIRM_NAVGAN_PROVINCE_MANAGER =
|
||||
|
||||
export const REJECT_NAVGAN_PROVINCE_MANAGER =
|
||||
BASE_URL + "/dashboard/province_manager/reject";
|
||||
export const EXPORT_NAVGAN_PROVINCE_MANAGER =
|
||||
BASE_URL + "/dashboard/province_manager/export";
|
||||
//navgan province manager
|
||||
|
||||
// refahi province manager
|
||||
@@ -197,3 +210,13 @@ export const DELETE_ROLE_MANAGEMENT =
|
||||
export const GET_PERMISSIONS_LIST =
|
||||
BASE_URL + "/dashboard/permissions/list"
|
||||
//role management
|
||||
|
||||
// reports
|
||||
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
|
||||
|
||||
//loan history
|
||||
export const GET_LOAN_HISTORY = BASE_URL + "/dashboard/navgan_loans"
|
||||
export const GET_HISTORY_DETAIL = BASE_URL + "/dashboard/navgan_loans"
|
||||
|
||||
@@ -13,6 +13,8 @@ import PaidIcon from '@mui/icons-material/Paid';
|
||||
import ManageAccountsIcon from '@mui/icons-material/ManageAccounts';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import AccessibilityIcon from '@mui/icons-material/Accessibility';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
import SecurityIcon from '@mui/icons-material/Security';
|
||||
|
||||
const sidebarMenu = [
|
||||
[
|
||||
@@ -24,6 +26,15 @@ const sidebarMenu = [
|
||||
selected: false,
|
||||
permission: "all",
|
||||
},
|
||||
{
|
||||
key: "sidebar.reports",
|
||||
name: "reports",
|
||||
type: "page",
|
||||
route: "/dashboard/reports",
|
||||
icon: <AssessmentIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "all",
|
||||
},
|
||||
{
|
||||
key: "sidebar.passenger-office-chief",
|
||||
secondary: "secondary.passenger-office",
|
||||
@@ -146,7 +157,7 @@ const sidebarMenu = [
|
||||
route: "/dashboard/expert-management",
|
||||
icon: <ManageAccountsIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_navgan_loan",
|
||||
permission: "manage_experts",
|
||||
},
|
||||
{
|
||||
key: "sidebar.user-management",
|
||||
@@ -166,6 +177,15 @@ const sidebarMenu = [
|
||||
selected: false,
|
||||
permission: "manage_roles",
|
||||
},
|
||||
{
|
||||
key: "sidebar.loan-history",
|
||||
name: "loan-history",
|
||||
type: "page",
|
||||
route: "/dashboard/loan-history",
|
||||
icon: <SecurityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_navgan_loan",
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
31
src/core/utils/gradientColorHandler.js
Normal file
31
src/core/utils/gradientColorHandler.js
Normal file
@@ -0,0 +1,31 @@
|
||||
export const calculateGradientColor = (percentage) => {
|
||||
let colorStops = [
|
||||
{percent: 0, color: [173, 3, 23]}, // 0%: #3D0C11
|
||||
{percent: 33, color: [245, 81, 81]}, // 33%: #D80032
|
||||
{percent: 34, color: [237, 204, 101]}, // 34%: #F2F7A1
|
||||
{percent: 66, color: [240, 184, 7]}, // 66%: #E9B824
|
||||
{percent: 67, color: [167, 211, 151]}, // 67%: #B0D9B1
|
||||
{percent: 100, color: [0, 91, 65]} // 100%: #005B41
|
||||
];
|
||||
|
||||
let segment;
|
||||
for (let i = 0; i < colorStops.length - 1; i++) {
|
||||
if (percentage >= colorStops[i].percent && percentage <= colorStops[i + 1].percent) {
|
||||
segment = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let color1 = colorStops[segment].color;
|
||||
let color2 = colorStops[segment + 1].color;
|
||||
let ratio = (percentage - colorStops[segment].percent) / (colorStops[segment + 1].percent - colorStops[segment].percent);
|
||||
|
||||
let color = [
|
||||
Math.round(color1[0] + ratio * (color2[0] - color1[0])),
|
||||
Math.round(color1[1] + ratio * (color2[1] - color1[1])),
|
||||
Math.round(color1[2] + ratio * (color2[2] - color1[2]))
|
||||
];
|
||||
|
||||
return `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
|
||||
|
||||
}
|
||||
11
src/core/utils/yearPeriodFinder.js
Normal file
11
src/core/utils/yearPeriodFinder.js
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export const LanguageProvider = ({children}) => {
|
||||
key: "fa",
|
||||
dir: "rtl",
|
||||
name: "فارسی",
|
||||
fontFamily: `IRANSans, sans-serif`,
|
||||
fontFamily: `IRANSansFaNum, sans-serif`,
|
||||
tableLocalization: FA_DATATABLE_LOCALIZATION,
|
||||
chartLocalization: FA_CHART_LOCALIZATION
|
||||
}
|
||||
|
||||
39
src/lib/app/hooks/useChart.jsx
Normal file
39
src/lib/app/hooks/useChart.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import useSWR from "swr";
|
||||
import {useState} from "react";
|
||||
|
||||
const useChart = (chart_url) => {
|
||||
const requestServer = useRequest({auth: true, notification: false})
|
||||
const [filterOption, setFilterOption] = useState([]);
|
||||
const queryString = filterOption.length !== 0
|
||||
? `?${filterOption.map((option) => `${option.key}=${option.value}`).join('&')}`
|
||||
: '';
|
||||
//swr config
|
||||
const fetcher = (...args) => {
|
||||
return requestServer(args, 'get').then(({data}) => {
|
||||
return data.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
isValidating,
|
||||
mutate
|
||||
} = useSWR(filterOption.length !== 0 ? `${chart_url}${queryString}` : "", fetcher, {
|
||||
revalidateIfStale: true,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
keepPreviousData: true
|
||||
});
|
||||
|
||||
return {
|
||||
reportList: data,
|
||||
isLoadingReportList: isValidating,
|
||||
errorReportList: !data,
|
||||
setFilterOption: setFilterOption,
|
||||
mutate: mutate
|
||||
}
|
||||
};
|
||||
|
||||
export default useChart;
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
21
src/pages/dashboard/loan-history/index.jsx
Normal file
21
src/pages/dashboard/loan-history/index.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import {parse} from "next-useragent";
|
||||
import DashboardLoanHistoryComponent from "@/components/dashboard/loan-history";
|
||||
|
||||
export default function LoanHistory() {
|
||||
return (
|
||||
<DashboardLoanHistoryComponent/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({req, locale}) {
|
||||
const {isBot} = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
title: "Dashboard.loan_history",
|
||||
isBot,
|
||||
locale,
|
||||
layout: {name: 'DashboardLayout', props: {permissions: ["manage_navgan_loan"]}}
|
||||
},
|
||||
};
|
||||
}
|
||||
20
src/pages/dashboard/reports/index.jsx
Normal file
20
src/pages/dashboard/reports/index.jsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import DashboardReportsComponent from "@/components/dashboard/reports";
|
||||
import {parse} from "next-useragent";
|
||||
|
||||
export default function Reports() {
|
||||
return (
|
||||
<DashboardReportsComponent/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({req, locale}) {
|
||||
const {isBot} = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
title: "Dashboard.reports",
|
||||
isBot,
|
||||
layout: {name: 'DashboardLayout'}
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user