Feature/user management
This commit is contained in:
205
src/components/dashboard/Users/DataTable.jsx
Normal file
205
src/components/dashboard/Users/DataTable.jsx
Normal 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;
|
||||
@@ -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;
|
||||
37
src/components/dashboard/Users/RowActions/Delete/index.jsx
Normal file
37
src/components/dashboard/Users/RowActions/Delete/index.jsx
Normal 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;
|
||||
369
src/components/dashboard/Users/RowActions/Edit/Form/index.jsx
Normal file
369
src/components/dashboard/Users/RowActions/Edit/Form/index.jsx
Normal 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;
|
||||
32
src/components/dashboard/Users/RowActions/Edit/index.jsx
Normal file
32
src/components/dashboard/Users/RowActions/Edit/index.jsx
Normal 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;
|
||||
17
src/components/dashboard/Users/RowActions/Online/index.jsx
Normal file
17
src/components/dashboard/Users/RowActions/Online/index.jsx
Normal 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;
|
||||
@@ -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;
|
||||
12
src/components/dashboard/Users/RowActions/index.jsx
Normal file
12
src/components/dashboard/Users/RowActions/index.jsx
Normal 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;
|
||||
416
src/components/dashboard/Users/Toolbar/Create/Form/index.jsx
Normal file
416
src/components/dashboard/Users/Toolbar/Create/Form/index.jsx
Normal 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;
|
||||
49
src/components/dashboard/Users/Toolbar/Create/index.jsx
Normal file
49
src/components/dashboard/Users/Toolbar/Create/index.jsx
Normal 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;
|
||||
10
src/components/dashboard/Users/Toolbar/index.jsx
Normal file
10
src/components/dashboard/Users/Toolbar/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import CreateUser from "./Create";
|
||||
|
||||
const Toolbar = ({ mutate }) => {
|
||||
return (
|
||||
<>
|
||||
<CreateUser mutate={mutate} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
14
src/components/dashboard/Users/index.jsx
Normal file
14
src/components/dashboard/Users/index.jsx
Normal 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;
|
||||
@@ -33,7 +33,7 @@ const CallHistory = ({ tab }) => {
|
||||
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
||||
) : (
|
||||
<Box>
|
||||
<ListItemOfCalls key={'first'} historyItem={firstItemOfHistory} />
|
||||
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
|
||||
{historyList && (
|
||||
<Divider>
|
||||
<Chip
|
||||
|
||||
Reference in New Issue
Block a user