work on phone validation part and complete structure with timeline

This commit is contained in:
2024-12-15 18:27:27 +03:30
parent 66518250db
commit 9fccf770d5
13 changed files with 466 additions and 87 deletions

View File

@@ -0,0 +1,33 @@
"use client";
import {Button, IconButton, useMediaQuery} from "@mui/material";
import {useTheme} from "@emotion/react";
import {useState} from "react";
import {AddCircle} from "@mui/icons-material";
import CreatePatrol from "../../Forms/CreatePatrol";
const OperatorCreate = ({mutate}) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
return (
<>
{isMobile ? (
<IconButton aria-label="ثبت اقدام جدید" color="primary" onClick={handleOpen}>
<AddCircle sx={{fontSize: "25px"}}/>
</IconButton>
) : (
<Button variant="contained" color="primary" startIcon={<AddCircle/>} onClick={handleOpen}>
ثبت اقدام
</Button>
)}
{open && <CreatePatrol open={open} setOpen={setOpen} mutate={mutate}/>}
</>
);
};
export default OperatorCreate;

View File

@@ -1,18 +1,18 @@
import { Button, CircularProgress } from "@mui/material";
import { useMemo, useState } from "react";
import {Button, CircularProgress} from "@mui/material";
import {useMemo, useState} from "react";
import moment from "jalali-moment";
import FileSaver from "file-saver";
import DescriptionIcon from "@mui/icons-material/Description";
import useRequest from "@/lib/hooks/useRequest";
import { EXPORT_OPERATOR_LIST } from "@/core/utils/routes";
import {EXPORT_OPERATOR_LIST} from "@/core/utils/routes";
const PrintExcel = ({ table, filterData }) => {
const PrintExcel = ({table, filterData}) => {
const [loading, setLoading] = useState(false);
const requestServer = useRequest();
const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => {
if (filter.value) {
// Check if there's an active filter
acc.push({ id: key, value: filter.value });
acc.push({id: key, value: filter.value});
}
return acc;
}, []);
@@ -20,22 +20,23 @@ const PrintExcel = ({ table, filterData }) => {
const filterParams = useMemo(() => {
const params = new URLSearchParams();
activeFilters.length > 0 &&
activeFilters.map((filter, index) => {
params.set(`${filter.id}`, filter.value);
});
activeFilters.map((filter, index) => {
params.set(`${filter.id}`, filter.value);
});
return params;
}, [activeFilters]);
const clickHandler = () => {
setLoading(true);
requestServer(`${EXPORT_OPERATOR_LIST}?${filterParams}`, "get", {
requestOptions: { responseType: "blob" },
requestOptions: {responseType: "blob"},
})
.then((response) => {
const filename = `خروجی کارتابل عملیات تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`;
FileSaver.saveAs(response.data, filename);
})
.catch(() => {})
.catch(() => {
})
.finally(() => {
setLoading(false);
});
@@ -45,10 +46,9 @@ const PrintExcel = ({ table, filterData }) => {
<Button
color="primary"
variant="contained"
size="small"
disabled={loading}
sx={{ textTransform: "unset", alignSelf: "center" }}
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : <DescriptionIcon />}
sx={{textTransform: "unset", alignSelf: "center"}}
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <DescriptionIcon/>}
onClick={clickHandler}
>
خروجی اکسل

View File

@@ -0,0 +1,80 @@
"use client";
import {Box, DialogContent, Tab, Tabs, useMediaQuery} from "@mui/material";
import {useTheme} from "@emotion/react";
import React, {useState} from "react";
import TapAndPlayIcon from '@mui/icons-material/TapAndPlay';
import TaxiAlertIcon from '@mui/icons-material/TaxiAlert';
import EngineeringIcon from '@mui/icons-material/Engineering';
import HandymanIcon from '@mui/icons-material/Handyman';
import VerifiedIcon from '@mui/icons-material/Verified';
import PatrolTimeLine from "./PatrolTimeLine";
import PhoneValidation from "./PhoneValidation";
function TabPanel(props) {
const {children, value, index} = props;
return (
<div role="tabpanel" hidden={value !== index}>
{value === index && <Box>{children}</Box>}
</div>
);
}
const PatrolForms = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [tabState, setTabState] = useState(0);
const handleChangeTab = (event, newValue) => {
setTabState(newValue);
};
return (
<>
<Tabs
allowScrollButtonsMobile
value={tabState}
onChange={handleChangeTab}
variant={`${isMobile ? "scrollable" : "fullWidth"}`}
sx={{
display: "flex",
justifyContent: "space-around",
width: "100%",
backgroundColor: "#efefef",
}}
>
<Tab icon={<TapAndPlayIcon/>} label="تایید هویت"></Tab>
<Tab icon={<TaxiAlertIcon/>} label="مشخصات گشت"></Tab>
<Tab icon={<EngineeringIcon/>} label="مشخصات راهداران"></Tab>
<Tab icon={<HandymanIcon/>} label="اقدامات حین گشت"></Tab>
<Tab icon={<VerifiedIcon/>} label="تکمیل درخواست"></Tab>
</Tabs>
<DialogContent dividers sx={{display: "flex"}}>
<Box sx={{flex: 1}}>
<TabPanel value={tabState} index={0}>
<PhoneValidation/>
</TabPanel>
<TabPanel value={tabState} index={1}>
<>moshakhasat gasht</>
</TabPanel>
<TabPanel value={tabState} index={2}>
<>moshakhasat rahdar</>
</TabPanel>
<TabPanel value={tabState} index={3}>
<>eghdamat heine gasht</>
</TabPanel>
<TabPanel value={tabState} index={4}>
<>taiidie</>
</TabPanel>
</Box>
{!isMobile && (
<Box sx={{display: "flex", alignItems: "center"}}>
<PatrolTimeLine/>
</Box>
)}
</DialogContent>
</>
);
};
export default PatrolForms;

View File

@@ -0,0 +1,122 @@
"use client";
import {Box, Typography} from "@mui/material";
import TapAndPlayIcon from '@mui/icons-material/TapAndPlay';
import TaxiAlertIcon from '@mui/icons-material/TaxiAlert';
import EngineeringIcon from '@mui/icons-material/Engineering';
import HandymanIcon from '@mui/icons-material/Handyman';
import VerifiedIcon from '@mui/icons-material/Verified';
import {
Timeline,
TimelineConnector,
TimelineContent,
TimelineDot,
TimelineItem,
timelineItemClasses,
TimelineSeparator,
} from "@mui/lab";
const PatrolTimeLine = ({tabState}) => {
return (
<Box sx={{display: "flex", alignItems: "center"}}>
<Timeline
position="right"
sx={{
[`& .${timelineItemClasses.root}:before`]: {flex: 0, padding: 0},
}}
>
<TimelineItem>
<TimelineSeparator>
<TimelineConnector/>
<TimelineDot sx={{backgroundColor: "#fff"}}>
<TapAndPlayIcon
color={tabState === 0 ? "primary" : "error"}
sx={{width: "1.5rem", height: "1.5rem"}}
/>
</TimelineDot>
<TimelineConnector/>
</TimelineSeparator>
<TimelineContent sx={{py: "12px", px: 2}}>
<Typography variant="h6">تایید هویت</Typography>
<Typography variant="caption" sx={{color: "#606060"}}>
تایید هویت با شماره همراه
</Typography>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineConnector/>
<TimelineDot sx={{backgroundColor: "#fff"}}>
<TaxiAlertIcon
color={tabState === 1 ? "primary" : "error"}
sx={{width: "1.5rem", height: "1.5rem"}}
/>
</TimelineDot>
<TimelineConnector/>
</TimelineSeparator>
<TimelineContent sx={{py: "12px", px: 2}}>
<Typography variant="h6">مشخصات گشت</Typography>
<Typography variant="caption" sx={{color: "#606060"}}>
تایید اطلاعات اولیه گشت
</Typography>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineConnector/>
<TimelineDot sx={{backgroundColor: "#fff"}}>
<EngineeringIcon
color={tabState === 1 ? "primary" : "error"}
sx={{width: "1.5rem", height: "1.5rem"}}
/>
</TimelineDot>
<TimelineConnector/>
</TimelineSeparator>
<TimelineContent sx={{py: "12px", px: 2}}>
<Typography variant="h6">مشخصات راهداران</Typography>
<Typography variant="caption" sx={{color: "#606060"}}>
راهدار/راهداران حاظر در گشت
</Typography>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineConnector/>
<TimelineDot sx={{backgroundColor: "#fff"}}>
<HandymanIcon
color={tabState === 1 ? "primary" : "error"}
sx={{width: "1.5rem", height: "1.5rem"}}
/>
</TimelineDot>
<TimelineConnector/>
</TimelineSeparator>
<TimelineContent sx={{py: "12px", px: 2}}>
<Typography variant="h6">اقدامات حین گشت</Typography>
<Typography variant="caption" sx={{color: "#606060"}}>
ثبت فعالیت های صورت گرفته حین گشت
</Typography>
</TimelineContent>
</TimelineItem>
<TimelineItem>
<TimelineSeparator>
<TimelineConnector/>
<TimelineDot sx={{backgroundColor: "#fff"}}>
<VerifiedIcon
color={tabState === 1 ? "primary" : "error"}
sx={{width: "1.5rem", height: "1.5rem"}}
/>
</TimelineDot>
<TimelineConnector/>
</TimelineSeparator>
<TimelineContent sx={{py: "12px", px: 2}}>
<Typography variant="h6">تکمیل درخواست</Typography>
<Typography variant="caption" sx={{color: "#606060"}}>
تکمیل درخواست و ثبت نهایی
</Typography>
</TimelineContent>
</TimelineItem>
</Timeline>
</Box>
);
};
export default PatrolTimeLine;

View File

@@ -0,0 +1,130 @@
"use client";
import {Box, Button, FormControl, InputLabel, OutlinedInput, Slide} from "@mui/material";
import React, {useEffect, useState} from "react";
import ForwardToInboxIcon from '@mui/icons-material/ForwardToInbox';
import QrCodeIcon from '@mui/icons-material/QrCode';
import useRequest from "@/lib/hooks/useRequest";
import {GET_OTP_TOKEN} from "@/core/utils/routes";
const PhoneValidation = () => {
const requestServer = useRequest({auth: true});
const [phoneNumber, setPhoneNumber] = useState("");
const [validPhone, setValidPhone] = useState(false);
const [delayPerRequest, setDelayPerRequest] = useState(false);
const [counter, setCounter] = useState(120);
const [otpSended, setOtpSended] = useState(false);
const handleNumericInput = (e) => {
e.target.value = e.target.value.replace(/[^0-9]/g, '');
};
useEffect(() => {
if (phoneNumber.length === 11) {
setValidPhone(true)
} else {
setValidPhone(false)
}
}, [phoneNumber]);
useEffect(() => {
if (counter === 0) {
setDelayPerRequest(false);
return;
}
if (delayPerRequest && counter > 0) {
const timer = setInterval(() => {
setCounter(prevCounter => prevCounter - 1);
}, 1000);
return () => clearInterval(timer);
}
}, [counter, delayPerRequest]);
const sendOtp = () => {
if (!validPhone) return false;
requestServer(`${GET_OTP_TOKEN}?phone_number=${phoneNumber}`, "get")
.then((response) => {
console.log("request sent");
})
.catch(() => {
});
//////////**** (mohammad) this part should keep in success but for now we go next level ****////////////
setOtpSended(true);
setDelayPerRequest(true);
};
// Format counter as MM:SS
const formatCounter = (counter) => {
const minutes = Math.floor(counter / 60).toString().padStart(2, '0');
const seconds = (counter % 60).toString().padStart(2, '0');
return `${minutes}:${seconds}`;
};
return (
<Box sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
my: 3
}}>
<Box sx={{display: "flex", alignItems: "center", gap: 2, width: {xs: "100%", sm: "70%"}}}>
<FormControl size="small" fullWidth variant="outlined">
<InputLabel htmlFor="phone_number">شماره تلفن مامور گشت</InputLabel>
<OutlinedInput
id="phone_number"
label={"شماره تلفن مامور گشت"}
disabled={delayPerRequest}
autoComplete="off"
size="small"
fullWidth
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
onInput={handleNumericInput}
type="text"
inputProps={{
maxLength: 11
}}
/>
</FormControl>
<Button onClick={sendOtp} disabled={!validPhone || delayPerRequest} variant="contained" color="success"
sx={{width: "200px", height: "37px"}}
endIcon={<ForwardToInboxIcon/>}>
{delayPerRequest ? formatCounter(counter) : "دریافت کد"}
</Button>
</Box>
<Slide in={otpSended} sx={{my: 5, width: {xs: "100%", sm: "70%"}}}>
<Box>
<FormControl size="small" fullWidth variant="outlined" focused>
<InputLabel htmlFor="otp_code">کد پیامک شده</InputLabel>
<OutlinedInput
id="otp_code"
label={"کد پیامک شده"}
placeholder="------"
autoComplete="off"
size="small"
fullWidth
type="text"
onInput={handleNumericInput}
inputProps={{
maxLength: 6,
inputMode: "numeric",
style: {
textAlign: "center",
letterSpacing: "10px",
},
}}
/>
</FormControl>
<Button fullWidth variant="contained" endIcon={<QrCodeIcon/>} sx={{mt: 2}}>
بررسی کد و رفتن به مرحله بعد
</Button>
</Box>
</Slide>
</Box>
);
};
export default PhoneValidation;

