feat: nested department sections, attaching department sections in the user editor

This commit is contained in:
2025-01-19 12:02:35 +04:00
parent 58ab96bdb1
commit 9dab596e87
34 changed files with 1023 additions and 488 deletions

View File

@@ -42,6 +42,7 @@
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"mantine-contextmenu": "^7.12.2",
"mantine-datatable": "^7.15.1",
"mantine-form-zod-resolver": "^1.1.0",
"mantine-react-table": "^2.0.0-beta.5",
"phone": "^3.1.49",

View File

@@ -186,6 +186,7 @@ export type { DeleteUserResponse } from './models/DeleteUserResponse';
export type { DepartmentBaseSchema } from './models/DepartmentBaseSchema';
export type { DepartmentSchema } from './models/DepartmentSchema';
export type { DepartmentSectionBaseSchema } from './models/DepartmentSectionBaseSchema';
export type { DepartmentSectionBriefSchema } from './models/DepartmentSectionBriefSchema';
export type { DepartmentSectionSchema } from './models/DepartmentSectionSchema';
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
@@ -215,6 +216,7 @@ export type { GetClientMarketplacesResponse } from './models/GetClientMarketplac
export type { GetDealBillById } from './models/GetDealBillById';
export type { GetDealProductsBarcodesPdfRequest } from './models/GetDealProductsBarcodesPdfRequest';
export type { GetDealProductsBarcodesPdfResponse } from './models/GetDealProductsBarcodesPdfResponse';
export type { GetDepartmentSectionsResponse } from './models/GetDepartmentSectionsResponse';
export type { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
export type { GetManagersResponse } from './models/GetManagersResponse';
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
@@ -350,10 +352,14 @@ export type { UpdateTransactionResponse } from './models/UpdateTransactionRespon
export type { UpdateTransactionSchema } from './models/UpdateTransactionSchema';
export type { UpdateTransactionTagRequest } from './models/UpdateTransactionTagRequest';
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 { UpdateUserResponse } from './models/UpdateUserResponse';
export type { UploadPassportImageResponse } from './models/UploadPassportImageResponse';
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 { UserUpdate } from './models/UserUpdate';
export type { ValidationError } from './models/ValidationError';

View File

@@ -5,5 +5,6 @@
export type AddUserRequest = {
userId: number;
sectionId: number;
isChief: boolean;
};

View File

@@ -4,6 +4,7 @@
/* eslint-disable */
export type DepartmentSectionBaseSchema = {
name: string;
departmentId: number;
departmentId: (number | null);
parentDepartmentSectionId: (number | null);
};

View 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;
};

View File

@@ -2,11 +2,13 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { UserSchema } from './UserSchema';
import type { UserDepartmentSectionSchema } from './UserDepartmentSectionSchema';
export type DepartmentSectionSchema = {
name: string;
departmentId: number;
departmentId: (number | null);
parentDepartmentSectionId: (number | null);
id: number;
users?: Array<UserSchema>;
users?: Array<UserDepartmentSectionSchema>;
sections?: Array<DepartmentSectionSchema>;
};

View 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>;
};

View 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>;
};

View File

@@ -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;
};

View 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;
};

View 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;
};

View File

