feat: departments and department sections

This commit is contained in:
2024-12-17 12:43:33 +04:00
parent 43bba73f06
commit 31a959236c
40 changed files with 1220 additions and 11 deletions

View File

@@ -7,6 +7,8 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } from './core/OpenAPI';
export type { AddUserRequest } from './models/AddUserRequest';
export type { AddUserResponse } from './models/AddUserResponse';
export type { AuthLoginRequest } from './models/AuthLoginRequest';
export type { AuthLoginResponse } from './models/AuthLoginResponse';
export type { BarcodeAttributeSchema } from './models/BarcodeAttributeSchema';
@@ -50,6 +52,10 @@ export type { CreateBoxInDealSchema } from './models/CreateBoxInDealSchema';
export type { CreateBoxInPalletSchema } from './models/CreateBoxInPalletSchema';
export type { CreateDealBillRequest } from './models/CreateDealBillRequest';
export type { CreateDealBillResponse } from './models/CreateDealBillResponse';
export type { CreateDepartmentRequest } from './models/CreateDepartmentRequest';
export type { CreateDepartmentResponse } from './models/CreateDepartmentResponse';
export type { CreateDepartmentSectionRequest } from './models/CreateDepartmentSectionRequest';
export type { CreateDepartmentSectionResponse } from './models/CreateDepartmentSectionResponse';
export type { CreateMarketplaceRequest } from './models/CreateMarketplaceRequest';
export type { CreateMarketplaceResponse } from './models/CreateMarketplaceResponse';
export type { CreatePalletResponse } from './models/CreatePalletResponse';
@@ -139,6 +145,8 @@ export type { DealUpdateServiceQuantityResponse } from './models/DealUpdateServi
export type { DealUpdateServiceRequest } from './models/DealUpdateServiceRequest';
export type { DealUpdateServiceResponse } from './models/DealUpdateServiceResponse';
export type { DeleteBoxResponse } from './models/DeleteBoxResponse';
export type { DeleteDepartmentResponse } from './models/DeleteDepartmentResponse';
export type { DeleteDepartmentSectionResponse } from './models/DeleteDepartmentSectionResponse';
export type { DeleteMarketplaceRequest } from './models/DeleteMarketplaceRequest';
export type { DeleteMarketplaceResponse } from './models/DeleteMarketplaceResponse';
export type { DeletePalletResponse } from './models/DeletePalletResponse';
@@ -156,6 +164,12 @@ export type { DeleteShippingWarehouseRequest } from './models/DeleteShippingWare
export type { DeleteShippingWarehouseResponse } from './models/DeleteShippingWarehouseResponse';
export type { DeleteTransactionResponse } from './models/DeleteTransactionResponse';
export type { DeleteTransactionTagResponse } from './models/DeleteTransactionTagResponse';
export type { DeleteUserRequest } from './models/DeleteUserRequest';
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 { DepartmentSectionSchema } from './models/DepartmentSectionSchema';
export type { FinishPauseByShiftIdResponse } from './models/FinishPauseByShiftIdResponse';
export type { FinishPauseByUserIdResponse } from './models/FinishPauseByUserIdResponse';
export type { FinishShiftByIdResponse } from './models/FinishShiftByIdResponse';
@@ -175,6 +189,7 @@ export type { GetAllTransactionsRequest } from './models/GetAllTransactionsReque
export type { GetAllTransactionsResponse } from './models/GetAllTransactionsResponse';
export type { GetAllTransactionTagsResponse } from './models/GetAllTransactionTagsResponse';
export type { GetAllUsersResponse } from './models/GetAllUsersResponse';
export type { GetAvailableUsersForDepartmentSectionResponse } from './models/GetAvailableUsersForDepartmentSectionResponse';
export type { GetBarcodeTemplateByIdRequest } from './models/GetBarcodeTemplateByIdRequest';
export type { GetBarcodeTemplateByIdResponse } from './models/GetBarcodeTemplateByIdResponse';
export type { GetClientMarketplacesRequest } from './models/GetClientMarketplacesRequest';
@@ -182,6 +197,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 { GetDepartmentsResponse } from './models/GetDepartmentsResponse';
export type { GetManagersResponse } from './models/GetManagersResponse';
export type { GetPaymentRecordsResponse } from './models/GetPaymentRecordsResponse';
export type { GetProductBarcodePdfRequest } from './models/GetProductBarcodePdfRequest';
@@ -271,6 +287,10 @@ export type { TransactionTagSchema } from './models/TransactionTagSchema';
export type { UpdateBoxRequest } from './models/UpdateBoxRequest';
export type { UpdateBoxResponse } from './models/UpdateBoxResponse';
export type { UpdateBoxSchema } from './models/UpdateBoxSchema';
export type { UpdateDepartmentRequest } from './models/UpdateDepartmentRequest';
export type { UpdateDepartmentResponse } from './models/UpdateDepartmentResponse';
export type { UpdateDepartmentSectionRequest } from './models/UpdateDepartmentSectionRequest';
export type { UpdateDepartmentSectionResponse } from './models/UpdateDepartmentSectionResponse';
export type { UpdateMarketplaceRequest } from './models/UpdateMarketplaceRequest';
export type { UpdateMarketplaceResponse } from './models/UpdateMarketplaceResponse';
export type { UpdatePayRateRequest } from './models/UpdatePayRateRequest';
@@ -307,6 +327,7 @@ export { BarcodeService } from './services/BarcodeService';
export { BillingService } from './services/BillingService';
export { ClientService } from './services/ClientService';
export { DealService } from './services/DealService';
export { DepartmentService } from './services/DepartmentService';
export { MarketplaceService } from './services/MarketplaceService';
export { PayrollService } from './services/PayrollService';
export { PositionService } from './services/PositionService';

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type AddUserRequest = {
userId: number;
sectionId: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type AddUserResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DepartmentBaseSchema } from './DepartmentBaseSchema';
export type CreateDepartmentRequest = {
department: DepartmentBaseSchema;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CreateDepartmentResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DepartmentSectionBaseSchema } from './DepartmentSectionBaseSchema';
export type CreateDepartmentSectionRequest = {
section: DepartmentSectionBaseSchema;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type CreateDepartmentSectionResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeleteDepartmentResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeleteDepartmentSectionResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeleteUserRequest = {
userId: number;
sectionId: number;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DeleteUserResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DepartmentBaseSchema = {
name: string;
};

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DepartmentSectionSchema } from './DepartmentSectionSchema';
export type DepartmentSchema = {
name: string;
id: number;
sections?: Array<DepartmentSectionSchema>;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type DepartmentSectionBaseSchema = {
name: string;
departmentId: number;
};

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { UserSchema } from './UserSchema';
export type DepartmentSectionSchema = {
name: string;
departmentId: number;
id: number;
users?: Array<UserSchema>;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { UserSchema } from './UserSchema';
export type GetAvailableUsersForDepartmentSectionResponse = {
users: Array<UserSchema>;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DepartmentSchema } from './DepartmentSchema';
export type GetDepartmentsResponse = {
departments: Array<DepartmentSchema>;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DepartmentSchema } from './DepartmentSchema';
export type UpdateDepartmentRequest = {
department: DepartmentSchema;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateDepartmentResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { DepartmentSectionSchema } from './DepartmentSectionSchema';
export type UpdateDepartmentSectionRequest = {
section: DepartmentSectionSchema;
};

View File

@@ -0,0 +1,9 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type UpdateDepartmentSectionResponse = {
ok: boolean;
message: string;
};

View File

@@ -0,0 +1,219 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AddUserRequest } from '../models/AddUserRequest';
import type { AddUserResponse } from '../models/AddUserResponse';
import type { CreateDepartmentRequest } from '../models/CreateDepartmentRequest';
import type { CreateDepartmentResponse } from '../models/CreateDepartmentResponse';
import type { CreateDepartmentSectionRequest } from '../models/CreateDepartmentSectionRequest';
import type { CreateDepartmentSectionResponse } from '../models/CreateDepartmentSectionResponse';
import type { DeleteDepartmentResponse } from '../models/DeleteDepartmentResponse';
import type { DeleteDepartmentSectionResponse } from '../models/DeleteDepartmentSectionResponse';
import type { DeleteUserRequest } from '../models/DeleteUserRequest';
import type { DeleteUserResponse } from '../models/DeleteUserResponse';
import type { GetAvailableUsersForDepartmentSectionResponse } from '../models/GetAvailableUsersForDepartmentSectionResponse';
import type { GetDepartmentsResponse } from '../models/GetDepartmentsResponse';
import type { UpdateDepartmentRequest } from '../models/UpdateDepartmentRequest';
import type { UpdateDepartmentResponse } from '../models/UpdateDepartmentResponse';
import type { UpdateDepartmentSectionRequest } from '../models/UpdateDepartmentSectionRequest';
import type { UpdateDepartmentSectionResponse } from '../models/UpdateDepartmentSectionResponse';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class DepartmentService {
/**
* Get Departments
* @returns GetDepartmentsResponse Successful Response
* @throws ApiError
*/
public static getDepartments(): CancelablePromise<GetDepartmentsResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/department/',
});
}
/**
* Create Department
* @returns CreateDepartmentResponse Successful Response
* @throws ApiError
*/
public static createDepartment({
requestBody,
}: {
requestBody: CreateDepartmentRequest,
}): CancelablePromise<CreateDepartmentResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/department/',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Update Department
* @returns UpdateDepartmentResponse Successful Response
* @throws ApiError
*/
public static updateDepartment({
requestBody,
}: {
requestBody: UpdateDepartmentRequest,
}): CancelablePromise<UpdateDepartmentResponse> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/department/',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Delete Department
* @returns DeleteDepartmentResponse Successful Response
* @throws ApiError
*/
public static deleteDepartment({
departmentId,
}: {
departmentId: number,
}): CancelablePromise<DeleteDepartmentResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/department/{department_id}',
path: {
'department_id': departmentId,
},
errors: {
422: `Validation Error`,
},
});
}
/**
* Create Section
* @returns CreateDepartmentSectionResponse Successful Response
* @throws ApiError
*/
public static createSection({
requestBody,
}: {
requestBody: CreateDepartmentSectionRequest,
}): CancelablePromise<CreateDepartmentSectionResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/department/section',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Update Section
* @returns UpdateDepartmentSectionResponse Successful Response
* @throws ApiError
*/
public static updateSection({
requestBody,
}: {
requestBody: UpdateDepartmentSectionRequest,
}): CancelablePromise<UpdateDepartmentSectionResponse> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/department/section',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Delete Section
* @returns DeleteDepartmentSectionResponse Successful Response
* @throws ApiError
*/
public static deleteSection({
sectionId,
}: {
sectionId: number,
}): CancelablePromise<DeleteDepartmentSectionResponse> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/department/section/{section_id}',
path: {
'section_id': sectionId,
},
errors: {
422: `Validation Error`,
},
});
}
/**
* Get Available Users For Department Section
* @returns GetAvailableUsersForDepartmentSectionResponse Successful Response
* @throws ApiError
*/
public static getAvailableUsersForSection({
sectionId,
}: {
sectionId: number,
}): CancelablePromise<GetAvailableUsersForDepartmentSectionResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/department/users/{section_id}',
path: {
'section_id': sectionId,
},
errors: {
422: `Validation Error`,
},
});
}
/**
* Add User
* @returns AddUserResponse Successful Response
* @throws ApiError
*/
public static addUser({
requestBody,
}: {
requestBody: AddUserRequest,
}): CancelablePromise<AddUserResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/department/users',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Delete User
* @returns DeleteUserResponse Successful Response
* @throws ApiError
*/
public static deleteUser({
requestBody,
}: {
requestBody: DeleteUserRequest,
}): CancelablePromise<DeleteUserResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/department/users/delete',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
}

