feat: nested department sections, attaching department sections in the user editor
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mantine-contextmenu": "^7.12.2",
|
"mantine-contextmenu": "^7.12.2",
|
||||||
|
"mantine-datatable": "^7.15.1",
|
||||||
"mantine-form-zod-resolver": "^1.1.0",
|
"mantine-form-zod-resolver": "^1.1.0",
|
||||||
"mantine-react-table": "^2.0.0-beta.5",
|
"mantine-react-table": "^2.0.0-beta.5",
|
||||||
"phone": "^3.1.49",
|
"phone": "^3.1.49",
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ export type { DeleteUserResponse } from './models/DeleteUserResponse';
|
|||||||
export type { DepartmentBaseSchema } from './models/DepartmentBaseSchema';
|
export type { DepartmentBaseSchema } from './models/DepartmentBaseSchema';
|
||||||
export type { DepartmentSchema } from './models/DepartmentSchema';
|
export type { DepartmentSchema } from './models/DepartmentSchema';
|
||||||
export type { DepartmentSectionBaseSchema } from './models/DepartmentSectionBaseSchema';
|
export type { DepartmentSectionBaseSchema } from './models/DepartmentSectionBaseSchema';
|
||||||
|
export type { DepartmentSectionBriefSchema } from './models/DepartmentSectionBriefSchema';
|
||||||
export type { DepartmentSectionSchema } from './models/DepartmentSectionSchema';
|
export type { DepartmentSectionSchema } from './models/DepartmentSectionSchema';
|
||||||
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
|
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
|
||||||
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
|
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
|
||||||
@@ -215,6 +216,7 @@ export type { GetClientMarketplacesResponse } from './models/GetClientMarketplac
|
|||||||
export type { GetDealBillById } from './models/GetDealBillById';
|
export type { GetDealBillById } from './models/GetDealBillById';
|
||||||
export type { GetDealProductsBarcodesPdfRequest } from './models/GetDealProductsBarcodesPdfRequest';
|
export type { GetDealProductsBarcodesPdfRequest } from './models/GetDealProductsBarcodesPdfRequest';
|
||||||
export type { GetDealProductsBarcodesPdfResponse } from './models/GetDealProductsBarcodesPdfResponse';
|
export type { GetDealProductsBarcodesPdfResponse } from './models/GetDealProductsBarcodesPdfResponse';
|
||||||
|
export type { GetDepartmentSectionsResponse } from './models/GetDepartmentSectionsResponse';
|
||||||
export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
|
export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
|
||||||
export type { GetManagersResponse } from './models/GetManagersResponse';
|
export type { GetManagersResponse } from './models/GetManagersResponse';
|
||||||
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
|
||||||
@@ -350,10 +352,14 @@ export type { UpdateTransactionResponse } from './models/UpdateTransactionRespon
|
|||||||
export type { UpdateTransactionSchema } from './models/UpdateTransactionSchema';
|
export type { UpdateTransactionSchema } from './models/UpdateTransactionSchema';
|
||||||
export type { UpdateTransactionTagRequest } from './models/UpdateTransactionTagRequest';
|
export type { UpdateTransactionTagRequest } from './models/UpdateTransactionTagRequest';
|
||||||
export type { UpdateTransactionTagResponse } from './models/UpdateTransactionTagResponse';
|
export type { UpdateTransactionTagResponse } from './models/UpdateTransactionTagResponse';
|
||||||
|
export type { UpdateUserDepartmentSectionsRequest } from './models/UpdateUserDepartmentSectionsRequest';
|
||||||
|
export type { UpdateUserDepartmentSectionsResponse } from './models/UpdateUserDepartmentSectionsResponse';
|
||||||
export type { UpdateUserRequest } from './models/UpdateUserRequest';
|
export type { UpdateUserRequest } from './models/UpdateUserRequest';
|
||||||
export type { UpdateUserResponse } from './models/UpdateUserResponse';
|
export type { UpdateUserResponse } from './models/UpdateUserResponse';
|
||||||
export type { UploadPassportImageResponse } from './models/UploadPassportImageResponse';
|
export type { UploadPassportImageResponse } from './models/UploadPassportImageResponse';
|
||||||
export type { UserCreate } from './models/UserCreate';
|
export type { UserCreate } from './models/UserCreate';
|
||||||
|
export type { UserDepartmentSectionSchema } from './models/UserDepartmentSectionSchema';
|
||||||
|
export type { UserDepartmentSectionsSchema } from './models/UserDepartmentSectionsSchema';
|
||||||
export type { UserSchema } from './models/UserSchema';
|
export type { UserSchema } from './models/UserSchema';
|
||||||
export type { UserUpdate } from './models/UserUpdate';
|
export type { UserUpdate } from './models/UserUpdate';
|
||||||
export type { ValidationError } from './models/ValidationError';
|
export type { ValidationError } from './models/ValidationError';
|
||||||
|
|||||||
@@ -5,5 +5,6 @@
|
|||||||
export type AddUserRequest = {
|
export type AddUserRequest = {
|
||||||
userId: number;
|
userId: number;
|
||||||
sectionId: number;
|
sectionId: number;
|
||||||
|
isChief: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export type DepartmentSectionBaseSchema = {
|
export type DepartmentSectionBaseSchema = {
|
||||||
name: string;
|
name: string;
|
||||||
departmentId: number;
|
departmentId: (number | null);
|
||||||
|
parentDepartmentSectionId: (number | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
11
src/client/models/DepartmentSectionBriefSchema.ts
Normal file
11
src/client/models/DepartmentSectionBriefSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type DepartmentSectionBriefSchema = {
|
||||||
|
name: string;
|
||||||
|
departmentId: (number | null);
|
||||||
|
parentDepartmentSectionId: (number | null);
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -2,11 +2,13 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import type { UserSchema } from './UserSchema';
|
import type { UserDepartmentSectionSchema } from './UserDepartmentSectionSchema';
|
||||||
export type DepartmentSectionSchema = {
|
export type DepartmentSectionSchema = {
|
||||||
name: string;
|
name: string;
|
||||||
departmentId: number;
|
departmentId: (number | null);
|
||||||
|
parentDepartmentSectionId: (number | null);
|
||||||
id: number;
|
id: number;
|
||||||
users?: Array<UserSchema>;
|
users?: Array<UserDepartmentSectionSchema>;
|
||||||
|
sections?: Array<DepartmentSectionSchema>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
9
src/client/models/GetDepartmentSectionsResponse.ts
Normal file
9
src/client/models/GetDepartmentSectionsResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { DepartmentSectionBriefSchema } from './DepartmentSectionBriefSchema';
|
||||||
|
export type GetDepartmentSectionsResponse = {
|
||||||
|
departmentSections: Array<DepartmentSectionBriefSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/UpdateUserDepartmentSectionsRequest.ts
Normal file
9
src/client/models/UpdateUserDepartmentSectionsRequest.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { UserDepartmentSectionsSchema } from './UserDepartmentSectionsSchema';
|
||||||
|
export type UpdateUserDepartmentSectionsRequest = {
|
||||||
|
departmentSections: Array<UserDepartmentSectionsSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type UpdateUserDepartmentSectionsResponse = {
|
||||||
|
ok: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
10
src/client/models/UserDepartmentSectionSchema.ts
Normal file
10
src/client/models/UserDepartmentSectionSchema.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { UserSchema } from './UserSchema';
|
||||||
|
export type UserDepartmentSectionSchema = {
|
||||||
|
user: UserSchema;
|
||||||
|
isChief: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
9
src/client/models/UserDepartmentSectionsSchema.ts
Normal file
9
src/client/models/UserDepartmentSectionsSchema.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* generated using openapi-typescript-codegen -- do not edit */
|
||||||
|
/* istanbul ignore file */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type UserDepartmentSectionsSchema = {
|
||||||
|
sectionId: number;
|
||||||
|
isChief: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import type { PassportImageSchema } from './PassportImageSchema';
|
|||||||
import type { PayRateSchema } from './PayRateSchema';
|
import type { PayRateSchema } from './PayRateSchema';
|
||||||
import type { PositionSchema } from './PositionSchema';
|
import type { PositionSchema } from './PositionSchema';
|
||||||
import type { RoleSchema } from './RoleSchema';
|
import type { RoleSchema } from './RoleSchema';
|
||||||
|
import type { UserDepartmentSectionsSchema } from './UserDepartmentSectionsSchema';
|
||||||
export type UserSchema = {
|
export type UserSchema = {
|
||||||
telegramId: number;
|
telegramId: number;
|
||||||
phoneNumber?: (string | null);
|
phoneNumber?: (string | null);
|
||||||
@@ -24,5 +25,6 @@ export type UserSchema = {
|
|||||||
id: number;
|
id: number;
|
||||||
role: RoleSchema;
|
role: RoleSchema;
|
||||||
position?: (PositionSchema | null);
|
position?: (PositionSchema | null);
|
||||||
|
departmentSections?: (Array<UserDepartmentSectionsSchema> | null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type { DeleteDepartmentSectionResponse } from '../models/DeleteDepartment
|
|||||||
import type { DeleteUserRequest } from '../models/DeleteUserRequest';
|
import type { DeleteUserRequest } from '../models/DeleteUserRequest';
|
||||||
import type { DeleteUserResponse } from '../models/DeleteUserResponse';
|
import type { DeleteUserResponse } from '../models/DeleteUserResponse';
|
||||||
import type { GetAvailableUsersForDepartmentSectionResponse } from '../models/GetAvailableUsersForDepartmentSectionResponse';
|
import type { GetAvailableUsersForDepartmentSectionResponse } from '../models/GetAvailableUsersForDepartmentSectionResponse';
|
||||||
|
import type { GetDepartmentSectionsResponse } from '../models/GetDepartmentSectionsResponse';
|
||||||
import type { GetDepartmentsResponse } from '../models/GetDepartmentsResponse';
|
import type { GetDepartmentsResponse } from '../models/GetDepartmentsResponse';
|
||||||
import type { UpdateDepartmentRequest } from '../models/UpdateDepartmentRequest';
|
import type { UpdateDepartmentRequest } from '../models/UpdateDepartmentRequest';
|
||||||
import type { UpdateDepartmentResponse } from '../models/UpdateDepartmentResponse';
|
import type { UpdateDepartmentResponse } from '../models/UpdateDepartmentResponse';
|
||||||
@@ -94,6 +95,17 @@ export class DepartmentService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get Sections
|
||||||
|
* @returns GetDepartmentSectionsResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getSections(): CancelablePromise<GetDepartmentSectionsResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/department/section',
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create Section
|
* Create Section
|
||||||
* @returns CreateDepartmentSectionResponse Successful Response
|
* @returns CreateDepartmentSectionResponse Successful Response
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import type { CreateUserRequest } from '../models/CreateUserRequest';
|
|||||||
import type { CreateUserResponse } from '../models/CreateUserResponse';
|
import type { CreateUserResponse } from '../models/CreateUserResponse';
|
||||||
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
|
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
|
||||||
import type { GetManagersResponse } from '../models/GetManagersResponse';
|
import type { GetManagersResponse } from '../models/GetManagersResponse';
|
||||||
|
import type { UpdateUserDepartmentSectionsRequest } from '../models/UpdateUserDepartmentSectionsRequest';
|
||||||
|
import type { UpdateUserDepartmentSectionsResponse } from '../models/UpdateUserDepartmentSectionsResponse';
|
||||||
import type { UpdateUserRequest } from '../models/UpdateUserRequest';
|
import type { UpdateUserRequest } from '../models/UpdateUserRequest';
|
||||||
import type { UpdateUserResponse } from '../models/UpdateUserResponse';
|
import type { UpdateUserResponse } from '../models/UpdateUserResponse';
|
||||||
import type { UploadPassportImageResponse } from '../models/UploadPassportImageResponse';
|
import type { UploadPassportImageResponse } from '../models/UploadPassportImageResponse';
|
||||||
@@ -45,6 +47,31 @@ export class UserService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Update User Department Sections
|
||||||
|
* @returns UpdateUserDepartmentSectionsResponse Successful Response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static updateUserDepartmentSections({
|
||||||
|
userId,
|
||||||
|
requestBody,
|
||||||
|
}: {
|
||||||
|
userId: number,
|
||||||
|
requestBody: UpdateUserDepartmentSectionsRequest,
|
||||||
|
}): CancelablePromise<UpdateUserDepartmentSectionsResponse> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/user/update/department-sections/{user_id}',
|
||||||
|
path: {
|
||||||
|
'user_id': userId,
|
||||||
|
},
|
||||||
|
body: requestBody,
|
||||||
|
mediaType: 'application/json',
|
||||||
|
errors: {
|
||||||
|
422: `Validation Error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Create
|
* Create
|
||||||
* @returns CreateUserResponse Successful Response
|
* @returns CreateUserResponse Successful Response
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { DepartmentSectionBriefSchema, DepartmentService } from "../../../../client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Anchor, Checkbox, Group, Stack, Text } from "@mantine/core";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
userSectionIsChiefMap: Map<number, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserDepartmentInput = ({ userSectionIsChiefMap }: Props) => {
|
||||||
|
const [allDepartmentSections, setAllDepartmentSections] = useState<DepartmentSectionBriefSchema[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
DepartmentService.getSections()
|
||||||
|
.then(res => {
|
||||||
|
setAllDepartmentSections(res.departmentSections);
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getUserParticipationRole = (sectionId: number) => {
|
||||||
|
if (!userSectionIsChiefMap.has(sectionId)) return;
|
||||||
|
if (userSectionIsChiefMap.get(sectionId)) return "Руководитель";
|
||||||
|
return "Сотрудник";
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleUserParticipationRole = (sectionId: number) => {
|
||||||
|
if (!userSectionIsChiefMap.has(sectionId)) return;
|
||||||
|
userSectionIsChiefMap.set(sectionId, !userSectionIsChiefMap.get(sectionId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSectionChoice = (sectionId: number) => {
|
||||||
|
if (!userSectionIsChiefMap.has(sectionId)) {
|
||||||
|
userSectionIsChiefMap.set(sectionId, false);
|
||||||
|
} else {
|
||||||
|
userSectionIsChiefMap.delete(sectionId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const userParticipationRoleButton = (sectionId: number) => {
|
||||||
|
const role = getUserParticipationRole(sectionId);
|
||||||
|
if (!role) return;
|
||||||
|
return (
|
||||||
|
<Anchor onClick={() => toggleUserParticipationRole(sectionId)}>
|
||||||
|
<Text size={"sm"}>{role}</Text>
|
||||||
|
</Anchor>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const departmentSectionChoice = (section: DepartmentSectionBriefSchema) => {
|
||||||
|
return (
|
||||||
|
<Group
|
||||||
|
key={section.id}
|
||||||
|
wrap={"nowrap"}
|
||||||
|
justify={"space-between"}
|
||||||
|
gap={0}
|
||||||
|
>
|
||||||
|
<Group wrap={"nowrap"} w={"70%"}>
|
||||||
|
<Checkbox
|
||||||
|
variant={"default"}
|
||||||
|
checked={userSectionIsChiefMap.has(section.id)}
|
||||||
|
onChange={() => onSectionChoice(section.id)}
|
||||||
|
/>
|
||||||
|
<Text size={"sm"}>{section.name}</Text>
|
||||||
|
</Group>
|
||||||
|
{userParticipationRoleButton(section.id)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
{allDepartmentSections.map((section) => departmentSectionChoice(section))}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserDepartmentInput;
|
||||||
@@ -1,26 +1,37 @@
|
|||||||
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
|
|
||||||
import { UserSchema } from "../../../../client";
|
import { UserSchema } from "../../../../client";
|
||||||
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
|
||||||
import { FC } from "react";
|
|
||||||
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
|
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
|
||||||
import { useUsersTableColumns } from "./columns.tsx";
|
import { useUsersTableColumns } from "./columns.tsx";
|
||||||
import { IconEdit, IconQrcode, IconTrash } from "@tabler/icons-react";
|
import { IconEdit, IconQrcode, IconTrash } from "@tabler/icons-react";
|
||||||
import { modals } from "@mantine/modals";
|
import { modals } from "@mantine/modals";
|
||||||
import { MRT_TableOptions } from "mantine-react-table";
|
import { MRT_TableOptions } from "mantine-react-table";
|
||||||
|
import { useUsersTabContext } from "../../tabs/Users/contexts/UsersTabContext.tsx";
|
||||||
|
|
||||||
type Props = CRUDTableProps<UserSchema>;
|
|
||||||
|
|
||||||
const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
const UsersTable = () => {
|
||||||
const columns = useUsersTableColumns();
|
const columns = useUsersTableColumns();
|
||||||
|
|
||||||
|
const {
|
||||||
|
usersCrud: {
|
||||||
|
items,
|
||||||
|
onChange,
|
||||||
|
onCreate,
|
||||||
|
onDelete,
|
||||||
|
},
|
||||||
|
refetch,
|
||||||
|
} = useUsersTabContext();
|
||||||
|
|
||||||
const onEditClick = (user: UserSchema) => {
|
const onEditClick = (user: UserSchema) => {
|
||||||
if (!onChange) return;
|
if (!onChange) return;
|
||||||
|
console.log(user);
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: "userFormModal",
|
modal: "userFormModal",
|
||||||
title: "Редактирование пользователя",
|
title: "Редактирование пользователя",
|
||||||
withCloseButton: false,
|
withCloseButton: false,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
onChange: onChange,
|
onChange,
|
||||||
element: user,
|
element: user,
|
||||||
|
refetch,
|
||||||
},
|
},
|
||||||
size: "md",
|
size: "md",
|
||||||
});
|
});
|
||||||
@@ -29,7 +40,6 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
|||||||
if (!onDelete) return;
|
if (!onDelete) return;
|
||||||
modals.openConfirmModal({
|
modals.openConfirmModal({
|
||||||
title: "Удаление пользователя",
|
title: "Удаление пользователя",
|
||||||
// centered: true,
|
|
||||||
children: (
|
children: (
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
Вы уверены что хотите удалить пользователя {user.firstName}{" "}
|
Вы уверены что хотите удалить пользователя {user.firstName}{" "}
|
||||||
@@ -48,7 +58,8 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
|
|||||||
title: "Редактирование пользователя",
|
title: "Редактирование пользователя",
|
||||||
withCloseButton: false,
|
withCloseButton: false,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
onCreate: onCreate,
|
onCreate,
|
||||||
|
refetch,
|
||||||
},
|
},
|
||||||
size: "md",
|
size: "md",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,193 +1,54 @@
|
|||||||
import { ContextModalProps } from "@mantine/modals";
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
import BaseFormModal, { CreateEditFormProps } from "../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
import { CreateEditFormProps } from "../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||||
import { UserSchema } from "../../../../client";
|
import { UserSchema } from "../../../../client";
|
||||||
import { useForm } from "@mantine/form";
|
import { Stack } from "@mantine/core";
|
||||||
import { Checkbox, Fieldset, Input, Stack, Textarea, TextInput } from "@mantine/core";
|
import CommonTab from "./tabs/CommonTab/CommonTab.tsx";
|
||||||
import RoleSelect from "../../components/RoleSelect/RoleSelect.tsx";
|
import DepartmentSectionsTab from "./tabs/DepartmentSectionsTab/DepartmentSectionsTab.tsx";
|
||||||
import PositionSelect from "../../components/PositionSelect/PositionSelect.tsx";
|
import { ModalTab, UsersModalTabSegmentedControl } from "./components/UsersModalTabSegmentedControl.tsx";
|
||||||
import { UserRoleEnum } from "../../../../shared/enums/UserRole.ts";
|
import { useState } from "react";
|
||||||
import { capitalize } from "lodash";
|
|
||||||
import { IMaskInput } from "react-imask";
|
type PropsRefetch = {
|
||||||
import phone from "phone";
|
refetch: () => void;
|
||||||
import PayRateSelect from "../../../../components/Selects/PayRateSelect/PayRateSelect.tsx";
|
}
|
||||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
type Props = CreateEditFormProps<UserSchema> & PropsRefetch;
|
||||||
import PassportImageDropzone from "../../components/PassportImageDropzone/PassportImageDropzone.tsx";
|
|
||||||
|
|
||||||
type Props = CreateEditFormProps<UserSchema>;
|
|
||||||
const UserFormModal = ({
|
const UserFormModal = ({
|
||||||
context,
|
context,
|
||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
}: ContextModalProps<Props>) => {
|
}: ContextModalProps<Props>) => {
|
||||||
const isEditing = "element" in innerProps;
|
const isEditing = "element" in innerProps;
|
||||||
const initialValues = isEditing
|
const [modalTab, setModalTab] = useState<ModalTab>(ModalTab.COMMON);
|
||||||
? innerProps.element
|
|
||||||
: {
|
const closeModal = () => {
|
||||||
isAdmin: false,
|
context.closeContextModal(id);
|
||||||
isBlocked: false,
|
}
|
||||||
isDeleted: false,
|
|
||||||
comment: "",
|
if (!isEditing) {
|
||||||
roleKey: UserRoleEnum.USER,
|
return (
|
||||||
};
|
<CommonTab closeModal={closeModal} formProps={innerProps} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const form = useForm<Partial<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 && "Неверно указан номер телефона",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<BaseFormModal
|
|
||||||
form={form}
|
|
||||||
closeOnSubmit
|
|
||||||
onClose={() => context.closeContextModal(id)}
|
|
||||||
{...innerProps}>
|
|
||||||
<BaseFormModal.Body>
|
|
||||||
<>
|
|
||||||
<Fieldset legend={"Общая информация"}>
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<TextInput
|
<UsersModalTabSegmentedControl
|
||||||
{...form.getInputProps("secondName")}
|
value={modalTab.toString()}
|
||||||
label={"Фамилия"}
|
onChange={event => setModalTab(parseInt(event))}
|
||||||
placeholder={"Введите фамилию пользователя"}
|
|
||||||
onChange={event =>
|
|
||||||
form
|
|
||||||
.getInputProps("secondName")
|
|
||||||
.onChange(
|
|
||||||
capitalize(
|
|
||||||
event.target.value,
|
|
||||||
).trim(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
{modalTab === ModalTab.COMMON ? (
|
||||||
label={"Имя"}
|
<CommonTab
|
||||||
placeholder={"Введите имя пользователя"}
|
closeModal={closeModal}
|
||||||
{...form.getInputProps("firstName")}
|
formProps={innerProps}
|
||||||
onChange={event =>
|
|
||||||
form
|
|
||||||
.getInputProps("firstName")
|
|
||||||
.onChange(
|
|
||||||
capitalize(
|
|
||||||
event.target.value,
|
|
||||||
).trim(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<TextInput
|
) : (
|
||||||
{...form.getInputProps("patronymic")}
|
<DepartmentSectionsTab
|
||||||
label={"Отчество"}
|
closeModal={closeModal}
|
||||||
placeholder={"Введите отчество пользователя"}
|
user={innerProps.element}
|
||||||
onChange={event =>
|
refetch={innerProps.refetch}
|
||||||
form
|
|
||||||
.getInputProps("patronymic")
|
|
||||||
.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>
|
|
||||||
<Input.Wrapper
|
|
||||||
label={"Серия и номер паспорта"}
|
|
||||||
error={form.getInputProps("passportData").error}>
|
|
||||||
<Input
|
|
||||||
component={IMaskInput}
|
|
||||||
mask="00 00 000000"
|
|
||||||
placeholder={"Введите серию и номер паспорта"}
|
|
||||||
{...form.getInputProps("passportData")}
|
|
||||||
/>
|
|
||||||
</Input.Wrapper>
|
|
||||||
{
|
|
||||||
isEditing && (
|
|
||||||
<PassportImageDropzone
|
|
||||||
imageUrlInputProps={
|
|
||||||
form.getInputProps(
|
|
||||||
"passportImageUrl",
|
|
||||||
) as BaseFormInputProps<string>
|
|
||||||
}
|
|
||||||
userId={innerProps?.element.id}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Stack>
|
|
||||||
</Fieldset>
|
|
||||||
<Fieldset legend={"Роль и должность"}>
|
|
||||||
<Stack>
|
|
||||||
<RoleSelect
|
|
||||||
label={"Роль пользователя"}
|
|
||||||
placeholder={"Выберите роль пользователя"}
|
|
||||||
{...form.getInputProps("role")}
|
|
||||||
/>
|
|
||||||
{form.values.role?.key ===
|
|
||||||
UserRoleEnum.EMPLOYEE && (
|
|
||||||
<>
|
|
||||||
<PositionSelect
|
|
||||||
label={"Должность сотрудника"}
|
|
||||||
placeholder={
|
|
||||||
"Выберите должность сотрудника"
|
|
||||||
}
|
|
||||||
{...form.getInputProps("position")}
|
|
||||||
/>
|
|
||||||
<PayRateSelect
|
|
||||||
label={"Тариф"}
|
|
||||||
placeholder={
|
|
||||||
"Выберите тариф сотрудника"
|
|
||||||
}
|
|
||||||
{...form.getInputProps("payRate")}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
|
export enum ModalTab {
|
||||||
|
COMMON,
|
||||||
|
DEPARTMENT_SECTIONS,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = Omit<SegmentedControlProps, "data">;
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
label: "Общее",
|
||||||
|
value: ModalTab.COMMON.toString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Отделы",
|
||||||
|
value: ModalTab.DEPARTMENT_SECTIONS.toString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const UsersModalTabSegmentedControl: FC<Props> = props => {
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
data={data}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { UserRoleEnum } from "../../../../../../shared/enums/UserRole.ts";
|
||||||
|
import { useForm } from "@mantine/form";
|
||||||
|
import { UserSchema } from "../../../../../../client";
|
||||||
|
import phone from "phone";
|
||||||
|
import BaseFormModal, { CreateEditFormProps } from "../../../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||||
|
import { Checkbox, Fieldset, Input, Stack, Textarea, TextInput } from "@mantine/core";
|
||||||
|
import { capitalize } from "lodash";
|
||||||
|
import { IMaskInput } from "react-imask";
|
||||||
|
import PassportImageDropzone from "../../../../components/PassportImageDropzone/PassportImageDropzone.tsx";
|
||||||
|
import { BaseFormInputProps } from "../../../../../../types/utils.ts";
|
||||||
|
import RoleSelect from "../../../../components/RoleSelect/RoleSelect.tsx";
|
||||||
|
import PositionSelect from "../../../../components/PositionSelect/PositionSelect.tsx";
|
||||||
|
import PayRateSelect from "../../../../../../components/Selects/PayRateSelect/PayRateSelect.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
closeModal: () => void;
|
||||||
|
formProps: CreateEditFormProps<UserSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CommonTab = ({
|
||||||
|
closeModal,
|
||||||
|
formProps,
|
||||||
|
}: Props) => {
|
||||||
|
const isEditing = "element" in formProps;
|
||||||
|
const initialValues = isEditing
|
||||||
|
? formProps.element
|
||||||
|
: {
|
||||||
|
isAdmin: false,
|
||||||
|
isBlocked: false,
|
||||||
|
isDeleted: false,
|
||||||
|
comment: "",
|
||||||
|
roleKey: UserRoleEnum.USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
const form = useForm<Partial<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 && "Неверно указан номер телефона",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseFormModal
|
||||||
|
form={form}
|
||||||
|
closeOnSubmit
|
||||||
|
onClose={closeModal}
|
||||||
|
{...formProps}>
|
||||||
|
<BaseFormModal.Body>
|
||||||
|
<>
|
||||||
|
<Fieldset legend={"Общая информация"}>
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
{...form.getInputProps("secondName")}
|
||||||
|
label={"Фамилия"}
|
||||||
|
placeholder={"Введите фамилию пользователя"}
|
||||||
|
onChange={event =>
|
||||||
|
form
|
||||||
|
.getInputProps("secondName")
|
||||||
|
.onChange(
|
||||||
|
capitalize(
|
||||||
|
event.target.value,
|
||||||
|
).trim(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={"Имя"}
|
||||||
|
placeholder={"Введите имя пользователя"}
|
||||||
|
{...form.getInputProps("firstName")}
|
||||||
|
onChange={event =>
|
||||||
|
form
|
||||||
|
.getInputProps("firstName")
|
||||||
|
.onChange(
|
||||||
|
capitalize(
|
||||||
|
event.target.value,
|
||||||
|
).trim(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
{...form.getInputProps("patronymic")}
|
||||||
|
label={"Отчество"}
|
||||||
|
placeholder={"Введите отчество пользователя"}
|
||||||
|
onChange={event =>
|
||||||
|
form
|
||||||
|
.getInputProps("patronymic")
|
||||||
|
.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>
|
||||||
|
<Input.Wrapper
|
||||||
|
label={"Серия и номер паспорта"}
|
||||||
|
error={form.getInputProps("passportData").error}>
|
||||||
|
<Input
|
||||||
|
component={IMaskInput}
|
||||||
|
mask="00 00 000000"
|
||||||
|
placeholder={"Введите серию и номер паспорта"}
|
||||||
|
{...form.getInputProps("passportData")}
|
||||||
|
/>
|
||||||
|
</Input.Wrapper>
|
||||||
|
{
|
||||||
|
isEditing && (
|
||||||
|
<PassportImageDropzone
|
||||||
|
imageUrlInputProps={
|
||||||
|
form.getInputProps(
|
||||||
|
"passportImageUrl",
|
||||||
|
) as BaseFormInputProps<string>
|
||||||
|
}
|
||||||
|
userId={formProps?.element.id}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Stack>
|
||||||
|
</Fieldset>
|
||||||
|
<Fieldset legend={"Роль и должность"}>
|
||||||
|
<Stack>
|
||||||
|
<RoleSelect
|
||||||
|
label={"Роль пользователя"}
|
||||||
|
placeholder={"Выберите роль пользователя"}
|
||||||
|
{...form.getInputProps("role")}
|
||||||
|
/>
|
||||||
|
{form.values.role?.key ===
|
||||||
|
UserRoleEnum.EMPLOYEE && (
|
||||||
|
<>
|
||||||
|
<PositionSelect
|
||||||
|
label={"Должность сотрудника"}
|
||||||
|
placeholder={
|
||||||
|
"Выберите должность сотрудника"
|
||||||
|
}
|
||||||
|
{...form.getInputProps("position")}
|
||||||
|
/>
|
||||||
|
<PayRateSelect
|
||||||
|
label={"Тариф"}
|
||||||
|
placeholder={
|
||||||
|
"Выберите тариф сотрудника"
|
||||||
|
}
|
||||||
|
{...form.getInputProps("payRate")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</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 CommonTab;
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import UserDepartmentInput from "../../../../components/UserDepartmentInput/UserDepartmentInput.tsx";
|
||||||
|
import { Button, Fieldset, Stack } from "@mantine/core";
|
||||||
|
import { useMap } from "@mantine/hooks";
|
||||||
|
import { UserSchema, UserService } from "../../../../../../client";
|
||||||
|
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
user: UserSchema;
|
||||||
|
closeModal: () => void;
|
||||||
|
refetch: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DepartmentSectionsTab = ({ user, closeModal, refetch }: Props) => {
|
||||||
|
const userSectionIsChiefMap = useMap<number, boolean>(
|
||||||
|
user.departmentSections?.map(s => [s.sectionId, s.isChief]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
const departmentSections = userSectionIsChiefMap
|
||||||
|
.entries()
|
||||||
|
.toArray()
|
||||||
|
.map(([sectionId, isChief]) => ({
|
||||||
|
sectionId,
|
||||||
|
isChief,
|
||||||
|
}));
|
||||||
|
|
||||||
|
UserService.updateUserDepartmentSections({
|
||||||
|
userId: user.id,
|
||||||
|
requestBody: {
|
||||||
|
departmentSections,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
refetch();
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
closeModal();
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Fieldset legend={"Отделы"}>
|
||||||
|
<UserDepartmentInput
|
||||||
|
userSectionIsChiefMap={userSectionIsChiefMap}
|
||||||
|
/>
|
||||||
|
</Fieldset>
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
Сохранить отделы
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DepartmentSectionsTab;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { DepartmentSchema, DepartmentSectionSchema } from "../../../../../client";
|
||||||
|
import { IconEdit, IconPlaylistAdd, IconTrash, IconUserPlus } from "@tabler/icons-react";
|
||||||
|
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
element: DepartmentSectionSchema | DepartmentSchema,
|
||||||
|
isSection: boolean,
|
||||||
|
level?: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DepartmentActions = ({ element, isSection, level = 1 }: Props) => {
|
||||||
|
const {
|
||||||
|
onCreateDepartmentClick,
|
||||||
|
onUpdateDepartmentClick,
|
||||||
|
onDeleteDepartmentClick,
|
||||||
|
onAddUserClick,
|
||||||
|
} = useDepartmentContext();
|
||||||
|
|
||||||
|
const getAction = (label: string, func: () => void, icon: ReactNode) => {
|
||||||
|
return (
|
||||||
|
<Tooltip label={label} key={label}>
|
||||||
|
<ActionIcon
|
||||||
|
variant={"default"}
|
||||||
|
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
func();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
isSection && (
|
||||||
|
getAction(
|
||||||
|
"Добавить пользователя в отдел",
|
||||||
|
() => onAddUserClick(element as DepartmentSectionSchema),
|
||||||
|
<IconUserPlus />,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
level < 9 && getAction(
|
||||||
|
"Добавить отдел",
|
||||||
|
() => onCreateDepartmentClick(true, {
|
||||||
|
departmentId: isSection ? null : element.id,
|
||||||
|
parentDepartmentSectionId: isSection ? element.id : null,
|
||||||
|
name: "",
|
||||||
|
}),
|
||||||
|
<IconPlaylistAdd />,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Редактировать",
|
||||||
|
() => onUpdateDepartmentClick(element, isSection),
|
||||||
|
<IconEdit />,
|
||||||
|
),
|
||||||
|
getAction(
|
||||||
|
"Удалить",
|
||||||
|
() => onDeleteDepartmentClick(element, isSection),
|
||||||
|
<IconTrash />,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex gap={"md"} mx={"md"} direction={"row"}>
|
||||||
|
{...actions}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DepartmentActions;
|
||||||
@@ -1,24 +1,15 @@
|
|||||||
import { Button, Group, rem } from "@mantine/core";
|
import { Button, Group, rem } from "@mantine/core";
|
||||||
import { IconChevronsDown, IconChevronsUp, IconPlus } from "@tabler/icons-react";
|
import { IconPlus } from "@tabler/icons-react";
|
||||||
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
|
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
|
||||||
|
|
||||||
|
|
||||||
const DepartmentButtons = () => {
|
const DepartmentButtons = () => {
|
||||||
const {
|
const {
|
||||||
departmentIds,
|
|
||||||
toggleAllDepartmentIds,
|
|
||||||
onCreateDepartmentClick,
|
onCreateDepartmentClick,
|
||||||
} = useDepartmentContext();
|
} = useDepartmentContext();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group gap={"md"}>
|
<Group gap={"md"}>
|
||||||
<Button
|
|
||||||
variant={"default"}
|
|
||||||
onClick={toggleAllDepartmentIds}
|
|
||||||
>
|
|
||||||
{departmentIds.length > 0 ? <IconChevronsUp /> : <IconChevronsDown />}
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
variant={"default"}
|
variant={"default"}
|
||||||
onClick={() => onCreateDepartmentClick(false)}
|
onClick={() => onCreateDepartmentClick(false)}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { DataTable } from "mantine-datatable";
|
||||||
|
import { Box, Stack } from "@mantine/core";
|
||||||
|
import { IconBuilding, IconChevronRight } from "@tabler/icons-react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import classes from "./DepartmentsTree/DepartmentsTree.module.css";
|
||||||
|
import { DepartmentSchema, DepartmentSectionSchema } from "../../../../../client";
|
||||||
|
import "mantine-datatable/styles.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
import DepartmentActions from "./DepartmentActions.tsx";
|
||||||
|
import UsersTable from "./UsersTable.tsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
parentItem: DepartmentSchema | DepartmentSectionSchema;
|
||||||
|
level: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DepartmentSectionsTable = ({ parentItem, level }: Props) => {
|
||||||
|
const [departmentSectionIds, setDepartmentSectionIds] = useState<number[]>(
|
||||||
|
parentItem.sections?.map(section => section.id) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
noHeader
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
noWrap: true,
|
||||||
|
render: ({ id, name }) => (
|
||||||
|
<Box component="span" ml={20 * level}>
|
||||||
|
<IconChevronRight
|
||||||
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
|
[classes.expandIconRotated]: departmentSectionIds.includes(id),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<IconBuilding className={classes.icon} />
|
||||||
|
<span>{name}</span>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (departmentSection) => (
|
||||||
|
<DepartmentActions element={departmentSection} isSection={true} level={level} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={parentItem.sections?.sort((a, b) => a.id - b.id)}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: departmentSectionIds, onRecordIdsChange: setDepartmentSectionIds },
|
||||||
|
content: ({ record }) => (
|
||||||
|
<Stack gap={0}>
|
||||||
|
{record.users && record.users.length > 0 && (
|
||||||
|
<UsersTable
|
||||||
|
key={`1 ${record.id}`}
|
||||||
|
sectionId={record.id}
|
||||||
|
users={record.users}
|
||||||
|
level={level + 1}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{record.sections && record.sections.length > 0 && (
|
||||||
|
<DepartmentSectionsTable
|
||||||
|
key={`2 ${record.id}`}
|
||||||
|
parentItem={record}
|
||||||
|
level={level + 1}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DepartmentSectionsTable;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Stack } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import DepartmentsTree from "./DepartmentsTree.tsx";
|
import DepartmentsTree from "./DepartmentsTree/DepartmentsTree.tsx";
|
||||||
import DepartmentButtons from "./DepartmentButtons.tsx";
|
import DepartmentButtons from "./DepartmentButtons.tsx";
|
||||||
import { DepartmentContextProvider } from "../contexts/DepartmentContext.tsx";
|
import { DepartmentContextProvider } from "../contexts/DepartmentContext.tsx";
|
||||||
|
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
import { Accordion, ActionIcon, Center, Flex, Text, Title, Tooltip } from "@mantine/core";
|
|
||||||
import { IconEdit, IconPlaylistAdd, IconTrash, IconUserPlus } from "@tabler/icons-react";
|
|
||||||
import { DepartmentSchema, DepartmentSectionSchema } from "../../../../../client";
|
|
||||||
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
|
|
||||||
import UsersTable from "./UsersTable.tsx";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
|
|
||||||
const DepartmentsTree = () => {
|
|
||||||
const {
|
|
||||||
departments,
|
|
||||||
departmentIds,
|
|
||||||
setDepartmentIds,
|
|
||||||
onCreateDepartmentClick,
|
|
||||||
onUpdateDepartmentClick,
|
|
||||||
onDeleteDepartmentClick,
|
|
||||||
onAddUserClick,
|
|
||||||
} = useDepartmentContext();
|
|
||||||
|
|
||||||
const getAction = (label: string, func: () => void, icon: ReactNode) => {
|
|
||||||
return (
|
|
||||||
<Tooltip label={label} key={label}>
|
|
||||||
<ActionIcon
|
|
||||||
variant={"default"}
|
|
||||||
onClick={func}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActions = (
|
|
||||||
element: DepartmentSectionSchema | DepartmentSchema,
|
|
||||||
isSection: boolean,
|
|
||||||
) => {
|
|
||||||
const actions = [
|
|
||||||
isSection ? (
|
|
||||||
getAction(
|
|
||||||
"Добавить пользователя в отдел",
|
|
||||||
() => onAddUserClick(element as DepartmentSectionSchema),
|
|
||||||
<IconUserPlus />,
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
getAction(
|
|
||||||
"Добавить отдел",
|
|
||||||
() => onCreateDepartmentClick(true, { departmentId: element.id, name: "" }),
|
|
||||||
<IconPlaylistAdd />,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
getAction(
|
|
||||||
"Редактировать",
|
|
||||||
() => onUpdateDepartmentClick(element, isSection),
|
|
||||||
<IconEdit />,
|
|
||||||
),
|
|
||||||
getAction(
|
|
||||||
"Удалить",
|
|
||||||
() => onDeleteDepartmentClick(element, isSection),
|
|
||||||
<IconTrash />,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex gap={"md"} mx={"md"} direction={"row"}>
|
|
||||||
{...actions}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUsersTable = (section: DepartmentSectionSchema) => {
|
|
||||||
if (!section.users || section.users.length === 0) {
|
|
||||||
return <Text>Сотрудники не добавлены</Text>;
|
|
||||||
}
|
|
||||||
return <UsersTable users={section.users} sectionId={section.id}/>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDepartmentSections = (department: DepartmentSchema) => {
|
|
||||||
if (!department.sections || department.sections.length === 0) return;
|
|
||||||
|
|
||||||
const accordionIds: string[] = [];
|
|
||||||
const sortedSections: DepartmentSectionSchema[] = department.sections?.sort((a, b) => a.id - b.id) ?? [];
|
|
||||||
const items = sortedSections.map(section => {
|
|
||||||
const value = `section ${section.id}`;
|
|
||||||
accordionIds.push(value);
|
|
||||||
return (
|
|
||||||
<Accordion.Item key={section.id} value={value}>
|
|
||||||
<Center>
|
|
||||||
<Accordion.Control>
|
|
||||||
<Title order={4}>Отдел - {section.name}</Title>
|
|
||||||
</Accordion.Control>
|
|
||||||
{getActions(section, true)}
|
|
||||||
</Center>
|
|
||||||
<Accordion.Panel>
|
|
||||||
{getUsersTable(section)}
|
|
||||||
</Accordion.Panel>
|
|
||||||
</Accordion.Item>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Accordion
|
|
||||||
multiple={true}
|
|
||||||
defaultValue={accordionIds}
|
|
||||||
bd={"solid 1px gray"}
|
|
||||||
>
|
|
||||||
{items}
|
|
||||||
</Accordion>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDepartments = departments.map((department) => {
|
|
||||||
return (
|
|
||||||
<Accordion.Item key={department.id} value={department.id.toString()}>
|
|
||||||
<Center>
|
|
||||||
<Accordion.Control>
|
|
||||||
<Title order={4}>Департамент - {department.name}</Title>
|
|
||||||
</Accordion.Control>
|
|
||||||
{getActions(department, false)}
|
|
||||||
</Center>
|
|
||||||
<Accordion.Panel>
|
|
||||||
{getDepartmentSections(department)}
|
|
||||||
</Accordion.Panel>
|
|
||||||
</Accordion.Item>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Accordion
|
|
||||||
multiple={true}
|
|
||||||
value={departmentIds}
|
|
||||||
onChange={value => setDepartmentIds(value)}
|
|
||||||
bd={"solid 1px gray"}
|
|
||||||
>
|
|
||||||
{getDepartments}
|
|
||||||
</Accordion>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DepartmentsTree;
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.icon {
|
||||||
|
width: rem(13px);
|
||||||
|
height: auto;
|
||||||
|
vertical-align: rem(-1px);
|
||||||
|
margin-right: rem(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandIcon {
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandIconRotated {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.usersTableBorder {
|
||||||
|
border-bottom: solid 1px var(--mantine-color-dark-5);
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { useDepartmentContext } from "../../contexts/DepartmentContext.tsx";
|
||||||
|
import { DataTable } from "mantine-datatable";
|
||||||
|
import { IconBuildings, IconChevronRight } from "@tabler/icons-react";
|
||||||
|
import classes from "./DepartmentsTree.module.css";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import "mantine-datatable/styles.css";
|
||||||
|
import DepartmentActions from "../DepartmentActions.tsx";
|
||||||
|
import DepartmentSectionsTable from "../DepartmentSectionsTable.tsx";
|
||||||
|
|
||||||
|
const DepartmentsTree = () => {
|
||||||
|
const { departments } = useDepartmentContext();
|
||||||
|
|
||||||
|
const [departmentIds, setDepartmentIds] = useState<number[]>([]);
|
||||||
|
let isInitialSetting = true;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInitialSetting && departments) {
|
||||||
|
isInitialSetting = false;
|
||||||
|
setDepartmentIds(departments.map(department => department.id));
|
||||||
|
}
|
||||||
|
}, [departments]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
noHeader
|
||||||
|
withTableBorder
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessor: "name",
|
||||||
|
title: "Департамент",
|
||||||
|
noWrap: true,
|
||||||
|
render: ({ id, name }) => (
|
||||||
|
<>
|
||||||
|
<IconChevronRight
|
||||||
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
|
[classes.expandIconRotated]: departmentIds?.includes(id),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<IconBuildings className={classes.icon} />
|
||||||
|
<span>{name}</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (department) => (
|
||||||
|
<DepartmentActions element={department} isSection={false} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={departments.sort((a, b) => a.id - b.id)}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: departmentIds, onRecordIdsChange: setDepartmentIds },
|
||||||
|
content: ({ record }) => (
|
||||||
|
<DepartmentSectionsTable parentItem={record} level={1} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DepartmentsTree;
|
||||||
@@ -1,63 +1,69 @@
|
|||||||
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
|
|
||||||
import useUsersTableColumns from "../hooks/useUsersTableColumns.tsx";
|
import useUsersTableColumns from "../hooks/useUsersTableColumns.tsx";
|
||||||
import { DepartmentService, UserSchema } from "../../../../../client";
|
import { UserDepartmentSectionSchema } from "../../../../../client";
|
||||||
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
|
import { DataTable } from "mantine-datatable";
|
||||||
import { IconTrash } from "@tabler/icons-react";
|
import "mantine-datatable/styles.css";
|
||||||
import { MRT_TableOptions } from "mantine-react-table";
|
import { Box } from "@mantine/core";
|
||||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
import { IconChevronRight, IconUsers } from "@tabler/icons-react";
|
||||||
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
|
import clsx from "clsx";
|
||||||
|
import classes from "./DepartmentsTree/DepartmentsTree.module.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
users: UserSchema[];
|
users: UserDepartmentSectionSchema[];
|
||||||
sectionId: number;
|
sectionId: number;
|
||||||
|
level: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UsersTable = ({ users, sectionId }: Props) => {
|
const UsersTable = ({ users, sectionId, level }: Props) => {
|
||||||
const columns = useUsersTableColumns();
|
const columns = useUsersTableColumns({ sectionId });
|
||||||
const { fetchDepartments } = useDepartmentContext();
|
const [userIds, setUserIds] = useState<number[]>([1]);
|
||||||
|
|
||||||
const onDeleteUserClick = (userId: number) => {
|
const header = [
|
||||||
DepartmentService.deleteUser({
|
{
|
||||||
requestBody: {
|
id: 1,
|
||||||
sectionId,
|
title: "Пользователи",
|
||||||
userId,
|
},
|
||||||
}
|
];
|
||||||
})
|
|
||||||
.then(({ok, message}) => {
|
|
||||||
fetchDepartments();
|
|
||||||
if (ok) return;
|
|
||||||
notifications.error({ message });
|
|
||||||
})
|
|
||||||
.catch(err => console.log(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseTable
|
<DataTable
|
||||||
data={users}
|
noHeader
|
||||||
columns={columns}
|
withColumnBorders
|
||||||
|
className={clsx(classes.usersTableBorder)}
|
||||||
restProps={
|
columns={[
|
||||||
{
|
{
|
||||||
enableSorting: false,
|
accessor: "title",
|
||||||
enableColumnActions: false,
|
noWrap: true,
|
||||||
enableRowActions: true,
|
render: ({ id, title }) => (
|
||||||
positionActionsColumn: "last",
|
<Box component="span" ml={20 * level}>
|
||||||
renderRowActions: ({ row }) => (
|
<IconChevronRight
|
||||||
<Flex gap="md">
|
className={clsx(classes.icon, classes.expandIcon, {
|
||||||
<Tooltip label="Удалить">
|
[classes.expandIconRotated]: userIds.includes(id),
|
||||||
<ActionIcon
|
})}
|
||||||
onClick={() => onDeleteUserClick(row.original.id)}
|
|
||||||
variant={"default"}>
|
|
||||||
<IconTrash />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
} as MRT_TableOptions<UserSchema>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)
|
<IconUsers className={classes.icon} />
|
||||||
}
|
<span>{title}</span>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
records={header}
|
||||||
|
rowExpansion={{
|
||||||
|
allowMultiple: true,
|
||||||
|
expanded: { recordIds: userIds, onRecordIdsChange: setUserIds },
|
||||||
|
content: () => (
|
||||||
|
<Box pl={20 * (level + 2.5)}>
|
||||||
|
<DataTable
|
||||||
|
withTableBorder
|
||||||
|
withColumnBorders
|
||||||
|
columns={columns}
|
||||||
|
records={users}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default UsersTable;
|
export default UsersTable;
|
||||||
|
|||||||
@@ -12,14 +12,11 @@ import useDepartmentSectionCrud from "../hooks/useDepartmentSectionCrud.tsx";
|
|||||||
|
|
||||||
type DepartmentContextState = {
|
type DepartmentContextState = {
|
||||||
departments: DepartmentSchema[],
|
departments: DepartmentSchema[],
|
||||||
departmentIds: string[],
|
|
||||||
setDepartmentIds: React.Dispatch<React.SetStateAction<string[]>>,
|
|
||||||
fetchDepartments: () => void,
|
fetchDepartments: () => void,
|
||||||
onCreateDepartmentClick: (isSection: boolean, element?: DepartmentSectionBaseSchema) => void,
|
onCreateDepartmentClick: (isSection: boolean, element?: DepartmentSectionBaseSchema) => void,
|
||||||
onUpdateDepartmentClick: (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => void,
|
onUpdateDepartmentClick: (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => void,
|
||||||
onDeleteDepartmentClick: (element: DepartmentSchema, isSection: boolean) => void,
|
onDeleteDepartmentClick: (element: DepartmentSchema, isSection: boolean) => void,
|
||||||
onAddUserClick: (departmentSection: DepartmentSectionSchema) => void,
|
onAddUserClick: (departmentSection: DepartmentSectionSchema) => void,
|
||||||
toggleAllDepartmentIds: () => void,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const DepartmentContext = createContext<DepartmentContextState | undefined>(
|
const DepartmentContext = createContext<DepartmentContextState | undefined>(
|
||||||
@@ -28,15 +25,11 @@ const DepartmentContext = createContext<DepartmentContextState | undefined>(
|
|||||||
|
|
||||||
const useDepartmentContextState = () => {
|
const useDepartmentContextState = () => {
|
||||||
const [departments, setDepartments] = useState<DepartmentSchema[]>([]);
|
const [departments, setDepartments] = useState<DepartmentSchema[]>([]);
|
||||||
const [departmentIds, setDepartmentIds] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const fetchDepartments = () => {
|
const fetchDepartments = () => {
|
||||||
DepartmentService.getDepartments()
|
DepartmentService.getDepartments()
|
||||||
.then(res => {
|
.then(res => {
|
||||||
setDepartments(res.departments);
|
setDepartments(res.departments);
|
||||||
if (departmentIds.length === 0) {
|
|
||||||
setDepartmentIds(res.departments.map(dep => dep.id.toString()));
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(e => console.log(e));
|
.catch(e => console.log(e));
|
||||||
};
|
};
|
||||||
@@ -66,17 +59,26 @@ const useDepartmentContextState = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCreateDepartmentClick = (isSection: boolean, element?: DepartmentSectionBaseSchema) => {
|
const onCreateDepartmentClick = (
|
||||||
|
isSection: boolean,
|
||||||
|
element?: DepartmentSectionBaseSchema,
|
||||||
|
) => {
|
||||||
const title: string = isSection ? "Создание отдела" : "Создание департамента";
|
const title: string = isSection ? "Создание отдела" : "Создание департамента";
|
||||||
openDepartmentModal(title, isSection, element);
|
openDepartmentModal(title, isSection, element);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateDepartmentClick = (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => {
|
const onUpdateDepartmentClick = (
|
||||||
|
element: DepartmentSchema | DepartmentSectionSchema,
|
||||||
|
isSection: boolean,
|
||||||
|
) => {
|
||||||
const title: string = isSection ? "Редактирование отдела" : "Редактирование департамента";
|
const title: string = isSection ? "Редактирование отдела" : "Редактирование департамента";
|
||||||
openDepartmentModal(title, isSection, element);
|
openDepartmentModal(title, isSection, element);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteDepartmentClick = (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => {
|
const onDeleteDepartmentClick = (
|
||||||
|
element: DepartmentSchema | DepartmentSectionSchema,
|
||||||
|
isSection: boolean,
|
||||||
|
) => {
|
||||||
modals.openConfirmModal({
|
modals.openConfirmModal({
|
||||||
title: "Удаление " + (isSection ? "отдела" : "департамента"),
|
title: "Удаление " + (isSection ? "отдела" : "департамента"),
|
||||||
children: (
|
children: (
|
||||||
@@ -90,7 +92,7 @@ const useDepartmentContextState = () => {
|
|||||||
if (isSection) {
|
if (isSection) {
|
||||||
departmentSectionsCrud.onDelete(element as DepartmentSectionSchema);
|
departmentSectionsCrud.onDelete(element as DepartmentSectionSchema);
|
||||||
} else {
|
} else {
|
||||||
departmentsCrud.onDelete(element);
|
departmentsCrud.onDelete(element as DepartmentSchema);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -106,26 +108,15 @@ const useDepartmentContextState = () => {
|
|||||||
fetchDepartments,
|
fetchDepartments,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const toggleAllDepartmentIds = () => {
|
|
||||||
if (departmentIds.length > 0) {
|
|
||||||
setDepartmentIds([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setDepartmentIds(departments.map(department => department.id.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
departments,
|
departments,
|
||||||
departmentIds,
|
|
||||||
setDepartmentIds,
|
|
||||||
fetchDepartments,
|
fetchDepartments,
|
||||||
onCreateDepartmentClick,
|
onCreateDepartmentClick,
|
||||||
onUpdateDepartmentClick,
|
onUpdateDepartmentClick,
|
||||||
onDeleteDepartmentClick,
|
onDeleteDepartmentClick,
|
||||||
onAddUserClick,
|
onAddUserClick,
|
||||||
toggleAllDepartmentIds,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,81 @@
|
|||||||
import { useMemo } from "react";
|
import { IconCheck, IconTrash, IconX } from "@tabler/icons-react";
|
||||||
import { MRT_ColumnDef } from "mantine-react-table";
|
import { DepartmentService, UserDepartmentSectionSchema } from "../../../../../client";
|
||||||
import { UserSchema } from "../../../../../client";
|
import { DataTableColumn } from "mantine-datatable";
|
||||||
|
import { ActionIcon, Box, Flex, Tooltip } from "@mantine/core";
|
||||||
|
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
sectionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
const useUsersTableColumns = () => {
|
const useUsersTableColumns = ({ sectionId }: Props) => {
|
||||||
return useMemo<MRT_ColumnDef<UserSchema>[]>(
|
const { fetchDepartments } = useDepartmentContext();
|
||||||
() => [
|
|
||||||
|
const onDeleteUserClick = (userId: number) => {
|
||||||
|
DepartmentService.deleteUser({
|
||||||
|
requestBody: {
|
||||||
|
sectionId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ ok, message }) => {
|
||||||
|
fetchDepartments();
|
||||||
|
if (ok) return;
|
||||||
|
notifications.error({ message });
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
header: "ФИО",
|
title: "ФИО",
|
||||||
Cell: ({ row }) =>
|
accessor: "user.secondName",
|
||||||
`${row.original.secondName} ${row.original.firstName} ${row.original.patronymic}`,
|
render: (userData) => (
|
||||||
|
<Box component="span">
|
||||||
|
<span>{userData.user.secondName} {userData.user.firstName} {userData.user.patronymic}</span>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "role.name",
|
accessor: "user.role.name",
|
||||||
header: "Роль",
|
title: "Роль",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "position.name",
|
accessor: "user.position.name",
|
||||||
header: "Должность",
|
title: "Должность",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "comment",
|
accessor: "user.comment",
|
||||||
header: "Доп. информация",
|
title: "Доп. информация",
|
||||||
}
|
},
|
||||||
],
|
{
|
||||||
[],
|
accessor: "isChief",
|
||||||
);
|
title: "Является руководителем",
|
||||||
|
render: (userData) =>
|
||||||
|
userData.isChief ? (
|
||||||
|
<IconCheck />
|
||||||
|
) : (
|
||||||
|
<IconX />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessor: "actions",
|
||||||
|
title: "",
|
||||||
|
width: "0%",
|
||||||
|
render: (userData) => (
|
||||||
|
<Flex gap="md" mx={"md"}>
|
||||||
|
<Tooltip label="Удалить">
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() => onDeleteUserClick(userData.user.id)}
|
||||||
|
variant={"default"}>
|
||||||
|
<IconTrash />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as DataTableColumn<UserDepartmentSectionSchema>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useUsersTableColumns;
|
export default useUsersTableColumns;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { ContextModalProps } from "@mantine/modals";
|
import { ContextModalProps } from "@mantine/modals";
|
||||||
import { Button, Flex, rem } from "@mantine/core";
|
import { Button, Checkbox, Flex, rem } from "@mantine/core";
|
||||||
import { DepartmentSectionSchema, DepartmentService } from "../../../../../client";
|
import { DepartmentSectionSchema, DepartmentService } from "../../../../../client";
|
||||||
import AddUserToDepartmentModalForm from "../types/AddUserToDepartmentModalForm.tsx";
|
import AddUserToDepartmentModalForm from "../types/AddUserToDepartmentModalForm.tsx";
|
||||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
@@ -16,7 +16,11 @@ const AddUserToDepartmentModal = ({
|
|||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
}: ContextModalProps<Props>) => {
|
}: ContextModalProps<Props>) => {
|
||||||
const form = useForm<Partial<AddUserToDepartmentModalForm>>({
|
const initialValues = {
|
||||||
|
isChief: false,
|
||||||
|
};
|
||||||
|
const form = useForm<AddUserToDepartmentModalForm>({
|
||||||
|
initialValues,
|
||||||
validate: {
|
validate: {
|
||||||
user: user => !user && "Необходимо выбрать пользователя",
|
user: user => !user && "Необходимо выбрать пользователя",
|
||||||
},
|
},
|
||||||
@@ -28,7 +32,8 @@ const AddUserToDepartmentModal = ({
|
|||||||
requestBody: {
|
requestBody: {
|
||||||
userId: form.values.user.id,
|
userId: form.values.user.id,
|
||||||
sectionId: innerProps.departmentSection.id,
|
sectionId: innerProps.departmentSection.id,
|
||||||
}
|
isChief: form.values.isChief,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.then(({ ok, message }) => {
|
.then(({ ok, message }) => {
|
||||||
notifications.guess(ok, { message });
|
notifications.guess(ok, { message });
|
||||||
@@ -50,6 +55,10 @@ const AddUserToDepartmentModal = ({
|
|||||||
{...form.getInputProps("user")}
|
{...form.getInputProps("user")}
|
||||||
sectionId={innerProps.departmentSection.id}
|
sectionId={innerProps.departmentSection.id}
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label={"Является руководителем"}
|
||||||
|
{...form.getInputProps("isChief", { type: "checkbox" })}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant={"default"}
|
variant={"default"}
|
||||||
type={"submit"}
|
type={"submit"}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { UserSchema } from "../../../../../client";
|
import { UserSchema } from "../../../../../client";
|
||||||
|
|
||||||
type AddUserToDepartmentModalForm = {
|
type AddUserToDepartmentModalForm = {
|
||||||
user: UserSchema;
|
user?: UserSchema;
|
||||||
|
isChief: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AddUserToDepartmentModalForm;
|
export default AddUserToDepartmentModalForm;
|
||||||
|
|||||||
@@ -1,50 +1,11 @@
|
|||||||
import useUsersList from "../../hooks/useUsersList.tsx";
|
|
||||||
import UsersTable from "../../components/UsersTable/UsersTable.tsx";
|
import UsersTable from "../../components/UsersTable/UsersTable.tsx";
|
||||||
import { UserSchema, UserService } from "../../../../client";
|
import { UsersTabContextProvider } from "./contexts/UsersTabContext.tsx";
|
||||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
|
||||||
|
|
||||||
const UsersTab = () => {
|
const UsersTab = () => {
|
||||||
const { objects: users, refetch } = useUsersList();
|
|
||||||
|
|
||||||
const onChange = (user: UserSchema) => {
|
|
||||||
UserService.updateUser({
|
|
||||||
requestBody: {
|
|
||||||
data: {
|
|
||||||
...user,
|
|
||||||
positionKey: user.position?.key,
|
|
||||||
roleKey: user.role.key,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).then(async ({ ok, message }) => {
|
|
||||||
notifications.guess(ok, { message });
|
|
||||||
if (!ok) return;
|
|
||||||
await refetch();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onDelete = async (user: UserSchema) => {
|
|
||||||
onChange({ ...user, isDeleted: true });
|
|
||||||
};
|
|
||||||
const onCreate = (user: UserSchema) => {
|
|
||||||
UserService.createUser({
|
|
||||||
requestBody: {
|
|
||||||
data: {
|
|
||||||
...user,
|
|
||||||
telegramId: -1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).then(async ({ ok, message }) => {
|
|
||||||
notifications.guess(ok, { message });
|
|
||||||
if (!ok) return;
|
|
||||||
await refetch();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<UsersTable
|
<UsersTabContextProvider>
|
||||||
items={users}
|
<UsersTable />
|
||||||
onChange={onChange}
|
</UsersTabContextProvider>
|
||||||
onDelete={onDelete}
|
|
||||||
onCreate={onCreate}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
86
src/pages/AdminPage/tabs/Users/contexts/UsersTabContext.tsx
Normal file
86
src/pages/AdminPage/tabs/Users/contexts/UsersTabContext.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { createContext, FC, useContext } from "react";
|
||||||
|
import { UserSchema, UserService } from "../../../../../client";
|
||||||
|
import useUsersList from "../../../hooks/useUsersList.tsx";
|
||||||
|
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||||
|
import { CRUDTableProps } from "../../../../../types/CRUDTable.tsx";
|
||||||
|
|
||||||
|
type UsersTabContextState = {
|
||||||
|
refetch: () => void;
|
||||||
|
usersCrud: CRUDTableProps<UserSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UsersTabContext = createContext<UsersTabContextState | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const useUsersTabContextState = () => {
|
||||||
|
const { objects: users, refetch } = useUsersList();
|
||||||
|
|
||||||
|
const onChange = (user: UserSchema) => {
|
||||||
|
UserService.updateUser({
|
||||||
|
requestBody: {
|
||||||
|
data: {
|
||||||
|
...user,
|
||||||
|
positionKey: user.position?.key,
|
||||||
|
roleKey: user.role.key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(async ({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
if (!ok) return;
|
||||||
|
await refetch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onDelete = async (user: UserSchema) => {
|
||||||
|
onChange({ ...user, isDeleted: true });
|
||||||
|
};
|
||||||
|
const onCreate = (user: UserSchema) => {
|
||||||
|
UserService.createUser({
|
||||||
|
requestBody: {
|
||||||
|
data: {
|
||||||
|
...user,
|
||||||
|
telegramId: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(async ({ ok, message }) => {
|
||||||
|
notifications.guess(ok, { message });
|
||||||
|
if (!ok) return;
|
||||||
|
await refetch();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const usersCrud: CRUDTableProps<UserSchema> = {
|
||||||
|
items: users,
|
||||||
|
onCreate,
|
||||||
|
onDelete,
|
||||||
|
onChange,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
usersCrud,
|
||||||
|
refetch,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type UsersTabContextProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UsersTabContextProvider: FC<UsersTabContextProviderProps> = ({ children }) => {
|
||||||
|
const state = useUsersTabContextState();
|
||||||
|
return (
|
||||||
|
<UsersTabContext.Provider value={state}>
|
||||||
|
{children}
|
||||||
|
</UsersTabContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUsersTabContext = () => {
|
||||||
|
const context = useContext(UsersTabContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
"useUsersTabContext must be used within a UsersTabContextProvider",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user