View File

@@ -0,0 +1,17 @@
"use client";
import {Dialog} from "@mui/material";
import PatrolForms from "@/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolForms";
const CreatePatrol = ({open, setOpen, mutate, rowId}) => {
return (
<Dialog open={open} fullWidth maxWidth="lg">
<PatrolForms
setOpen={setOpen}
mutate={mutate}
rowId={rowId}
/>
</Dialog>
);
};
export default CreatePatrol;

View File

@@ -1,10 +1,13 @@
import PrintExcel from "./ExcelPrint";
import OperatorCreate from "./Actions/Create";
import {Box} from "@mui/material";
const Toolbar = ({ table, filterData }) => {
const Toolbar = ({table, filterData, mutate}) => {
return (
<>
<PrintExcel table={table} filterData={filterData} />
</>
<Box sx={{display: "flex", gap: 1}}>
<OperatorCreate table={table} mutate={mutate}/>
<PrintExcel table={table} filterData={filterData}/>
</Box>
);
};
export default Toolbar;

View File

@@ -1,13 +1,13 @@
"use client";
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import OperatorList from "@/components/dashboard/roadPatrols/operator/OperatorList";
import {Stack} from "@mui/material";
import OperatorList from "./OperatorList";
const OperatorPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"عملیات"} />
<OperatorList />
<PageTitle title={"عملیات"}/>
<OperatorList/>
</Stack>
);
};

