Feature/user management

This commit is contained in:
AmirHossein Mahmoodi
2025-05-10 05:31:24 +00:00
parent bc59952f7b
commit f07d27b273
101 changed files with 7136 additions and 14 deletions

View File

@@ -0,0 +1,205 @@
"use client";
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
import { GET_USER_LIST } from "@/core/utils/routes";
import { Box, Stack } from "@mui/material";
import { useMemo } from "react";
import RowActions from "./RowActions";
import Toolbar from "./Toolbar";
import useProvinces from "@/lib/hooks/useProvince";
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
import { Power, PowerOff } from "@mui/icons-material";
import useRoles from "@/lib/hooks/useRoles";
import OnlineCell from "./RowActions/Online";
import TelephoneId from "./RowActions/TelephoneId";
const DataTable = () => {
const columns = useMemo(
() => [
{
accessorKey: "id",
header: "کد یکتا",
id: "id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
header: "استان",
id: "province_id",
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
grow: false,
size: 130,
ColumnSelectComponent: (props) => {
const { provinces, errorProvinces, loadingProvinces } =
useProvinces();
const getColumnSelectOptions = useMemo(() => {
if (loadingProvinces) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorProvinces) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "کل کشور" },
...provinces.map((province) => ({
value: province.id,
label: province.name,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
return (
<CustomSelectByDependency
{...props}
value={
loadingProvinces ? "loading" : props.filterParameters.value
}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}</>,
},
{
header: "شماره تماس داخلی",
id: "telephone_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
},
{
accessorKey: "username",
header: "نام کاربری",
id: "username",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "position",
header: "سمت",
id: "position",
enableColumnFilter: false,
grow: false,
size: 100,
},
{
accessorKey: "phone_number",
header: "شماره همراه",
id: "phone_number",
enableColumnFilter: false,
grow: false,
size: 100,
},
{
header: "نقش",
id: "role__id",
enableColumnFilter: false,
grow: false,
size: 100,
enableColumnFilter: true,
datatype: "numeric",
filterMode: "equals",
grow: false,
size: 130,
ColumnSelectComponent: (props) => {
const { roles, errorRoles, loadingRoles } = useRoles();
const getColumnSelectOptions = useMemo(() => {
if (loadingRoles) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorRoles) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "همه نقش ها" },
...roles.map((role) => ({
value: role.id,
label: role.name_fa,
})),
];
}, [roles, errorRoles, loadingRoles]);
return (
<CustomSelectByDependency
{...props}
value={loadingRoles ? "loading" : props.filterParameters.value}
columnSelectOption={getColumnSelectOptions}
/>
);
},
Cell: ({ row }) => <>{row.original.roles[0].name_fa}</>,
},
{
accessorKey: "national_id",
header: "کد ملی",
id: "national_id",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
},
{
accessorKey: "is_online",
header: "وضعیت برخط",
id: "is_online",
enableColumnFilter: true,
datatype: "text",
filterMode: "equals",
sortDescFirst: false,
columnFilterModeOptions: ["equals", "contains"],
grow: false,
size: 100,
Cell: ({ row }) => <OnlineCell user_id={row.original.id} />,
},
{
accessorKey: "gender",
header: "جنسیت",
id: "gender",
enableColumnFilter: false,
grow: false,
size: 100,
Cell: ({ renderedCellValue }) => (
<>{renderedCellValue == "male" ? "آقا" : "خانم"}</>
),
},
],
[],
);
return (
<>
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
<DataTableWithAuth
need_filter={true}
columns={columns}
sorting={[{ id: "id", desc: true }]}
table_url={GET_USER_LIST}
page_name={"usersPage"}
table_name={"usersList"}
TableToolbar={Toolbar}
enableRowActions
positionActionsColumn={"last"}
RowActions={RowActions}
/>
</Box>
</>
);
};
export default DataTable;

View File

@@ -0,0 +1,56 @@
import { DELETE_USER } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import {
Button,
DialogActions,
DialogContent,
Stack,
Typography,
} from "@mui/material";
import { useState } from "react";
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
const [submitting, setSubmitting] = useState(false);
const requestServer = useRequest({ notificationSuccess: true });
const handleClick = async () => {
setSubmitting(true);
try {
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
mutate();
setOpenDeleteDialog(false);
} catch (error) {
} finally {
setSubmitting(false);
}
};
return (
<>
<DialogContent>
<Stack alignItems="center" spacing={2}>
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
</Stack>
</DialogContent>
<DialogActions
sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}
>
<Button
onClick={() => setOpenDeleteDialog(false)}
variant="outlined"
color="secondary"
>
خیر !
</Button>
<Button
onClick={handleClick}
variant="contained"
color="error"
disabled={submitting}
>
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
</Button>
</DialogActions>
</>
);
};
export default DeleteContent;

