LFFE-10 merging from develop on feature

This commit is contained in:
2023-11-15 10:01:57 +03:30
127 changed files with 2116 additions and 537 deletions

View File

@@ -0,0 +1,48 @@
import PrintIcon from "@mui/icons-material/Print";
import {Button, CircularProgress} from "@mui/material";
import {useTranslations} from "next-intl";
import {useRouter} from "next/router";
import {useState} from "react";
import useRequest from "@/lib/app/hooks/useRequest";
import {GET_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
import usePrint from "@/lib/app/hooks/usePrint";
const PrintReport = () => {
const t = useTranslations();
const router = useRouter()
const [loading, setLoading] = useState(false)
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
const {setPrintPage, setPrintTitle} = usePrint()
const clickHandler = () => {
setLoading(true)
const params = new URLSearchParams();
params.set("start", '0');
params.set("filters", '[]');
params.set("sorting", '[]');
requestServer(`${GET_MACHINARY_OFFICE}?${params}`, 'get').then((response) => {
const _data = response.data.data
setPrintPage(_data.length)
setPrintTitle(t('MachinaryOffice.print_report'))
sessionStorage.setItem('report-print', JSON.stringify(_data))
router.push('/dashboard/machinery-expert/prints/report')
}).catch(() => {
setLoading(false)
})
}
return (
<Button
color="primary"
variant="contained"
size="small"
disabled={loading}
sx={{textTransform: "unset", alignSelf: "center"}}
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <PrintIcon/>}
onClick={clickHandler}
>
{t("MachinaryOffice.print_report")}
</Button>
)
}
export default PrintReport

View File

