fix: projects editor to selected project editor, moved attributes editor
This commit is contained in:
		@@ -308,6 +308,7 @@ export type { ProductUploadImageResponse } from './models/ProductUploadImageResp
 | 
				
			|||||||
export type { ProfitChartDataItem } from './models/ProfitChartDataItem';
 | 
					export type { ProfitChartDataItem } from './models/ProfitChartDataItem';
 | 
				
			||||||
export type { ProfitTableDataItem } from './models/ProfitTableDataItem';
 | 
					export type { ProfitTableDataItem } from './models/ProfitTableDataItem';
 | 
				
			||||||
export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy';
 | 
					export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy';
 | 
				
			||||||
 | 
					export type { ProjectGeneralInfoSchema } from './models/ProjectGeneralInfoSchema';
 | 
				
			||||||
export type { ProjectSchema } from './models/ProjectSchema';
 | 
					export type { ProjectSchema } from './models/ProjectSchema';
 | 
				
			||||||
export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema';
 | 
					export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema';
 | 
				
			||||||
export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema';
 | 
					export type { ReceiptPalletSchema } from './models/ReceiptPalletSchema';
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								src/client/models/ProjectGeneralInfoSchema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/client/models/ProjectGeneralInfoSchema.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					/* generated using openapi-typescript-codegen -- do not edit */
 | 
				
			||||||
 | 
					/* istanbul ignore file */
 | 
				
			||||||
 | 
					/* tslint:disable */
 | 
				
			||||||
 | 
					/* eslint-disable */
 | 
				
			||||||
 | 
					export type ProjectGeneralInfoSchema = {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2,8 +2,8 @@
 | 
				
			|||||||
/* istanbul ignore file */
 | 
					/* istanbul ignore file */
 | 
				
			||||||
/* tslint:disable */
 | 
					/* tslint:disable */
 | 
				
			||||||
/* eslint-disable */
 | 
					/* eslint-disable */
 | 
				
			||||||
import type { ProjectSchema } from './ProjectSchema';
 | 
					import type { ProjectGeneralInfoSchema } from './ProjectGeneralInfoSchema';
 | 
				
			||||||
export type UpdateProjectRequest = {
 | 
					export type UpdateProjectRequest = {
 | 
				
			||||||
    project: ProjectSchema;
 | 
					    project: ProjectGeneralInfoSchema;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
 | 
				
			|||||||
import { BoardSchema, BoardService } from "../../../../../client";
 | 
					import { BoardSchema, BoardService } from "../../../../../client";
 | 
				
			||||||
import { modals } from "@mantine/modals";
 | 
					import { modals } from "@mantine/modals";
 | 
				
			||||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
					import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
				
			||||||
import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
 | 
					import { useProjectsContext } from "../../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    boards: BoardSchema[];
 | 
					    boards: BoardSchema[];
 | 
				
			||||||
@@ -11,7 +11,7 @@ type Props = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const useBoards = ({ boards, refetchBoards }: Props) => {
 | 
					const useBoards = ({ boards, refetchBoards }: Props) => {
 | 
				
			||||||
    const [selectedBoard, setSelectedBoard] = useState<BoardSchema | null>(null);
 | 
					    const [selectedBoard, setSelectedBoard] = useState<BoardSchema | null>(null);
 | 
				
			||||||
    const { selectedProject: project } = useCardPageContext();
 | 
					    const { selectedProject: project } = useProjectsContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (boards.length > 0 && selectedBoard === null) {
 | 
					        if (boards.length > 0 && selectedBoard === null) {
 | 
				
			||||||
@@ -24,7 +24,7 @@ const useBoards = ({ boards, refetchBoards }: Props) => {
 | 
				
			|||||||
            let newBoard = boards.find(board => board.id === selectedBoard.id);
 | 
					            let newBoard = boards.find(board => board.id === selectedBoard.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!newBoard && boards.length > 0) {
 | 
					            if (!newBoard && boards.length > 0) {
 | 
				
			||||||
                newBoard = boards[0]
 | 
					                newBoard = boards[0];
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            setSelectedBoard(newBoard ?? null);
 | 
					            setSelectedBoard(newBoard ?? null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import { IconCheck, IconLayoutGridRemove, IconTrash } from "@tabler/icons-react"
 | 
				
			|||||||
import { useContextMenu } from "mantine-contextmenu";
 | 
					import { useContextMenu } from "mantine-contextmenu";
 | 
				
			||||||
import useCardSummaryState from "./useCardSummaryState.tsx";
 | 
					import useCardSummaryState from "./useCardSummaryState.tsx";
 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    cardSummary: CardSummary;
 | 
					    cardSummary: CardSummary;
 | 
				
			||||||
@@ -18,7 +19,8 @@ type Props = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
 | 
					const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
 | 
				
			||||||
    const { showContextMenu } = useContextMenu();
 | 
					    const { showContextMenu } = useContextMenu();
 | 
				
			||||||
    const { selectedProject, setSelectedCard } = useCardPageContext();
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
 | 
					    const { setSelectedCard } = useCardPageContext();
 | 
				
			||||||
    const { onDelete, onComplete, onDeleteFromGroup } = useCardSummaryState();
 | 
					    const { onDelete, onComplete, onDeleteFromGroup } = useCardSummaryState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
					    const isServicesAndProductsIncluded = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,8 +9,8 @@ import { groupBy, has, uniq } from "lodash";
 | 
				
			|||||||
import { CardGroupView } from "../CardGroupView/CardGroupView.tsx";
 | 
					import { CardGroupView } from "../CardGroupView/CardGroupView.tsx";
 | 
				
			||||||
import CreateDealsFromFileButton from "../CreateCardsFromFileButton/CreateDealsFromFileButton.tsx";
 | 
					import CreateDealsFromFileButton from "../CreateCardsFromFileButton/CreateDealsFromFileButton.tsx";
 | 
				
			||||||
import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
 | 
					import DragState from "../../../../pages/CardsPage/enums/DragState.ts";
 | 
				
			||||||
import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
 | 
					 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    status: StatusSchema;
 | 
					    status: StatusSchema;
 | 
				
			||||||
@@ -27,7 +27,7 @@ export const CardsDndColumn: FC<Props> = ({
 | 
				
			|||||||
                                              dragState,
 | 
					                                              dragState,
 | 
				
			||||||
                                              withCreateButton = false,
 | 
					                                              withCreateButton = false,
 | 
				
			||||||
                                          }) => {
 | 
					                                          }) => {
 | 
				
			||||||
    const { selectedProject } = useCardPageContext();
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
    const isCreatingDealFromFileEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
					    const isCreatingDealFromFileEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
				
			||||||
    const isDropDisabled = dragState === DragState.DRAG_STATUS;
 | 
					    const isDropDisabled = dragState === DragState.DRAG_STATUS;
 | 
				
			||||||
    const droppableId = status.id.toString();
 | 
					    const droppableId = status.id.toString();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ import { CardService, StatusSchema } from "../../../../client";
 | 
				
			|||||||
import { useQueryClient } from "@tanstack/react-query";
 | 
					import { useQueryClient } from "@tanstack/react-query";
 | 
				
			||||||
import { dateWithoutTimezone } from "../../../../shared/lib/date.ts";
 | 
					import { dateWithoutTimezone } from "../../../../shared/lib/date.ts";
 | 
				
			||||||
import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx";
 | 
					import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx";
 | 
				
			||||||
import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
 | 
					 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    status: StatusSchema;
 | 
					    status: StatusSchema;
 | 
				
			||||||
@@ -20,7 +20,7 @@ const CreateCardButton = ({ status }: Props) => {
 | 
				
			|||||||
    const queryClient = useQueryClient();
 | 
					    const queryClient = useQueryClient();
 | 
				
			||||||
    const { prefillCard, setPrefillCard } = usePrefillCardContext();
 | 
					    const { prefillCard, setPrefillCard } = usePrefillCardContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { selectedProject } = useCardPageContext();
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
    const isPrefillingDealEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
					    const isPrefillingDealEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,8 +9,8 @@ import ShippingWarehouseAutocomplete
 | 
				
			|||||||
    from "../../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
 | 
					    from "../../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
 | 
				
			||||||
import BaseMarketplaceSelect from "../../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
					import BaseMarketplaceSelect from "../../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
 | 
				
			||||||
import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx";
 | 
					import { usePrefillCardContext } from "../../../../pages/CardsPage/contexts/PrefillCardContext.tsx";
 | 
				
			||||||
import { useCardPageContext } from "../../../../pages/CardsPage/contexts/CardPageContext.tsx";
 | 
					 | 
				
			||||||
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
					import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    onSubmit: (quickDeal: QuickCard) => void;
 | 
					    onSubmit: (quickDeal: QuickCard) => void;
 | 
				
			||||||
@@ -18,7 +18,7 @@ type Props = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CreateCardForm: FC<Props> = ({ onSubmit, onCancel }) => {
 | 
					const CreateCardForm: FC<Props> = ({ onSubmit, onCancel }) => {
 | 
				
			||||||
    const { selectedProject } = useCardPageContext();
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
    const isPrefillingEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
					    const isPrefillingEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
 | 
				
			||||||
    const { prefillOnOpen, prefillCard } = usePrefillCardContext();
 | 
					    const { prefillOnOpen, prefillCard } = usePrefillCardContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										68
									
								
								src/contexts/ProjectsContext.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/contexts/ProjectsContext.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					import React, { createContext, FC, useContext, useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { ProjectSchema } from "../client";
 | 
				
			||||||
 | 
					import useProjects from "../hooks/useProjects.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ProjectsContextState = {
 | 
				
			||||||
 | 
					    selectedProject: ProjectSchema | null;
 | 
				
			||||||
 | 
					    setSelectedProject: React.Dispatch<React.SetStateAction<ProjectSchema | null>>;
 | 
				
			||||||
 | 
					    refetchProjects: () => void;
 | 
				
			||||||
 | 
					    projects: ProjectSchema[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ProjectsContext = createContext<ProjectsContextState | undefined>(
 | 
				
			||||||
 | 
					    undefined,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const useProjectsContextState = () => {
 | 
				
			||||||
 | 
					    const { projects, refetchProjects } = useProjects();
 | 
				
			||||||
 | 
					    const [selectedProject, setSelectedProject] = useState<ProjectSchema | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const refetch = () => {
 | 
				
			||||||
 | 
					        refetchProjects();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (projects.length > 0) {
 | 
				
			||||||
 | 
					            if (selectedProject) {
 | 
				
			||||||
 | 
					                setSelectedProject(
 | 
				
			||||||
 | 
					                    projects.find(project => project.id === selectedProject.id) ?? null,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                setSelectedProject(projects[0]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            setSelectedProject(null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [projects]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        projects,
 | 
				
			||||||
 | 
					        refetchProjects: refetch,
 | 
				
			||||||
 | 
					        selectedProject,
 | 
				
			||||||
 | 
					        setSelectedProject,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ProjectsContextProviderProps = {
 | 
				
			||||||
 | 
					    children: React.ReactNode;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ProjectsContextProvider: FC<ProjectsContextProviderProps> = ({ children }) => {
 | 
				
			||||||
 | 
					    const state = useProjectsContextState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <ProjectsContext.Provider value={state}>
 | 
				
			||||||
 | 
					            {children}
 | 
				
			||||||
 | 
					        </ProjectsContext.Provider>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useProjectsContext = () => {
 | 
				
			||||||
 | 
					    const context = useContext(ProjectsContext);
 | 
				
			||||||
 | 
					    if (!context) {
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					            "useProjectsContext must be used within a ProjectsContextProvider",
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return context;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import { type FullProjectSchema, ProjectService } from "../../../client";
 | 
					import { type FullProjectSchema, ProjectService } from "../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useProjects = () => {
 | 
					const useProjects = () => {
 | 
				
			||||||
@@ -23,6 +23,7 @@ import { DatesProvider } from "@mantine/dates";
 | 
				
			|||||||
import { modals } from "./modals/modals.ts";
 | 
					import { modals } from "./modals/modals.ts";
 | 
				
			||||||
import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx";
 | 
					import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx";
 | 
				
			||||||
import { ContextMenuProvider } from "mantine-contextmenu";
 | 
					import { ContextMenuProvider } from "mantine-contextmenu";
 | 
				
			||||||
 | 
					import { ProjectsContextProvider } from "./contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configuring router
 | 
					// Configuring router
 | 
				
			||||||
const router = createRouter({ routeTree });
 | 
					const router = createRouter({ routeTree });
 | 
				
			||||||
@@ -43,22 +44,21 @@ const queryClient = new QueryClient();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Configuring OpenAPI
 | 
					// Configuring OpenAPI
 | 
				
			||||||
OpenAPI.BASE = import.meta.env.VITE_API_URL;
 | 
					OpenAPI.BASE = import.meta.env.VITE_API_URL;
 | 
				
			||||||
OpenAPI.TOKEN = JSON.parse(localStorage.getItem("authState") || "{}")[
 | 
					OpenAPI.TOKEN = JSON.parse(localStorage.getItem("authState") || "{}")["accessToken"];
 | 
				
			||||||
    "accessToken"
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
					ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
				
			||||||
    <Provider store={store}>
 | 
					    <Provider store={store}>
 | 
				
			||||||
        <QueryClientProvider client={queryClient}>
 | 
					        <QueryClientProvider client={queryClient}>
 | 
				
			||||||
            <MantineProvider defaultColorScheme={"dark"}>
 | 
					            <MantineProvider defaultColorScheme={"dark"}>
 | 
				
			||||||
                <ContextMenuProvider>
 | 
					                <ContextMenuProvider>
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    <ModalsProvider
 | 
					                    <ModalsProvider
 | 
				
			||||||
                        labels={{ confirm: "Да", cancel: "Нет" }}
 | 
					                        labels={{ confirm: "Да", cancel: "Нет" }}
 | 
				
			||||||
                        modals={modals}>
 | 
					                        modals={modals}>
 | 
				
			||||||
                        <DatesProvider settings={{ locale: "ru" }}>
 | 
					                        <DatesProvider settings={{ locale: "ru" }}>
 | 
				
			||||||
                            <TasksProvider>
 | 
					                            <TasksProvider>
 | 
				
			||||||
 | 
					                                <ProjectsContextProvider>
 | 
				
			||||||
                                    <RouterProvider router={router} />
 | 
					                                    <RouterProvider router={router} />
 | 
				
			||||||
                                    <Notifications />
 | 
					                                    <Notifications />
 | 
				
			||||||
 | 
					                                </ProjectsContextProvider>
 | 
				
			||||||
                            </TasksProvider>
 | 
					                            </TasksProvider>
 | 
				
			||||||
                        </DatesProvider>
 | 
					                        </DatesProvider>
 | 
				
			||||||
                    </ModalsProvider>
 | 
					                    </ModalsProvider>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,9 +35,9 @@ import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/
 | 
				
			|||||||
import SelectScannedProductModal from "../pages/ReceiptPage/modals/SelectScannedProductModal.tsx";
 | 
					import SelectScannedProductModal from "../pages/ReceiptPage/modals/SelectScannedProductModal.tsx";
 | 
				
			||||||
import BoardModal from "../pages/CardsPage/modals/BoardModal/BoardModal.tsx";
 | 
					import BoardModal from "../pages/CardsPage/modals/BoardModal/BoardModal.tsx";
 | 
				
			||||||
import StatusModal from "../pages/CardsPage/modals/StatusModal/StatusModal.tsx";
 | 
					import StatusModal from "../pages/CardsPage/modals/StatusModal/StatusModal.tsx";
 | 
				
			||||||
import AttributeModal from "../pages/CardsPage/drawers/ProjectsEditorDrawer/tabs/AttributesTab/modals/AttributeModal.tsx";
 | 
					import AttributeModal from "../pages/AdminPage/tabs/Attributes/modals/AttributeModal.tsx";
 | 
				
			||||||
import CreateProjectModal
 | 
					import CreateProjectModal
 | 
				
			||||||
    from "../pages/CardsPage/drawers/ProjectsEditorDrawer/tabs/ProjectsTab/modals/CreateProjectModal.tsx";
 | 
					    from "../pages/CardsPage/drawers/ProjectEditDrawer/tabs/General/modals/CreateProjectModal.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const modals = {
 | 
					export const modals = {
 | 
				
			||||||
    enterDeadline: EnterDeadlineModal,
 | 
					    enterDeadline: EnterDeadlineModal,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ import {
 | 
				
			|||||||
    IconCalendarUser,
 | 
					    IconCalendarUser,
 | 
				
			||||||
    IconCoins,
 | 
					    IconCoins,
 | 
				
			||||||
    IconCurrencyDollar,
 | 
					    IconCurrencyDollar,
 | 
				
			||||||
    IconQrcode,
 | 
					    IconQrcode, IconSubtask,
 | 
				
			||||||
    IconTopologyStar3,
 | 
					    IconTopologyStar3,
 | 
				
			||||||
    IconUser,
 | 
					    IconUser,
 | 
				
			||||||
} from "@tabler/icons-react";
 | 
					} from "@tabler/icons-react";
 | 
				
			||||||
@@ -21,6 +21,7 @@ import { RootState } from "../../redux/store.ts";
 | 
				
			|||||||
import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
 | 
					import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
 | 
				
			||||||
import { ReactNode } from "react";
 | 
					import { ReactNode } from "react";
 | 
				
			||||||
import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
 | 
					import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
 | 
				
			||||||
 | 
					import Attributes from "./tabs/Attributes/Attributes.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AdminPage = () => {
 | 
					const AdminPage = () => {
 | 
				
			||||||
    const userRole = useSelector((state: RootState) => state.auth.role);
 | 
					    const userRole = useSelector((state: RootState) => state.auth.role);
 | 
				
			||||||
@@ -88,6 +89,13 @@ const AdminPage = () => {
 | 
				
			|||||||
                                Доходы и расходы
 | 
					                                Доходы и расходы
 | 
				
			||||||
                            </Tabs.Tab>
 | 
					                            </Tabs.Tab>
 | 
				
			||||||
                        )}
 | 
					                        )}
 | 
				
			||||||
 | 
					                        {isAdmin && (
 | 
				
			||||||
 | 
					                            <Tabs.Tab
 | 
				
			||||||
 | 
					                                value={"attributes"}
 | 
				
			||||||
 | 
					                                leftSection={<IconSubtask />}>
 | 
				
			||||||
 | 
					                                Атрибуты карточек
 | 
				
			||||||
 | 
					                            </Tabs.Tab>
 | 
				
			||||||
 | 
					                        )}
 | 
				
			||||||
                    </Tabs.List>
 | 
					                    </Tabs.List>
 | 
				
			||||||
                    {getTabPanel("users", <UsersTab />)}
 | 
					                    {getTabPanel("users", <UsersTab />)}
 | 
				
			||||||
                    {getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
 | 
					                    {getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
 | 
				
			||||||
@@ -96,6 +104,7 @@ const AdminPage = () => {
 | 
				
			|||||||
                    {getTabPanel("workShiftsPlanning", <WorkShiftsPlanning />)}
 | 
					                    {getTabPanel("workShiftsPlanning", <WorkShiftsPlanning />)}
 | 
				
			||||||
                    {getTabPanel("workShifts", <WorkShiftsTab />)}
 | 
					                    {getTabPanel("workShifts", <WorkShiftsTab />)}
 | 
				
			||||||
                    {getTabPanel("transactions", <TransactionsTab />)}
 | 
					                    {getTabPanel("transactions", <TransactionsTab />)}
 | 
				
			||||||
 | 
					                    {getTabPanel("attributes", <Attributes />)}
 | 
				
			||||||
                </Tabs>
 | 
					                </Tabs>
 | 
				
			||||||
            </PageBlock>
 | 
					            </PageBlock>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,15 @@
 | 
				
			|||||||
import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
 | 
					import { BaseTable } from "../../../../components/BaseTable/BaseTable.tsx";
 | 
				
			||||||
import useAttributesTableColumns from "./hooks/attributesTableColumns.tsx";
 | 
					import useAttributesTableColumns from "./hooks/attributesTableColumns.tsx";
 | 
				
			||||||
import useAttributesList from "../../../../../../hooks/useAttributesList.tsx";
 | 
					import useAttributesList from "../../../../hooks/useAttributesList.tsx";
 | 
				
			||||||
import { ActionIcon, Flex, Group, Stack, Text, Tooltip } from "@mantine/core";
 | 
					import { ActionIcon, Flex, Group, Stack, Text, Tooltip } from "@mantine/core";
 | 
				
			||||||
import InlineButton from "../../../../../../components/InlineButton/InlineButton.tsx";
 | 
					import InlineButton from "../../../../components/InlineButton/InlineButton.tsx";
 | 
				
			||||||
import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
 | 
					import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
 | 
				
			||||||
import { modals } from "@mantine/modals";
 | 
					import { modals } from "@mantine/modals";
 | 
				
			||||||
import { AttributeSchema, AttributeService } from "../../../../../../client";
 | 
					import { AttributeSchema, AttributeService } from "../../../../client";
 | 
				
			||||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
 | 
					import { notifications } from "../../../../shared/lib/notifications.ts";
 | 
				
			||||||
import { MRT_TableOptions } from "mantine-react-table";
 | 
					import { MRT_TableOptions } from "mantine-react-table";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const AttributesTab = () => {
 | 
					const Attributes = () => {
 | 
				
			||||||
    const columns = useAttributesTableColumns();
 | 
					    const columns = useAttributesTableColumns();
 | 
				
			||||||
    const { objects: attributes, refetch: refetchAttributes } = useAttributesList();
 | 
					    const { objects: attributes, refetch: refetchAttributes } = useAttributesList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,7 +66,7 @@ const AttributesTab = () => {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Stack>
 | 
					        <Stack>
 | 
				
			||||||
            <Group>
 | 
					            <Group>
 | 
				
			||||||
                <InlineButton onClick={onCreateAttributeClick}>
 | 
					                <InlineButton onClick={onCreateAttributeClick} mt={"md"}>
 | 
				
			||||||
                    <IconPlus />
 | 
					                    <IconPlus />
 | 
				
			||||||
                    Добавить атрибут
 | 
					                    Добавить атрибут
 | 
				
			||||||
                </InlineButton>
 | 
					                </InlineButton>
 | 
				
			||||||
@@ -109,4 +109,4 @@ const AttributesTab = () => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default AttributesTab;
 | 
					export default Attributes;
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import ObjectSelect, { ObjectSelectProps } from "../../../../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
					import ObjectSelect, { ObjectSelectProps } from "../../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
import { AttributeTypeSchema } from "../../../../../../../client";
 | 
					import { AttributeTypeSchema } from "../../../../../client";
 | 
				
			||||||
import useAttributeTypesList from "../hooks/useAttributeTypesList.tsx";
 | 
					import useAttributeTypesList from "../hooks/useAttributeTypesList.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = Omit<ObjectSelectProps<AttributeTypeSchema>, "data">;
 | 
					type Props = Omit<ObjectSelectProps<AttributeTypeSchema>, "data">;
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Checkbox, NumberInput, TextInput } from "@mantine/core";
 | 
					import { Checkbox, NumberInput, TextInput } from "@mantine/core";
 | 
				
			||||||
import { UseFormReturnType } from "@mantine/form";
 | 
					import { UseFormReturnType } from "@mantine/form";
 | 
				
			||||||
import { DatePickerInput, DateTimePicker } from "@mantine/dates";
 | 
					import { DatePickerInput, DateTimePicker } from "@mantine/dates";
 | 
				
			||||||
import { AttributeSchema } from "../../../../../../../client";
 | 
					import { AttributeSchema } from "../../../../../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    form: UseFormReturnType<Partial<AttributeSchema>>;
 | 
					    form: UseFormReturnType<Partial<AttributeSchema>>;
 | 
				
			||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
import { useMemo } from "react";
 | 
					import { useMemo } from "react";
 | 
				
			||||||
import { MRT_ColumnDef } from "mantine-react-table";
 | 
					import { MRT_ColumnDef } from "mantine-react-table";
 | 
				
			||||||
import { AttributeSchema } from "../../../../../../../client";
 | 
					import { AttributeSchema } from "../../../../../client";
 | 
				
			||||||
import { IconCheck, IconX } from "@tabler/icons-react";
 | 
					import { IconCheck, IconX } from "@tabler/icons-react";
 | 
				
			||||||
import { formatDate, formatDateTime } from "../../../../../../../types/utils.ts";
 | 
					import { formatDate, formatDateTime } from "../../../../../types/utils.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useAttributesTableColumns = () => {
 | 
					const useAttributesTableColumns = () => {
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { AttributeService } from "../../../../../../../client";
 | 
					import { AttributeService } from "../../../../../client";
 | 
				
			||||||
import ObjectList from "../../../../../../../hooks/objectList.tsx";
 | 
					import ObjectList from "../../../../../hooks/objectList.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useAttributeTypesList = () =>
 | 
					const useAttributeTypesList = () =>
 | 
				
			||||||
    ObjectList({
 | 
					    ObjectList({
 | 
				
			||||||
@@ -2,11 +2,11 @@ import { ContextModalProps } from "@mantine/modals";
 | 
				
			|||||||
import { Button, Checkbox, Stack, Textarea, TextInput } from "@mantine/core";
 | 
					import { Button, Checkbox, Stack, Textarea, TextInput } from "@mantine/core";
 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import AttributeTypeSelect from "../components/AttributeTypeSelect.tsx";
 | 
					import AttributeTypeSelect from "../components/AttributeTypeSelect.tsx";
 | 
				
			||||||
import { AttributeSchema, AttributeService } from "../../../../../../../client";
 | 
					import { AttributeSchema, AttributeService } from "../../../../../client";
 | 
				
			||||||
import { convertRussianToSnakeCase } from "../../../utils/stringConverting.ts";
 | 
					import { convertRussianToSnakeCase } from "../../../../CardsPage/drawers/ProjectEditDrawer/utils/stringConverting.ts";
 | 
				
			||||||
import { useForm } from "@mantine/form";
 | 
					import { useForm } from "@mantine/form";
 | 
				
			||||||
import DefaultAttributeValueInput from "../components/DefaultAttributeValueInput.tsx";
 | 
					import DefaultAttributeValueInput from "../components/DefaultAttributeValueInput.tsx";
 | 
				
			||||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
 | 
					import { notifications } from "../../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { ActionIcon, Flex, rem, Text } from "@mantine/core";
 | 
					import { ActionIcon, Flex, rem, Text } from "@mantine/core";
 | 
				
			||||||
import { IconEdit, IconMenu2, IconMenuDeep } from "@tabler/icons-react";
 | 
					import { IconEdit, IconMenu2, IconMenuDeep, IconPlus } from "@tabler/icons-react";
 | 
				
			||||||
import { motion } from "framer-motion";
 | 
					import { motion } from "framer-motion";
 | 
				
			||||||
import styles from "../../ui/CardsPage.module.css";
 | 
					import styles from "../../ui/CardsPage.module.css";
 | 
				
			||||||
import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
 | 
					import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
 | 
				
			||||||
@@ -7,30 +7,41 @@ import DisplayMode from "../../enums/DisplayMode.ts";
 | 
				
			|||||||
import { UseFormReturnType } from "@mantine/form";
 | 
					import { UseFormReturnType } from "@mantine/form";
 | 
				
			||||||
import { CardsPageState } from "../../hooks/useCardsPageState.tsx";
 | 
					import { CardsPageState } from "../../hooks/useCardsPageState.tsx";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { ProjectSchema } from "../../../../client";
 | 
					 | 
				
			||||||
import ObjectSelect from "../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
					import ObjectSelect from "../../../../components/ObjectSelect/ObjectSelect.tsx";
 | 
				
			||||||
import CardsTableFiltersModal from "../../modals/CardsTableFiltersModal.tsx";
 | 
					import CardsTableFiltersModal from "../../modals/CardsTableFiltersModal.tsx";
 | 
				
			||||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
 | 
					import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
 | 
				
			||||||
import { useSelector } from "react-redux";
 | 
					import { useSelector } from "react-redux";
 | 
				
			||||||
import { RootState } from "../../../../redux/store.ts";
 | 
					import { RootState } from "../../../../redux/store.ts";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					import { modals } from "@mantine/modals";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					type Props = {
 | 
				
			||||||
    displayMode: DisplayMode;
 | 
					    displayMode: DisplayMode;
 | 
				
			||||||
    setDisplayMode: React.Dispatch<React.SetStateAction<DisplayMode>>;
 | 
					    setDisplayMode: React.Dispatch<React.SetStateAction<DisplayMode>>;
 | 
				
			||||||
    form: UseFormReturnType<CardsPageState>;
 | 
					    form: UseFormReturnType<CardsPageState>;
 | 
				
			||||||
    projects: ProjectSchema[];
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CardsPageHeader = ({
 | 
					const CardsPageHeader = ({
 | 
				
			||||||
                             displayMode,
 | 
					                             displayMode,
 | 
				
			||||||
                             setDisplayMode,
 | 
					                             setDisplayMode,
 | 
				
			||||||
                             form,
 | 
					                             form,
 | 
				
			||||||
                             projects,
 | 
					 | 
				
			||||||
                         }: Props) => {
 | 
					                         }: Props) => {
 | 
				
			||||||
    const { openProjectsEditor } = useProjectsEditorContext();
 | 
					    const { openProjectsEditor } = useProjectsEditorContext();
 | 
				
			||||||
 | 
					    const { selectedProject, setSelectedProject, projects, refetchProjects } = useProjectsContext();
 | 
				
			||||||
    const userRole = useSelector((state: RootState) => state.auth.role);
 | 
					    const userRole = useSelector((state: RootState) => state.auth.role);
 | 
				
			||||||
    const isAdmin = userRole === "admin";
 | 
					    const isAdmin = userRole === "admin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleCreateClick = () => {
 | 
				
			||||||
 | 
					        modals.openContextModal({
 | 
				
			||||||
 | 
					            modal: "createProjectModal",
 | 
				
			||||||
 | 
					            title: "Создание проекта",
 | 
				
			||||||
 | 
					            withCloseButton: false,
 | 
				
			||||||
 | 
					            innerProps: {
 | 
				
			||||||
 | 
					                refetchProjects,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const getHeaderInputsBoard = () => {
 | 
					    const getHeaderInputsBoard = () => {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
@@ -40,17 +51,26 @@ const CardsPageHeader = ({
 | 
				
			|||||||
                }}
 | 
					                }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                {isAdmin && (
 | 
					                {isAdmin && (
 | 
				
			||||||
 | 
					                    <>
 | 
				
			||||||
 | 
					                        <ActionIcon
 | 
				
			||||||
 | 
					                            size={"lg"}
 | 
				
			||||||
 | 
					                            onClick={handleCreateClick}
 | 
				
			||||||
 | 
					                            variant={"default"}>
 | 
				
			||||||
 | 
					                            <IconPlus />
 | 
				
			||||||
 | 
					                        </ActionIcon>
 | 
				
			||||||
                        <ActionIcon
 | 
					                        <ActionIcon
 | 
				
			||||||
                            size={"lg"}
 | 
					                            size={"lg"}
 | 
				
			||||||
                            onClick={openProjectsEditor}
 | 
					                            onClick={openProjectsEditor}
 | 
				
			||||||
                            variant={"default"}>
 | 
					                            variant={"default"}>
 | 
				
			||||||
                            <IconEdit />
 | 
					                            <IconEdit />
 | 
				
			||||||
                        </ActionIcon>
 | 
					                        </ActionIcon>
 | 
				
			||||||
 | 
					                    </>
 | 
				
			||||||
                )}
 | 
					                )}
 | 
				
			||||||
                <ObjectSelect
 | 
					                <ObjectSelect
 | 
				
			||||||
                    placeholder={"Выберите проект"}
 | 
					                    placeholder={"Выберите проект"}
 | 
				
			||||||
                    data={projects}
 | 
					                    data={projects}
 | 
				
			||||||
                    {...form.getInputProps("project")}
 | 
					                    value={selectedProject}
 | 
				
			||||||
 | 
					                    onChange={setSelectedProject}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
import React, { createContext, FC, useContext, useEffect, useState } from "react";
 | 
					import React, { createContext, FC, useContext, useEffect, useState } from "react";
 | 
				
			||||||
import { CardSchema, CardService, ProjectSchema } from "../../../client";
 | 
					import { CardSchema, CardService } from "../../../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CardPageContextState = {
 | 
					type CardPageContextState = {
 | 
				
			||||||
    selectedCard?: CardSchema;
 | 
					    selectedCard?: CardSchema;
 | 
				
			||||||
    setSelectedCard: (card: CardSchema | undefined) => void;
 | 
					    setSelectedCard: (card: CardSchema | undefined) => void;
 | 
				
			||||||
    refetchCards: () => Promise<void>;
 | 
					    refetchCards: () => Promise<void>;
 | 
				
			||||||
    refetchCard: () => void;
 | 
					    refetchCard: () => void;
 | 
				
			||||||
    selectedProject?: ProjectSchema | null;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CardPageContext = createContext<CardPageContextState | undefined>(
 | 
					const CardPageContext = createContext<CardPageContextState | undefined>(
 | 
				
			||||||
@@ -16,7 +15,6 @@ const CardPageContext = createContext<CardPageContextState | undefined>(
 | 
				
			|||||||
type CardPageContextStateProps = {
 | 
					type CardPageContextStateProps = {
 | 
				
			||||||
    refetchCards: () => Promise<void>;
 | 
					    refetchCards: () => Promise<void>;
 | 
				
			||||||
    defaultCardId?: number;
 | 
					    defaultCardId?: number;
 | 
				
			||||||
    selectedProject?: ProjectSchema | null;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useCardPageContextState = (props: CardPageContextStateProps) => {
 | 
					const useCardPageContextState = (props: CardPageContextStateProps) => {
 | 
				
			||||||
@@ -41,7 +39,6 @@ const useCardPageContextState = (props: CardPageContextStateProps) => {
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
        selectedCard,
 | 
					        selectedCard,
 | 
				
			||||||
        setSelectedCard,
 | 
					        setSelectedCard,
 | 
				
			||||||
        selectedProject: props.selectedProject,
 | 
					 | 
				
			||||||
        refetchCards,
 | 
					        refetchCards,
 | 
				
			||||||
        refetchCard,
 | 
					        refetchCard,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,37 +5,28 @@ type ProjectsEditorContextState = {
 | 
				
			|||||||
    openedProjectsEditor: boolean;
 | 
					    openedProjectsEditor: boolean;
 | 
				
			||||||
    openProjectsEditor: () => void;
 | 
					    openProjectsEditor: () => void;
 | 
				
			||||||
    closeProjectsEditor: () => void;
 | 
					    closeProjectsEditor: () => void;
 | 
				
			||||||
    onUpdate: () => void;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectsEditorContext = createContext<ProjectsEditorContextState | undefined>(
 | 
					const ProjectsEditorContext = createContext<ProjectsEditorContextState | undefined>(
 | 
				
			||||||
    undefined,
 | 
					    undefined,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProjectsEditorContextStateProps = {
 | 
					const useProjectsEditorContextState = () => {
 | 
				
			||||||
    onUpdate: () => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const useProjectsEditorContextState = ({ onUpdate }: ProjectsEditorContextStateProps) => {
 | 
					 | 
				
			||||||
    const [opened, { open, close }] = useDisclosure(false);
 | 
					    const [opened, { open, close }] = useDisclosure(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        openedProjectsEditor: opened,
 | 
					        openedProjectsEditor: opened,
 | 
				
			||||||
        openProjectsEditor: open,
 | 
					        openProjectsEditor: open,
 | 
				
			||||||
        closeProjectsEditor: close,
 | 
					        closeProjectsEditor: close,
 | 
				
			||||||
        onUpdate,
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ProjectsEditorContextProviderProps = {
 | 
					type ProjectsEditorContextProviderProps = {
 | 
				
			||||||
    children: React.ReactNode;
 | 
					    children: React.ReactNode;
 | 
				
			||||||
} & ProjectsEditorContextStateProps;
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ProjectsEditorContextProvider: FC<ProjectsEditorContextProviderProps> = ({
 | 
					export const ProjectsEditorContextProvider: FC<ProjectsEditorContextProviderProps> = ({ children }) => {
 | 
				
			||||||
                                                                                          children,
 | 
					    const state = useProjectsEditorContextState();
 | 
				
			||||||
                                                                                          ...props
 | 
					 | 
				
			||||||
                                                                                      }) => {
 | 
					 | 
				
			||||||
    const state = useProjectsEditorContextState(props);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <ProjectsEditorContext.Provider value={state}>
 | 
					        <ProjectsEditorContext.Provider value={state}>
 | 
				
			||||||
@@ -48,7 +39,7 @@ export const useProjectsEditorContext = () => {
 | 
				
			|||||||
    const context = useContext(ProjectsEditorContext);
 | 
					    const context = useContext(ProjectsEditorContext);
 | 
				
			||||||
    if (!context) {
 | 
					    if (!context) {
 | 
				
			||||||
        throw new Error(
 | 
					        throw new Error(
 | 
				
			||||||
            "useProjectsEditorContext must be used within a ProjectsEditorContextProvider",
 | 
					            "useProjectEditorContext must be used within a ProjectEditorContextProvider",
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return context;
 | 
					    return context;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
import { Box, Drawer, rem, Tabs } from "@mantine/core";
 | 
					import { Box, Drawer, rem, Tabs } from "@mantine/core";
 | 
				
			||||||
import { IconSettings, IconSubtask } from "@tabler/icons-react";
 | 
					import { IconHexagons, IconSettings, IconSubtask } from "@tabler/icons-react";
 | 
				
			||||||
import { ReactNode } from "react";
 | 
					import { ReactNode } from "react";
 | 
				
			||||||
import { motion } from "framer-motion";
 | 
					import { motion } from "framer-motion";
 | 
				
			||||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
 | 
					import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
 | 
				
			||||||
import ProjectsTab from "./tabs/ProjectsTab/ProjectsTab.tsx";
 | 
					import General from "./tabs/General/General.tsx";
 | 
				
			||||||
import AttributesTab from "./tabs/AttributesTab/AttributesTab.tsx";
 | 
					import Attributes from "./tabs/Attributes/Attributes.tsx";
 | 
				
			||||||
 | 
					import Modules from "./tabs/Modules/Modules.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ProjectsEditorDrawer = () => {
 | 
					const ProjectEditDrawer = () => {
 | 
				
			||||||
    const { closeProjectsEditor, openedProjectsEditor } = useProjectsEditorContext();
 | 
					    const { closeProjectsEditor, openedProjectsEditor } = useProjectsEditorContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const getTabPanel = (value: string, component: ReactNode) => {
 | 
					    const getTabPanel = (value: string, component: ReactNode) => {
 | 
				
			||||||
@@ -45,16 +46,21 @@ const ProjectsEditorDrawer = () => {
 | 
				
			|||||||
                },
 | 
					                },
 | 
				
			||||||
            }}>
 | 
					            }}>
 | 
				
			||||||
            <Tabs
 | 
					            <Tabs
 | 
				
			||||||
                defaultValue={"projects"}
 | 
					                defaultValue={"general"}
 | 
				
			||||||
                flex={1}
 | 
					                flex={1}
 | 
				
			||||||
                variant={"outline"}
 | 
					                variant={"outline"}
 | 
				
			||||||
                orientation={"vertical"}
 | 
					                orientation={"vertical"}
 | 
				
			||||||
                keepMounted={false}>
 | 
					                keepMounted={false}>
 | 
				
			||||||
                <Tabs.List>
 | 
					                <Tabs.List>
 | 
				
			||||||
                    <Tabs.Tab
 | 
					                    <Tabs.Tab
 | 
				
			||||||
                        value={"projects"}
 | 
					                        value={"general"}
 | 
				
			||||||
                        leftSection={<IconSettings />}>
 | 
					                        leftSection={<IconSettings />}>
 | 
				
			||||||
                        Проекты
 | 
					                        Общее
 | 
				
			||||||
 | 
					                    </Tabs.Tab>
 | 
				
			||||||
 | 
					                    <Tabs.Tab
 | 
				
			||||||
 | 
					                        value={"modules"}
 | 
				
			||||||
 | 
					                        leftSection={<IconHexagons />}>
 | 
				
			||||||
 | 
					                        Модули
 | 
				
			||||||
                    </Tabs.Tab>
 | 
					                    </Tabs.Tab>
 | 
				
			||||||
                    <Tabs.Tab
 | 
					                    <Tabs.Tab
 | 
				
			||||||
                        value={"attributes"}
 | 
					                        value={"attributes"}
 | 
				
			||||||
@@ -63,11 +69,12 @@ const ProjectsEditorDrawer = () => {
 | 
				
			|||||||
                    </Tabs.Tab>
 | 
					                    </Tabs.Tab>
 | 
				
			||||||
                </Tabs.List>
 | 
					                </Tabs.List>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {getTabPanel("projects", <ProjectsTab />)}
 | 
					                {getTabPanel("general", <General/>)}
 | 
				
			||||||
                {getTabPanel("attributes", <AttributesTab />)}
 | 
					                {getTabPanel("attributes", <Attributes/>)}
 | 
				
			||||||
 | 
					                {getTabPanel("modules", <Modules />)}
 | 
				
			||||||
            </Tabs>
 | 
					            </Tabs>
 | 
				
			||||||
        </Drawer>
 | 
					        </Drawer>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default ProjectsEditorDrawer;
 | 
					export default ProjectEditDrawer;
 | 
				
			||||||
@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					import { ProjectService } from "../../../../../../client";
 | 
				
			||||||
 | 
					import useAttributesList from "../../../../../../hooks/useAttributesList.tsx";
 | 
				
			||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { useSet } from "@mantine/hooks";
 | 
				
			||||||
 | 
					import useAttributesTableColumns from "./hooks/attributesTableColumns.tsx";
 | 
				
			||||||
 | 
					import { notifications } from "../../../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					import { rem, Stack } from "@mantine/core";
 | 
				
			||||||
 | 
					import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
 | 
				
			||||||
 | 
					import eqSet from "../../utils/eqSet.ts";
 | 
				
			||||||
 | 
					import InlineButton from "../../../../../../components/InlineButton/InlineButton.tsx";
 | 
				
			||||||
 | 
					import { IconCheck } from "@tabler/icons-react";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Attributes = () => {
 | 
				
			||||||
 | 
					    const { selectedProject: project, refetchProjects } = useProjectsContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { objects: attributes } = useAttributesList();
 | 
				
			||||||
 | 
					    const [defaultSelectedAttributes, setDefaultSelectedAttributes] = useState(new Set<number>(project?.attributes.map(m => m.id)));
 | 
				
			||||||
 | 
					    const selectedAttributes = useSet<number>(project?.attributes.map(a => a.id));
 | 
				
			||||||
 | 
					    const columns = useAttributesTableColumns({ selectedAttributes });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        selectedAttributes.clear();
 | 
				
			||||||
 | 
					        project?.attributes.forEach(attribute => {
 | 
				
			||||||
 | 
					            selectedAttributes.add(attribute.id);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        setDefaultSelectedAttributes(new Set([...selectedAttributes]));
 | 
				
			||||||
 | 
					    }, [project]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onUpdateAttributesClick = () => {
 | 
				
			||||||
 | 
					        if (!project) return;
 | 
				
			||||||
 | 
					        ProjectService.updateProjectAttributes({
 | 
				
			||||||
 | 
					            requestBody: {
 | 
				
			||||||
 | 
					                projectId: project.id,
 | 
				
			||||||
 | 
					                attributeIds: selectedAttributes.values().toArray(),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(({ ok, message }) => {
 | 
				
			||||||
 | 
					                if (!ok) {
 | 
				
			||||||
 | 
					                    notifications.error({ message });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                refetchProjects();
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Stack gap={rem(10)}>
 | 
				
			||||||
 | 
					            <BaseTable
 | 
				
			||||||
 | 
					                data={attributes}
 | 
				
			||||||
 | 
					                columns={columns}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                restProps={{
 | 
				
			||||||
 | 
					                    enableSorting: false,
 | 
				
			||||||
 | 
					                    enableColumnActions: false,
 | 
				
			||||||
 | 
					                    enableRowVirtualization: true,
 | 
				
			||||||
 | 
					                    mantineTableContainerProps: { style: { maxHeight: "88vh" } },
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {!eqSet(selectedAttributes, defaultSelectedAttributes) && (
 | 
				
			||||||
 | 
					                <InlineButton onClick={onUpdateAttributesClick}>
 | 
				
			||||||
 | 
					                    <IconCheck />
 | 
				
			||||||
 | 
					                    Сохранить
 | 
				
			||||||
 | 
					                </InlineButton>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					        </Stack>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Attributes;
 | 
				
			||||||
@@ -0,0 +1,114 @@
 | 
				
			|||||||
 | 
					import { Button, Fieldset, Flex, rem, Stack, Text, TextInput } from "@mantine/core";
 | 
				
			||||||
 | 
					import { ProjectService } from "../../../../../../client";
 | 
				
			||||||
 | 
					import { useForm } from "@mantine/form";
 | 
				
			||||||
 | 
					import { notifications } from "../../../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					import { isEqual } from "lodash";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					import { modals } from "@mantine/modals";
 | 
				
			||||||
 | 
					import { useProjectsEditorContext } from "../../../../contexts/ProjectsEditorContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ProjectForm = {
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const General = () => {
 | 
				
			||||||
 | 
					    const { selectedProject: project, refetchProjects } = useProjectsContext();
 | 
				
			||||||
 | 
					    const { closeProjectsEditor } = useProjectsEditorContext();
 | 
				
			||||||
 | 
					    if (!project) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const form = useForm<ProjectForm>({
 | 
				
			||||||
 | 
					        initialValues: project,
 | 
				
			||||||
 | 
					        validate: {
 | 
				
			||||||
 | 
					            name: name => !name && "Название проекта не введено",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onProjectDelete = () => {
 | 
				
			||||||
 | 
					        ProjectService.deleteProject({
 | 
				
			||||||
 | 
					            projectId: project.id,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(({ ok, message }) => {
 | 
				
			||||||
 | 
					                if (!ok) {
 | 
				
			||||||
 | 
					                    notifications.error({ message });
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                closeProjectsEditor();
 | 
				
			||||||
 | 
					                refetchProjects();
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onDeleteProjectClick = () => {
 | 
				
			||||||
 | 
					        modals.openConfirmModal({
 | 
				
			||||||
 | 
					            title: "Удаление проекта",
 | 
				
			||||||
 | 
					            children: (
 | 
				
			||||||
 | 
					                <Text size="sm">
 | 
				
			||||||
 | 
					                    Вы уверены что хотите удалить проект "{project.name}"?
 | 
				
			||||||
 | 
					                </Text>
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            labels: { confirm: "Да", cancel: "Нет" },
 | 
				
			||||||
 | 
					            confirmProps: { color: "red" },
 | 
				
			||||||
 | 
					            onConfirm: () => onProjectDelete(),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const onSubmit = (values: ProjectForm) => {
 | 
				
			||||||
 | 
					        ProjectService.updateProject({
 | 
				
			||||||
 | 
					            requestBody: {
 | 
				
			||||||
 | 
					                project: {
 | 
				
			||||||
 | 
					                    id: project.id,
 | 
				
			||||||
 | 
					                    name: values.name,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(({ ok, message }) => {
 | 
				
			||||||
 | 
					                if (!ok) {
 | 
				
			||||||
 | 
					                    notifications.error({ message });
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                refetchProjects();
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <form onSubmit={form.onSubmit(values => onSubmit(values))}>
 | 
				
			||||||
 | 
					            <Stack>
 | 
				
			||||||
 | 
					                <Fieldset legend={"Общие параметры"}>
 | 
				
			||||||
 | 
					                    <Stack>
 | 
				
			||||||
 | 
					                        <TextInput
 | 
				
			||||||
 | 
					                            label={"Название"}
 | 
				
			||||||
 | 
					                            {...form.getInputProps("name")}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </Stack>
 | 
				
			||||||
 | 
					                </Fieldset>
 | 
				
			||||||
 | 
					                <Flex direction={"row-reverse"} gap={rem(10)}>
 | 
				
			||||||
 | 
					                    <Button
 | 
				
			||||||
 | 
					                        variant={"default"}
 | 
				
			||||||
 | 
					                        type={"submit"}
 | 
				
			||||||
 | 
					                        disabled={isEqual(project, form.values)}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Сохранить изменения
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                    <Button
 | 
				
			||||||
 | 
					                        type={"reset"}
 | 
				
			||||||
 | 
					                        variant={"default"}
 | 
				
			||||||
 | 
					                        disabled={isEqual(project, form.values)}
 | 
				
			||||||
 | 
					                        onClick={() => form.reset()}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Отменить изменения
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                    <Button
 | 
				
			||||||
 | 
					                        variant={"default"}
 | 
				
			||||||
 | 
					                        onClick={() => onDeleteProjectClick()}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        Удалить
 | 
				
			||||||
 | 
					                    </Button>
 | 
				
			||||||
 | 
					                </Flex>
 | 
				
			||||||
 | 
					            </Stack>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default General;
 | 
				
			||||||
@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					import useModulesList from "./hooks/useModulesList.tsx";
 | 
				
			||||||
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
 | 
					import { useSet } from "@mantine/hooks";
 | 
				
			||||||
 | 
					import useModulesTableColumns from "./hooks/modulesTableColumns.tsx";
 | 
				
			||||||
 | 
					import { ProjectService } from "../../../../../../client";
 | 
				
			||||||
 | 
					import { notifications } from "../../../../../../shared/lib/notifications.ts";
 | 
				
			||||||
 | 
					import { rem, Stack } from "@mantine/core";
 | 
				
			||||||
 | 
					import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
 | 
				
			||||||
 | 
					import eqSet from "../../utils/eqSet.ts";
 | 
				
			||||||
 | 
					import InlineButton from "../../../../../../components/InlineButton/InlineButton.tsx";
 | 
				
			||||||
 | 
					import { IconCheck } from "@tabler/icons-react";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Modules = () => {
 | 
				
			||||||
 | 
					    const { selectedProject: project, refetchProjects } = useProjectsContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { objects: modules } = useModulesList();
 | 
				
			||||||
 | 
					    const [defaultSelectedModules, setDefaultSelectedModules] = useState(
 | 
				
			||||||
 | 
					        new Set<number>(project?.modules.map(m => m.id)),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const selectedModules = useSet<number>();
 | 
				
			||||||
 | 
					    const columns = useModulesTableColumns({ selectedModules });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        selectedModules.clear();
 | 
				
			||||||
 | 
					        project?.modules.forEach(module => {
 | 
				
			||||||
 | 
					            selectedModules.add(module.id);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        setDefaultSelectedModules(new Set([...selectedModules]));
 | 
				
			||||||
 | 
					    }, [project]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const updateProjectModules = () => {
 | 
				
			||||||
 | 
					        if (!project) return;
 | 
				
			||||||
 | 
					        ProjectService.updateProjectModules({
 | 
				
			||||||
 | 
					            requestBody: {
 | 
				
			||||||
 | 
					                projectId: project.id,
 | 
				
			||||||
 | 
					                moduleIds: selectedModules.values().toArray(),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(({ ok, message }) => {
 | 
				
			||||||
 | 
					                if (!ok) {
 | 
				
			||||||
 | 
					                    notifications.error({ message });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                refetchProjects();
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(err => console.log(err));
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Stack gap={rem(10)}>
 | 
				
			||||||
 | 
					            <BaseTable
 | 
				
			||||||
 | 
					                data={modules}
 | 
				
			||||||
 | 
					                columns={columns}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                restProps={{
 | 
				
			||||||
 | 
					                    enableSorting: false,
 | 
				
			||||||
 | 
					                    enableColumnActions: false,
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            {!eqSet(selectedModules, defaultSelectedModules) && (
 | 
				
			||||||
 | 
					                <InlineButton onClick={updateProjectModules}>
 | 
				
			||||||
 | 
					                    <IconCheck />
 | 
				
			||||||
 | 
					                    Сохранить
 | 
				
			||||||
 | 
					                </InlineButton>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					        </Stack>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Modules;
 | 
				
			||||||
@@ -1,43 +0,0 @@
 | 
				
			|||||||
import { Group } from "@mantine/core";
 | 
					 | 
				
			||||||
import { ProjectSchema } from "../../../../../../client";
 | 
					 | 
				
			||||||
import ModulesPicker from "./components/ModulesPicker.tsx";
 | 
					 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					 | 
				
			||||||
import AttributesPicker from "./components/AttributesPicker.tsx";
 | 
					 | 
				
			||||||
import useProjects from "../../../../hooks/useProjects.tsx";
 | 
					 | 
				
			||||||
import ProjectsEditor from "./components/ProjectsEditor.tsx";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ProjectsTab = () => {
 | 
					 | 
				
			||||||
    const { projects, refetchProjects } = useProjects();
 | 
					 | 
				
			||||||
    const [selectedProject, setSelectedProject] = useState<ProjectSchema | null>(null);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        if (projects.length > 0) {
 | 
					 | 
				
			||||||
            if (!selectedProject) {
 | 
					 | 
				
			||||||
                setSelectedProject(projects[0]);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                setSelectedProject(projects.find((project) => project.id === selectedProject.id) ?? null);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }, [projects]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <Group align={"flex-start"}>
 | 
					 | 
				
			||||||
            <ProjectsEditor
 | 
					 | 
				
			||||||
                projects={projects}
 | 
					 | 
				
			||||||
                refetchProjects={refetchProjects}
 | 
					 | 
				
			||||||
                selectedProject={selectedProject}
 | 
					 | 
				
			||||||
                setSelectedProject={setSelectedProject}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <ModulesPicker
 | 
					 | 
				
			||||||
                project={selectedProject}
 | 
					 | 
				
			||||||
                refetchProjects={refetchProjects}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <AttributesPicker
 | 
					 | 
				
			||||||
                project={selectedProject}
 | 
					 | 
				
			||||||
                refetchProjects={refetchProjects}
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
        </Group>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ProjectsTab;
 | 
					 | 
				
			||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
import { ProjectSchema, ProjectService } from "../../../../../../../client";
 | 
					 | 
				
			||||||
import styles from "../../../ProjectsEditorDrawer.module.css";
 | 
					 | 
				
			||||||
import { Center, rem, Stack, Title } from "@mantine/core";
 | 
					 | 
				
			||||||
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
 | 
					 | 
				
			||||||
import useAttributesList from "../../../../../../../hooks/useAttributesList.tsx";
 | 
					 | 
				
			||||||
import useAttributesTableColumns from "../hooks/attributesTableColumns.tsx";
 | 
					 | 
				
			||||||
import { useSet } from "@mantine/hooks";
 | 
					 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					 | 
				
			||||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
 | 
					 | 
				
			||||||
import InlineButton from "../../../../../../../components/InlineButton/InlineButton.tsx";
 | 
					 | 
				
			||||||
import { IconCheck } from "@tabler/icons-react";
 | 
					 | 
				
			||||||
import eqSet from "../../../utils/eqSet.ts";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
    project: ProjectSchema | null;
 | 
					 | 
				
			||||||
    refetchProjects: () => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const AttributesPicker = ({ project, refetchProjects }: Props) => {
 | 
					 | 
				
			||||||
    const { objects: attributes } = useAttributesList();
 | 
					 | 
				
			||||||
    const [defaultSelectedAttributes, setDefaultSelectedAttributes] = useState(new Set<number>(project?.attributes.map(m => m.id)));
 | 
					 | 
				
			||||||
    const selectedAttributes = useSet<number>(project?.attributes.map(a => a.id));
 | 
					 | 
				
			||||||
    const columns = useAttributesTableColumns({ selectedAttributes });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        selectedAttributes.clear();
 | 
					 | 
				
			||||||
        project?.attributes.forEach(attribute => {
 | 
					 | 
				
			||||||
            selectedAttributes.add(attribute.id);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        setDefaultSelectedAttributes(new Set([...selectedAttributes]));
 | 
					 | 
				
			||||||
    }, [project]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const onUpdateAttributesClick = () => {
 | 
					 | 
				
			||||||
        if (!project) return;
 | 
					 | 
				
			||||||
        ProjectService.updateProjectAttributes({
 | 
					 | 
				
			||||||
            requestBody: {
 | 
					 | 
				
			||||||
                projectId: project.id,
 | 
					 | 
				
			||||||
                attributeIds: selectedAttributes.values().toArray(),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
            .then(({ ok, message }) => {
 | 
					 | 
				
			||||||
                if (!ok) {
 | 
					 | 
				
			||||||
                    notifications.error({ message });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                refetchProjects();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .catch(err => console.log(err));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <div className={styles["container-wrapper"]} style={{ flex: 2 }}>
 | 
					 | 
				
			||||||
            <Stack gap={rem(10)}>
 | 
					 | 
				
			||||||
                <Center>
 | 
					 | 
				
			||||||
                    <Title order={4}>Атрибуты проекта</Title>
 | 
					 | 
				
			||||||
                </Center>
 | 
					 | 
				
			||||||
                <BaseTable
 | 
					 | 
				
			||||||
                    data={attributes}
 | 
					 | 
				
			||||||
                    columns={columns}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    restProps={{
 | 
					 | 
				
			||||||
                        enableSorting: false,
 | 
					 | 
				
			||||||
                        enableColumnActions: false,
 | 
					 | 
				
			||||||
                        enableRowVirtualization: true,
 | 
					 | 
				
			||||||
                        mantineTableContainerProps: { style: { maxHeight: "88vh" } },
 | 
					 | 
				
			||||||
                    }}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                {!eqSet(selectedAttributes, defaultSelectedAttributes) && (
 | 
					 | 
				
			||||||
                    <InlineButton onClick={onUpdateAttributesClick}>
 | 
					 | 
				
			||||||
                        <IconCheck />
 | 
					 | 
				
			||||||
                        Сохранить
 | 
					 | 
				
			||||||
                    </InlineButton>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
            </Stack>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default AttributesPicker;
 | 
					 | 
				
			||||||
@@ -1,78 +0,0 @@
 | 
				
			|||||||
import { ProjectSchema, ProjectService } from "../../../../../../../client";
 | 
					 | 
				
			||||||
import useModulesList from "../hooks/useModulesList.tsx";
 | 
					 | 
				
			||||||
import styles from "../../../ProjectsEditorDrawer.module.css";
 | 
					 | 
				
			||||||
import { useSet } from "@mantine/hooks";
 | 
					 | 
				
			||||||
import useModulesTableColumns from "../hooks/modulesTableColumns.tsx";
 | 
					 | 
				
			||||||
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
 | 
					 | 
				
			||||||
import { Center, rem, Stack, Title } from "@mantine/core";
 | 
					 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					 | 
				
			||||||
import InlineButton from "../../../../../../../components/InlineButton/InlineButton.tsx";
 | 
					 | 
				
			||||||
import { IconCheck } from "@tabler/icons-react";
 | 
					 | 
				
			||||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
 | 
					 | 
				
			||||||
import eqSet from "../../../utils/eqSet.ts";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
    project: ProjectSchema | null;
 | 
					 | 
				
			||||||
    refetchProjects: () => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ModulesPicker = ({ project, refetchProjects }: Props) => {
 | 
					 | 
				
			||||||
    const { objects: modules } = useModulesList();
 | 
					 | 
				
			||||||
    const [defaultSelectedModules, setDefaultSelectedModules] = useState(
 | 
					 | 
				
			||||||
        new Set<number>(project?.modules.map(m => m.id)),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    const selectedModules = useSet<number>();
 | 
					 | 
				
			||||||
    const columns = useModulesTableColumns({ selectedModules });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        selectedModules.clear();
 | 
					 | 
				
			||||||
        project?.modules.forEach(module => {
 | 
					 | 
				
			||||||
            selectedModules.add(module.id);
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        setDefaultSelectedModules(new Set([...selectedModules]));
 | 
					 | 
				
			||||||
    }, [project]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const updateProjectModules = () => {
 | 
					 | 
				
			||||||
        if (!project) return;
 | 
					 | 
				
			||||||
        ProjectService.updateProjectModules({
 | 
					 | 
				
			||||||
            requestBody: {
 | 
					 | 
				
			||||||
                projectId: project.id,
 | 
					 | 
				
			||||||
                moduleIds: selectedModules.values().toArray(),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
            .then(({ ok, message }) => {
 | 
					 | 
				
			||||||
                if (!ok) {
 | 
					 | 
				
			||||||
                    notifications.error({ message });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                refetchProjects();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .catch(err => console.log(err));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <div className={styles["container-wrapper"]}>
 | 
					 | 
				
			||||||
            <Stack gap={rem(10)}>
 | 
					 | 
				
			||||||
                <Center>
 | 
					 | 
				
			||||||
                    <Title order={4}>Модули проекта</Title>
 | 
					 | 
				
			||||||
                </Center>
 | 
					 | 
				
			||||||
                <BaseTable
 | 
					 | 
				
			||||||
                    data={modules}
 | 
					 | 
				
			||||||
                    columns={columns}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    restProps={{
 | 
					 | 
				
			||||||
                        enableSorting: false,
 | 
					 | 
				
			||||||
                        enableColumnActions: false,
 | 
					 | 
				
			||||||
                    }}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                {!eqSet(selectedModules, defaultSelectedModules) && (
 | 
					 | 
				
			||||||
                    <InlineButton onClick={updateProjectModules}>
 | 
					 | 
				
			||||||
                        <IconCheck />
 | 
					 | 
				
			||||||
                        Сохранить
 | 
					 | 
				
			||||||
                    </InlineButton>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
            </Stack>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ModulesPicker;
 | 
					 | 
				
			||||||
@@ -1,95 +0,0 @@
 | 
				
			|||||||
import styles from "../../../ProjectsEditorDrawer.module.css";
 | 
					 | 
				
			||||||
import { ActionIcon, Center, Flex, rem, Stack, Title, Tooltip } from "@mantine/core";
 | 
					 | 
				
			||||||
import InlineButton from "../../../../../../../components/InlineButton/InlineButton.tsx";
 | 
					 | 
				
			||||||
import { IconCheck, IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
 | 
					 | 
				
			||||||
import { BaseTable } from "../../../../../../../components/BaseTable/BaseTable.tsx";
 | 
					 | 
				
			||||||
import { MRT_TableOptions } from "mantine-react-table";
 | 
					 | 
				
			||||||
import { FullProjectSchema, ProjectSchema } from "../../../../../../../client";
 | 
					 | 
				
			||||||
import useProjectsTableColumns from "../hooks/projectsTableColumns.tsx";
 | 
					 | 
				
			||||||
import useProjectsTab from "../hooks/useProjectsTab.tsx";
 | 
					 | 
				
			||||||
import React from "react";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
    projects: ProjectSchema[];
 | 
					 | 
				
			||||||
    refetchProjects: () => void;
 | 
					 | 
				
			||||||
    selectedProject: ProjectSchema | null;
 | 
					 | 
				
			||||||
    setSelectedProject: React.Dispatch<React.SetStateAction<ProjectSchema | null>>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ProjectsEditor = ({
 | 
					 | 
				
			||||||
                            projects,
 | 
					 | 
				
			||||||
                            refetchProjects,
 | 
					 | 
				
			||||||
                            selectedProject,
 | 
					 | 
				
			||||||
                            setSelectedProject,
 | 
					 | 
				
			||||||
                        }: Props) => {
 | 
					 | 
				
			||||||
    const {
 | 
					 | 
				
			||||||
        editingProjects,
 | 
					 | 
				
			||||||
        handleEditClick,
 | 
					 | 
				
			||||||
        handleDeleteClick,
 | 
					 | 
				
			||||||
        handleCreateClick,
 | 
					 | 
				
			||||||
    } = useProjectsTab({
 | 
					 | 
				
			||||||
        refetchProjects,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const columns = useProjectsTableColumns({ editingProjects, selectedProject, setSelectedProject });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <div className={styles["container-wrapper"]} style={{ flex: 1.5 }}>
 | 
					 | 
				
			||||||
            <Stack gap={rem(10)}>
 | 
					 | 
				
			||||||
                <Center>
 | 
					 | 
				
			||||||
                    <Title order={4}>Проекты</Title>
 | 
					 | 
				
			||||||
                </Center>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                <BaseTable
 | 
					 | 
				
			||||||
                    data={projects}
 | 
					 | 
				
			||||||
                    columns={columns}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    restProps={
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            enableSorting: false,
 | 
					 | 
				
			||||||
                            enableColumnActions: false,
 | 
					 | 
				
			||||||
                            enableRowActions: true,
 | 
					 | 
				
			||||||
                            positionActionsColumn: "last",
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            renderRowActions: ({ row }) => (
 | 
					 | 
				
			||||||
                                <Flex gap="md">
 | 
					 | 
				
			||||||
                                    <Tooltip label="Редактировать">
 | 
					 | 
				
			||||||
                                        <ActionIcon
 | 
					 | 
				
			||||||
                                            onClick={() => handleEditClick(row.original)}
 | 
					 | 
				
			||||||
                                            variant={"default"}>
 | 
					 | 
				
			||||||
                                            {
 | 
					 | 
				
			||||||
                                                editingProjects.has(row.original.id) ? (
 | 
					 | 
				
			||||||
                                                    <IconCheck />
 | 
					 | 
				
			||||||
                                                ) : (
 | 
					 | 
				
			||||||
                                                    <IconEdit />
 | 
					 | 
				
			||||||
                                                )
 | 
					 | 
				
			||||||
                                            }
 | 
					 | 
				
			||||||
                                        </ActionIcon>
 | 
					 | 
				
			||||||
                                    </Tooltip>
 | 
					 | 
				
			||||||
                                    <Tooltip label={"Удалить"}>
 | 
					 | 
				
			||||||
                                        <ActionIcon
 | 
					 | 
				
			||||||
                                            onClick={() => handleDeleteClick(row.original)}
 | 
					 | 
				
			||||||
                                            disabled={row.original.boardsCount > 0}
 | 
					 | 
				
			||||||
                                            variant={"default"}>
 | 
					 | 
				
			||||||
                                            <IconTrash />
 | 
					 | 
				
			||||||
                                        </ActionIcon>
 | 
					 | 
				
			||||||
                                    </Tooltip>
 | 
					 | 
				
			||||||
                                </Flex>
 | 
					 | 
				
			||||||
                            ),
 | 
					 | 
				
			||||||
                        } as MRT_TableOptions<FullProjectSchema>
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                <InlineButton
 | 
					 | 
				
			||||||
                    variant={"default"}
 | 
					 | 
				
			||||||
                    onClick={handleCreateClick}
 | 
					 | 
				
			||||||
                    style={{ border: "dashed var(--item-border-size) var(--mantine-color-default-border)" }}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                    <IconPlus />
 | 
					 | 
				
			||||||
                    Добавить
 | 
					 | 
				
			||||||
                </InlineButton>
 | 
					 | 
				
			||||||
            </Stack>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default ProjectsEditor;
 | 
					 | 
				
			||||||
@@ -1,83 +0,0 @@
 | 
				
			|||||||
import { ProjectSchema, ProjectService } from "../../../../../../../client";
 | 
					 | 
				
			||||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
 | 
					 | 
				
			||||||
import { useProjectsEditorContext } from "../../../../../contexts/ProjectsEditorContext.tsx";
 | 
					 | 
				
			||||||
import { useMap } from "@mantine/hooks";
 | 
					 | 
				
			||||||
import { modals } from "@mantine/modals";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
    refetchProjects: () => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const useProjectsTab = ({ refetchProjects }: Props) => {
 | 
					 | 
				
			||||||
    const { onUpdate } = useProjectsEditorContext();
 | 
					 | 
				
			||||||
    const editingProjects = useMap<number, ProjectSchema>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const updateProject = (project: ProjectSchema) => {
 | 
					 | 
				
			||||||
        ProjectService.updateProject({
 | 
					 | 
				
			||||||
            requestBody: {
 | 
					 | 
				
			||||||
                project,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
            .then(({ ok, message }) => {
 | 
					 | 
				
			||||||
                if (!ok) {
 | 
					 | 
				
			||||||
                    notifications.error({ message });
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                editingProjects.delete(project.id);
 | 
					 | 
				
			||||||
                refetchProjects();
 | 
					 | 
				
			||||||
                onUpdate();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .catch(err => console.log(err));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleEditClick = (project: ProjectSchema) => {
 | 
					 | 
				
			||||||
        const editedProject = editingProjects.get(project.id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!editedProject) {
 | 
					 | 
				
			||||||
            editingProjects.set(project.id, project);
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (editedProject.name.length === 0) {
 | 
					 | 
				
			||||||
            notifications.error({ message: "Имя проекта не может быть пустым" });
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        updateProject(project);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleDeleteClick = (project: ProjectSchema) => {
 | 
					 | 
				
			||||||
        ProjectService.deleteProject({
 | 
					 | 
				
			||||||
            projectId: project.id,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
            .then(({ ok, message }) => {
 | 
					 | 
				
			||||||
                if (!ok) {
 | 
					 | 
				
			||||||
                    notifications.error({ message });
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                refetchProjects();
 | 
					 | 
				
			||||||
                onUpdate();
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            .catch(err => console.log(err));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleCreateClick = () => {
 | 
					 | 
				
			||||||
        modals.openContextModal({
 | 
					 | 
				
			||||||
            modal: "createProjectModal",
 | 
					 | 
				
			||||||
            title: "Создание проекта",
 | 
					 | 
				
			||||||
            withCloseButton: false,
 | 
					 | 
				
			||||||
            innerProps: {
 | 
					 | 
				
			||||||
                refetchProjects,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
        editingProjects,
 | 
					 | 
				
			||||||
        handleEditClick,
 | 
					 | 
				
			||||||
        handleDeleteClick,
 | 
					 | 
				
			||||||
        handleCreateClick,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default useProjectsTab;
 | 
					 | 
				
			||||||
@@ -1,18 +1,17 @@
 | 
				
			|||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import { BoardSchema, BoardService } from "../../../client";
 | 
					import { BoardSchema, BoardService } from "../../../client";
 | 
				
			||||||
 | 
					import { useProjectsContext } from "../../../contexts/ProjectsContext.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
    projectId?: number;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useBoards = ({ projectId }: Props) => {
 | 
					const useBoards = () => {
 | 
				
			||||||
 | 
					    const { selectedProject } = useProjectsContext();
 | 
				
			||||||
    const [boards, setBoards] = useState<BoardSchema[]>([]);
 | 
					    const [boards, setBoards] = useState<BoardSchema[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const refetchBoards = () => {
 | 
					    const refetchBoards = () => {
 | 
				
			||||||
        if (!projectId) return;
 | 
					        if (!selectedProject) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        BoardService.getBoards({
 | 
					        BoardService.getBoards({
 | 
				
			||||||
            projectId,
 | 
					            projectId: selectedProject.id,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
            .then(data => {
 | 
					            .then(data => {
 | 
				
			||||||
                setBoards(data.boards);
 | 
					                setBoards(data.boards);
 | 
				
			||||||
@@ -22,7 +21,7 @@ const useBoards = ({ projectId }: Props) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        refetchBoards();
 | 
					        refetchBoards();
 | 
				
			||||||
    }, [projectId]);
 | 
					    }, [selectedProject]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        boards,
 | 
					        boards,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,28 +3,21 @@ import { useForm } from "@mantine/form";
 | 
				
			|||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import { BaseMarketplaceSchema, BoardSchema, ClientSchema, ProjectSchema, StatusSchema } from "../../../client";
 | 
					import { BaseMarketplaceSchema, BoardSchema, ClientSchema, ProjectSchema, StatusSchema } from "../../../client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
type Props = {
 | 
					 | 
				
			||||||
    projects: ProjectSchema[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type CardsPageState = {
 | 
					export type CardsPageState = {
 | 
				
			||||||
    id: number | null;
 | 
					    id: number | null;
 | 
				
			||||||
    marketplace: BaseMarketplaceSchema | null;
 | 
					    marketplace: BaseMarketplaceSchema | null;
 | 
				
			||||||
    client: ClientSchema | null;
 | 
					    client: ClientSchema | null;
 | 
				
			||||||
    project: ProjectSchema | null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    projectForTable: ProjectSchema | null;
 | 
					    projectForTable: ProjectSchema | null;
 | 
				
			||||||
    board: BoardSchema | null;
 | 
					    board: BoardSchema | null;
 | 
				
			||||||
    status: StatusSchema | null;
 | 
					    status: StatusSchema | null;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useCardsPageState = ({ projects }: Props) => {
 | 
					const useCardsPageState = () => {
 | 
				
			||||||
    const { objects } = useCardSummariesFull();
 | 
					    const { objects } = useCardSummariesFull();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const form = useForm<CardsPageState>({
 | 
					    const form = useForm<CardsPageState>({
 | 
				
			||||||
        initialValues: {
 | 
					        initialValues: {
 | 
				
			||||||
            project: null,
 | 
					 | 
				
			||||||
            id: null,
 | 
					            id: null,
 | 
				
			||||||
            marketplace: null,
 | 
					            marketplace: null,
 | 
				
			||||||
            client: null,
 | 
					            client: null,
 | 
				
			||||||
@@ -51,7 +44,7 @@ const useCardsPageState = ({ projects }: Props) => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        if (form.values.projectForTable) {
 | 
					        if (form.values.projectForTable) {
 | 
				
			||||||
            result = result.filter(
 | 
					            result = result.filter(
 | 
				
			||||||
                obj => obj.board.projectId === form.values.project?.id,
 | 
					                obj => obj.board.projectId === form.values.projectForTable?.id,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (form.values.board) {
 | 
					            if (form.values.board) {
 | 
				
			||||||
@@ -78,12 +71,6 @@ const useCardsPageState = ({ projects }: Props) => {
 | 
				
			|||||||
        applyFilters();
 | 
					        applyFilters();
 | 
				
			||||||
    }, [form.values, objects]);
 | 
					    }, [form.values, objects]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        if (projects.length > 0 && form.values.project === null) {
 | 
					 | 
				
			||||||
            form.setFieldValue("project", projects[0]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }, [projects]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return { data, form };
 | 
					    return { data, form };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,16 +13,14 @@ import { useParams } from "@tanstack/react-router";
 | 
				
			|||||||
import { PrefillCardsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
 | 
					import { PrefillCardsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
 | 
				
			||||||
import DisplayMode from "../enums/DisplayMode.ts";
 | 
					import DisplayMode from "../enums/DisplayMode.ts";
 | 
				
			||||||
import CardsPageHeader from "../components/CardsPageHeader/CardsPageHeader.tsx";
 | 
					import CardsPageHeader from "../components/CardsPageHeader/CardsPageHeader.tsx";
 | 
				
			||||||
import useProjects from "../hooks/useProjects.tsx";
 | 
					 | 
				
			||||||
import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
 | 
					import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
 | 
				
			||||||
import useBoards from "../hooks/useBoards.tsx";
 | 
					import useBoards from "../hooks/useBoards.tsx";
 | 
				
			||||||
import { ProjectsEditorContextProvider } from "../contexts/ProjectsEditorContext.tsx";
 | 
					import { ProjectsEditorContextProvider } from "../contexts/ProjectsEditorContext.tsx";
 | 
				
			||||||
import ProjectsEditorDrawer from "../drawers/ProjectsEditorDrawer/ProjectsEditorDrawer.tsx";
 | 
					import ProjectEditDrawer from "../drawers/ProjectEditDrawer/ProjectEditDrawer.tsx";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CardsPage: FC = () => {
 | 
					export const CardsPage: FC = () => {
 | 
				
			||||||
    const { projects, refetchProjects } = useProjects();
 | 
					    const { data, form } = useCardsPageState();
 | 
				
			||||||
    const { data, form } = useCardsPageState({ projects });
 | 
					    const { boards, refetchBoards } = useBoards();
 | 
				
			||||||
    const { boards, refetchBoards } = useBoards({ projectId: form.values.project?.id });
 | 
					 | 
				
			||||||
    const { dealId } = useParams({ strict: false });
 | 
					    const { dealId } = useParams({ strict: false });
 | 
				
			||||||
    const { summariesRaw, refetch: refetchSummaries } = useCardSummaries();
 | 
					    const { summariesRaw, refetch: refetchSummaries } = useCardSummaries();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,24 +72,20 @@ export const CardsPage: FC = () => {
 | 
				
			|||||||
                padding: 0,
 | 
					                padding: 0,
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					            <ProjectsEditorContextProvider>
 | 
				
			||||||
                <CardPageContextProvider
 | 
					                <CardPageContextProvider
 | 
				
			||||||
                    defaultCardId={(dealId && parseInt(dealId)) || undefined}
 | 
					                    defaultCardId={(dealId && parseInt(dealId)) || undefined}
 | 
				
			||||||
                    refetchCards={async () => {
 | 
					                    refetchCards={async () => {
 | 
				
			||||||
                        await refetchSummaries();
 | 
					                        await refetchSummaries();
 | 
				
			||||||
                    }}
 | 
					                    }}
 | 
				
			||||||
                selectedProject={form.values.project}
 | 
					 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    <PrefillCardContextProvider>
 | 
					                    <PrefillCardContextProvider>
 | 
				
			||||||
                        <PrefillCardsWithExcelContextProvider>
 | 
					                        <PrefillCardsWithExcelContextProvider>
 | 
				
			||||||
                        <ProjectsEditorContextProvider onUpdate={refetchProjects}>
 | 
					 | 
				
			||||||
                            <CardsPageHeader
 | 
					                            <CardsPageHeader
 | 
				
			||||||
                                form={form}
 | 
					                                form={form}
 | 
				
			||||||
                                displayMode={displayMode}
 | 
					                                displayMode={displayMode}
 | 
				
			||||||
                                setDisplayMode={setDisplayMode}
 | 
					                                setDisplayMode={setDisplayMode}
 | 
				
			||||||
                                projects={projects}
 | 
					 | 
				
			||||||
                            />
 | 
					                            />
 | 
				
			||||||
                            <ProjectsEditorDrawer />
 | 
					 | 
				
			||||||
                        </ProjectsEditorContextProvider>
 | 
					 | 
				
			||||||
                            <PageBlock
 | 
					                            <PageBlock
 | 
				
			||||||
                                style={{
 | 
					                                style={{
 | 
				
			||||||
                                    display: "flex",
 | 
					                                    display: "flex",
 | 
				
			||||||
@@ -103,10 +97,12 @@ export const CardsPage: FC = () => {
 | 
				
			|||||||
                                {getBody()}
 | 
					                                {getBody()}
 | 
				
			||||||
                            </PageBlock>
 | 
					                            </PageBlock>
 | 
				
			||||||
                            <CardEditDrawer />
 | 
					                            <CardEditDrawer />
 | 
				
			||||||
 | 
					                            <ProjectEditDrawer />
 | 
				
			||||||
                            <CardPrefillDrawer />
 | 
					                            <CardPrefillDrawer />
 | 
				
			||||||
                        </PrefillCardsWithExcelContextProvider>
 | 
					                        </PrefillCardsWithExcelContextProvider>
 | 
				
			||||||
                    </PrefillCardContextProvider>
 | 
					                    </PrefillCardContextProvider>
 | 
				
			||||||
                </CardPageContextProvider>
 | 
					                </CardPageContextProvider>
 | 
				
			||||||
 | 
					            </ProjectsEditorContextProvider>
 | 
				
			||||||
        </PageBlock>
 | 
					        </PageBlock>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ export enum Modules {
 | 
				
			|||||||
    EMPLOYEES = "employees",
 | 
					    EMPLOYEES = "employees",
 | 
				
			||||||
    CLIENTS = "clients",
 | 
					    CLIENTS = "clients",
 | 
				
			||||||
    MANAGERS = "managers",
 | 
					    MANAGERS = "managers",
 | 
				
			||||||
 | 
					    MEGA_MODULE = "hui",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => {
 | 
					const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user