View File

@@ -25,6 +25,9 @@ import ScanningModal from "./ScanningModal/ScanningModal.tsx";
import TransactionFormModal from "../pages/AdminPage/tabs/Transactions/modals/TransactionFormModal.tsx";
import TransactionTagsModal from "../pages/AdminPage/tabs/Transactions/modals/TransactionTagsModal.tsx";
import ShippingProductModal from "../pages/LeadsPage/tabs/ShippingTab/modals/ShippingProductModal.tsx";
import DepartmentModal from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/DepartmentModal.tsx";
import AddUserToDepartmentModal
from "../pages/AdminPage/tabs/OrganizationalStructureTab/modals/AddUserToDepartmentModal.tsx";
export const modals = {
enterDeadline: EnterDeadlineModal,
@@ -54,4 +57,6 @@ export const modals = {
transactionFormModal: TransactionFormModal,
transactionTagsModal: TransactionTagsModal,
shippingProductModal: ShippingProductModal,
departmentModal: DepartmentModal,
addUserToDepartmentModal: AddUserToDepartmentModal,
};

View File

@@ -2,14 +2,13 @@ import styles from "./AdminPage.module.css";
import { Tabs } from "@mantine/core";
import PageBlock from "../../components/PageBlock/PageBlock.tsx";
import {
IconBriefcase,
IconTopologyStar3,
IconCalendarUser,
IconCoins,
IconCurrencyDollar,
IconQrcode,
IconUser,
} from "@tabler/icons-react";
import RolesAndPositionsTab from "./tabs/RolesAndPositions/RolesAndPositionsTab.tsx";
import UsersTab from "./tabs/Users/UsersTab.tsx";
import { motion } from "framer-motion";
import FinancesTab from "./tabs/Finances/FinancesTab.tsx";
@@ -18,6 +17,7 @@ import { WorkShiftsTab } from "./tabs/WorkShifts/WorkShiftsTab.tsx";
import { TransactionsTab } from "./tabs/Transactions/TransactionsTab.tsx";
import { useSelector } from "react-redux";
import { RootState } from "../../redux/store.ts";
import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
const AdminPage = () => {
const userRole = useSelector((state: RootState) => state.auth.role);
@@ -45,8 +45,8 @@ const AdminPage = () => {
)}
<Tabs.Tab
value={"rolesAndPositions"}
leftSection={<IconBriefcase />}>
Должности
leftSection={<IconTopologyStar3 />}>
Организационная структура
</Tabs.Tab>
{isAdmin && (
<Tabs.Tab
@@ -81,7 +81,7 @@ const AdminPage = () => {
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}>
<RolesAndPositionsTab />
<OrganizationalStructureTab />
</motion.div>
</Tabs.Panel>
<Tabs.Panel value={"finances"}>

View File

@@ -0,0 +1,48 @@
import { Tabs } from "@mantine/core";
import { IconBriefcase, IconSitemap } from "@tabler/icons-react";
import { motion } from "framer-motion";
import RolesAndPositions from "./components/RolesAndPositions.tsx";
import Departments from "./components/Departments.tsx";
const OrganizationalStructureTab = () => {
return (
<Tabs
keepMounted={false}
defaultValue={"departments"}
color={"gray.6"}>
<Tabs.List
justify={"center"}
grow>
<Tabs.Tab
value={"departments"}
leftSection={<IconSitemap />}>
Департаменты и отделы
</Tabs.Tab>
<Tabs.Tab
value={"positions"}
leftSection={<IconBriefcase />}>
Должности
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value={"departments"}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}>
<Departments />
</motion.div>
</Tabs.Panel>
<Tabs.Panel value={"positions"}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}>
<RolesAndPositions />
</motion.div>
</Tabs.Panel>
</Tabs>
);
};
export default OrganizationalStructureTab;

View File

@@ -0,0 +1,35 @@
import { Button, Group, rem } from "@mantine/core";
import { IconChevronsDown, IconChevronsUp, 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)}
>
<Group gap={rem(10)}>
<IconPlus />
Добавить департамент
</Group>
</Button>
</Group>
);
};
export default DepartmentButtons;

