TF-102 Merge branch 'bugfix/TF-102_fixed_bug' into 'develop'

This commit is contained in:
AmirHossein Mahmoodi
2023-09-27 08:36:47 +00:00
17 changed files with 356 additions and 161 deletions

BIN
public/icons/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -15,6 +15,8 @@
"Resend_code_in": "ارسال مجدد کد در",
"seconds_later": "ثانیه دیگر",
"upload_file_text": "بارگذاری فایل",
"online_message": "شما به اینترنت وصل هستید",
"offline_message": "اتصال شما به اینترنت قطع شده است",
"header": {
"open_profile": "پروفایل",
"edit_profile": "ویرایش پروفایل",
@@ -111,7 +113,8 @@
"loan_follow_up_page": "پیگیری درخواست",
"loan_request_correction": "اصلاح درخواست",
"loan_request_detail": "مشاهده جزئیات",
"request_number": "درخواست شماره"
"request_number": "درخواست شماره",
"no_request_found": "درخواستی یافت نشد"
},
"LoanRequest": {
"loan_request_page": "ثبت درخواست وام",
@@ -132,6 +135,8 @@
"text_field_vehicle_type": "نام وسیله نقلیه",
"text_field_enter_your_vehicle_type": "نام وسیله نقلیه خود را وارد کنید",
"text_field_province": "استان",
"text_field_loading_provinces_list": "درحال دریافت استان ها",
"text_field_error_fetching_provinces": "خطا در دریافت استان ها",
"text_field_enter_your_province": "استان خود را وارد کنید",
"text_field_navgan_id": "شماره ناوگان",
"text_field_enter_your_navgan_id": "شماره ناوگان خود را وارد کنید",

57
public/manifest.json Normal file
View File

@@ -0,0 +1,57 @@
{
"short_name": "Loan Facilities Dashboard",
"name": "Loan Facilities User Dashboard",
"description": "Loan Facilities",
"version": "1.0.0",
"version_name": "1.0.0 beta",
"start_url": "/dashboard",
"orientation": "portrait",
"display": "standalone",
"theme_color": "#084070",
"background_color": "#084070",
"icons": [
{
"src": "/icons/maskable_icon.png",
"sizes": "any",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icons/maskable_icon_x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"categories": [
"government"
]
}

View File

@@ -1,39 +1,30 @@
import StyledForm from "@/core/components/StyledForm";
import UploadSystem from "@/core/components/UploadSystem";
import {GET_PROVINCE_LIST, SEND_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes";
import useDirection from "@/lib/app/hooks/useDirection";
import useUser from "@/lib/app/hooks/useUser";
import {SEND_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes";
import DataSaverOnIcon from "@mui/icons-material/DataSaverOn";
import {Box, Button, Chip, Divider, Stack, TextField} from "@mui/material";
import {
Box,
Button,
Chip,
Divider,
FormControl,
FormHelperText,
InputLabel,
MenuItem,
Select,
Stack,
TextField
} from "@mui/material";
import {Field, Formik} from "formik";
import {useTranslations} from "next-intl";
import {useEffect, useState} from "react";
import * as Yup from "yup";
import SelectBox from "@/core/components/SelectBox";
import useRequest from "@/lib/app/hooks/useRequest";
import useProvince from "@/lib/app/hooks/useProvince";
const AddFormComponent = () => {
const t = useTranslations();
const {directionApp} = useDirection();
const {token} = useUser();
const requestServer = useRequest()
const [provinceList, setProvinceList] = useState([]);
// get province list
useEffect(() => {
requestServer(GET_PROVINCE_LIST, "get", {auth: true, notification: false})
.then(function ({data}) {
const formattedData = data.map((province, index) => ({
id: index,
name: province.name,
value: province.id,
}));
setProvinceList(formattedData);
}).catch(() => {
})
}, []);
// end get province list
const {provinceList, isLoadingProvinceList, errorProvinceList} = useProvince()
// initial values, validation and request action of form
const initialValues = {
@@ -183,27 +174,48 @@ const AddFormComponent = () => {
<Box
sx={{
mx: {xs: 0, sm: 2},
my: 2,
mt: 0,
width: "100%",
}}
>
<Field
name="province"
label={t("LoanRequest.text_field_province")} // t("LoanRequest.text_field_enter_your_province")
<FormControl
error={props.touched.province && !!props.errors.province}
sx={{width: "100%", mt: 2}}
size="small"
selectType="province"
component={SelectBox}
selectors={provinceList}
select={props.values.province}
setFieldValue={props.setFieldValue}
setFieldTouched={props.setFieldTouched}
error={
!!(props.touched.province && props.errors.province)
}
helperText={
props.touched.province ? props.errors.province : null
}
/>
>
<InputLabel>{t("LoanRequest.text_field_province")}</InputLabel>
<Select
name="province"
disabled={isLoadingProvinceList || errorProvinceList}
label={t("LoanRequest.text_field_province")}
value={props.values.province}
onChange={(e) => {
props.setFieldValue("province", e.target.value);
}}
onBlur={props.handleBlur}
fullWidth
variant="outlined"
>
{isLoadingProvinceList ? (
<MenuItem>
{t("LoanRequest.text_field_loading_provinces_list")}
</MenuItem>
) : errorProvinceList ? (
<MenuItem>
{t("LoanRequest.text_field_error_fetching_provinces")}
</MenuItem>
) : (
provinceList.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.name}
</MenuItem>
))
)}
</Select>
<FormHelperText>
{props.touched.province && props.errors.province ? props.errors.province : ""}
</FormHelperText>
</FormControl>
</Box>
</Box>
<Box

View File

@@ -1,19 +1,18 @@
import DashboardLayouts from "@/layouts/dashboardLayouts";
import {
Grid,
Stack,
} from "@mui/material";
import {Grid, Stack, Typography,} from "@mui/material";
import {useEffect, useState} from "react";
import {SHOW_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes";
import useLoading from "@/lib/app/hooks/useLoading";
import useRequest from "@/lib/app/hooks/useRequest";
import moment from "jalali-moment";
import RequestCard from "@/components/dashboard/refahi/followUp-loan/RequestCard";
import {useTranslations} from "next-intl";
const LoanFollowUpComponent = () => {
const t = useTranslations()
const requestServer = useRequest();
const {setLoadingPage} = useLoading();
const [requestsList, setRequestsList] = useState([]);
const [requestsList, setRequestsList] = useState(null);
// get form data
useEffect(() => {
setLoadingPage(true)
@@ -33,28 +32,23 @@ const LoanFollowUpComponent = () => {
})
}, []);
return (
<DashboardLayouts>
<Stack
spacing={2}
sx={{
p: 4,
width: "100%",
}}
return (<DashboardLayouts>
<Stack
spacing={2}
sx={{
p: 4, width: "100%",
}}
>
{requestsList == null ? '' : requestsList.length ? (<Grid
container
columnSpacing={2}
rowSpacing={2}
sx={{alignItems: "start", justifyContent: "center"}}
>
<Grid
container
columnSpacing={2}
rowSpacing={2}
sx={{alignItems: "start", justifyContent: "center"}}
>
{requestsList.map((item, index) => (
<RequestCard item={item} key={item.id}/>
))}
</Grid>
</Stack>
</DashboardLayouts>
);
{requestsList.map((item, index) => (<RequestCard item={item} key={item.id}/>))}
</Grid>) : (<Typography sx={{textAlign: 'center'}}>{t('LoanFollowUp.no_request_found')}</Typography>)}
</Stack>
</DashboardLayouts>);
};
export default LoanFollowUpComponent;

View File

@@ -0,0 +1,58 @@
import Head from "next/head";
const GlobalHead = () => {
return (
<Head>
<meta
name="application-name"
content={process.env.NEXT_PUBLIC_API_NAME}
/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta
name="apple-mobile-web-app-title"
content={process.env.NEXT_PUBLIC_API_NAME}
/>
<meta name="description" content="Marhaba does it for you"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="format-detection" content="date=no"/>
<meta name="format-detection" content="address=no"/>
<meta name="format-detection" content="email=no"/>
<meta name="mobile-web-app-capable" content="yes"/>
<link rel="apple-touch-icon" href="/icons/maskable_icon_x512.png"/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="/icons/maskable_icon_x128.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/icons/maskable_icon_x192.png"
/>
<meta name="google" content="notranslate"/>
<meta name="robots" content="noindex"/>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<link
rel="icon"
type="image/svg"
sizes="32x32"
href="/icons/favicon.png"
/>
<link
rel="icon"
type="image/svg"
sizes="16x16"
href="/icons/favicon.png"
/>
</Head>
)
}
export default GlobalHead

View File

@@ -0,0 +1,34 @@
import useNetwork from "@/lib/app/hooks/useNetwork";
import {useEffect, useRef} from "react";
import {toast} from "react-toastify";
import WifiIcon from '@mui/icons-material/Wifi';
import WifiOffIcon from '@mui/icons-material/WifiOff';
import {useTranslations} from "next-intl";
const NetworkComponent = () => {
const toastId = useRef(null);
const network = useNetwork()
const t = useTranslations()
useEffect(() => {
if (network.online) {
toast.update(toastId.current, {
type: toast.TYPE.SUCCESS,
render: t('online_message'),
autoClose: 2000,
closeButton: true,
closeOnClick: true,
icon: <WifiIcon/>
});
return
}
toast.dismiss()
toastId.current = toast.warn(t('offline_message'), {
autoClose: false, closeButton: false, closeOnClick: false, icon: <WifiOffIcon/>
})
}, [network.online]);
return ''
}
export default NetworkComponent

View File

@@ -4,7 +4,7 @@ import {toast} from "react-toastify";
const ErrorNotification = (t, status, message) => {
toast(
({closeToast}) => (
() => (
<>
<Box
sx={{
@@ -36,4 +36,4 @@ const ErrorNotification = (t, status, message) => {
);
};
export default ErrorNotification;
export default ErrorNotification;

View File

@@ -3,9 +3,10 @@ import {toast} from "react-toastify";
const PendingNotification = (t) => {
toast(t("notifications.pending"), {
autoClose: false,
closeButton: false,
closeOnClick: false,
draggable: false,
});
};
export default PendingNotification;
export default PendingNotification;

View File

@@ -39,4 +39,4 @@ const SuccessNotification = (t, status) => {
);
};
export default SuccessNotification;
export default SuccessNotification;

View File

@@ -1,10 +1,10 @@
import ReportIcon from "@mui/icons-material/Report";
import {Box, Divider, Typography} from "@mui/material";
import {Box, Typography} from "@mui/material";
import {toast} from "react-toastify";
const WarningNotification = (t, status) => {
toast(
({closeToast}) => (
() => (
<>
<Box
sx={{
@@ -18,15 +18,15 @@ const WarningNotification = (t, status) => {
<ReportIcon color="warning" sx={{mr: 1.6}}/>
<Box sx={{display: "flex", flexDirection: "column"}}>
<Typography color="warning.main" variant="button">
{t("warning")} ({t("code")}: {status})
{t("notifications.warning")} ({t("notifications.code")}:{" "}
{status})
</Typography>
<Typography variant="caption">
{t("warning_static_text")}
{t("notifications.warning_static_text")}
</Typography>
</Box>
</Box>
</Box>
<Divider sx={{my: 1}}/>
</>
),
{
@@ -37,4 +37,4 @@ const WarningNotification = (t, status) => {
);
};
export default WarningNotification;
export default WarningNotification;

View File

@@ -3,11 +3,11 @@ import WarningNotification from "@/core/components/notifications/WarningNotifica
import {toast} from "react-toastify";
export const errorSetting = (t, notification) => {
//todo
if (notification) toast.dismiss();
}
export const errorRequest = (t, notification) => {
//todo
if (notification) toast.dismiss();
}
export const errorResponse = (response, clearToken, t, notification) => {
@@ -48,7 +48,7 @@ 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)
if (notification) ErrorNotification(t, response.status, response.data.message)
}
const errorValidation = (response, t, notification) => {
if (notification) {
@@ -59,4 +59,4 @@ const errorValidation = (response, t, notification) => {
ErrorNotification(t, response.status, errorsArray[item][0]);
})
}
}
}

View File

@@ -2,12 +2,13 @@ import theme from "@/core/utils/theme";
import useLanguage from "@/lib/app/hooks/useLanguage";
import useLoading from "@/lib/app/hooks/useLoading";
import useUser from "@/lib/app/hooks/useUser";
import Head from "next/head";
import NextNProgress from "nextjs-progressbar";
import {useEffect} from "react";
import {ToastContainer} from "react-toastify";
import useDirection from "@/lib/app/hooks/useDirection";
import "react-toastify/dist/ReactToastify.css";
import NetworkComponent from "@/core/components/NetworkComponent";
import GlobalHead from "@/core/components/GlobalHead";
function AppLayout({children, isBot}) {
const {languageIsReady} = useLanguage();
@@ -40,66 +41,16 @@ function AppLayout({children, isBot}) {
if (!languageIsReady) return;
}
return (
<>
<Head>
<meta
name="application-name"
content={process.env.NEXT_PUBLIC_API_NAME}
/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta
name="apple-mobile-web-app-title"
content={process.env.NEXT_PUBLIC_API_NAME}
/>
<meta name="description" content="Marhaba does it for you"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="format-detection" content="date=no"/>
<meta name="format-detection" content="address=no"/>
<meta name="format-detection" content="email=no"/>
<meta name="mobile-web-app-capable" content="yes"/>
<link rel="apple-touch-icon" href="/icons/maskable_icon_x512.png"/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="/icons/maskable_icon_x128.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/icons/maskable_icon_x192.png"
/>
<meta name="google" content="notranslate"/>
<meta name="robots" content="noindex"/>
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
<link
rel="icon"
type="image/svg"
sizes="32x32"
href="/images/logo.png"
/>
<link
rel="icon"
type="image/svg"
sizes="16x16"
href="/images/logo.png"
/>
</Head>
<NextNProgress
color={theme.palette.secondary.dark}
options={{showSpinner: false}}
/>
<ToastContainer position={directionApp === "ltr" ? "top-left" : "top-right"} rtl={directionApp === 'rtl'}/>
{children}
</>
);
return (<>
<GlobalHead/>
<NextNProgress
color={theme.palette.secondary.dark}
options={{showSpinner: false}}
/>
<ToastContainer position={directionApp === "ltr" ? "top-left" : "top-right"} rtl={directionApp === 'rtl'}/>
<NetworkComponent/>
{children}
</>);
}
export default AppLayout;
export default AppLayout;

View File

@@ -0,0 +1,58 @@
import {useEffect, useState} from "react";
function getNetworkConnection() {
return (
navigator.connection ||
navigator.mozConnection ||
navigator.webkitConnection ||
null
);
}
function getNetworkConnectionInfo() {
const connection = getNetworkConnection();
if (!connection) {
return {};
}
return {
rtt: connection.rtt,
type: connection.type,
saveData: connection.saveData,
downLink: connection.downLink,
downLinkMax: connection.downLinkMax,
effectiveType: connection.effectiveType,
};
}
function useNetwork() {
const [state, setState] = useState(() => {
return {
online: navigator.onLine,
};
});
useEffect(() => {
const handleOnline = () => {
setState((prevState) => ({
...prevState,
online: true,
}));
};
const handleOffline = () => {
setState((prevState) => ({
...prevState,
online: false,
}));
};
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return state;
}
export default useNetwork;

View File

@@ -0,0 +1,29 @@
import {GET_PROVINCE_LIST} from "@/core/data/apiRoutes";
import useRequest from "@/lib/app/hooks/useRequest";
import useSWR from "swr";
const useProvince = () => {
const requestServer = useRequest({auth: true, notification: false})
//swr config
const fetcher = (...args) => {
return requestServer(args, 'get').then(({data}) => {
return data;
}).catch(() => {
})
};
const {data, isLoading} = useSWR(GET_PROVINCE_LIST, fetcher, {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
});
return {
provinceList: data,
isLoadingProvinceList: isLoading,
errorProvinceList: !data
}
};
export default useProvince;

View File

@@ -4,27 +4,23 @@ import PendingNotification from "@/core/components/notifications/PendingNotifica
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";
const defaultOptions = {
auth: false,
data: {},
requestOptions: {
auth: false, data: {}, requestOptions: {
headers: {}
},
notification: true,
pending: true,
success: {
}, notification: true, pending: true, success: {
notification: {
show: true,
},
},
failed: {
}, failed: {
notification: {
show: true,
},
},
}
const useRequest = (initOptions) => {
const network = useNetwork()
const t = useTranslations()
const {token, clearToken} = useUser()
let _options = {...defaultOptions, ...initOptions}
@@ -32,20 +28,20 @@ const useRequest = (initOptions) => {
function requestServer(url = '', method = 'get', options) {
_options = {..._options, ...options}
if (_options.auth) _options = {
..._options,
requestOptions: {
..._options, requestOptions: {
..._options.requestOptions,
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)
axios({
url: url,
method: method,
data: _options.data,
..._options.requestOptions
url: url, method: method, data: _options.data, ..._options.requestOptions
})
.then(response => {
successRequest(response, t, _options)

View File

@@ -11,7 +11,7 @@ export default function MyDocument(props) {
<Head>
<meta name="theme-color" content={theme.palette.primary.main}/>
<meta name="emotion-insertion-point" content=""/>
<link rel="shortcut icon" href="/icons/favicon.ico"/>
<link rel="shortcut icon" href="/icons/favicon.png"/>
<link rel="manifest" href="/manifest.json"/>
{emotionStyleTags}
</Head>