@@ -6,6 +6,7 @@ import type { PassportImageSchema } from './PassportImageSchema';
import type { PayRateSchema } from './PayRateSchema';
import type { PositionSchema } from './PositionSchema';
import type { RoleSchema } from './RoleSchema';
import type { UserDepartmentSectionsSchema } from './UserDepartmentSectionsSchema';
export type UserSchema = {
telegramId: number;
phoneNumber?: (string | null);
@@ -24,5 +25,6 @@ export type UserSchema = {
id: number;
role: RoleSchema;
position?: (PositionSchema | null);
departmentSections?: (Array<UserDepartmentSectionsSchema> | null);
};

View File

@@ -13,6 +13,7 @@ import type { DeleteDepartmentSectionResponse } from '../models/DeleteDepartment
import type { DeleteUserRequest } from '../models/DeleteUserRequest';
import type { DeleteUserResponse } from '../models/DeleteUserResponse';
import type { GetAvailableUsersForDepartmentSectionResponse } from '../models/GetAvailableUsersForDepartmentSectionResponse';
import type { GetDepartmentSectionsResponse } from '../models/GetDepartmentSectionsResponse';
import type { GetDepartmentsResponse } from '../models/GetDepartmentsResponse';
import type { UpdateDepartmentRequest } from '../models/UpdateDepartmentRequest';
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
* @returns CreateDepartmentSectionResponse Successful Response

View File

@@ -7,6 +7,8 @@ import type { CreateUserRequest } from '../models/CreateUserRequest';
import type { CreateUserResponse } from '../models/CreateUserResponse';
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
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 { UpdateUserResponse } from '../models/UpdateUserResponse';
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
* @returns CreateUserResponse Successful Response

View File

@@ -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;

View File

@@ -1,26 +1,37 @@
import { CRUDTableProps } from "../../../../types/CRUDTable.tsx";
import { UserSchema } from "../../../../client";
import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
import { FC } from "react";
import { ActionIcon, Button, Flex, rem, Text, Tooltip } from "@mantine/core";
import { useUsersTableColumns } from "./columns.tsx";
import { IconEdit, IconQrcode, IconTrash } from "@tabler/icons-react";
import { modals } from "@mantine/modals";
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 {
usersCrud: {
items,
onChange,
onCreate,
onDelete,
},
refetch,
} = useUsersTabContext();
const onEditClick = (user: UserSchema) => {
if (!onChange) return;
console.log(user);
modals.openContextModal({
modal: "userFormModal",
title: "Редактирование пользователя",
withCloseButton: false,
innerProps: {
onChange: onChange,
onChange,
element: user,
refetch,
},
size: "md",
});
@@ -29,7 +40,6 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
if (!onDelete) return;
modals.openConfirmModal({
title: "Удаление пользователя",
// centered: true,
children: (
<Text size="sm">
Вы уверены что хотите удалить пользователя {user.firstName}{" "}
@@ -48,7 +58,8 @@ const UsersTable: FC<Props> = ({ items, onChange, onDelete, onCreate }) => {
title: "Редактирование пользователя",
withCloseButton: false,
innerProps: {
onCreate: onCreate,
onCreate,
refetch,
},
size: "md",
});

View File

@@ -1,193 +1,54 @@
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 { 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";
import PayRateSelect from "../../../../components/Selects/PayRateSelect/PayRateSelect.tsx";
import { BaseFormInputProps } from "../../../../types/utils.ts";
import PassportImageDropzone from "../../components/PassportImageDropzone/PassportImageDropzone.tsx";
import { Stack } from "@mantine/core";
import CommonTab from "./tabs/CommonTab/CommonTab.tsx";
import DepartmentSectionsTab from "./tabs/DepartmentSectionsTab/DepartmentSectionsTab.tsx";
import { ModalTab, UsersModalTabSegmentedControl } from "./components/UsersModalTabSegmentedControl.tsx";
import { useState } from "react";
type PropsRefetch = {
refetch: () => void;
}
type Props = CreateEditFormProps<UserSchema> & PropsRefetch;
type Props = CreateEditFormProps<UserSchema>;
const UserFormModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const isEditing = "element" in innerProps;
const initialValues = isEditing
? innerProps.element
: {
isAdmin: false,
isBlocked: false,
isDeleted: false,
comment: "",
roleKey: UserRoleEnum.USER,
};
const [modalTab, setModalTab] = useState<ModalTab>(ModalTab.COMMON);
const closeModal = () => {
context.closeContextModal(id);
}
if (!isEditing) {
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 (
<BaseFormModal
form={form}
closeOnSubmit
onClose={() => context.closeContextModal(id)}
{...innerProps}>
<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={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>
</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>
<Stack>
<UsersModalTabSegmentedControl
value={modalTab.toString()}
onChange={event => setModalTab(parseInt(event))}
/>
{modalTab === ModalTab.COMMON ? (
<CommonTab
closeModal={closeModal}
formProps={innerProps}
/>
) : (
<DepartmentSectionsTab
closeModal={closeModal}
user={innerProps.element}
refetch={innerProps.refetch}
/>
)}
</Stack>
);
};

View File

@@ -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}
/>
);
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,24 +1,15 @@
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";
const DepartmentButtons = () => {
const {
departmentIds,
toggleAllDepartmentIds,
onCreateDepartmentClick,
} = useDepartmentContext();
return (
<Group gap={"md"}>
<Button
variant={"default"}
onClick={toggleAllDepartmentIds}
>
{departmentIds.length > 0 ? <IconChevronsUp /> : <IconChevronsDown />}
</Button>
<Button
variant={"default"}
onClick={() => onCreateDepartmentClick(false)}

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
import { Stack } from "@mantine/core";
import DepartmentsTree from "./DepartmentsTree.tsx";
import DepartmentsTree from "./DepartmentsTree/DepartmentsTree.tsx";
import DepartmentButtons from "./DepartmentButtons.tsx";
import { DepartmentContextProvider } from "../contexts/DepartmentContext.tsx";

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -1,63 +1,69 @@
import { BaseTable } from "../../../../../components/BaseTable/BaseTable.tsx";
import useUsersTableColumns from "../hooks/useUsersTableColumns.tsx";
import { DepartmentService, UserSchema } from "../../../../../client";
import { ActionIcon, Flex, Tooltip } from "@mantine/core";
import { IconTrash } from "@tabler/icons-react";
import { MRT_TableOptions } from "mantine-react-table";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import { useDepartmentContext } from "../contexts/DepartmentContext.tsx";
import { UserDepartmentSectionSchema } from "../../../../../client";
import { DataTable } from "mantine-datatable";
import "mantine-datatable/styles.css";
import { Box } from "@mantine/core";
import { IconChevronRight, IconUsers } from "@tabler/icons-react";
import clsx from "clsx";
import classes from "./DepartmentsTree/DepartmentsTree.module.css";
import { useState } from "react";
type Props = {
users: UserSchema[];
users: UserDepartmentSectionSchema[];
sectionId: number;
level: number;
}
const UsersTable = ({ users, sectionId }: Props) => {
const columns = useUsersTableColumns();
const { fetchDepartments } = useDepartmentContext();
const UsersTable = ({ users, sectionId, level }: Props) => {
const columns = useUsersTableColumns({ sectionId });
const [userIds, setUserIds] = useState<number[]>([1]);
const onDeleteUserClick = (userId: number) => {
DepartmentService.deleteUser({
requestBody: {
sectionId,
userId,
}
})
.then(({ok, message}) => {
fetchDepartments();
if (ok) return;
notifications.error({ message });
})
.catch(err => console.log(err));
}
const header = [
{
id: 1,
title: "Пользователи",
},
];
return (
<BaseTable
data={users}
columns={columns}
restProps={
<DataTable
noHeader
withColumnBorders
className={clsx(classes.usersTableBorder)}
columns={[
{
enableSorting: false,
enableColumnActions: false,
enableRowActions: true,
positionActionsColumn: "last",
renderRowActions: ({ row }) => (
<Flex gap="md">
<Tooltip label="Удалить">
<ActionIcon
onClick={() => onDeleteUserClick(row.original.id)}
variant={"default"}>
<IconTrash />
</ActionIcon>
</Tooltip>
</Flex>
accessor: "title",
noWrap: true,
render: ({ id, title }) => (
<Box component="span" ml={20 * level}>
<IconChevronRight
className={clsx(classes.icon, classes.expandIcon, {
[classes.expandIconRotated]: userIds.includes(id),
})}
/>
<IconUsers className={classes.icon} />
<span>{title}</span>
</Box>
),
} as MRT_TableOptions<UserSchema>
}
},
]}
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;

View File

@@ -12,14 +12,11 @@ import useDepartmentSectionCrud from "../hooks/useDepartmentSectionCrud.tsx";
type DepartmentContextState = {
departments: DepartmentSchema[],
departmentIds: string[],
setDepartmentIds: React.Dispatch<React.SetStateAction<string[]>>,
fetchDepartments: () => void,
onCreateDepartmentClick: (isSection: boolean, element?: DepartmentSectionBaseSchema) => void,
onUpdateDepartmentClick: (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => void,
onDeleteDepartmentClick: (element: DepartmentSchema, isSection: boolean) => void,
onAddUserClick: (departmentSection: DepartmentSectionSchema) => void,
toggleAllDepartmentIds: () => void,
};
const DepartmentContext = createContext<DepartmentContextState | undefined>(
@@ -28,15 +25,11 @@ const DepartmentContext = createContext<DepartmentContextState | undefined>(
const useDepartmentContextState = () => {
const [departments, setDepartments] = useState<DepartmentSchema[]>([]);
const [departmentIds, setDepartmentIds] = useState<string[]>([]);
const fetchDepartments = () => {
DepartmentService.getDepartments()
.then(res => {
setDepartments(res.departments);
if (departmentIds.length === 0) {
setDepartmentIds(res.departments.map(dep => dep.id.toString()));
}
})
.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 ? "Создание отдела" : "Создание департамента";
openDepartmentModal(title, isSection, element);
};
const onUpdateDepartmentClick = (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => {
const onUpdateDepartmentClick = (
element: DepartmentSchema | DepartmentSectionSchema,
isSection: boolean,
) => {
const title: string = isSection ? "Редактирование отдела" : "Редактирование департамента";
openDepartmentModal(title, isSection, element);
};
const onDeleteDepartmentClick = (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => {
const onDeleteDepartmentClick = (
element: DepartmentSchema | DepartmentSectionSchema,
isSection: boolean,
) => {
modals.openConfirmModal({
title: "Удаление " + (isSection ? "отдела" : "департамента"),
children: (
@@ -90,7 +92,7 @@ const useDepartmentContextState = () => {
if (isSection) {
departmentSectionsCrud.onDelete(element as DepartmentSectionSchema);
} else {
departmentsCrud.onDelete(element);
departmentsCrud.onDelete(element as DepartmentSchema);
}
},
});
@@ -106,26 +108,15 @@ const useDepartmentContextState = () => {
fetchDepartments,
},
});
}
const toggleAllDepartmentIds = () => {
if (departmentIds.length > 0) {
setDepartmentIds([]);
return;
}
setDepartmentIds(departments.map(department => department.id.toString()));
}
};
return {
departments,
departmentIds,
setDepartmentIds,
fetchDepartments,
onCreateDepartmentClick,
onUpdateDepartmentClick,
onDeleteDepartmentClick,
onAddUserClick,
toggleAllDepartmentIds,
};
};

View File

@@ -1,31 +1,81 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { UserSchema } from "../../../../../client";
import { IconCheck, IconTrash, IconX } from "@tabler/icons-react";
import { DepartmentService, UserDepartmentSectionSchema } 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 = () => {
return useMemo<MRT_ColumnDef<UserSchema>[]>(
() => [
{
header: "ФИО",
Cell: ({ row }) =>
`${row.original.secondName} ${row.original.firstName} ${row.original.patronymic}`,
const useUsersTableColumns = ({ sectionId }: Props) => {
const { fetchDepartments } = useDepartmentContext();
const onDeleteUserClick = (userId: number) => {
DepartmentService.deleteUser({
requestBody: {
sectionId,
userId,
},
{
accessorKey: "role.name",
header: "Роль",
},
{
accessorKey: "position.name",
header: "Должность",
},
{
accessorKey: "comment",
header: "Доп. информация",
}
],
[],
);
})
.then(({ ok, message }) => {
fetchDepartments();
if (ok) return;
notifications.error({ message });
})
.catch(err => console.log(err));
};
return [
{
title: "ФИО",
accessor: "user.secondName",
render: (userData) => (
<Box component="span">
<span>{userData.user.secondName} {userData.user.firstName} {userData.user.patronymic}</span>
</Box>
),
},
{
accessor: "user.role.name",
title: "Роль",
},
{
accessor: "user.position.name",
title: "Должность",
},
{
accessor: "user.comment",
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;

View File

@@ -1,6 +1,6 @@
import { useForm } from "@mantine/form";
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 AddUserToDepartmentModalForm from "../types/AddUserToDepartmentModalForm.tsx";
import { notifications } from "../../../../../shared/lib/notifications.ts";
@@ -12,11 +12,15 @@ type Props = {
}
const AddUserToDepartmentModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const form = useForm<Partial<AddUserToDepartmentModalForm>>({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const initialValues = {
isChief: false,
};
const form = useForm<AddUserToDepartmentModalForm>({
initialValues,
validate: {
user: user => !user && "Необходимо выбрать пользователя",
},
@@ -28,7 +32,8 @@ const AddUserToDepartmentModal = ({
requestBody: {
userId: form.values.user.id,
sectionId: innerProps.departmentSection.id,
}
isChief: form.values.isChief,
},
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
@@ -50,6 +55,10 @@ const AddUserToDepartmentModal = ({
{...form.getInputProps("user")}
sectionId={innerProps.departmentSection.id}
/>
<Checkbox
label={"Является руководителем"}
{...form.getInputProps("isChief", { type: "checkbox" })}
/>
<Button
variant={"default"}
type={"submit"}

View File

@@ -1,7 +1,8 @@
import { UserSchema } from "../../../../../client";
type AddUserToDepartmentModalForm = {
user: UserSchema;
user?: UserSchema;
isChief: boolean;
}
export default AddUserToDepartmentModalForm;

View File

@@ -1,50 +1,11 @@
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";
import { UsersTabContextProvider } from "./contexts/UsersTabContext.tsx";
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 (
<UsersTable
items={users}
onChange={onChange}
onDelete={onDelete}
onCreate={onCreate}
/>
<UsersTabContextProvider>
<UsersTable />
</UsersTabContextProvider>
);
};

View 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;
};