View File

@@ -0,0 +1,37 @@
import DeleteIcon from "@mui/icons-material/Delete";
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import DeleteContent from "./DeleteContent";
const Delete = ({ rowId, mutate }) => {
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
return (
<>
<Tooltip title="حذف">
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
<DeleteIcon />
</IconButton>
</Tooltip>
<Dialog
open={openDeleteDialog}
onClose={() => setOpenDeleteDialog(false)}
PaperProps={{
sx: {
boxShadow:
"rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
},
}}
dir="rtl"
maxWidth={"md"}
>
<DialogTitle>حذف</DialogTitle>
<DeleteContent
rowId={rowId}
mutate={mutate}
setOpenDeleteDialog={setOpenDeleteDialog}
/>
</Dialog>
</>
);
};
export default Delete;

View File

@@ -0,0 +1,369 @@
import LtrTextField from "@/core/components/LtrTextField";
import StyledForm from "@/core/components/StyledForm";
import validateNationalCode from "@/core/utils/nationalCodeValidation";
import { UPDATE_USER } from "@/core/utils/routes";
import useProvinces from "@/lib/hooks/useProvince";
import useRequest from "@/lib/hooks/useRequest";
import useRoles from "@/lib/hooks/useRoles";
import { yupResolver } from "@hookform/resolvers/yup";
import {
Button,
Chip,
DialogActions,
DialogContent,
Divider,
FormControl,
FormHelperText,
Grid,
InputLabel,
MenuItem,
OutlinedInput,
Select,
Stack,
TextField,
} from "@mui/material";
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
const validationSchema = object().shape({
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
username: string().required("نام کاربری خود را وارد کنید"),
phone_number: string()
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
.required("شماره همراه خود را وارد کنید"),
gender: string().required("جنسیت خود را وارد کنید"),
national_id: string()
.test(
"max",
"کد ملی باید شامل 10 رقم باشد",
(value) => value.toString().length === 10,
)
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
validateNationalCode(value),
)
.required("کد ملی خود را وارد کنید"),
position: string().required("سمت خود را وارد کنید"),
province_id: string().required("استان خود را وارد کنید"),
role_id: string().required("نقش خود را وارد کنید"),
});
const UpdateUserForm = ({ values, setOpen, mutate }) => {
const requestServer = useRequest({ notificationSuccess: true });
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
const { roles, errorRoles, loadingRoles } = useRoles();
const defaultValues = {
full_name: values.full_name,
username: values.username,
position: values.position,
gender: values.gender,
national_id: values.national_id,
phone_number: values.phone_number,
province_id: values.province_id,
role_id: values.roles[0].id,
};
const provinceSelectOptions = useMemo(() => {
if (loadingProvinces) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorProvinces) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "انتخاب استان" },
...provinces.map((province) => ({
value: province.id,
label: province.name,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
const roleSelectOptions = useMemo(() => {
if (loadingRoles) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorRoles) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "انتخاب نقش" },
...roles.map((role) => ({
value: role.id,
label: role.name_fa,
})),
];
}, [roles, errorRoles, loadingRoles]);
const {
control,
handleSubmit,
formState: { isSubmitting, errors },
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
const onSubmit = async (data) => {
const formData = new FormData();
Object.keys(data).forEach((key) => {
formData.append(key, data[key]);
});
try {
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
data: formData,
});
setOpen(false);
mutate();
} catch (error) {}
};
return (
<>
<DialogContent dividers>
<Divider sx={{ mb: 3 }}>
<Chip color="primary" variant="outlined" label="کاربر جدید" />
</Divider>
<StyledForm
id="UpdateUserForm"
onSubmit={handleSubmit(onSubmit)}
style={{ width: "100%", height: "100%" }}
>
<Stack spacing={2}>
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="نام کاربری"
variant="outlined"
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"username"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<TextField
{...field}
label="نام و نام خانوادگی"
variant="outlined"
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"full_name"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<TextField
{...field}
label="سمت"
variant="outlined"
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"position"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="شماره همراه"
variant="outlined"
fullWidth
type="tel"
onChange={(e) => {
const inputValue = event.target.value;
if (isNaN(Number(inputValue))) {
return;
}
if (inputValue.length > 11) {
return;
}
field.onChange(inputValue);
}}
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"phone_number"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<FormControl fullWidth error={!!error}>
<InputLabel id={`label-phone-number`} shrink>
جنسیت
</InputLabel>
<Select
labelId={`label-phone-number`}
id={"phone-number"}
name={`gender`}
value={field.value}
input={<OutlinedInput label={"جنسیت"} />}
onChange={(e) => field.onChange(e.target.value)}
displayEmpty
>
<MenuItem value={"male"}>مرد</MenuItem>
<MenuItem value={"female"}>زن</MenuItem>
</Select>
<FormHelperText error={!!error} sx={{ mt: 1 }}>
{!!error && error.message}
</FormHelperText>
</FormControl>
);
}}
name={"gender"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="شماره ملی"
variant="outlined"
fullWidth
type="tel"
onChange={(e) => {
const inputValue = event.target.value;
if (isNaN(Number(inputValue))) {
return;
}
if (inputValue.length > 10) {
return;
}
field.onChange(inputValue);
}}
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"national_id"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<FormControl fullWidth error={!!error}>
<InputLabel id={`label-province`} shrink>
استان
</InputLabel>
<Select
labelId={`label-province`}
id={"province"}
name={`province_id`}
value={loadingProvinces ? "loading" : field.value}
input={<OutlinedInput label={"استان"} />}
onChange={(e) => field.onChange(e.target.value)}
displayEmpty
>
{provinceSelectOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText error={!!error} sx={{ mt: 1 }}>
{!!error && error.message}
</FormHelperText>
</FormControl>
);
}}
name={"province_id"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<FormControl fullWidth error={!!error}>
<InputLabel id={`label-role`} shrink>
نقش
</InputLabel>
<Select
labelId={`label-role`}
id={"role"}
name={`role_id`}
value={loadingRoles ? "loading" : field.value}
input={<OutlinedInput label={"نقش"} />}
onChange={(e) => field.onChange(e.target.value)}
displayEmpty
>
{roleSelectOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText error={!!error} sx={{ mt: 1 }}>
{!!error && error.message}
</FormHelperText>
</FormControl>
);
}}
name={"role_id"}
/>
</Grid>
</Grid>
</Stack>
</StyledForm>
</DialogContent>
<DialogActions>
<Button
disabled={isSubmitting}
variant="outlined"
color="secondary"
size="large"
onClick={() => setOpen(false)}
>
بستن
</Button>
<Button
type="submit"
disabled={isSubmitting}
form="UpdateUserForm"
size="large"
autoFocus
variant="contained"
>
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
</Button>
</DialogActions>
</>
);
};
export default UpdateUserForm;

