k
This commit is contained in:
5
src/pages/AdminPage/AdminPage.module.css
Normal file
5
src/pages/AdminPage/AdminPage.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
53
src/pages/AdminPage/AdminPage.tsx
Normal file
53
src/pages/AdminPage/AdminPage.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import styles from './AdminPage.module.css';
|
||||
import {Tabs} from "@mantine/core";
|
||||
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
|
||||
import {IconBriefcase, IconUser, IconUsersGroup} from "@tabler/icons-react";
|
||||
import RolesAndPositionsTab from "./tabs/RolesAndPositions/RolesAndPositionsTab.tsx";
|
||||
import UsersTab from "./tabs/Users/UsersTab.tsx";
|
||||
import {motion} from "framer-motion";
|
||||
|
||||
const AdminPage = () => {
|
||||
|
||||
return (
|
||||
<div className={styles['container']}>
|
||||
<PageBlock fullHeight>
|
||||
<Tabs variant={"outline"} keepMounted={false} defaultValue={"users"}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value={"users"} leftSection={<IconUser/>}>
|
||||
Пользователи
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value={"rolesAndPositions"} leftSection={<IconBriefcase/>}>
|
||||
Роли и должности
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab value={"employees"} leftSection={<IconUsersGroup/>}>
|
||||
Сотрудники
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"users"}>
|
||||
<motion.div
|
||||
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}>
|
||||
<UsersTab/>
|
||||
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"rolesAndPositions"}>
|
||||
<motion.div
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}
|
||||
>
|
||||
<RolesAndPositionsTab/>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
|
||||
</Tabs>
|
||||
</PageBlock>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdminPage;
|
||||
@@ -0,0 +1,19 @@
|
||||
import ObjectSelect, {ObjectSelectProps} from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import {PositionSchema} from "../../../../client";
|
||||
import {FC} from "react";
|
||||
import usePositionsList from "../../hooks/usePositionsList.tsx";
|
||||
|
||||
type Props = Omit<ObjectSelectProps<PositionSchema>, 'data' | 'getLabelFn' | 'getValueFn'>;
|
||||
|
||||
const PositionSelect: FC<Props> = (props) => {
|
||||
const {objects: positions} = usePositionsList();
|
||||
return (
|
||||
<ObjectSelect
|
||||
getLabelFn={(position) => position.name}
|
||||
getValueFn={(position) => position.key}
|
||||
data={positions}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default PositionSelect;
|
||||
@@ -0,0 +1,41 @@
|
||||
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
|
||||
import {PositionSchema} from "../../../../client";
|
||||
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {usePositionsTableColumns} from "./columns.tsx";
|
||||
import {FC} from "react";
|
||||
import {Button, Flex, rem} from "@mantine/core";
|
||||
|
||||
type Props = CRUDTableProps<PositionSchema>;
|
||||
|
||||
const PositionsTable: FC<Props> = ({items}) => {
|
||||
const columns = usePositionsTableColumns();
|
||||
return (
|
||||
|
||||
<BaseTable
|
||||
restProps={{
|
||||
enableTopToolbar: true,
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
renderTopToolbar: () => (
|
||||
<Flex p={rem(10)}>
|
||||
|
||||
<Button
|
||||
variant={"default"}
|
||||
onClick={() => {
|
||||
|
||||
}}
|
||||
>
|
||||
Создать должность
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
)
|
||||
}}
|
||||
data={items}
|
||||
columns={columns}
|
||||
/>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default PositionsTable;
|
||||
16
src/pages/AdminPage/components/PositionsTable/columns.tsx
Normal file
16
src/pages/AdminPage/components/PositionsTable/columns.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import {useMemo} from "react";
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {PositionSchema} from "../../../../client";
|
||||
|
||||
export const usePositionsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<PositionSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "key",
|
||||
header: "Ключ"
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Название должности"
|
||||
}
|
||||
], []);
|
||||
}
|
||||
19
src/pages/AdminPage/components/RoleSelect/RoleSelect.tsx
Normal file
19
src/pages/AdminPage/components/RoleSelect/RoleSelect.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import ObjectSelect, {ObjectSelectProps} from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import {RoleSchema} from "../../../../client";
|
||||
import {FC} from "react";
|
||||
import useRolesList from "../../hooks/useRolesList.tsx";
|
||||
|
||||
type Props = Omit<ObjectSelectProps<RoleSchema>, 'data' | 'getLabelFn' | 'getValueFn'>;
|
||||
|
||||
const RolesSelect: FC<Props> = (props) => {
|
||||
const {objects: roles} = useRolesList();
|
||||
return (
|
||||
<ObjectSelect
|
||||
getLabelFn={(position) => position.name}
|
||||
getValueFn={(position) => position.key}
|
||||
data={roles}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
export default RolesSelect;
|
||||
81
src/pages/AdminPage/components/UsersTable/UsersTable.tsx
Normal file
81
src/pages/AdminPage/components/UsersTable/UsersTable.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import {CRUDTableProps} from "../../../../types/CRUDTable.tsx";
|
||||
import {UserSchema} from "../../../../client";
|
||||
import {BaseTable} from "../../../../components/BaseTable/BaseTable.tsx";
|
||||
import {FC} from "react";
|
||||
import {ActionIcon, Flex, Text, Tooltip} from "@mantine/core";
|
||||
import {useUsersTableColumns} from "./columns.tsx";
|
||||
import {IconEdit, IconTrash} from "@tabler/icons-react";
|
||||
import {modals} from "@mantine/modals";
|
||||
import {MRT_TableOptions} from "mantine-react-table";
|
||||
|
||||
type Props = CRUDTableProps<UserSchema>;
|
||||
|
||||
const UsersTable: FC<Props> = ({items, onChange, onDelete}) => {
|
||||
const columns = useUsersTableColumns();
|
||||
const onEditClick = (user: UserSchema) => {
|
||||
if (!onChange) return;
|
||||
modals.openContextModal({
|
||||
modal: "userFormModal",
|
||||
title: 'Редактирование пользователя',
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
onChange: onChange,
|
||||
element: user,
|
||||
},
|
||||
size: "md"
|
||||
})
|
||||
}
|
||||
const onDeleteClick = (user: UserSchema) => {
|
||||
if (!onDelete) return;
|
||||
modals.openConfirmModal({
|
||||
title: 'Удаление пользователя',
|
||||
centered: true,
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить пользователя {user.firstName} {user.secondName}
|
||||
</Text>
|
||||
),
|
||||
labels: {confirm: 'Да', cancel: "Нет"},
|
||||
confirmProps: {color: 'red'},
|
||||
onConfirm: () => onDelete(user)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseTable
|
||||
data={items}
|
||||
columns={columns}
|
||||
restProps={{
|
||||
enableTopToolbar: false,
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableRowActions: true,
|
||||
renderRowActions: ({row}) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip onClick={() => {
|
||||
onDeleteClick(row.original);
|
||||
}} label="Удалить">
|
||||
<ActionIcon variant={"default"}>
|
||||
<IconTrash/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
onClick={() => {
|
||||
onEditClick(row.original)
|
||||
}}
|
||||
label="Редактировать">
|
||||
<ActionIcon
|
||||
variant={"default"}>
|
||||
<IconEdit/>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<UserSchema>}
|
||||
|
||||
/>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersTable;
|
||||
40
src/pages/AdminPage/components/UsersTable/columns.tsx
Normal file
40
src/pages/AdminPage/components/UsersTable/columns.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import {useMemo} from "react";
|
||||
import {MRT_ColumnDef} from "mantine-react-table";
|
||||
import {UserSchema} from "../../../../client";
|
||||
import {IconCheck, IconX} from "@tabler/icons-react";
|
||||
|
||||
export const useUsersTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<UserSchema>[]>(() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "ID"
|
||||
},
|
||||
{
|
||||
accessorKey: "telegramId",
|
||||
header: "ID Телеграм"
|
||||
},
|
||||
{
|
||||
accessorKey: "phoneNumber",
|
||||
header: "Номер телефона"
|
||||
},
|
||||
{
|
||||
accessorKey: "role.name",
|
||||
header: "Роль"
|
||||
},
|
||||
{
|
||||
accessorKey: "comment",
|
||||
header: "Дополнительная информация"
|
||||
},
|
||||
{
|
||||
accessorKey: "isAdmin",
|
||||
header: "Администратор",
|
||||
Cell: ({row}) => row.original.isAdmin ? <IconCheck/> : <IconX/>
|
||||
},
|
||||
{
|
||||
accessorKey: "isBlocked",
|
||||
header: "Заблокирован",
|
||||
Cell: ({row}) => row.original.isBlocked ? <IconCheck/> : <IconX/>
|
||||
},
|
||||
], []);
|
||||
}
|
||||
|
||||
9
src/pages/AdminPage/hooks/usePositionsList.tsx
Normal file
9
src/pages/AdminPage/hooks/usePositionsList.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import {PositionService} from "../../../client";
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
|
||||
const usePositionsList = () => ObjectList({
|
||||
queryFn: PositionService.getAllPositions,
|
||||
getObjectsFn: response => response.positions,
|
||||
queryKey: "getAllPositions"
|
||||
})
|
||||
export default usePositionsList;
|
||||
9
src/pages/AdminPage/hooks/useRolesList.tsx
Normal file
9
src/pages/AdminPage/hooks/useRolesList.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
import {RoleService} from "../../../client";
|
||||
|
||||
const useRolesList = () => ObjectList({
|
||||
queryFn: RoleService.getAllRoles,
|
||||
getObjectsFn: response => response.roles,
|
||||
queryKey: "getAllRoles"
|
||||
})
|
||||
export default useRolesList;
|
||||
11
src/pages/AdminPage/hooks/useUsersList.tsx
Normal file
11
src/pages/AdminPage/hooks/useUsersList.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import ObjectList from "../../../hooks/objectList.tsx";
|
||||
import {UserService} from "../../../client";
|
||||
|
||||
|
||||
const useUsersList = () => ObjectList({
|
||||
queryFn: UserService.getAllUsers,
|
||||
getObjectsFn: (response) => response.users,
|
||||
queryKey: "getAllUsers"
|
||||
});
|
||||
|
||||
export default useUsersList;
|
||||
108
src/pages/AdminPage/modals/UserFormModal/UserFormModal.tsx
Normal file
108
src/pages/AdminPage/modals/UserFormModal/UserFormModal.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import {ContextModalProps} from "@mantine/modals";
|
||||
import BaseFormModal, {EditProps} from "../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import {UserSchema} from "../../../../client";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {Checkbox, Fieldset, Input, Stack, Textarea, TextInput} from "@mantine/core";
|
||||
import RoleSelect from "../../components/RoleSelect/RoleSelect.tsx";
|
||||
import PositionSelect from "../../components/PositionSelect/PositionSelect.tsx";
|
||||
import {UserRoleEnum} from "../../../../shared/enums/UserRole.ts";
|
||||
import {capitalize} from "lodash";
|
||||
import {IMaskInput} from "react-imask";
|
||||
import phone from "phone";
|
||||
|
||||
type Props = EditProps<UserSchema>;
|
||||
const UserFormModal = ({context, id, innerProps}: ContextModalProps<Props>) => {
|
||||
const initialValues = innerProps.element;
|
||||
|
||||
const form = useForm<UserSchema>({
|
||||
initialValues: initialValues,
|
||||
validate: {
|
||||
firstName: value => !value.trim() && "Укажите имя пользователя",
|
||||
secondName: value => !value.trim() && "Укажите фамилию",
|
||||
position: (value, values) => ((values.role.key === UserRoleEnum.EMPLOYEE) && (!value)) && 'Необходимо указать должность сотрудника',
|
||||
phoneNumber: value => !phone(value || '', {
|
||||
country: "",
|
||||
strictDetection: false,
|
||||
validateMobilePrefix: false
|
||||
}).isValid && 'Неверно указан номер телефона',
|
||||
}
|
||||
});
|
||||
console.log(form.getInputProps('isAdmin'))
|
||||
return (<BaseFormModal
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={() => context.closeContextModal(id)}
|
||||
{...innerProps}
|
||||
>
|
||||
<BaseFormModal.Body>
|
||||
<>
|
||||
<Fieldset legend={"Общая информация"}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label={"Имя"}
|
||||
placeholder={"Введите имя пользователя"}
|
||||
{...form.getInputProps("firstName")}
|
||||
onChange={event => form.getInputProps('firstName').onChange(capitalize(event.target.value).trim())}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
{...form.getInputProps("secondName")}
|
||||
label={"Фамилия"}
|
||||
placeholder={"Введите фамилию пользователя"}
|
||||
onChange={event => form.getInputProps('secondName').onChange(capitalize(event.target.value).trim())}
|
||||
/>
|
||||
<Input.Wrapper
|
||||
label={"Номер телефона"}
|
||||
error={form.getInputProps("phoneNumber").error}
|
||||
>
|
||||
<Input
|
||||
component={IMaskInput}
|
||||
mask="+7 000 000-00-00"
|
||||
placeholder={"Введите номер телефона"}
|
||||
{...form.getInputProps("phoneNumber")}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Роль и должность"}>
|
||||
<Stack>
|
||||
|
||||
<RoleSelect
|
||||
label={"Роль пользователя"}
|
||||
placeholder={"Выберите роль пользователя"}
|
||||
{...form.getInputProps('role')}
|
||||
/>
|
||||
{form.values.role.key === UserRoleEnum.EMPLOYEE &&
|
||||
<PositionSelect
|
||||
label={"Должность сотрудника"}
|
||||
placeholder={"Выберите должность сотрудника"}
|
||||
{...form.getInputProps('position')}
|
||||
/>
|
||||
}
|
||||
</Stack>
|
||||
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Дополнительные параметры"}>
|
||||
<Stack>
|
||||
<Checkbox
|
||||
label={"Права администратора"}
|
||||
|
||||
{...form.getInputProps('isAdmin', {type: "checkbox"})}
|
||||
/>
|
||||
<Checkbox
|
||||
label={"Заблокирован"}
|
||||
{...form.getInputProps('isBlocked', {type: "checkbox"})}
|
||||
/>
|
||||
<Textarea
|
||||
label={"Дополнительная информация"}
|
||||
{...form.getInputProps('comment')}
|
||||
/>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
</>
|
||||
</BaseFormModal.Body>
|
||||
</BaseFormModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserFormModal;
|
||||
@@ -0,0 +1,39 @@
|
||||
import {Tabs} from "@mantine/core";
|
||||
import PositionsTable from "../../components/PositionsTable/PositionsTable.tsx";
|
||||
import usePositionsList from "../../hooks/usePositionsList.tsx";
|
||||
import {motion} from "framer-motion";
|
||||
|
||||
const RolesAndPositionsTab = () => {
|
||||
const {objects: positions} = usePositionsList();
|
||||
return (
|
||||
<Tabs w={"100%"}
|
||||
variant={"default"}
|
||||
keepMounted={false}
|
||||
defaultValue={"roles"}
|
||||
>
|
||||
<Tabs.List grow>
|
||||
<Tabs.Tab value={"roles"}>Роли</Tabs.Tab>
|
||||
<Tabs.Tab value={"positions"}>Должности</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value={"roles"}>
|
||||
<motion.div
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}
|
||||
>
|
||||
<PositionsTable items={positions}/>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value={"positions"}>
|
||||
<motion.div
|
||||
initial={{scaleY: 0}}
|
||||
animate={{scaleY: 1}}
|
||||
transition={{duration: 0.1}}
|
||||
>
|
||||
<PositionsTable items={positions}/>
|
||||
</motion.div>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
export default RolesAndPositionsTab;
|
||||
35
src/pages/AdminPage/tabs/Users/UsersTab.tsx
Normal file
35
src/pages/AdminPage/tabs/Users/UsersTab.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import useUsersList from "../../hooks/useUsersList.tsx";
|
||||
import UsersTable from "../../components/UsersTable/UsersTable.tsx";
|
||||
import {UserSchema, UserService} from "../../../../client";
|
||||
import {notifications} from "../../../../shared/lib/notifications.ts";
|
||||
|
||||
const UsersTab = () => {
|
||||
const {objects: users, refetch} = useUsersList();
|
||||
|
||||
const onChange = (user: UserSchema) => {
|
||||
UserService.updateUser({
|
||||
requestBody: {
|
||||
data: {
|
||||
...user,
|
||||
positionKey: user.position?.key,
|
||||
}
|
||||
}
|
||||
}).then(async ({ok, message}) => {
|
||||
notifications.guess(ok, {message});
|
||||
if (!ok) return;
|
||||
await refetch();
|
||||
})
|
||||
}
|
||||
const onDelete = async (user: UserSchema) => {
|
||||
onChange({...user, isDeleted: true});
|
||||
}
|
||||
return (
|
||||
<UsersTable
|
||||
items={users}
|
||||
onChange={onChange}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersTab;
|
||||
Reference in New Issue
Block a user