View File

@@ -0,0 +1,17 @@
import { Stack } from "@mantine/core";
import DepartmentsTree from "./DepartmentsTree.tsx";
import DepartmentButtons from "./DepartmentButtons.tsx";
import { DepartmentContextProvider } from "../contexts/DepartmentContext.tsx";
const Departments = () => {
return (
<DepartmentContextProvider>
<Stack mt={"md"}>
<DepartmentButtons />
<DepartmentsTree />
</Stack>
</DepartmentContextProvider>
);
};
export default Departments;

View File

@@ -0,0 +1,139 @@
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

@@ -1,9 +1,9 @@
import PositionsTable from "../../components/PositionsTable/PositionsTable.tsx";
import usePositionsList from "../../hooks/usePositionsList.tsx";
import { PositionSchema, PositionService } from "../../../../client";
import { notifications } from "../../../../shared/lib/notifications.ts";
import PositionsTable from "../../../components/PositionsTable/PositionsTable.tsx";
import usePositionsList from "../../../hooks/usePositionsList.tsx";
import { PositionSchema, PositionService } from "../../../../../client";
import { notifications } from "../../../../../shared/lib/notifications.ts";
const RolesAndPositionsTab = () => {
const RolesAndPositions = () => {
const { objects: positions, refetch } = usePositionsList();
const onCreate = (position: PositionSchema) => {
PositionService.createPosition({
@@ -36,4 +36,4 @@ const RolesAndPositionsTab = () => {
/>
);
};
export default RolesAndPositionsTab;
export default RolesAndPositions;

View File

@@ -0,0 +1,27 @@
import { FC } from "react";
import ObjectSelect, { ObjectSelectProps } from "../../../../../components/ObjectSelect/ObjectSelect.tsx";
import { UserSchema } from "../../../../../client";
import useAvailableUsersList from "../hooks/useAvailableUsersList.tsx";
type SectionData = {
sectionId: number;
}
type Props = SectionData & Omit<
ObjectSelectProps<UserSchema | null>,
"data" | "getValueFn" | "getLabelFn"
>;
const UserForDepartmentSelect: FC<Props> = props => {
const { objects: users } = useAvailableUsersList({ sectionId: props.sectionId });
return (
<ObjectSelect
data={users}
getLabelFn={(user: UserSchema) => `${user.firstName} ${user.secondName}`}
getValueFn={(user: UserSchema) => user.id.toString()}
{...props}
/>
);
};
export default UserForDepartmentSelect;

View File

@@ -0,0 +1,63 @@
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";
type Props = {
users: UserSchema[];
sectionId: number;
}
const UsersTable = ({ users, sectionId }: Props) => {
const columns = useUsersTableColumns();
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 (
<BaseTable
data={users}
columns={columns}
restProps={
{
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>
),
} as MRT_TableOptions<UserSchema>
}
/>
)
}
export default UsersTable;

View File

@@ -0,0 +1,153 @@
import React, { createContext, FC, useContext, useEffect, useState } from "react";
import {
DepartmentSchema,
DepartmentSectionBaseSchema,
DepartmentSectionSchema,
DepartmentService,
} from "../../../../../client";
import { modals } from "@mantine/modals";
import { Text } from "@mantine/core";
import useDepartmentCrud from "../hooks/useDepartmentCrud.tsx";
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>(
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));
};
const departmentsCrud = useDepartmentCrud({ fetchDepartments });
const departmentSectionsCrud = useDepartmentSectionCrud({ fetchDepartments });
useEffect(() => {
fetchDepartments();
}, []);
const openDepartmentModal = (
title: string,
isDepartmentSection: boolean,
element?: DepartmentSchema | DepartmentSectionSchema | DepartmentSectionBaseSchema,
) => {
modals.openContextModal({
modal: "departmentModal",
title,
withCloseButton: false,
innerProps: {
departmentsCrud,
departmentSectionsCrud,
isDepartmentSection,
element,
},
});
};
const onCreateDepartmentClick = (isSection: boolean, element?: DepartmentSectionBaseSchema) => {
const title: string = isSection ? "Создание отдела" : "Создание департамента";
openDepartmentModal(title, isSection, element);
};
const onUpdateDepartmentClick = (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => {
const title: string = isSection ? "Редактирование отдела" : "Редактирование департамента";
openDepartmentModal(title, isSection, element);
};
const onDeleteDepartmentClick = (element: DepartmentSchema | DepartmentSectionSchema, isSection: boolean) => {
modals.openConfirmModal({
title: "Удаление " + (isSection ? "отдела" : "департамента"),
children: (
<Text size="sm">
Вы уверены что хотите удалить {isSection ? "отдел" : "департамент"} {element.name}?
</Text>
),
labels: { confirm: "Да", cancel: "Нет" },
confirmProps: { color: "red" },
onConfirm: () => {
if (isSection) {
departmentSectionsCrud.onDelete(element as DepartmentSectionSchema);
} else {
departmentsCrud.onDelete(element);
}
},
});
};
const onAddUserClick = (departmentSection: DepartmentSectionSchema) => {
modals.openContextModal({
modal: "addUserToDepartmentModal",
title: `Добавление пользователя в отдел ${departmentSection.name}`,
withCloseButton: false,
innerProps: {
departmentSection,
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,
};
};
type DepartmentContextProviderProps = {
children: React.ReactNode;
};
export const DepartmentContextProvider: FC<DepartmentContextProviderProps> = ({ children }) => {
const state = useDepartmentContextState();
return (
<DepartmentContext.Provider value={state}>
{children}
</DepartmentContext.Provider>
);
};
export const useDepartmentContext = () => {
const context = useContext(DepartmentContext);
if (!context) {
throw new Error(
"useDepartmentContext must be used within a DepartmentContextProvider",
);
}
return context;
};

View File

@@ -0,0 +1,16 @@
import { DepartmentService } from "../../../../../client";
import ObjectList from "../../../../../hooks/objectList.tsx";
type Props = {
sectionId: number;
}
const useAvailableUsersList = ({ sectionId }: Props) =>
ObjectList({
queryFn: () => DepartmentService.getAvailableUsersForSection({ sectionId }),
getObjectsFn: response => response.users,
queryKey: "getAvailableUsersForDepartmentSection",
});
export default useAvailableUsersList;

View File

@@ -0,0 +1,49 @@
import { useCRUD } from "../../../../../hooks/useCRUD.tsx";
import { DepartmentBaseSchema, DepartmentSchema, DepartmentService } from "../../../../../client";
import { notifications } from "../../../../../shared/lib/notifications.ts";
export type DepartmentCrud = {
onCreate: (element: DepartmentBaseSchema) => void,
onDelete: (element: DepartmentSchema) => void,
onChange: (element: DepartmentSchema) => void
}
type Props = {
fetchDepartments: () => void;
}
const useDepartmentCrud = ({ fetchDepartments }: Props): DepartmentCrud => {
return useCRUD<DepartmentSchema, DepartmentBaseSchema>({
onChange: department => {
DepartmentService.updateDepartment({
requestBody: { department },
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
fetchDepartments();
})
.catch(err => console.log(err));
},
onCreate: department => {
DepartmentService.createDepartment({
requestBody: { department },
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
fetchDepartments();
})
.catch(err => console.log(err));
},
onDelete: department => {
DepartmentService.deleteDepartment({ departmentId: department.id })
.then(({ ok, message }) => {
notifications.guess(ok, { message });
fetchDepartments();
})
.catch(err => console.log(err));
},
});
};
export default useDepartmentCrud;

View File

@@ -0,0 +1,49 @@
import { useCRUD } from "../../../../../hooks/useCRUD.tsx";
import { DepartmentSectionBaseSchema, DepartmentSectionSchema, DepartmentService } from "../../../../../client";
import { notifications } from "../../../../../shared/lib/notifications.ts";
export type DepartmentSectionCrud = {
onCreate: (element: DepartmentSectionBaseSchema) => void,
onDelete: (element: DepartmentSectionSchema) => void,
onChange: (element: DepartmentSectionSchema) => void
}
type Props = {
fetchDepartments: () => void;
}
const useDepartmentSectionCrud = ({ fetchDepartments }: Props): DepartmentSectionCrud => {
return useCRUD<DepartmentSectionSchema, DepartmentSectionBaseSchema>({
onChange: section => {
DepartmentService.updateSection({
requestBody: { section },
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
fetchDepartments();
})
.catch(err => console.log(err));
},
onCreate: section => {
DepartmentService.createSection({
requestBody: { section },
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
fetchDepartments();
})
.catch(err => console.log(err));
},
onDelete: section => {
DepartmentService.deleteSection({ sectionId: section.id })
.then(({ ok, message }) => {
notifications.guess(ok, { message });
fetchDepartments();
})
.catch(err => console.log(err));
},
});
};
export default useDepartmentSectionCrud;

View File

@@ -0,0 +1,31 @@
import { useMemo } from "react";
import { MRT_ColumnDef } from "mantine-react-table";
import { UserSchema } from "../../../../../client";
const useUsersTableColumns = () => {
return useMemo<MRT_ColumnDef<UserSchema>[]>(
() => [
{
header: "ФИО",
Cell: ({ row }) =>
`${row.original.secondName} ${row.original.firstName} ${row.original.patronymic}`,
},
{
accessorKey: "role.name",
header: "Роль",
},
{
accessorKey: "position.name",
header: "Должность",
},
{
accessorKey: "comment",
header: "Доп. информация",
}
],
[],
);
};
export default useUsersTableColumns;

View File

@@ -0,0 +1,64 @@
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import { Button, Flex, rem } from "@mantine/core";
import { DepartmentSectionSchema, DepartmentService } from "../../../../../client";
import AddUserToDepartmentModalForm from "../types/AddUserToDepartmentModalForm.tsx";
import { notifications } from "../../../../../shared/lib/notifications.ts";
import UserForDepartmentSelect from "../components/UserForDepartmentSelect.tsx";
type Props = {
departmentSection: DepartmentSectionSchema;
fetchDepartments: () => void;
}
const AddUserToDepartmentModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const form = useForm<Partial<AddUserToDepartmentModalForm>>({
validate: {
user: user => !user && "Необходимо выбрать пользователя",
},
});
const onSubmit = () => {
if (!form.values.user) return;
DepartmentService.addUser({
requestBody: {
userId: form.values.user.id,
sectionId: innerProps.departmentSection.id,
}
})
.then(({ ok, message }) => {
notifications.guess(ok, { message });
innerProps.fetchDepartments();
})
.catch(err => console.log(err));
context.closeContextModal(id);
};
return (
<form onSubmit={form.onSubmit(() => onSubmit())}>
<Flex
direction={"column"}
gap={rem(10)}
>
<UserForDepartmentSelect
label={"Пользователь"}
{...form.getInputProps("user")}
sectionId={innerProps.departmentSection.id}
/>
<Button
variant={"default"}
type={"submit"}
>
Сохранить
</Button>
</Flex>
</form>
);
};
export default AddUserToDepartmentModal;

View File

@@ -0,0 +1,79 @@
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import { Button, Flex, rem, TextInput } from "@mantine/core";
import { DepartmentModalForm } from "../types/DepartmentModalForm.tsx";
import { DepartmentSchema, DepartmentSectionBaseSchema, DepartmentSectionSchema } from "../../../../../client";
import { DepartmentCrud } from "../hooks/useDepartmentCrud.tsx";
import { DepartmentSectionCrud } from "../hooks/useDepartmentSectionCrud.tsx";
type Props = {
departmentsCrud: DepartmentCrud;
departmentSectionsCrud: DepartmentSectionCrud;
element?: DepartmentSchema | DepartmentSectionSchema | DepartmentSectionBaseSchema;
isDepartmentSection: boolean;
}
const DepartmentModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const {
departmentsCrud,
departmentSectionsCrud,
element,
isDepartmentSection,
} = innerProps;
const initialValues: DepartmentModalForm = {
name: element?.name ?? "",
};
const form = useForm<DepartmentModalForm>({
initialValues,
validate: {
name: name => !name && "Необходимо указать название",
},
});
const onSubmit = () => {
if (isDepartmentSection) {
const sectionData = { ...element as DepartmentSectionSchema, ...form.values };
if (sectionData.id) {
departmentSectionsCrud.onChange(sectionData);
} else {
departmentSectionsCrud.onCreate(sectionData);
}
} else {
const departmentData = element as DepartmentSchema;
if (element) {
departmentsCrud.onChange({ ...form.values, id: departmentData.id });
} else {
departmentsCrud.onCreate(form.values);
}
}
context.closeContextModal(id);
};
return (
<form onSubmit={form.onSubmit(() => onSubmit())}>
<Flex
direction={"column"}
gap={rem(10)}
>
<TextInput
label={"Название"}
{...form.getInputProps("name")}
/>
<Button
variant={"default"}
type={"submit"}
>
Сохранить
</Button>
</Flex>
</form>
);
};
export default DepartmentModal;

View File

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

View File

@@ -0,0 +1,3 @@
export type DepartmentModalForm = {
name: string;
}