View File

@@ -0,0 +1,32 @@
"use client";
import { Edit } from "@mui/icons-material";
import { Dialog, IconButton, Tooltip } from "@mui/material";
import { useState } from "react";
import UpdateUserForm from "./Form";
const UpdateUser = ({ values, mutate }) => {
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
return (
<>
<Tooltip title="ویرایش">
<IconButton onClick={handleOpen}>
<Edit />
</IconButton>
</Tooltip>
<Dialog maxWidth="sm" fullWidth={true} open={open}>
<UpdateUserForm
open={open}
setOpen={setOpen}
mutate={mutate}
values={values}
/>
</Dialog>
</>
);
};
export default UpdateUser;

View File

@@ -0,0 +1,17 @@
import { useSocket } from "@/lib/contexts/socket";
import { Power, PowerOff } from "@mui/icons-material";
import { Stack } from "@mui/material";
const OnlineCell = ({ user_id }) => {
const { clientsOnline } = useSocket();
return (
<Stack alignItems={"center"}>
{clientsOnline.some((co) => co.user_id == user_id) ? (
<Power color="success" />
) : (
<PowerOff color="error" />
)}
</Stack>
);
};
export default OnlineCell;

View File

@@ -0,0 +1,11 @@
import { useSocket } from "@/lib/contexts/socket";
import { Stack } from "@mui/material";
const TelephoneId = ({ user_id }) => {
const { clientsOnline } = useSocket();
const telephone_id = clientsOnline.find(
(co) => co.user_id == user_id,
)?.telephone_id;
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
};
export default TelephoneId;

View File

@@ -0,0 +1,12 @@
import { Box } from "@mui/material";
import Delete from "./Delete";
import UpdateUser from "./Edit";
const RowActions = ({ row, mutate }) => {
return (
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
<Delete rowId={row.original.id} mutate={mutate} />
<UpdateUser values={row.original} mutate={mutate} />
</Box>
);
};
export default RowActions;