View File

@@ -1,15 +1,15 @@
"use client";
import { useMemo } from "react";
import { Box, Typography } from "@mui/material";
import {useMemo} from "react";
import {Box, Typography} from "@mui/material";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import Toolbar from "./Toolbar";
import { GET_SUPERVISOR_LIST } from "@/core/utils/routes";
import {GET_SUPERVISOR_LIST} from "@/core/utils/routes";
import moment from "jalali-moment";
import RowActions from "./RowActions";
import useProvinces from "@/lib/hooks/useProvince";
const OperatorList = () => {
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
const SuperviserList = () => {
const {provinces, errorProvinces, loadingProvinces} = useProvinces();
const citiesListApi = "https://rms.rmto.ir/public/contents/edarate_shahri_by_province";
const columns = useMemo(
() => [
@@ -31,11 +31,11 @@ const OperatorList = () => {
filterFn: "equals",
columnSelectOption: () => {
if (loadingProvinces) {
return [{ value: "", label: "در حال بارگذاری..." }];
return [{value: "", label: "در حال بارگذاری..."}];
}
if (errorProvinces) {
return [{ value: "", label: "خطا در بارگذاری" }];
return [{value: "", label: "خطا در بارگذاری"}];
}
return provinces.map((province) => ({
@@ -54,10 +54,10 @@ const OperatorList = () => {
dependencyId: "province",
columnSelectOption: (dependencyField) => {
if (dependencyField.value === "") {
return [{ value: "", label: "ابتدا استان را انتخاب کنید" }];
return [{value: "", label: "ابتدا استان را انتخاب کنید"}];
}
return [{ value: dependencyField.value, label: dependencyField.value }];
return [{value: dependencyField.value, label: dependencyField.value}];
},
},
{
@@ -95,7 +95,7 @@ const OperatorList = () => {
datatype: "date",
filterFn: "equals",
columnFilterModeOptions: ["equals", "contains"],
Cell: ({ renderedCellValue }) => (
Cell: ({renderedCellValue}) => (
<Typography variant="body2">
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("YYYY/MM/DD") : "-"}
</Typography>
@@ -109,7 +109,7 @@ const OperatorList = () => {
datatype: "date",
filterFn: "equals",
columnFilterModeOptions: ["equals", "contains"],
Cell: ({ renderedCellValue }) => (
Cell: ({renderedCellValue}) => (
<Typography variant="body2">
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("YYYY/MM/DD") : "-"}
</Typography>
@@ -123,7 +123,7 @@ const OperatorList = () => {
datatype: "date",
filterFn: "equals",
columnFilterModeOptions: ["equals", "contains"],
Cell: ({ renderedCellValue }) => (
Cell: ({renderedCellValue}) => (
<Typography variant="body2">
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("YYYY/MM/DD") : "-"}
</Typography>
@@ -135,7 +135,7 @@ const OperatorList = () => {
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<Box sx={{p: 1, border: 1, borderColor: "divider", borderRadius: 1}}>
<DataTableWithAuth
table_title={"ارزیابی"}
need_filter={true}
@@ -152,4 +152,4 @@ const OperatorList = () => {
</>
);
};
export default OperatorList;
export default SuperviserList;

View File

@@ -1,13 +1,13 @@
"use client";
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import SupervisorList from "./SupervisorList";
import {Stack} from "@mui/material";
import SuperviserList from "./SupervisorList";
const SupervisorPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"ارزیابی"} />
<SupervisorList />
<PageTitle title={"ارزیابی"}/>
<SuperviserList/>
</Stack>
);
};