@@ -1,16 +1,19 @@
import UploadSystem from "@/core/components/UploadSystem";
import useNotification from "@/lib/app/hooks/useNotification";
import useRequest from "@/lib/app/hooks/useRequest";
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
import {useFormik} from "formik";
import {useTranslations} from "next-intl";
import {CONFIRM_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
import {useState} from "react";
import PriceField from "@/core/components/PriceField";
import * as Yup from "yup";
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const vehicle_amount = {
"اتوبوس": 7000,
"مینی بوس": 2000,
}
const ConfirmContent = ({mutate, setOpenConfirmDialog, rowId, vehicle_type}) => {
const t = useTranslations();
const [selectedImage, setSelectedImage] = useState("");
const [fileType, setfileType] = useState(null);
@@ -18,14 +21,24 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const [showAddIcon, setShowAddIcon] = useState(true);
const requestServer = useRequest({auth: true})
const {update_notification} = useNotification()
const validationSchema = Yup.object().shape({
proposed_amount: Yup.mixed().test(
"is-number",
`${t("ConfirmDialog.proposed_amount_number")}`,
(value) => !isNaN(value)
).test("positive", `${t("ConfirmDialog.proposed_amount_positive")}`, (value) => value >= 0)
.required(t("ConfirmDialog.proposed_amount_error"))
).test("max-amount", `${t("MachinaryOffice.max_amount", {value: parseInt(vehicle_amount[vehicle_type]).toLocaleString('en')})}`,
(value) => value <= vehicle_amount[vehicle_type])
.test("positive", `${t("ConfirmDialog.proposed_amount_positive")}`, (value) => value >= 0)
.required(t("ConfirmDialog.proposed_amount_error")),
confirm_img: Yup.mixed()
.required(t("MachinaryOffice.upload_file_required"))
.test('fileSize', `${t("MachinaryOffice.upload_file_unit")}`, (value) => {
return value.size <= 2 * 1024 * 1024;
})
.test('fileType', `${t("MachinaryOffice.upload_file_format")}`, (value) => {
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf'];
return allowedTypes.includes(value.type);
})
});
const formik = useFormik({
@@ -37,9 +50,8 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("proposed_amount", values.proposed_amount);
if (values.description != "") formData.append("description", values.description);
if (values.description != "") formData.append("expert_description", values.description);
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
requestServer(`${CONFIRM_MACHINARY_OFFICE}/${rowId}`, 'post', {
data: formData,
}).then((response) => {
@@ -60,19 +72,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const handleUploadChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const maxFileSize = 2 * 1024 * 1024;
if (uploadedFile.size > maxFileSize) {
UploadFileNotification(t);
event.target.value = "";
return;
}
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setSelectedImage(URL.createObjectURL(uploadedFile));
setfileType(fileType);
setfileName(fileName);
formik.setFieldValue("confirm_img", uploadedFile);
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
formik.setFieldTouched("confirm_img", true);
})
setShowAddIcon(false);
}
};
@@ -129,7 +136,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
/>
</Stack>
<Stack>
<Typography>{t("UploadSystem.upload_file")}{t("UploadSystem.optional")}</Typography>
<Typography>{t("MachinaryOffice.upload_file")}</Typography>
<UploadSystem
selectedImage={selectedImage}
handleUploadChange={handleUploadChange} // Pass the updated function directly
@@ -140,9 +147,19 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
fileName={fileName}
imageAlt={t("app_name")}
imageSize={[250, 150]}
onBlur={formik.handleBlur("confirm_img")}
error={
formik.touched.confirm_img &&
Boolean(formik.errors.confirm_img)
}
setShowAddIcon={setShowAddIcon}
showAddIcon={showAddIcon}
/>
<FormHelperText
error={Boolean(formik.errors.confirm_img)}
>
{formik.touched.confirm_img && formik.errors.confirm_img}
</FormHelperText>
</Stack>
</Stack>
</DialogContent>

View File

@@ -1,10 +1,10 @@
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
import {useState} from "react";
import ConfirmContent from "./ConfirmContent";
import CallMadeIcon from '@mui/icons-material/CallMade';
const Confirm = ({rowId, mutate}) => {
const Confirm = ({rowId, mutate, vehicle_type}) => {
const t = useTranslations();
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
return (
@@ -16,13 +16,14 @@ const Confirm = ({rowId, mutate}) => {
setOpenConfirmDialog(true)
}}
>
<ThumbUpAltIcon/>
<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'}}}>
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
<DialogTitle>{t("MachinaryOffice.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}
vehicle_type={vehicle_type}/>
</Dialog>
</>
)

View File

@@ -6,7 +6,7 @@ import {useFormik} from "formik";
import {useTranslations} from "next-intl";
import {useState} from "react";
import * as Yup from "yup";
import {REJECT_INSPECTOR_EXPERT} from "@/core/data/apiRoutes";
import {REJECT_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
const [selectedImage, setSelectedImage] = useState("");
@@ -29,10 +29,10 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("description", values.description);
formData.append("expert_description", values.description);
if (values.reject_img != null) formData.append("attachment", values.reject_img);
requestServer(`${REJECT_INSPECTOR_EXPERT}/${rowId}`, 'post', {
requestServer(`${REJECT_MACHINARY_OFFICE}/${rowId}`, 'post', {
data: formData,
}).then((response) => {
setOpenRejectDialog(false)

View File

@@ -0,0 +1,96 @@
import PrintablePage from "@/core/components/PrintablePage";
import {Box, Stack, Typography} from "@mui/material";
const Content = ({data}) => {
return (
<>
<PrintablePage key={data.id} header={true} footer={true}>
<Box sx={{mt: 6}}>
<Typography
sx={{
fontFamily: 'Bnazanin',
fontWeight: 'bold',
fontSize: '1.2rem',
textAlign: 'center'
}}> {'گزارش کارشناسی ماشین آلات'} </Typography>
</Box>
<Box textAlign={'justify'} sx={{lineHeight: '1.8', mt: 2}}>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', pl: 4}}> {'با توجه به نامه شماره'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'معاون محترم برنامه ریزی سازمان راهداری و حمل و نقل جاده ای در خصوص تسهیلات بند (الف) تبصره (18) قانون بودجه سال'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'موضوع بازسازی ناوگان اتوبوس و مینی بوس و نیز تصویب آن درشورای برنامه ریزی و توسعه استان'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و نامه شماره'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'اداره کل راهداری و حمل و نقل جاده ای'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'در خصوص استفاده از ظرفیت انجمن های صنفی رانندگان در تاریخ'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'از ناوگان اتوبوس/مینی بوس به شماره پلاک انتظامی'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>25پ4582</Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin'}}> {'و شماره هوشمند ناوگان'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>5875698725</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'به مالکیت آقای'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>امیرحسین محمودی</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'با کد ملی'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>0311318897</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و شماره تماس'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>09120630655</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'ساکن شهرستان'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'در جلسه مطرح شد که نتیجه آن به شرح ذیل مورد تایید می باشد.'} </Typography>
</Box>
<Box sx={{border: 1, borderRadius: 1, mt: 3, p: 2}}>
<Typography
sx={{fontFamily: 'Bnazanin'}}> {'مبلغ پیشنهادی به عدد .............................................. ریال و به حروف ...................................................................... ریال می باشد.'} </Typography>
<Typography
sx={{
lineBreak: 'anywhere',
fontFamily: 'Bnazanin',
}}> {'توضیحات : ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................'} </Typography>
<Stack direction={'row'} justifyContent={'space-between'}>
<Typography
sx={{fontFamily: 'Bnazanin'}}> {'نام و نام خانوادگی کارشناس:'} </Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}> {'تاریخ و امضاء'} </Typography>
</Stack>
</Box>
</PrintablePage>
</>
)
}
export default Content

View File

@@ -0,0 +1,60 @@
import {useEffect, useState} from "react";
import CenterLayout from "@/layouts/CenterLayout";
import {useTranslations} from "next-intl";
import {Stack, Typography} from "@mui/material";
import Content from "@/components/dashboard/machinary-office/Prints/report/Content";
const ReportComponent = () => {
const t = useTranslations()
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [isNotData, setIsNotData] = useState(true)
useEffect(() => {
if (sessionStorage.getItem('report-print') === null) {
setLoading(false)
setIsNotData(true)
sessionStorage.setItem('report-print', 'seen')
} else if (sessionStorage.getItem('report-print') === 'seen') {
setLoading(false)
setData(null)
setIsNotData(true)
} else if (data) {
setLoading(false)
sessionStorage.setItem('report-print', 'seen')
setIsNotData(false)
} else {
setLoading(true)
setData(JSON.parse(sessionStorage.getItem('report-print')))
setIsNotData(true)
}
}, [data]);
return (
<Stack sx={{bgcolor: '#efefef', width: isNotData ? '100%' : 'auto', height: isNotData ? '100%' : 'auto'}}>
{loading ? (
<CenterLayout>
<Stack>
<Typography variant={'h5'}>
{t('print_loading')}
</Typography>
</Stack>
</CenterLayout>
) : !data ? (
<CenterLayout>
<Typography variant={'h5'}>
{t('data_not_found')}
</Typography>
</CenterLayout>
) : !data.length ? (
<CenterLayout>
<Typography variant={'h5'}>
{t('data_not_found')}
</Typography>
</CenterLayout>
) : data.map(item => <Content key={item.id} data={item}/>)}
</Stack>
)
}
export default ReportComponent

View File

@@ -3,11 +3,11 @@ import Confirm from "./Form/ConfirmForm";
import Reject from "./Form/RejectForm";
const TableRow = ({row, mutate}) => {
return (
<Box sx={{display: "flex", flexWrap: "nowrap", gap: "8px"}}>
<Confirm
rowId={row.getValue("id")}
vehicle_type={row.original.vehicle_type}
mutate={mutate}
/>
<Reject

View File

@@ -1,13 +0,0 @@
import {Stack, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
function TableToolbar() {
const t = useTranslations();
return (
<Stack direction={"row"} spacing={2}>
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
</Stack>
);
}
export default TableToolbar;

View File

@@ -0,0 +1,12 @@
import {Stack} from "@mui/material";
import PrintReport from "@/components/dashboard/machinary-office/Buttons/printReport";
const TableToolbar = () => {
return (
<Stack direction={"row"} spacing={2}>
<PrintReport/>
</Stack>
)
}
export default TableToolbar

View File

@@ -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/machinary-office/TableToolbar";
function DashboardMachinaryOfficeComponent() {
const t = useTranslations();
@@ -160,7 +161,8 @@ function DashboardMachinaryOfficeComponent() {
tableUrl={GET_MACHINARY_OFFICE}
columns={columns}
selectableRow={false}
enableCustomToolbar={false}
enableCustomToolbar={true}
CustomToolbar={TableToolbar}
enableLastUpdate={true}
sorting={[{
id: 'id', desc: false

View File

@@ -1,12 +1,12 @@
import UploadSystem from "@/core/components/UploadSystem";
import useNotification from "@/lib/app/hooks/useNotification";
import useRequest from "@/lib/app/hooks/useRequest";
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
import {useFormik} from "formik";
import {useTranslations} from "next-intl";
import {CONFIRM_NAVGAN_PROVINCE_MANAGER} from "@/core/data/apiRoutes";
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
import {useState} from "react";
import * as Yup from "yup";
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const t = useTranslations();
@@ -17,14 +17,27 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const requestServer = useRequest({auth: true})
const {update_notification} = useNotification()
const validationSchema = Yup.object().shape({
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
.test('fileSize', `${t("NavganProvinceManager.upload_file_unit")}`, (value) => {
if (!value) return true
return value.size <= 2 * 1024 * 1024;
})
.test('fileType', `${t("NavganProvinceManager.upload_file_format")}`, (value) => {
if (!value) return true
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
return allowedTypes.includes(value.type);
})
})
const formik = useFormik({
initialValues: {
description: "",
confirm_img: null
},
}, validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
if (values.description != "") formData.append("description", values.description);
if (values.description != "") formData.append("expert_description", values.description);
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
requestServer(`${CONFIRM_NAVGAN_PROVINCE_MANAGER}/${rowId}`, 'post', {
@@ -47,23 +60,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const handleUploadChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const maxFileSize = 2 * 1024 * 1024;
if (uploadedFile.size > maxFileSize) {
UploadFileNotification(t);
event.target.value = "";
return;
}
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setSelectedImage(URL.createObjectURL(uploadedFile));
setfileType(fileType);
setfileName(fileName);
formik.setFieldValue("confirm_img", uploadedFile);
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
formik.setFieldTouched("confirm_img", true);
})
setShowAddIcon(false);
}
};
return (
<>
<DialogContent>
@@ -96,7 +103,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
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>
</DialogContent>
@@ -106,7 +123,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
{t("ConfirmDialog.button-cancel")}
</Button>
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
disabled={formik.isSubmitting}>
disabled={formik.isSubmitting || !formik.isValid}>
{t("ConfirmDialog.button-confirm")}
</Button>
</DialogActions>

View File

@@ -1,9 +1,8 @@
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
import {useState} 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);
@@ -16,12 +15,12 @@ const Confirm = ({rowId, mutate}) => {
setOpenConfirmDialog(true)
}}
>
<ThumbUpAltIcon/>
<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'}}}>
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
<DialogTitle>{t("NavganProvinceManager.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
</Dialog>
</>

View File

@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("description", values.description);
formData.append("expert_description", values.description);
if (values.reject_img != null) formData.append("attachment", values.reject_img);
requestServer(`${REJECT_NAVGAN_PROVINCE_MANAGER}/${rowId}`, 'post', {

View File

@@ -1,13 +0,0 @@
import {Stack, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
function TableToolbar() {
const t = useTranslations();
return (
<Stack direction={"row"} spacing={2}>
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
</Stack>
);
}
export default TableToolbar;

View File

@@ -0,0 +1,48 @@
import PrintIcon from "@mui/icons-material/Print";
import {Button, CircularProgress} from "@mui/material";
import {useTranslations} from "next-intl";
import {useRouter} from "next/router";
import {useState} from "react";
import useRequest from "@/lib/app/hooks/useRequest";
import {GET_PASSENGER_BOSS} from "@/core/data/apiRoutes";
import usePrint from "@/lib/app/hooks/usePrint";
const PrintFinalCreditAmount = () => {
const t = useTranslations();
const router = useRouter()
const [loading, setLoading] = useState(false)
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
const {setPrintPage, setPrintTitle} = usePrint()
const clickHandler = () => {
setLoading(true)
const params = new URLSearchParams();
params.set("start", '0');
params.set("filters", '[]');
params.set("sorting", '[]');
requestServer(`${GET_PASSENGER_BOSS}?${params}`, 'get').then((response) => {
const _data = response.data.data
setPrintPage(_data.length)
setPrintTitle(t('PassengerBoss.print_final_credit_amount'))
sessionStorage.setItem('final-credit-amount-print', JSON.stringify(_data))
router.push('/dashboard/passenger-boss/prints/final-credit-amount')
}).catch(() => {
setLoading(false)
})
}
return (
<Button
color="primary"
variant="contained"
size="small"
disabled={loading}
sx={{textTransform: "unset", alignSelf: "center"}}
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <PrintIcon/>}
onClick={clickHandler}
>
{t("PassengerBoss.print_final_credit_amount")}
</Button>
)
}
export default PrintFinalCreditAmount

View File

@@ -1,16 +1,19 @@
import UploadSystem from "@/core/components/UploadSystem";
import useNotification from "@/lib/app/hooks/useNotification";
import useRequest from "@/lib/app/hooks/useRequest";
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
import {useFormik} from "formik";
import {useTranslations} from "next-intl";
import {CONFIRM_PASSENGER_BOSS} from "@/core/data/apiRoutes";
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
import {useState} from "react";
import PriceField from "@/core/components/PriceField";
import * as Yup from "yup";
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const vehicle_amount = {
"اتوبوس": 7000,
"مینی بوس": 2000,
}
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog, vehicle_type}) => {
const t = useTranslations();
const [selectedImage, setSelectedImage] = useState("");
const [fileType, setfileType] = useState(null);
@@ -24,8 +27,19 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
"is-number",
`${t("ConfirmDialog.approved_amount_number")}`,
(value) => !isNaN(value)
).test("positive", `${t("ConfirmDialog.approved_amount_positive")}`, (value) => value >= 0)
.required(t("ConfirmDialog.approved_amount_error"))
).test("max-amount", `${t("PassengerBoss.max_amount", {value: parseInt(vehicle_amount[vehicle_type]).toLocaleString('en')})}`,
(value) => value <= vehicle_amount[vehicle_type])
.test("positive", `${t("ConfirmDialog.approved_amount_positive")}`, (value) => value >= 0)
.required(t("ConfirmDialog.approved_amount_error")),
confirm_img: Yup.mixed()
.required(t("PassengerBoss.upload_file_required"))
.test('fileSize', `${t("PassengerBoss.upload_file_unit")}`, (value) => {
return value.size <= 2 * 1024 * 1024;
})
.test('fileType', `${t("PassengerBoss.upload_file_format")}`, (value) => {
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
return allowedTypes.includes(value.type);
})
});
const formik = useFormik({
@@ -37,8 +51,8 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("approved_amount", values.approved_amount);
if (values.description != "") formData.append("description", values.description);
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
if (values.description != "") formData.append("expert_description", values.description);
formData.append("attachment", values.confirm_img);
requestServer(`${CONFIRM_PASSENGER_BOSS}/${rowId}`, 'post', {
data: formData,
@@ -60,19 +74,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const handleUploadChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const maxFileSize = 2 * 1024 * 1024;
if (uploadedFile.size > maxFileSize) {
UploadFileNotification(t);
event.target.value = "";
return;
}
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setSelectedImage(URL.createObjectURL(uploadedFile));
setfileType(fileType);
setfileName(fileName);
formik.setFieldValue("confirm_img", uploadedFile);
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
formik.setFieldTouched("confirm_img", true);
})
setShowAddIcon(false);
}
};
@@ -83,7 +92,6 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
formik.setFieldValue("approved_amount", formik.values.approved_amount)
}
};
return (
<>
<DialogContent>
@@ -129,7 +137,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
/>
</Stack>
<Stack>
<Typography>{t("UploadSystem.upload_file")}{t("UploadSystem.optional")}</Typography>
<Typography>{t("PassengerBoss.upload_file")}</Typography>
<UploadSystem
selectedImage={selectedImage}
handleUploadChange={handleUploadChange} // Pass the updated function directly
@@ -142,7 +150,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
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>
</DialogContent>

View File

@@ -1,10 +1,10 @@
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
import {useState} from "react";
import ConfirmContent from "./ConfirmContent";
import CallMadeIcon from '@mui/icons-material/CallMade';
const Confirm = ({rowId, mutate}) => {
const Confirm = ({rowId, mutate, vehicle_type}) => {
const t = useTranslations();
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
return (
@@ -16,13 +16,14 @@ const Confirm = ({rowId, mutate}) => {
setOpenConfirmDialog(true)
}}
>
<ThumbUpAltIcon/>
<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'}}}>
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
<DialogTitle>{t("PassengerBoss.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}
vehicle_type={vehicle_type}/>
</Dialog>
</>
)

View File

@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("description", values.description);
formData.append("expert_description", values.description);
if (values.reject_img != null) formData.append("attachment", values.reject_img);
requestServer(`${REJECT_PASSENGER_BOSS}/${rowId}`, 'post', {

View File

@@ -0,0 +1,123 @@
import PrintablePage from "@/core/components/PrintablePage";
import {Box, Grid, Stack, Typography} from "@mui/material";
const Content = ({data}) => {
return (
<>
<PrintablePage key={data.id} header={true} footer={true}>
<Box sx={{mt: 6}}>
<Typography
sx={{
fontFamily: 'Bnazanin',
fontWeight: 'bold',
fontSize: '1.2rem',
textAlign: 'center'
}}> {'فرم صورتجلسه تعیین مبلغ نهایی اعتبار'} </Typography>
</Box>
<Box textAlign={'justify'} sx={{lineHeight: '1.8', mt: 2}}>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', pl: 4}}> {'با توجه به نامه شماره'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'معاون محترم برنامه ریزی سازمان راهداری و حمل و نقل جاده ای در خصوص تسهیلات بند (الف) تبصره (18) قانون بودجه سال'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'موضوع بازسازی ناوگان اتوبوس و مینی بوس و نیز تصویب آن درشورای برنامه ریزی و توسعه استان'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و نامه شماره'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'اداره کل راهداری و حمل و نقل جاده ای'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'در خصوص استفاده از ظرفیت انجمن های صنفی رانندگان در تاریخ'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'از ناوگان اتوبوس/مینی بوس به شماره پلاک انتظامی'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>25پ4582</Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin'}}> {'و شماره هوشمند ناوگان'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>5875698725</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'به مالکیت آقای'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>امیرحسین محمودی</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'با کد ملی'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>0311318897</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و شماره تماس'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>09120630655</Typography>
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'ساکن شهرستان'} </Typography>
<Typography component={'span'}
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
<Typography
sx={{fontFamily: 'Bnazanin'}}
component={'span'}> {'در جلسه مطرح شد که نتیجه آن به شرح ذیل مورد تایید می باشد.'} </Typography>
</Box>
<Box sx={{border: 1, borderRadius: 1, mt: 3, p: 2}}>
<Typography textAlign={'justify'}
sx={{fontFamily: 'Bnazanin'}}> {'با توجه به گزارش واصله از اداره ماشین آلات (نامه در پیوست موجود می باشد) جهت بازسازی ناوگان مذکور مبلغ .............................................. ریال معادل .............................................. تومان مورد تایید می باشد.'} </Typography>
</Box>
<Grid container columns={4} sx={{border: 1, borderRadius: 1, mt: 3}}>
<Grid item xs={1} sx={{borderRight: 1, aspectRatio: '3 / 1'}}>
<Stack alignItems={'center'}>
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>رئیس انجمن</Typography>
</Stack>
</Grid>
<Grid item xs={1} sx={{borderRight: 1, aspectRatio: '3 / 1'}}>
<Stack alignItems={'center'}>
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>رئیس اداره مسافر</Typography>
</Stack>
</Grid>
<Grid item xs={1} sx={{borderRight: 1, aspectRatio: '3 / 1'}}>
</Grid>
<Grid item xs={1} sx={{aspectRatio: '3 / 1'}}>
</Grid>
<Grid item xs={1} sx={{borderRight: 1, borderTop: 1, aspectRatio: '2 / 1'}}>
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
</Stack>
</Grid>
<Grid item xs={1} sx={{borderRight: 1, borderTop: 1, aspectRatio: '2 / 1'}}>
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
</Stack>
</Grid>
<Grid item xs={1} sx={{borderRight: 1, borderTop: 1, aspectRatio: '2 / 1'}}>
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
</Stack>
</Grid>
<Grid item xs={1} sx={{borderTop: 1, aspectRatio: '2 / 1'}}>
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
</Stack>
</Grid>
</Grid>
</PrintablePage>
</>
)
}
export default Content

View File

@@ -0,0 +1,60 @@
import {useEffect, useState} from "react";
import CenterLayout from "@/layouts/CenterLayout";
import {useTranslations} from "next-intl";
import {Stack, Typography} from "@mui/material";
import Content from "@/components/dashboard/passenger-boss/Prints/final-credit-amount/Content";
const FinalCreditAmountComponent = () => {
const t = useTranslations()
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [isNotData, setIsNotData] = useState(true)
useEffect(() => {
if (sessionStorage.getItem('final-credit-amount-print') === null) {
setLoading(false)
setIsNotData(true)
sessionStorage.setItem('final-credit-amount-print', 'seen')
} else if (sessionStorage.getItem('final-credit-amount-print') === 'seen') {
setLoading(false)
setData(null)
setIsNotData(true)
} else if (data) {
setLoading(false)
sessionStorage.setItem('final-credit-amount-print', 'seen')
setIsNotData(false)
} else {
setLoading(true)
setData(JSON.parse(sessionStorage.getItem('final-credit-amount-print')))
setIsNotData(true)
}
}, [data]);
return (
<Stack sx={{bgcolor: '#efefef', width: isNotData ? '100%' : 'auto', height: isNotData ? '100%' : 'auto'}}>
{loading ? (
<CenterLayout>
<Stack>
<Typography variant={'h5'}>
{t('print_loading')}
</Typography>
</Stack>
</CenterLayout>
) : !data ? (
<CenterLayout>
<Typography variant={'h5'}>
{t('data_not_found')}
</Typography>
</CenterLayout>
) : !data.length ? (
<CenterLayout>
<Typography variant={'h5'}>
{t('data_not_found')}
</Typography>
</CenterLayout>
) : data.map(item => <Content key={item.id} data={item}/>)}
</Stack>
)
}
export default FinalCreditAmountComponent

View File

@@ -8,6 +8,7 @@ const TableRow = ({row, mutate}) => {
<Box sx={{display: "flex", flexWrap: "nowrap", gap: "8px"}}>
<Confirm
rowId={row.getValue("id")}
vehicle_type={row.original.vehicle_type}
mutate={mutate}
/>
<Reject

View File

@@ -1,13 +0,0 @@
import {Stack, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
function TableToolbar() {
const t = useTranslations();
return (
<Stack direction={"row"} spacing={2}>
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
</Stack>
);
}
export default TableToolbar;

View File

@@ -0,0 +1,12 @@
import {Stack} from "@mui/material";
import PrintFinalCreditAmount from "@/components/dashboard/passenger-boss/Buttons/printFinalCreditAmount";
const TableToolbar = () => {
return (
<Stack direction={"row"} spacing={2}>
<PrintFinalCreditAmount/>
</Stack>
)
}
export default TableToolbar

View File

@@ -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-boss/TableToolbar";
function DashboardPassengerOfficeComponent() {
const t = useTranslations();
@@ -161,7 +162,8 @@ function DashboardPassengerOfficeComponent() {
tableUrl={GET_PASSENGER_BOSS}
columns={columns}
selectableRow={false}
enableCustomToolbar={false}
enableCustomToolbar={true}
CustomToolbar={TableToolbar}
enableLastUpdate={true}
sorting={[{
id: 'id', desc: false

View File

@@ -1,12 +1,12 @@
import UploadSystem from "@/core/components/UploadSystem";
import useNotification from "@/lib/app/hooks/useNotification";
import useRequest from "@/lib/app/hooks/useRequest";
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
import {useFormik} from "formik";
import {useTranslations} from "next-intl";
import {CONFIRM_PASSENGER_OFFICE} from "@/core/data/apiRoutes";
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
import {useState} from "react";
import * as Yup from "yup";
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const t = useTranslations();
@@ -17,11 +17,24 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const requestServer = useRequest({auth: true})
const {update_notification} = useNotification()
const validationSchema = Yup.object().shape({
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
.test('fileSize', `${t("PassengerOffice.upload_file_unit")}`, (value) => {
if (!value) return true
return value.size <= 2 * 1024 * 1024;
})
.test('fileType', `${t("PassengerOffice.upload_file_format")}`, (value) => {
if (!value) return true
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
return allowedTypes.includes(value.type);
})
})
const formik = useFormik({
initialValues: {
description: "",
confirm_img: null
},
}, validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
if (values.description != "") formData.append("description", values.description);
@@ -47,19 +60,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const handleUploadChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const maxFileSize = 2 * 1024 * 1024;
if (uploadedFile.size > maxFileSize) {
UploadFileNotification(t);
event.target.value = "";
return;
}
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setSelectedImage(URL.createObjectURL(uploadedFile));
setfileType(fileType);
setfileName(fileName);
formik.setFieldValue("confirm_img", uploadedFile);
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
formik.setFieldTouched("confirm_img", true);
})
setShowAddIcon(false);
}
};
@@ -96,7 +104,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
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>
</DialogContent>
@@ -106,7 +124,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
{t("ConfirmDialog.button-cancel")}
</Button>
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
disabled={formik.isSubmitting}>
disabled={formik.isSubmitting || !formik.isValid}>
{t("ConfirmDialog.button-confirm")}
</Button>
</DialogActions>

View File

@@ -1,9 +1,8 @@
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
import {useState} 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);
@@ -16,12 +15,12 @@ const Confirm = ({rowId, mutate}) => {
setOpenConfirmDialog(true)
}}
>
<ThumbUpAltIcon/>
<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'}}}>
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
<DialogTitle>{t("PassengerOffice.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
</Dialog>
</>

View File

@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("description", values.description);
formData.append("expert_description", values.description);
if (values.reject_img != null) formData.append("attachment", values.reject_img);
requestServer(`${REJECT_PASSENGER_OFFICE}/${rowId}`, 'post', {

View File

@@ -1,13 +0,0 @@
import {Stack, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
function TableToolbar() {
const t = useTranslations();
return (
<Stack direction={"row"} spacing={2}>
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
</Stack>
);
}
export default TableToolbar;

View File

@@ -1,6 +1,6 @@
import {
Button,
Checkbox,
Checkbox, CircularProgress,
DialogActions,
DialogContent,
FormControl,
@@ -9,7 +9,7 @@ import {
FormLabel,
Grid,
Stack,
TextField
TextField, Typography
} from "@mui/material";
import {useTranslations} from "next-intl";
import useRequest from "@/lib/app/hooks/useRequest";
@@ -21,7 +21,7 @@ import usePermissions from "@/lib/app/hooks/usePermissions";
const CreateContent = ({mutate, setOpenConfirmDialog}) => {
const t = useTranslations();
const requestServer = useRequest({auth: true})
const {permissions_list} = usePermissions()
const {permissions_list, isLoading} = usePermissions()
const validationSchema = Yup.object().shape({
name: Yup.string().required(t("AddDialog.name_error")),
name_fa: Yup.string().required(t("AddDialog.name_fa_error")),
@@ -93,29 +93,40 @@ const CreateContent = ({mutate, setOpenConfirmDialog}) => {
sx={{mt: 2}}
>
<FormHelperText>{formik.touched.permissions && formik.errors.permissions}</FormHelperText>
<Grid container spacing={2}>
{permissions_list ? (
permissions_list.map((permission) => (
<Grid key={permission.id} item xs={6}>
<FormControlLabel
control={
<Checkbox
checked={formik.values.permissions.includes(permission.id)}
onChange={(e) => {
if (e.target.checked) {
formik.setFieldValue("permissions", [...formik.values.permissions, permission.id])
} else {
formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id))
}
}}
{isLoading ?
<Stack direction={'row'} alignItems={'center'} spacing={2}
justifyContent={'center'}>
<CircularProgress size={20}/>
<Typography
variant={'caption'}>{t("AddDialog.loading_permissions_list")}</Typography>
</Stack>
: (
<Grid container spacing={2}>
<>
{permissions_list.map((permission, index) => (
<Grid key={permission.id} item xs={6} data-testid= "PermissionList-checkbox">
<FormControlLabel
control={
<Checkbox
data-testid= {`PermissionList-checkbox-${index}`}
checked={formik.values.permissions.includes(permission.id)}
onChange={(e) => {
if (e.target.checked) {
formik.setFieldValue("permissions", [...formik.values.permissions, permission.id])
} else {
formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id))
}
}}
/>
}
label={permission.name_fa}
/>
}
label={permission.name_fa}
/>
</Grid>
))
) : null}
</Grid>
</Grid>
))}
</>
</Grid>
)
}
</FormControl>
</FormLabel>
</Stack>

View File

@@ -24,6 +24,7 @@ const DeleteForm = ({rowId, mutate}) => {
const handleSubmit = () => {
setIsSubmitting(true)
requestServer(`${DELETE_ROLE_MANAGEMENT}/${rowId}`, 'delete').then((response) => {
setOpenConfirmDialog(false)
mutate()
update_notification()
}).catch(() => {

View File

@@ -4,7 +4,7 @@ import usePermissions from "@/lib/app/hooks/usePermissions";
import useRequest from "@/lib/app/hooks/useRequest";
import {
Button,
Checkbox,
Checkbox, CircularProgress,
DialogActions,
DialogContent,
FormControl,
@@ -13,7 +13,7 @@ import {
FormLabel,
Grid,
Stack,
TextField
TextField, Typography
} from "@mui/material"
import {useFormik} from "formik";
import {useTranslations} from "next-intl";
@@ -23,7 +23,7 @@ const UpdateContent = ({row, mutate, setOpenConfirmDialog}) => {
const t = useTranslations();
const requestServer = useRequest({auth: true})
const {update_notification} = useNotification()
const {permissions_list} = usePermissions()
const {permissions_list, isLoading} = usePermissions()
const validationSchema = Yup.object().shape({
name: Yup.string().required(t("UpdateDialog.name_error")),
@@ -93,29 +93,40 @@ const UpdateContent = ({row, mutate, setOpenConfirmDialog}) => {
sx={{mt: 2}}
>
<FormHelperText>{formik.touched.permissions && formik.errors.permissions}</FormHelperText>
<Grid container spacing={2}>
{permissions_list ? (
permissions_list.map((permission) => (
<Grid key={permission.id} item xs={6}>
<FormControlLabel
control={
<Checkbox
checked={formik.values.permissions.includes(permission.id)}
onChange={(e) => {
if (e.target.checked) {
formik.setFieldValue("permissions", [...formik.values.permissions, permission.id])
} else {
formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id))
}
}}
{isLoading ?
<Stack direction={'row'} alignItems={'center'} spacing={2}
justifyContent={'center'}>
<CircularProgress size={20}/>
<Typography
variant={'caption'}>{t("UpdateDialog.loading_permissions_list")}</Typography>
</Stack>
: (
<Grid container spacing={2}>
<>
{permissions_list.map((permission) => (
<Grid key={permission.id} item xs={6}>
<FormControlLabel
control={
<Checkbox
data-testid="PermissionList-checkbox"
checked={formik.values.permissions.includes(permission.id)}
onChange={(e) => {
if (e.target.checked) {
formik.setFieldValue("permissions", [...formik.values.permissions, permission.id])
} else {
formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id))
}
}}
/>
}
label={permission.name_fa}
/>
}
label={permission.name_fa}
/>
</Grid>
))
) : null}
</Grid>
</Grid>
))}
</>
</Grid>
)
}
</FormControl>
</FormLabel>
</Stack>

View File

@@ -1,12 +1,12 @@
import UploadSystem from "@/core/components/UploadSystem";
import useNotification from "@/lib/app/hooks/useNotification";
import useRequest from "@/lib/app/hooks/useRequest";
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
import {useFormik} from "formik";
import {useTranslations} from "next-intl";
import {CONFIRM_TRANSPORTATION_ASSISTANCE} from "@/core/data/apiRoutes";
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
import {useState} from "react";
import * as Yup from "yup";
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const t = useTranslations();
@@ -17,14 +17,27 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const requestServer = useRequest({auth: true})
const {update_notification} = useNotification()
const validationSchema = Yup.object().shape({
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
.test('fileSize', `${t("TransportationAssistance.upload_file_unit")}`, (value) => {
if (!value) return true
return value.size <= 2 * 1024 * 1024;
})
.test('fileType', `${t("TransportationAssistance.upload_file_format")}`, (value) => {
if (!value) return true
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
return allowedTypes.includes(value.type);
})
})
const formik = useFormik({
initialValues: {
description: "",
confirm_img: null
},
}, validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
if (values.description != "") formData.append("description", values.description);
if (values.description != "") formData.append("expert_description", values.description);
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
requestServer(`${CONFIRM_TRANSPORTATION_ASSISTANCE}/${rowId}`, 'post', {
@@ -47,19 +60,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
const handleUploadChange = (event) => {
const uploadedFile = event.target?.files?.[0];
if (uploadedFile) {
const maxFileSize = 2 * 1024 * 1024;
if (uploadedFile.size > maxFileSize) {
UploadFileNotification(t);
event.target.value = "";
return;
}
const fileType = event.target?.files?.[0].type;
const fileName = event.target?.files?.[0].name;
setSelectedImage(URL.createObjectURL(uploadedFile));
setfileType(fileType);
setfileName(fileName);
formik.setFieldValue("confirm_img", uploadedFile);
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
formik.setFieldTouched("confirm_img", true);
})
setShowAddIcon(false);
}
};
@@ -96,7 +104,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
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>
</DialogContent>
@@ -106,7 +124,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
{t("ConfirmDialog.button-cancel")}
</Button>
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
disabled={formik.isSubmitting || !formik.dirty || !formik.isValid}>
disabled={formik.isSubmitting || !formik.isValid}>
{t("ConfirmDialog.button-confirm")}
</Button>
</DialogActions>

View File

@@ -1,9 +1,8 @@
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
import {useState} 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);
@@ -16,12 +15,12 @@ const Confirm = ({rowId, mutate}) => {
setOpenConfirmDialog(true)
}}
>
<ThumbUpAltIcon/>
<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'}}}>
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
<DialogTitle>{t("TransportationAssistance.confirm")}</DialogTitle>
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
</Dialog>
</>

View File

@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
validationSchema,
onSubmit: (values, {setSubmitting}) => {
const formData = new FormData();
formData.append("description", values.description);
formData.append("expert_description", values.description);
if (values.reject_img != null) formData.append("attachment", values.reject_img);
requestServer(`${REJECT_TRANSPORTATION_ASSISTANCE}/${rowId}`, 'post', {

View File

@@ -1,13 +0,0 @@
import {Stack, Tooltip} from "@mui/material";
import {useTranslations} from "next-intl";
function TableToolbar() {
const t = useTranslations();
return (
<Stack direction={"row"} spacing={2}>
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
</Stack>
);
}
export default TableToolbar;

View File

@@ -0,0 +1,61 @@
import {render, screen, waitFor} from "@testing-library/react";
import FirstComponent from "@/components/first";
import MockAppWithProviders from "../../../../mocks/AppWithProvider";
import {server} from "../../../../mocks/server";
import {rest} from "msw";
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
describe("First Component From First Page", () => {
describe("Rendering", () => {
it("App Name Text Rendered", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const appNameElement = screen.queryByText(/سامانه جامع تسهیلات/i);
expect(appNameElement).toBeInTheDocument()
});
it("App version Text Rendered", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const versionControler = screen.queryByText(process.env.NEXT_PUBLIC_API_VERSION, {exact: false});
expect(versionControler).toBeInTheDocument()
});
it("Powered By Rendered With Currect URL", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const linkElement = screen.queryByText('توسعه یافته توسط وایتل');
expect(linkElement).toBeInTheDocument()
expect(linkElement).toHaveAttribute('href', process.env.NEXT_PUBLIC_POWERED_BY_URL);
});
});
describe("Behavioral", () => {
it("Show Login Button And Do Not Show Dashboard Button When User Is Not Authenticated", async () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
await waitFor(() => {
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).toBeInTheDocument()
expect(authenticationButtonDashboard).not.toBeInTheDocument()
})
});
it("Show Dashboard Button And Do Not Show Login Button When User Is Authenticated", async () => {
localStorage.setItem("_token", 'token');
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
await waitFor(() => {
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).not.toBeInTheDocument()
expect(authenticationButtonDashboard).toBeInTheDocument()
})
});
it("Show Login Button And Do Not Show Dashboard Button When User Authentication Is Expired", async () => {
localStorage.setItem("_token", 'token');
server.use(rest.get(GET_USER_ROUTE, (req, res, ctx) => {
return res(ctx.status(401))
}))
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
await waitFor(() => {
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).toBeInTheDocument()
expect(authenticationButtonDashboard).not.toBeInTheDocument()
})
});
});
});

View File

@@ -1,11 +1,10 @@
import {NextLinkComposed} from "@/core/components/LinkRouting";
import LinkRouting, {NextLinkComposed} from "@/core/components/LinkRouting";
import CenterLayout from "@/layouts/CenterLayout";
import FullPageLayout from "@/layouts/FullPageLayout";
import useUser from "@/lib/app/hooks/useUser";
import {Button, Stack, Typography} from "@mui/material";
import {useTranslations} from "next-intl";
import SvgDashboard from "@/core/components/svgs/SvgDashboard";
import process from "next/dist/build/webpack/loaders/resolve-url-loader/lib/postcss";
const FirstComponent = () => {
const t = useTranslations();
@@ -18,28 +17,16 @@ const FirstComponent = () => {
<Typography variant="h5" sx={{textAlign: "center"}}>
{t("app_name")}
</Typography>
{isAuth ? (
<Button
variant="outlined"
component={NextLinkComposed}
to={{
pathname: "/dashboard",
}}
>
{t("dashboard")}
</Button>
) : (
<Button
sx={{mx: 2}}
variant="contained"
component={NextLinkComposed}
to={{
pathname: "/login-expert",
}}
>
{t("login_expert")}
</Button>
)}
<Button
data-testid="button-login-or-dashboard"
variant={isAuth ? "outlined" : "contained"}
component={NextLinkComposed}
to={{
pathname: isAuth ? "/dashboard" : "/login-expert",
}}
>
{isAuth ? t("dashboard") : t("login_expert")}
</Button>
</CenterLayout>
<Stack direction="row" alignItems="center" justifyContent="center">
<Typography variant={"caption"}
@@ -47,10 +34,22 @@ const FirstComponent = () => {
color: 'primary.main',
fontFamily: 'Arial',
fontWeight: 'bold'
}}>v{process.env.NEXT_PUBLIC_API_VERSION}</Typography>
}}
>
v{process.env.NEXT_PUBLIC_API_VERSION}
</Typography>
</Stack>
<Stack direction="row" alignItems="center" justifyContent="center">
<LinkRouting
sx={{margin: 0.5, fontSize: "14px"}}
href={process.env.NEXT_PUBLIC_POWERED_BY_URL}
target="_blank"
>
{t("powered_by_witel")}
</LinkRouting>
</Stack>
</FullPageLayout>
);
};
export default FirstComponent;
export default FirstComponent;

View File

@@ -0,0 +1,65 @@
import LoadingHardPage from "@/core/components/LoadingHardPage";
import RolePermissionMiddleware from "@/middlewares/RolePermission";
import BreadCrumbs from "@/components/layouts/Dashboard/Breadcrumbs";
import FullPageLayout from "@/layouts/FullPageLayout";
import {useEffect, useMemo, useState} from "react";
import sidebarMenu from "@/core/data/sidebarMenu";
import {useRouter} from "next/router";
import {useTranslations} from "next-intl";
const Content = (props) => {
const t = useTranslations()
const router = useRouter()
const [routing, setRouting] = useState(false)
const [routingItem, setRoutingItem] = useState({})
const pageList = useMemo(() => {
const list = []
for (const menu of sidebarMenu) {
for (const item of menu) {
if (item.type === 'menu') {
for (const subItem of item.subItem) {
list.push(subItem)
}
} else {
list.push(item)
}
}
}
return list
}, [])
useEffect(() => {
const handlerStartRoute = (url) => {
setRoutingItem(pageList.find(page => page.route === url))
setRouting(true)
}
const handlerCompleteRoute = () => {
setRouting(false)
}
router.events.on('routeChangeStart', handlerStartRoute)
router.events.on('routeChangeComplete', handlerCompleteRoute)
return () => {
router.events.off('routeChangeStart', handlerStartRoute)
router.events.off('routeChangeComplete', handlerCompleteRoute)
}
}, [router]);
return (
<FullPageLayout sx={{mt: 3, position: 'relative'}}>
<LoadingHardPage icon={routingItem?.icon}
label={routingItem?.key ? `${t('routing_to')} ${t(routingItem?.key)}` : ''}
loading={routing} width={100} height={100}
sx={{position: "absolute", bgcolor: "#fffc"}}>
<RolePermissionMiddleware requiredPermissions={props.permissions}>
<BreadCrumbs isVisible={true}/>
{props.children}
</RolePermissionMiddleware>
</LoadingHardPage>
</FullPageLayout>
)
}
export default Content

View File

@@ -1,5 +1,5 @@
import MenuIcon from "@mui/icons-material/Menu";
import {AppBar, Box, Container, CssBaseline, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
import {AppBar, Box, Container, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
import ProfileMenu from "./ProfileMenu";
function Header({drawerWidth, handleDrawerToggle}) {
@@ -7,7 +7,6 @@ function Header({drawerWidth, handleDrawerToggle}) {
return (
<>
<CssBaseline/>
<AppBar
position="fixed"
sx={{

View File

@@ -1,5 +1,5 @@
import {Divider, List} from "@mui/material";
import {useEffect, useReducer} from "react";
import {Fragment, useEffect, useReducer, useState} from "react";
import SidebarListItem from "./SidebarListItem";
import sidebarMenu from "@/core/data/sidebarMenu";
import {useRouter} from "next/router";
@@ -10,18 +10,48 @@ function reducer(state, action) {
case "COLLAPSE_MENU":
return state.map((itemsArr) =>
itemsArr.map((item) =>
action.key == item.key
? {...item, showSubItem: !item.showSubItem}
: item
action.key === item.key ? {...item, showSubItem: !item.showSubItem} : item
)
);
case "SELECTED":
return state.map((itemsArr) =>
itemsArr.map((item) =>
action.route == item.route
? {...item, selected: true}
: {...item, selected: false}
)
itemsArr.map((item) => {
return item.type === "page"
? {...item, selected: action.route === item.route, routing: false}
: item.subItem && Array.isArray(item.subItem)
? {
...item,
subItem: item.subItem.map((subitem) => ({
...subitem,
selected: subitem.route === action.route,
routing: false
})),
showSubItem: item.subItem.some(
(subitem) => subitem.selected
),
}
: item;
})
);
case "SELECTING":
return state.map((itemsArr) =>
itemsArr.map((item) => {
return item.type === "page"
? {...item, selected: action.route === item.route, routing: action.route === item.route}
: item.subItem && Array.isArray(item.subItem)
? {
...item,
subItem: item.subItem.map((subitem) => ({
...subitem,
selected: subitem.route === action.route,
routing: subitem.route === action.route
})),
showSubItem: item.subItem.some(
(subitem) => subitem.selected
),
}
: item;
})
);
default:
throw new Error();
@@ -32,26 +62,48 @@ export default function SidebarList({handleDrawerToggle}) {
const [itemMenu, dispatch] = useReducer(reducer, sidebarMenu);
const {user} = useUser();
const router = useRouter();
const [selectedKey, setSelectedKey] = useState(null);
useEffect(() => {
dispatch({type: "SELECTED", route: router.pathname});
}, [router.pathname]);
setSelectedKey(router.pathname);
const handlerStartRoute = (url) => {
dispatch({type: "SELECTING", route: url});
}
const filteredItemMenu = itemMenu[0].filter(
(item) => user.permissions.includes(item.permission) || item.permission === "all"
);
router.events.on('routeChangeStart', handlerStartRoute)
return () => {
router.events.off('routeChangeStart', handlerStartRoute)
}
}, [router]);
useEffect(() => {
selectedKey && document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
behavior: "smooth",
block: "center"
});
}, [selectedKey]);
return (
<List>
{filteredItemMenu.map((item, index) => (
<SidebarListItem
item={item}
dispatch={dispatch}
key={item.key}
handleDrawerToggle={handleDrawerToggle}
/>
<List dense={true} sx={{overflow: "scroll"}}>
{itemMenu.map((itemArr, index) => (
<Fragment key={index}>
{itemArr.map((item) =>
<Fragment key={item.key}>
{(user?.permissions?.includes(item.permission) || item.permission === "all") &&
<SidebarListItem
item={item}
dispatch={dispatch}
handleDrawerToggle={handleDrawerToggle}
/>}
</Fragment>
)}
<Divider/>
</Fragment>
))}
{filteredItemMenu.length > 0 && <Divider/>}
</List>
);
}

View File

@@ -1,7 +1,16 @@
import {NextLinkComposed} from "@/core/components/LinkRouting";
import ExpandLess from "@mui/icons-material/ExpandLess";
import ExpandMore from "@mui/icons-material/ExpandMore";
import {Badge, IconButton, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography,} from "@mui/material";
import {
Badge,
CircularProgress,
IconButton,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Typography,
} from "@mui/material";
import {useTranslations} from "next-intl";
import {Fragment} from "react";
import SidebarListSubItem from "./SidebarListSubItem";
@@ -9,23 +18,27 @@ import useNotification from "@/lib/app/hooks/useNotification";
const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
const t = useTranslations();
const {notification_count} = useNotification()
const {notification_count} = useNotification();
const hasSubItems = item.type === "menu" && item.subItem && item.subItem.length > 0;
const renderBadge = () => {
return !hasSubItems ? (
<IconButton>
<Badge
badgeContent={notification_count ? notification_count[item.name] : 0}
color="error"
variant="standard"
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
/>
</IconButton>
) : null;
};
return (
<Fragment key={item.key}>
<ListItem disablePadding secondaryAction={
<IconButton>
<Badge
badgeContent={notification_count ? notification_count[item.name] : 0}
color="error"
variant="standard"
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
/>
</IconButton>
}>
<>
<ListItem data-route={item.route} disablePadding secondaryAction={renderBadge()}>
<ListItemButton
selected={item.selected}
{...(item.type == "page" && {
@@ -35,10 +48,9 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
},
})}
onClick={() => {
if (item.type == "menu") {
if (hasSubItems) {
dispatch({type: "COLLAPSE_MENU", key: item.key});
}
handleDrawerToggle();
}}
sx={{
minHeight: 48,
@@ -50,10 +62,13 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
minWidth: 0,
justifyContent: "center",
color: "primary.main",
width: 40,
height: 24,
pr: 2,
}}
>
{item.icon}
{item.routing ?
<CircularProgress size={24} color="inherit"/> : item.icon}
</ListItemIcon>
<ListItemText
primary={t(item.key)}
@@ -66,9 +81,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
}
/>
{item.type == "menu" &&
(item.showSubItem ? <ExpandLess/> : <ExpandMore/>)}
{hasSubItems && (item.showSubItem ? <ExpandLess/> : <ExpandMore/>)}
</ListItemButton>
</ListItem>
{item.subItem && (
@@ -77,7 +90,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
handleDrawerToggle={handleDrawerToggle}
/>
)}
</Fragment>
</>
);
};

View File

@@ -1,18 +1,44 @@
import {NextLinkComposed} from "@/core/components/LinkRouting";
import InboxIcon from "@mui/icons-material/MoveToInbox";
import {Collapse, List, ListItemButton, ListItemIcon, ListItemText,} from "@mui/material";
import {
Badge,
CircularProgress,
Collapse,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Typography,
} from "@mui/material";
import {useTranslations} from "next-intl";
import {Fragment} from "react";
import useNotification from "@/lib/app/hooks/useNotification";
const SidebarListSubItem = ({item, handleDrawerToggle}) => {
const t = useTranslations();
const {notification_count} = useNotification();
const renderBadge = (subitem) => (
<IconButton>
<Badge
badgeContent={notification_count ? notification_count[subitem.name] : 0}
color="error"
variant="standard"
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
/>
</IconButton>
);
return (
<Collapse in={item.showSubItem} timeout="auto" unmountOnExit>
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6"}}>
{item.subItem.map((subitem, index) => (
<Fragment key={subitem.key}>
<Collapse in={item.showSubItem} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6", pr: 0}}>
{item.subItem.map((subitem) => (
<ListItem key={subitem.key} disablePadding secondaryAction={renderBadge(subitem)}>
<ListItemButton
selected={subitem.selected}
component={NextLinkComposed}
to={{
pathname: subitem.route,
@@ -20,28 +46,32 @@ const SidebarListSubItem = ({item, handleDrawerToggle}) => {
sx={{
minHeight: 48,
}}
onClick={(event) => {
if (item.type == "menu") {
dispatch({type: "COLLAPSE_MENU", key: item.key});
}
handleDrawerToggle();
}}
>
<ListItemIcon
sx={{
minWidth: 0,
justifyContent: "center",
width: 40,
height: 24,
pr: 2,
}}
>
<InboxIcon/>
{subitem.routing ?
<CircularProgress size={24} color="inherit"/> : subitem.icon}
</ListItemIcon>
<ListItemText primary={t(subitem.key)}/>
<ListItemText primary={t(subitem.key)} secondary={
subitem.secondary !== undefined ? (
<Typography variant="caption" color="textSecondary">
{t(subitem.secondary)}
</Typography>
) : null
}/>
</ListItemButton>
</Fragment>
</ListItem>
))}
</List>
</Collapse>
);
};

View File

@@ -1,13 +1,31 @@
import {Box, Drawer} from "@mui/material";
import {Box, Drawer, useMediaQuery} from "@mui/material";
import SidebarDrawer from "./SidebarDrawer";
import {useTheme} from "@mui/material/styles";
const Sidebar = (props) => {
const theme = useTheme();
const isUpSm = useMediaQuery((theme.breakpoints.up('sm')))
return (
<Box
component="nav"
sx={{width: {md: props.drawerWidth}, flexShrink: {sm: 0}}}
aria-label="mailbox folders"
>
> {isUpSm ? (
<Drawer
variant="permanent"
sx={{
display: {xs: "none", md: "block"},
"& .MuiDrawer-paper": {
boxSizing: "border-box",
width: props.drawerWidth,
overflow: "hidden",
},
}}
open
>
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
</Drawer>
) : (
<Drawer
container={props.container}
variant="temporary"
@@ -21,24 +39,13 @@ const Sidebar = (props) => {
"& .MuiDrawer-paper": {
boxSizing: "border-box",
width: props.drawerWidth,
overflow: "hidden",
},
}}
>
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
</Drawer>
<Drawer
variant="permanent"
sx={{
display: {xs: "none", md: "block"},
"& .MuiDrawer-paper": {
boxSizing: "border-box",
width: props.drawerWidth,
},
}}
open
>
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
</Drawer>
)}
</Box>
);
};

View File

@@ -1,10 +1,9 @@
import {useState} from "react";
import Header from "./header";
import Sidebar from "./sidebar";
import {Toolbar} from "@mui/material";
import BreadCrumbs from "./breadcrumbs";
import FullPageLayout from "@/layouts/FullPageLayout";
import RolePermissionMiddleware from "@/middlewares/RolePermission";
import Header from "@/components/layouts/Dashboard/Header";
import Sidebar from "@/components/layouts/Dashboard/Sidebar";
import Content from "@/components/layouts/Dashboard/Content";
const drawerWidth = 240;
@@ -17,6 +16,7 @@ const DashboardLayouts = (props) => {
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};
return (
<FullPageLayout direction="row">
<Header
@@ -34,12 +34,7 @@ const DashboardLayouts = (props) => {
sx={{flexGrow: 1, width: {sm: `calc(100% - ${drawerWidth}px)`}}}
>
<Toolbar/>
<FullPageLayout sx={{mt: 3}}>
<RolePermissionMiddleware requiredPermissions={props.permissions}>
<BreadCrumbs isVisible={true}/>
{props.children}
</RolePermissionMiddleware>
</FullPageLayout>
<Content {...props}/>
</FullPageLayout>
</FullPageLayout>
);

View File

@@ -0,0 +1,18 @@
import {Avatar, Stack, Typography} from "@mui/material";
import useUser from "@/lib/app/hooks/useUser";
export default function ProfileData() {
const {user} = useUser();
return (
<Stack alignItems="center" spacing={2} sx={{p: 3}}>
<Avatar
sx={{width: "80px", height: "80px"}}
alt="User Image"
src={user.avatar}
/>
<Typography sx={{fontSize: 15, fontWeight: 600}} textAlign="center">
{user.username}
</Typography>
</Stack>
);
}

View File

@@ -0,0 +1,62 @@
import {Avatar, IconButton, Menu, Tooltip} from "@mui/material";
import {useState} from "react";
import ProfileData from "./ProfileData";
import ProfileOptions from "./ProfileOptions";
import {useTranslations} from "next-intl";
import useUser from "@/lib/app/hooks/useUser";
function ProfileMenu() {
const t = useTranslations();
const [anchorElUser, setAnchorElUser] = useState(null);
const {user} = useUser();
const handleOpenUserMenu = (event) => {
setAnchorElUser(event.currentTarget);
};
const handleCloseUserMenu = () => {
setAnchorElUser(null);
};
return (
<>
<Tooltip title={t("header.open_profile")} arrow>
<IconButton onClick={handleOpenUserMenu} sx={{p: 0}}>
<Avatar
sx={{
width: 24,
height: 24,
backgroundColor: "#fff",
color: "primary.main",
}}
alt="User Image"
src={user.avatar}
/>
</IconButton>
</Tooltip>
<Menu
MenuListProps={{sx: {py: 0}}}
sx={{
mt: 6,
}}
id="menu-appbar"
anchorEl={anchorElUser}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu}
>
<ProfileData/>
<ProfileOptions handleCloseUserMenu={handleCloseUserMenu}/>
</Menu>
</>
);
}
export default ProfileMenu;

View File

@@ -0,0 +1,49 @@
import {Box, Button, MenuItem, Typography} from "@mui/material";
import MeetingRoomIcon from "@mui/icons-material/MeetingRoom";
import useUser from "@/lib/app/hooks/useUser";
import {useTranslations} from "next-intl";
export default function ProfileOptionLogOut({handleCloseUserMenu}) {
const t = useTranslations();
const {clearToken} = useUser();
const handleClickLogOut = () => {
handleCloseUserMenu();
clearToken();
};
return (
<>
<MenuItem
component={Button}
to={{
pathname: "/dashboard/logout",
}}
sx={{
display: "flex",
justifyContent: "center",
borderTop: 1,
px: 3,
py: 1.5,
borderColor: "#e1e1e1",
textTransform: "unset",
}}
onClick={handleClickLogOut}
>
<Box sx={{display: "flex", alignItems: "center", flex: 1}}>
<Box
sx={{
display: "flex",
alignItems: "center",
color: "primary.main",
pr: 2,
}}
>
<MeetingRoomIcon/>
</Box>
<Typography sx={{flex: 1}} textAlign="start">
{t("header.logout")}
</Typography>
</Box>
</MenuItem>
</>
);
}

View File

@@ -0,0 +1,49 @@
import {Box, MenuItem, Typography} from "@mui/material";
import {NextLinkComposed} from "@/core/components/LinkRouting";
import {useTranslations} from "next-intl";
import headerProfileItems from "@/core/data/headerProfileItems";
import ProfileOptionLogOut from "./ProfileOptionLogOut";
export default function ProfileOptions({handleCloseUserMenu}) {
const t = useTranslations();
return (
<>
{headerProfileItems.map((profile_item) => (
<MenuItem
component={NextLinkComposed}
to={{
pathname: profile_item.route,
}}
sx={{
display: "flex",
justifyContent: "center",
borderTop: 1,
px: 3,
py: 1.5,
borderColor: "#e1e1e1",
}}
key={profile_item.key}
onClick={handleCloseUserMenu}
>
<Box sx={{display: "flex", alignItems: "center", flex: 1}}>
<Box
sx={{
display: "flex",
alignItems: "center",
color: "primary.main",
pr: 2,
}}
>
{profile_item.icon}
</Box>
<Typography sx={{flex: 1}} textAlign="start">
{t(profile_item.name)}
</Typography>
</Box>
</MenuItem>
))}
<ProfileOptionLogOut handleCloseUserMenu={handleCloseUserMenu}/>
</>
);
}

View File

@@ -0,0 +1,72 @@
import {AppBar, Button, Container, Stack, Toolbar, Typography, useTheme} from "@mui/material";
import {useTranslations} from "next-intl";
import {useRouter} from "next/router";
import {useState} from "react";
import usePrint from "@/lib/app/hooks/usePrint";
function Header({drawerWidth, handleDrawerToggle}) {
const theme = useTheme();
const t = useTranslations()
const router = useRouter()
const {printDetails} = usePrint()
const [disabled, setDisabled] = useState(false)
return (
<AppBar
position="fixed"
sx={{
width: '100%',
displayPrint: 'none'
}}
>
<Container maxWidth="xxl">
<Toolbar
disableGutters
sx={{
display: "flex",
}}
>
<Stack
direction="row"
sx={{
flex: 1,
position: "relative",
...theme.mixins.toolbar,
}}
>
<Button onClick={() => {
setDisabled(true);
if (disabled) return
router.back()
}}
size={'large'} sx={{
color: "inherit",
}}>{t('button_back_to_previous')}</Button>
</Stack>
<Stack
alignItems={'center'}
justifyContent={'center'}
sx={{
flex: 1,
position: "relative",
...theme.mixins.toolbar,
}}
spacing={1}
>
<Typography>{printDetails.title}</Typography>
<Typography variant={'body2'}>{'تعداد صفحه:'} {printDetails.count}</Typography>
</Stack>
<Stack direction="row" spacing={4} justifyContent="flex-end" sx={{flex: 1}}>
<Button onClick={() => {
if (disabled) return;
print()
}} size={'large'} variant={'contained'}
color={'secondary'}>{t('btn_print')}</Button>
</Stack>
</Toolbar>
</Container>
</AppBar>
);
}
export default Header;

View File

@@ -0,0 +1,14 @@
import Header from "@/components/layouts/Print/Header";
import {Toolbar} from "@mui/material";
const Print = (props) => {
return (
<>
<Header/>
<Toolbar sx={{displayPrint: 'none'}}/>
{props.children}
</>
)
}
export default Print

View File

@@ -1,5 +1,4 @@
import LinkRouting from "@/core/components/LinkRouting";
import PasswordField from "@/core/components/PasswordField";
import StyledForm from "@/core/components/StyledForm";
import {GET_USER_TOKEN} from "@/core/data/apiRoutes";
import CenterLayout from "@/layouts/CenterLayout";
@@ -14,6 +13,7 @@ import * as Yup from "yup";
import useDirection from "@/lib/app/hooks/useDirection";
import SvgLogin from "@/core/components/svgs/SvgLogin";
import useRequest from "@/lib/app/hooks/useRequest";
import PasswordField from "@/core/components/PasswordField";
const LoginComponent = () => {
const t = useTranslations();

View File

@@ -14,7 +14,6 @@ function DataTable(props) {
const [columnFilters, setColumnFilters] = useState([]);
const [sorting, setSorting] = useState(props.sorting || []);
const [pagination, setPagination] = useState({pageIndex: 0, pageSize: 10});
const [rowCount, setRowCount] = useState(0);
const [columnFilterFns, setColumnFilterFns] = useState(() => {
let output = {};
const list = props.columns.map((item) => item.enableColumnFilter ? {[item.id]: item.filterFn} : {[item.id]: ""});
@@ -34,8 +33,11 @@ function DataTable(props) {
const tableLocalization = useMemo(() => languageList.find((item) => item.key == languageApp).tableLocalization, [languageApp, languageList]);
const fetchUrl = useMemo(() => {
const url = new URL(props.tableUrl);
url.searchParams.set("start", `${pagination.pageIndex * pagination.pageSize}`);
const params = new URLSearchParams();
params.set(
"start",
`${pagination.pageIndex * pagination.pageSize}`
);
const filters = columnFilters.map((filter) => {
let datatype;
for (const i in props.columns) {
@@ -47,10 +49,10 @@ function DataTable(props) {
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
};
});
url.searchParams.set("size", pagination.pageSize);
url.searchParams.set("filters", JSON.stringify(filters ?? []));
url.searchParams.set("sorting", JSON.stringify(sorting ?? []));
return url;
params.set("size", pagination.pageSize);
params.set("filters", JSON.stringify(filters ?? []));
params.set("sorting", JSON.stringify(sorting ?? []));
return `${props.tableUrl}?${params}`;
}, [props.tableUrl, columnFilters, columnFilterFns, pagination, sorting, props.columns,]);
const {data, isValidating, mutate} = useSWR(fetchUrl, (...args) =>
@@ -60,7 +62,7 @@ function DataTable(props) {
}).then((response) => response.data).catch(() => {
})
, {
revalidateIfStale: false,
revalidateIfStale: true,
revalidateOnFocus: false,
revalidateOnReconnect: true,
keepPreviousData: true

View File

@@ -1,4 +1,4 @@
import {Backdrop, Box, styled} from "@mui/material";
import {Backdrop, Box, Stack, styled, Typography} from "@mui/material";
import SvgLoading from "@/core/components/svgs/SvgLoading";
const LoadingImage = styled(Box)({
@@ -9,7 +9,7 @@ const LoadingImage = styled(Box)({
},
"50%": {
// opacity: 1,
transform: "scale(2)",
transform: "scale(.5)",
},
"100%": {
// opacity: 0,
@@ -19,20 +19,29 @@ const LoadingImage = styled(Box)({
animation: "load 2s infinite",
});
const LoadingHardPage = ({children, loading}) => {
const LoadingHardPage = ({children, loading, sx = {}, icon = null, width = 200, height = 200, label = ''}) => {
return (
<>
<Backdrop
sx={{bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1}}
sx={{bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1, ...sx}}
open={loading}
>
<LoadingImage
width={100}
height={100}
>
<SvgLoading width={100}
height={100}/>
</LoadingImage>
<Stack alignItems={'center'} spacing={2}>
<LoadingImage
width={width}
height={height}
>
{icon ? (
<Box sx={{color: "primary.main", width: width, height: height}}>
{icon}
</Box>
) : (
<SvgLoading width={width}
height={height}/>
)}
</LoadingImage>
<Typography variant={'body2'} sx={{color: "primary.main"}}>{label}</Typography>
</Stack>
</Backdrop>
{children}
</>

View File

@@ -0,0 +1,11 @@
import {styled, TextField} from "@mui/material";
const LtrTextField = styled(TextField)`
.MuiInputBase-input {
/* @noflip */
direction: ltr;
text-align: left;
}
`;
export default LtrTextField

View File

@@ -6,13 +6,13 @@ import WifiOffIcon from '@mui/icons-material/WifiOff';
import {useTranslations} from "next-intl";
const NetworkComponent = () => {
const toastId = useRef(null);
const networkToastId = useRef(null);
const network = useNetwork()
const t = useTranslations()
useEffect(() => {
if (network.online) {
toast.update(toastId.current, {
toast.update(networkToastId.current, {
type: toast.TYPE.SUCCESS,
render: t('online_message'),
autoClose: 2000,
@@ -22,8 +22,9 @@ const NetworkComponent = () => {
});
return
}
toast.dismiss()
toastId.current = toast.warn(t('offline_message'), {
networkToastId.current = toast.warn(t('offline_message'), {
containerId: 'connection',
draggable: false,
autoClose: false, closeButton: false, closeOnClick: false, icon: <WifiOffIcon/>
})
}, [network.online]);

View File

@@ -1,12 +1,13 @@
import {Stack, TextField, Typography} from "@mui/material";
import {Stack, Typography} from "@mui/material";
import {useTranslations} from "next-intl";
import LtrTextField from "@/core/components/LtrTextField";
const PriceField = (props) => {
const t = useTranslations();
return (
<Stack spacing={1}>
<Stack>
<TextField
<LtrTextField
{...props}
/>
</Stack>

View File

@@ -0,0 +1,82 @@
import {Box, Container, Divider, Grid, Paper, Stack, Typography} from '@mui/material';
const PrintablePage = ({children, header, footer}) => (
<>
<Box sx={{width: "100%", height: 8, displayPrint: 'none'}}/>
<Container
sx={{
width: '21cm',
minHeight: '29.7cm',
margin: '0 auto',
backgroundColor: 'white',
p: '16px !important',
pb: footer ? '84px!important' : '',
position: 'relative'
}}
component="article" maxWidth="false">
<Grid container columns={5} sx={{pt: 2, px: 4, display: header ? '' : 'none'}}>
<Grid item xs={1}>
<Box sx={{width: 70}}>
<Box component={'img'} sx={{width: '100%', height: '100%', objectFit: 'contain'}}
src={'/images/logo.png'}/>
</Box>
</Grid>
<Grid item xs={3} sx={{textAlign: 'center'}}>
<Typography sx={{fontFamily: 'Bnazanin'}}>باسمه تعالی</Typography>
<Typography sx={{fontFamily: 'Bnazanin'}}>جمهوری اسلامی ایران</Typography>
<Typography sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>وزارت راه و
شهرسازی</Typography>
<Typography sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>سازمان
راهداری و حمل و نقل جاده
ای</Typography>
</Grid>
<Grid item xs={1}>
<Stack>
<Typography sx={{fontFamily: 'Bnazanin'}}>شماره:</Typography>
<Typography sx={{fontFamily: 'Bnazanin'}}>تاریخ:</Typography>
<Typography sx={{fontFamily: 'Bnazanin'}}>پیوست:</Typography>
</Stack>
</Grid>
</Grid>
<Paper elevation={0} sx={{px: 8}}>
{children}
</Paper>
<Box sx={{
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
px: 4,
pb: 2,
display: footer ? '' : 'none'
}}>
<Divider sx={{borderStyle: 'double', borderBottomWidth: 3, borderColor: '#000'}}/>
<Stack sx={{my: .5}}>
<Box sx={{textAlign: 'center'}}>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 1}}> آدرس: تهران-بلوار
کشاورز-خیابان فلسطین جنوبی-خیابان
دمشق-پلاک17 </Typography>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 1}}> تلفن: 88-88804379 </Typography>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 1}}> تلفن گویا: 88804400 </Typography>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 1}}> کدپستی: 1416753941 </Typography>
</Box>
<Box sx={{textAlign: 'center'}}>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 2}}>صندوق پستی: 3773-14155</Typography>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 2}}>پست الکترونیک: info@rmto.ir</Typography>
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
sx={{fontFamily: 'Bnazanin', px: 2}}>سایت الکترونیک: www.rmto.ir</Typography>
</Box>
</Stack>
</Box>
</Container>
<Box sx={{width: "100%", height: 8, displayPrint: 'none'}}/>
</>
);
export default PrintablePage;

View File

@@ -10,7 +10,6 @@ const UploadSystem = ({
handleUploadChange,
fieldname,
setFieldValue,
imageAlt,
imageSize,
fileType,
fileName,
@@ -19,7 +18,6 @@ const UploadSystem = ({
}) => {
const t = useTranslations();
const fileInputRef = useRef(null);
const handleClick = () => {
fileInputRef.current.click();
};
@@ -69,13 +67,14 @@ const UploadSystem = ({
variant="subtitle2"
sx={{
fontWeight: 600,
fontSize: "1rem",
fontSize: "0.8rem",
color: "#a19d9d",
mt: 1,
}}
textAlign="center"
>
{t("UploadSystem.upload_file")}
{t("UploadSystem.upload_file_format")}<br/>
{t("UploadSystem.upload_file_unit")}
</Typography>
</Box>
</>

View File

@@ -2,8 +2,8 @@ import DangerousIcon from "@mui/icons-material/Dangerous";
import {Box, Typography} from "@mui/material";
import {toast} from "react-toastify";
const ErrorNotification = (t, status, message) => {
toast(
const ErrorNotification = (pushToastList, notificationType, t, status, message) => {
const toastId = toast(
() => (
<>
<Box
@@ -29,11 +29,13 @@ const ErrorNotification = (t, status, message) => {
</>
),
{
containerId: 'validation',
autoClose: false,
closeOnClick: false,
draggable: false,
}
);
pushToastList(notificationType, toastId);
};
export default ErrorNotification;
export default ErrorNotification;

View File

@@ -1,12 +1,14 @@
import {toast} from "react-toastify";
const PendingNotification = (t) => {
toast(t("notifications.pending"), {
const PendingNotification = (pushToastList, notificationType, t) => {
const toastId = toast(t("notifications.pending"), {
containerId: 'validation',
autoClose: false,
closeButton: false,
closeOnClick: false,
draggable: false,
});
pushToastList(notificationType, toastId);
};
export default PendingNotification;
export default PendingNotification;

View File

@@ -2,8 +2,8 @@ import BeenhereIcon from "@mui/icons-material/Beenhere";
import {Box, Typography} from "@mui/material";
import {toast} from "react-toastify";
const SuccessNotification = (t, status) => {
toast(
const SuccessNotification = (pushToastList, notificationType, t, status) => {
const toastId = toast(
() => (
<>
<Box
@@ -30,6 +30,7 @@ const SuccessNotification = (t, status) => {
</>
),
{
containerId: 'validation',
autoClose: 3000,
hideProgressBar: true,
pauseOnHover: true,
@@ -37,6 +38,7 @@ const SuccessNotification = (t, status) => {
draggable: true,
}
);
pushToastList(notificationType, toastId);
};
export default SuccessNotification;
export default SuccessNotification;

View File

@@ -26,6 +26,8 @@ const UploadFileNotification = (t) => {
</>
),
{
containerId: 'validation',
toastId: 'upload',
autoClose: 3000,
hideProgressBar: true,
pauseOnHover: true,
@@ -35,4 +37,4 @@ const UploadFileNotification = (t) => {
);
};
export default UploadFileNotification;
export default UploadFileNotification;

View File

@@ -2,8 +2,8 @@ import ReportIcon from "@mui/icons-material/Report";
import {Box, Typography} from "@mui/material";
import {toast} from "react-toastify";
const WarningNotification = (t, status) => {
toast(
const WarningNotification = (pushToastList, notificationType, t, status) => {
const toastId = toast(
() => (
<>
<Box
@@ -30,11 +30,13 @@ const WarningNotification = (t, status) => {
</>
),
{
containerId: 'validation',
autoClose: false,
closeOnClick: false,
draggable: false,
}
);
pushToastList(notificationType, toastId);
};
export default WarningNotification;
export default WarningNotification;

View File

@@ -1,54 +1,27 @@
import {toast} from "react-toastify";
import ErrorNotification from "./ErrorNotification";
import WarningNotification from "./WarningNotification";
import SuccessNotification from "./SuccessNotification";
import pendingNotification from "@/core/components/notifications/PendingNotification";
import WarningNotification from "@/core/components/notifications/WarningNotification";
import ErrorNotification from "@/core/components/notifications/ErrorNotification";
import SuccessNotification from "@/core/components/notifications/SuccessNotification";
const Notifications = async (t, response) => {
const {status, data} = response != undefined ? response : ""
toast.dismiss();
switch (status) {
case 200:
SuccessNotification(t, status);
const Notifications = (pushToastList, notificationType, t, status, message) => {
switch (notificationType) {
case "pending":
pendingNotification(pushToastList, notificationType, t);
break;
case 400:
ErrorNotification(t, status);
case "warning":
WarningNotification(pushToastList, notificationType, t, status);
break;
case 401:
ErrorNotification(t, status);
case "error":
if (message) {
ErrorNotification(pushToastList, notificationType, t, status, message)
} else {
ErrorNotification(pushToastList, notificationType, t, status)
}
break;
case 403:
ErrorNotification(t, status);
break;
case 422:
ErrorNotification(t, status, data.message);
break;
case 500:
WarningNotification(t, status);
break;
case 503:
WarningNotification(t, status);
break;
case 504:
WarningNotification(t, status);
break;
default:
toast(t("notifications.pending"), {
autoClose: false,
closeOnClick: false,
draggable: false,
});
case "success":
SuccessNotification(pushToastList, notificationType, t, status);
break;
}
};
export default Notifications;
/*
usage document
** for pending use ( Notifications( t, undefined) ) this before your request.
** for success use ( Notifications( t, response) ) this inside .then() of your request.
** for Error and Warning use ( Notifications( t, error.response) ) this inside .catche() of your request.
end usage document
*/

View File

@@ -21,7 +21,7 @@ const sidebarMenu = [
key: "sidebar.dashboard",
type: "page",
route: "/dashboard",
icon: <SpaceDashboardIcon/>,
icon: <SpaceDashboardIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "all",
},
@@ -40,7 +40,7 @@ const sidebarMenu = [
name: "passenger_office_chief",
type: "page",
route: "/dashboard/passenger-office-chief",
icon: <AirlineSeatReclineNormalIcon/>,
icon: <AirlineSeatReclineNormalIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_passenger_office_navgan",
},
@@ -49,7 +49,7 @@ const sidebarMenu = [
name: "machinery_expert",
type: "page",
route: "/dashboard/machinery-expert",
icon: <DirectionsCarFilledIcon/>,
icon: <DirectionsCarFilledIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_machinery_navgan",
},
@@ -59,7 +59,7 @@ const sidebarMenu = [
name: "province_working_group",
type: "page",
route: "/dashboard/passenger-boss",
icon: <AssignmentIndIcon/>,
icon: <AssignmentIndIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_province_working_group_navgan",
},
@@ -69,7 +69,7 @@ const sidebarMenu = [
name: "transportation_assistant",
type: "page",
route: "/dashboard/transportation-assistant",
icon: <DirectionsRailwayIcon/>,
icon: <DirectionsRailwayIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_transportation_navgan",
},
@@ -79,7 +79,7 @@ const sidebarMenu = [
name: "province_manager_navgan",
type: "page",
route: "/dashboard/navgan-province-manager",
icon: <DesktopWindowsIcon/>,
icon: <DesktopWindowsIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_province_affairs_navgan",
},
@@ -88,7 +88,7 @@ const sidebarMenu = [
name: "province_head_expert",
type: "page",
route: "/dashboard/province-head-expert",
icon: <GavelIcon/>,
icon: <GavelIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_province_headquarter_refahi",
},
@@ -97,7 +97,7 @@ const sidebarMenu = [
name: "inspector_expert",
type: "page",
route: "/dashboard/inspector-expert",
icon: <GradingIcon/>,
icon: <GradingIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_inspector_refahi",
},
@@ -106,7 +106,7 @@ const sidebarMenu = [
name: "commercial_chief",
type: "page",
route: "/dashboard/commercial-chief",
icon: <BusinessCenterIcon/>,
icon: <BusinessCenterIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_commercial_refahi",
},
@@ -115,7 +115,7 @@ const sidebarMenu = [
name: "development_assistant",
type: "page",
route: "/dashboard/development-assistant",
icon: <Diversity3Icon/>,
icon: <Diversity3Icon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_development_refahi",
},
@@ -125,7 +125,7 @@ const sidebarMenu = [
name: "province_manager_refahi",
type: "page",
route: "/dashboard/refahi-province-manager",
icon: <SupervisedUserCircleIcon/>,
icon: <SupervisedUserCircleIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_province_affairs_refahi",
},
@@ -135,7 +135,7 @@ const sidebarMenu = [
name: "refahi_loan_management",
type: "page",
route: "/dashboard/refahi-loan-management",
icon: <PaidIcon/>,
icon: <PaidIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_refahi_loan",
},
@@ -145,7 +145,7 @@ const sidebarMenu = [
name: "navgan_loan_management",
type: "page",
route: "/dashboard/navgan-loan-management",
icon: <PaidIcon/>,
icon: <PaidIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_navgan_loan",
},
@@ -154,7 +154,7 @@ const sidebarMenu = [
name: "expert_management",
type: "page",
route: "/dashboard/expert-management",
icon: <ManageAccountsIcon/>,
icon: <ManageAccountsIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_experts",
},
@@ -163,7 +163,7 @@ const sidebarMenu = [
name: "user_management",
type: "page",
route: "/dashboard/user-management",
icon: <PersonIcon/>,
icon: <PersonIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_users",
},
@@ -172,7 +172,7 @@ const sidebarMenu = [
name: "role_management",
type: "page",
route: "/dashboard/role-management",
icon: <AccessibilityIcon/>,
icon: <AccessibilityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
selected: false,
permission: "manage_roles",
},

View File

@@ -1,45 +1,48 @@
import ErrorNotification from "@/core/components/notifications/ErrorNotification";
import WarningNotification from "@/core/components/notifications/WarningNotification";
import {toast} from "react-toastify";
import Notifications from "@/core/components/notifications";
export const errorSetting = (t, notification) => {
if (notification) toast.dismiss();
export const errorSetting = (dismissToastList, t, notification) => {
if (notification) {
dismissToastList(["pending", "warning", "error", "success"])
}
}
export const errorRequest = (t, notification) => {
if (notification) toast.dismiss();
}
export const errorResponse = (response, clearToken, t, notification) => {
if (notification) toast.dismiss();
if (isServerError(response.status)) {
errorServer(response, t, notification)
} else if (isClientError(response.status)) {
errorClient(response, clearToken, t, notification)
export const errorRequest = (dismissToastList, t, notification) => {
if (notification) {
dismissToastList(["pending", "warning", "error", "success"])
}
}
const errorServer = (response, t, notification) => {
if (notification) WarningNotification(t, response.status);
export const errorResponse = (pushToastList, dismissToastList, response, clearToken, t, notification) => {
if (notification) {
dismissToastList(["pending", "warning", "error", "success"])
}
if (isServerError(response.status)) {
errorServer(pushToastList, response, t, notification)
} else if (isClientError(response.status)) {
errorClient(pushToastList, response, clearToken, t, notification)
}
}
const errorClient = (response, clearToken, t, notification) => {
const errorServer = (pushToastList, response, t, notification) => {
if (notification) Notifications(pushToastList, "warning", t, response.status);
}
const errorClient = (pushToastList, response, clearToken, t, notification) => {
switch (response.status) {
case 401:
clearToken()
if (notification) ErrorNotification(t, response.status)
if (notification) Notifications(pushToastList, "error", t, response.status);
break;
case 422:
if ('type' in response.data) {
errorLogic(response, t, notification)
errorLogic(pushToastList, response, t, notification)
break;
}
errorValidation(response, t, notification)
errorValidation(pushToastList, response, t, notification)
break;
case 429:
if (notification) ErrorNotification(t, response.status)
if (notification) Notifications(pushToastList, "error", t, response.status);
break
default:
if (notification) ErrorNotification(t, response.status)
if (notification) Notifications(pushToastList, "error", t, response.status);
break
}
}
@@ -47,16 +50,16 @@ const errorClient = (response, clearToken, t, notification) => {
const isServerError = status => status >= 500 && status <= 599;
const isClientError = status => status >= 400 && status <= 499;
const errorLogic = (response, t, notification) => {
if (notification) ErrorNotification(t, response.status, response.data.message)
const errorLogic = (pushToastList, response, t, notification) => {
if (notification) Notifications(pushToastList, "error", t, response.status, response.data.message);
}
const errorValidation = (response, t, notification) => {
const errorValidation = (pushToastList, response, t, notification) => {
if (notification) {
const errorsMap = Object.keys(response.data.errors)
const errorsArray = response.data.errors
errorsMap.map((item, index) => {
ErrorNotification(t, response.status, errorsArray[item][0]);
Notifications(pushToastList, "error", t, response.status, errorsArray[item][0]);
})
}
}

View File

@@ -1,9 +1,8 @@
import SuccessNotification from "@/core/components/notifications/SuccessNotification";
import {toast} from "react-toastify";
import Notifications from "@/core/components/notifications";
export const successRequest = (response, t, options) => {
export const successRequest = (pushToastList, dismissToastList, response, t, options) => {
if (options.notification && options.success.notification.show) {
toast.dismiss();
SuccessNotification(t, response.status)
dismissToastList(["pending", "warning", "error", "success"])
Notifications(pushToastList, "success", t, response.status);
}
}

View File

@@ -47,7 +47,16 @@ function AppLayout({children, isBot}) {
color={theme.palette.secondary.dark}
options={{showSpinner: false}}
/>
<ToastContainer position={directionApp === "ltr" ? "top-left" : "top-right"} rtl={directionApp === 'rtl'}/>
<ToastContainer
enableMultiContainer
containerId="validation"
position={directionApp === "ltr" ? "top-left" : "top-right"}
rtl={directionApp === 'rtl'}/>
<ToastContainer
enableMultiContainer
containerId={'connection'}
position={directionApp === "ltr" ? "top-right" : "top-left"}
rtl={directionApp === 'rtl'}/>
<NetworkComponent/>
{children}
</>);

View File

@@ -0,0 +1,12 @@
import WithAuthMiddleware from "@/middlewares/WithAuth";
import Print from "@/components/layouts/Print";
const PrintLayout = (props) => {
return (
<WithAuthMiddleware>
<Print {...props}/>
</WithAuthMiddleware>
)
}
export default PrintLayout

View File

@@ -1,14 +1,16 @@
import DashboardLayout from "@/layouts/DashboardLayout";
import {Fragment} from "react";
import PrintLayout from "@/layouts/PrintLayout";
const layoutList = {
DashboardLayout
DashboardLayout,
PrintLayout
}
const Layout = ({layout = {}, children}) => {
const Component = layoutList[layout?.name] || Fragment
const props = layout.props || {}
const props = layout?.props || {}
return (
<Component {...props}>

View File

@@ -1,8 +1,8 @@
import {FA_DATATABLE_LOCALIZATION} from "&/locales/fa/datatable";
import {FA_CHART_LOCALIZATION} from "&/locales/fa/chart";
import {useRouter} from "next/router";
import {createContext, useEffect, useState} from "react";
import useUser from "../hooks/useUser";
import {FA_CHART_LOCALIZATION} from "&/locales/fa/chart";
export const LanguageContext = createContext();
@@ -20,7 +20,7 @@ export const LanguageProvider = ({children}) => {
];
const {user, userChangedLanguage, changeLanguageState} = useUser();
const [languageIsReady, setLanguageIsReady] = useState(false);
const [languageApp, setLanguageApp] = useState();
const [languageApp, setLanguageApp] = useState(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
const [directionApp, setDirectionApp] = useState(
process.env.NEXT_PUBLIC_DEFAULT_DIRECTION
);
@@ -28,9 +28,7 @@ export const LanguageProvider = ({children}) => {
useEffect(() => {
const lang = localStorage.getItem("_language");
if (!lang && !languageApp) {
setLanguageApp(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
} else if (lang) {
if (lang) {
setLanguageApp(lang);
}
}, []);

View File

@@ -0,0 +1,24 @@
import {createContext, useReducer} from "react";
export const PrintContext = createContext();
const reducer = (state, action) => {
switch (action.type) {
case "SET_PRINT_PAGE":
return {
...state, count: action.count
};
case "SET_PRINT_TITLE":
return {
...state, title: action.title
};
}
};
export const PrintProvider = ({children}) => {
const [printDetails, printDispatch] = useReducer(reducer, {title: '', count: 0});
return (
<PrintContext.Provider value={{printDetails, printDispatch}}>
{children}
</PrintContext.Provider>
);
};

View File

@@ -0,0 +1,37 @@
import {createContext, useReducer} from "react";
import {toast} from "react-toastify";
export const ToastContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case "PUSH":
return {
...state,
[action.toast_type]: [...state[action.toast_type], action.toast_id]
};
case "DISMISS":
action.toast_type.map((item) => {
state[item].map((id) => {
toast.dismiss(id);
})
state[item] = []
});
return state;
}
};
export const ToastProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, {
pending: [],
error: [],
warning: [],
success: []
});
return (
<ToastContext.Provider value={{state, dispatch}}>
{children}
</ToastContext.Provider>
);
};

View File

@@ -1,7 +1,6 @@
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
import axios from "axios";
import {createContext, useCallback, useEffect, useReducer} from "react";
import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
const initialUser = {
isAuth: false,
@@ -78,13 +77,7 @@ export const UserProvider = ({children}) => {
if (typeof callback === "function") callback(data);
})
.catch(error => {
if (error.response) {
errorResponse(error.response, clearToken, null, false)
} else if (error.request) {
errorRequest(null, false)
} else {
errorSetting(null, false)
}
if (error.response.status === 401) clearToken()
})
},
[state.token]

View File

@@ -11,15 +11,16 @@ const usePermissions = () => {
})
};
const {data} = useSWR(GET_PERMISSIONS_LIST, fetcher, {
const {data, isLoading} = useSWR(GET_PERMISSIONS_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
revalidateOnReconnect: false,
keepPreviousData : true
})
const permissions_list = data
//swr config
// render data
return {permissions_list}
return {permissions_list, isLoading}
}
export default usePermissions;

View File

@@ -0,0 +1,18 @@
import {useContext} from "react";
import {PrintContext} from "@/lib/app/contexts/print";
const usePrint = () => {
const {printDetails, printDispatch} = useContext(PrintContext);
const setPrintPage = (count) => {
printDispatch({type: 'SET_PRINT_PAGE', count})
}
const setPrintTitle = (title) => {
printDispatch({type: 'SET_PRINT_TITLE', title})
}
return {printDetails, setPrintPage, setPrintTitle}
};
export default usePrint;

View File

@@ -1,10 +1,11 @@
import axios from "axios";
import {successRequest} from "@/core/utils/succesHandler";
import PendingNotification from "@/core/components/notifications/PendingNotification";
import {useTranslations} from "next-intl";
import useUser from "@/lib/app/hooks/useUser";
import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
import useNetwork from "@/lib/app/hooks/useNetwork";
import Notifications from "@/core/components/notifications";
import useToast from "@/lib/app/hooks/useToast";
const defaultOptions = {
auth: false, data: {}, requestOptions: {
@@ -23,6 +24,7 @@ const useRequest = (initOptions) => {
const network = useNetwork()
const t = useTranslations()
const {token, clearToken} = useUser()
const {pushToastList, dismissToastList} = useToast();
let _options = {...defaultOptions, ...initOptions}
function requestServer(url = '', method = 'get', options) {
@@ -33,27 +35,30 @@ const useRequest = (initOptions) => {
headers: {..._options.requestOptions.headers, authorization: `Bearer ${token}`}
}
}
return new Promise((resolve, reject) => {
if (!network.online) {
reject()
return
}
if (_options.notification && _options.failed.notification.show && _options.pending) PendingNotification(t)
if (_options.notification && _options.failed.notification.show && _options.pending) {
dismissToastList(["pending", "warning", "error", "success"]);
Notifications(pushToastList, "pending", t);
}
axios({
url: url, method: method, data: _options.data, ..._options.requestOptions
})
.then(response => {
successRequest(response, t, _options)
successRequest(pushToastList, dismissToastList, response, t, _options)
resolve(response)
})
.catch(error => {
if (error.response) {
errorResponse(error.response, clearToken, t, _options.notification && _options.failed.notification.show)
errorResponse(pushToastList, dismissToastList, error.response, clearToken, t, _options.notification && _options.failed.notification.show)
} else if (error.request) {
errorRequest(t, _options.notification && _options.failed.notification.show)
errorRequest(dismissToastList, t, _options.notification && _options.failed.notification.show)
} else {
errorSetting(t, _options.notification && _options.failed.notification.show)
errorSetting(dismissToastList, t, _options.notification && _options.failed.notification.show)
}
reject(error)
})

View File

@@ -0,0 +1,21 @@
import {useContext} from "react";
import {ToastContext} from "@/lib/app/contexts/toast";
const useToast = () => {
const {dispatch} = useContext(ToastContext);
const pushToastList = (toast_type, toast_id) => {
dispatch({type: "PUSH", toast_type, toast_id});
};
const dismissToastList = (toast_type) => {
dispatch({type: "DISMISS", toast_type});
};
return {
pushToastList,
dismissToastList
}
};
export default useToast;

View File

@@ -8,6 +8,8 @@ import "moment/locale/fa";
import {NextIntlProvider} from "next-intl";
import TitlePage from "@/core/components/TitlePage";
import Layout from "@/layouts";
import {ToastProvider} from "@/lib/app/contexts/toast";
import {PrintProvider} from "@/lib/app/contexts/print";
const App = ({Component, pageProps}) => {
@@ -15,15 +17,19 @@ const App = ({Component, pageProps}) => {
<>
<UserProvider>
<LanguageProvider>
<NextIntlProvider messages={pageProps.messages || {}}>
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
<MuiLayout isBot={pageProps.isBot}>
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
<LoadingProvider>
<AppLayout isBot={pageProps.isBot}>
<Layout layout={pageProps.layout}>
<Component {...pageProps} />
</Layout>
</AppLayout>
<ToastProvider>
<AppLayout isBot={pageProps.isBot}>
<PrintProvider>
<Layout layout={pageProps.layout}>
<Component {...pageProps} />
</Layout>
</PrintProvider>
</AppLayout>
</ToastProvider>
</LoadingProvider>
</MuiLayout>
</NextIntlProvider>

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.change_password",
isBot,
locale,
layout: {name: 'DashboardLayout'}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.commercial_chief_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_commercial_refahi"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.development_assistant_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_development_refahi"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.edit_profile",
isBot,
locale,
layout: {name: 'DashboardLayout'}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.expert_management",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_experts"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.dashboard_page",
isBot,
locale,
layout: {name: 'DashboardLayout'}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.inspector_expert_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_inspector_refahi"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.machinary_office_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_machinery_navgan"]}}
},
};

View File

@@ -0,0 +1,21 @@
import {parse} from "next-useragent";
import ReportComponent from "@/components/dashboard/machinary-office/Prints/report";
export default function Report() {
return (
<ReportComponent/>
)
}
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.machinary_office_page",
isBot,
locale,
layout: {name: 'PrintLayout'}
},
};
}

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.loan_management_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_navgan_loan"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.province_manager_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_province_affairs_navgan"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.passenger_boss_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_province_working_group_navgan"]}}
},
};

View File

@@ -0,0 +1,21 @@
import {parse} from "next-useragent";
import FinalCreditAmountComponent from "@/components/dashboard/passenger-boss/Prints/final-credit-amount";
export default function FinalCreditAmount() {
return (
<FinalCreditAmountComponent/>
)
}
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.passenger_boss_page",
isBot,
locale,
layout: {name: 'PrintLayout'}
},
};
}

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.passenger_office_page",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_passenger_office_navgan"]}}
},
};

View File

@@ -14,6 +14,7 @@ export async function getServerSideProps({req, locale}) {
messages: (await import(`&/locales/${locale}/app.json`)).default,
title: "Dashboard.province_head_expert",
isBot,
locale,
layout: {name: 'DashboardLayout', props: {permissions: ["manage_province_headquarter_refahi"]}}
},
};

Some files were not shown because too many files have changed in this diff Show More