View File

@@ -0,0 +1,416 @@
import LtrTextField from "@/core/components/LtrTextField";
import StyledForm from "@/core/components/StyledForm";
import validateNationalCode from "@/core/utils/nationalCodeValidation";
import useProvinces from "@/lib/hooks/useProvince";
import { yupResolver } from "@hookform/resolvers/yup";
import {
Button,
Chip,
DialogActions,
DialogContent,
Divider,
FormControl,
FormHelperText,
Grid,
IconButton,
InputAdornment,
InputLabel,
MenuItem,
OutlinedInput,
Select,
Stack,
TextField,
} from "@mui/material";
import { Controller, useForm } from "react-hook-form";
import { object, string } from "yup";
import { useMemo, useState } from "react";
import useRoles from "@/lib/hooks/useRoles";
import useRequest from "@/lib/hooks/useRequest";
import { CREATE_USER } from "@/core/utils/routes";
import { Visibility, VisibilityOff } from "@mui/icons-material";
const defaultValues = {
full_name: "",
username: "",
position: "",
gender: "male",
national_id: "",
phone_number: "",
province_id: "",
password: "",
role_id: "",
};
const validationSchema = object().shape({
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
username: string().required("نام کاربری خود را وارد کنید"),
phone_number: string()
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
.required("شماره همراه خود را وارد کنید"),
gender: string().required("جنسیت خود را وارد کنید"),
national_id: string()
.test(
"max",
"کد ملی باید شامل 10 رقم باشد",
(value) => value.toString().length === 10,
)
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
validateNationalCode(value),
)
.required("کد ملی خود را وارد کنید"),
password: string()
.required("رمز عبور خود را وارد کنید")
.matches(
/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
"رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد",
),
position: string().required("سمت خود را وارد کنید"),
province_id: string().required("استان خود را وارد کنید"),
role_id: string().required("نقش خود را وارد کنید"),
});
const CreateUserForm = ({ setOpen, mutate }) => {
const requestServer = useRequest({ notificationSuccess: true });
const [showPassword, setShowPassword] = useState(false);
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
const { roles, errorRoles, loadingRoles } = useRoles();
const handleClickShowPassword = () => setShowPassword((show) => !show);
const provinceSelectOptions = useMemo(() => {
if (loadingProvinces) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorProvinces) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "انتخاب استان" },
...provinces.map((province) => ({
value: province.id,
label: province.name,
})),
];
}, [provinces, errorProvinces, loadingProvinces]);
const roleSelectOptions = useMemo(() => {
if (loadingRoles) {
return [{ value: "loading", label: "در حال بارگذاری..." }];
}
if (errorRoles) {
return [{ value: "error", label: "خطا در بارگذاری" }];
}
return [
{ value: "", label: "انتخاب نقش" },
...roles.map((role) => ({
value: role.id,
label: role.name_fa,
})),
];
}, [roles, errorRoles, loadingRoles]);
const {
control,
handleSubmit,
formState: { isSubmitting, errors },
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
const onSubmit = async (data) => {
const formData = new FormData();
Object.keys(data).forEach((key) => {
formData.append(key, data[key]);
});
try {
await requestServer(CREATE_USER, "post", {
data: formData,
});
setOpen(false);
mutate();
} catch (error) {}
};
return (
<>
<DialogContent dividers>
<Divider sx={{ mb: 3 }}>
<Chip color="primary" variant="outlined" label="کاربر جدید" />
</Divider>
<StyledForm
id="createUserForm"
onSubmit={handleSubmit(onSubmit)}
style={{ width: "100%", height: "100%" }}
>
<Stack spacing={2}>
<Grid container spacing={2}>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="نام کاربری"
variant="outlined"
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"username"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="رمز عبور"
variant="outlined"
type={showPassword ? "text" : "password"}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">
<IconButton onClick={handleClickShowPassword}>
{showPassword ? (
<VisibilityOff />
) : (
<Visibility />
)}
</IconButton>
</InputAdornment>
),
},
}}
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"password"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<TextField
{...field}
label="نام و نام خانوادگی"
variant="outlined"
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"full_name"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<TextField
{...field}
label="سمت"
variant="outlined"
fullWidth
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"position"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="شماره همراه"
variant="outlined"
fullWidth
type="tel"
onChange={(e) => {
const inputValue = event.target.value;
if (isNaN(Number(inputValue))) {
return;
}
if (inputValue.length > 11) {
return;
}
field.onChange(inputValue);
}}
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"phone_number"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<FormControl fullWidth error={!!error}>
<InputLabel id={`label-phone-number`} shrink>
جنسیت
</InputLabel>
<Select
labelId={`label-phone-number`}
id={"phone-number"}
name={`gender`}
value={field.value}
input={<OutlinedInput label={"جنسیت"} />}
onChange={(e) => field.onChange(e.target.value)}
displayEmpty
>
<MenuItem value={"male"}>مرد</MenuItem>
<MenuItem value={"female"}>زن</MenuItem>
</Select>
<FormHelperText error={!!error} sx={{ mt: 1 }}>
{!!error && error.message}
</FormHelperText>
</FormControl>
);
}}
name={"gender"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<LtrTextField
{...field}
label="شماره ملی"
variant="outlined"
fullWidth
type="tel"
onChange={(e) => {
const inputValue = event.target.value;
if (isNaN(Number(inputValue))) {
return;
}
if (inputValue.length > 10) {
return;
}
field.onChange(inputValue);
}}
error={!!error}
helperText={!!error && error.message}
/>
);
}}
name={"national_id"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<FormControl fullWidth error={!!error}>
<InputLabel id={`label-province`} shrink>
استان
</InputLabel>
<Select
labelId={`label-province`}
id={"province"}
name={`province_id`}
value={field.value}
input={<OutlinedInput label={"استان"} />}
onChange={(e) => field.onChange(e.target.value)}
displayEmpty
>
{provinceSelectOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText error={!!error} sx={{ mt: 1 }}>
{!!error && error.message}
</FormHelperText>
</FormControl>
);
}}
name={"province_id"}
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Controller
control={control}
render={({ field, fieldState: { error } }) => {
return (
<FormControl fullWidth error={!!error}>
<InputLabel id={`label-role`} shrink>
نقش
</InputLabel>
<Select
labelId={`label-role`}
id={"role"}
name={`role_id`}
value={field.value}
input={<OutlinedInput label={"نقش"} />}
onChange={(e) => field.onChange(e.target.value)}
displayEmpty
>
{roleSelectOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText error={!!error} sx={{ mt: 1 }}>
{!!error && error.message}
</FormHelperText>
</FormControl>
);
}}
name={"role_id"}
/>
</Grid>
</Grid>
</Stack>
</StyledForm>
</DialogContent>
<DialogActions>
<Button
disabled={isSubmitting}
variant="outlined"
color="secondary"
size="large"
onClick={() => setOpen(false)}
>
بستن
</Button>
<Button
type="submit"
disabled={isSubmitting}
form="createUserForm"
size="large"
autoFocus
variant="contained"
>
{isSubmitting ? "در حال ثبت کاربر جدید..." : "ثبت کاربر جدید"}
</Button>
</DialogActions>
</>
);
};
export default CreateUserForm;

View File

@@ -0,0 +1,49 @@
"use client";
import { AddCircle } from "@mui/icons-material";
import {
Button,
Dialog,
IconButton,
useMediaQuery,
useTheme,
} from "@mui/material";
import CreateUserForm from "./Form";
import { useState } from "react";
const CreateUser = ({ 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
size={"small"}
variant="contained"
color="primary"
startIcon={<AddCircle />}
onClick={handleOpen}
>
کاربر جدید
</Button>
)}
<Dialog maxWidth="sm" fullWidth={true} open={open}>
<CreateUserForm open={open} setOpen={setOpen} mutate={mutate} />
</Dialog>
</>
);
};
export default CreateUser;

View File

@@ -0,0 +1,10 @@
import CreateUser from "./Create";
const Toolbar = ({ mutate }) => {
return (
<>
<CreateUser mutate={mutate} />
</>
);
};
export default Toolbar;

View File

@@ -0,0 +1,14 @@
import PageTitle from "@/core/components/PageTitle";
import { Stack } from "@mui/material";
import DataTable from "./DataTable";
const UsersPage = () => {
return (
<Stack spacing={1}>
<PageTitle title={"کاربران"} />
<DataTable />
</Stack>
);
};
export default UsersPage;

View File

@@ -33,7 +33,7 @@ const CallHistory = ({ tab }) => {
<ErrorOrEmpty isErrorOrEmpty="empty" />
) : (
<Box>
<ListItemOfCalls key={'first'} historyItem={firstItemOfHistory} />
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
{historyList && (
<Divider>
<Chip