fix: projects editor to selected project editor, moved attributes editor

This commit is contained in:
2025-03-02 16:49:28 +04:00
parent 17e6c5f23a
commit e151e4bc5e
44 changed files with 476 additions and 512 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -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 = () => {

View File

@@ -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>
<RouterProvider router={router} /> <ProjectsContextProvider>
<Notifications /> <RouterProvider router={router} />
<Notifications />
</ProjectsContextProvider>
</TasksProvider> </TasksProvider>
</DatesProvider> </DatesProvider>
</ModalsProvider> </ModalsProvider>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = () => {

View File

@@ -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({

View File

@@ -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 = {

View File

@@ -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"} <ActionIcon
onClick={openProjectsEditor} size={"lg"}
variant={"default"}> onClick={handleCreateClick}
<IconEdit /> variant={"default"}>
</ActionIcon> <IconPlus />
</ActionIcon>
<ActionIcon
size={"lg"}
onClick={openProjectsEditor}
variant={"default"}>
<IconEdit />
</ActionIcon>
</>
)} )}
<ObjectSelect <ObjectSelect
placeholder={"Выберите проект"} placeholder={"Выберите проект"}
data={projects} data={projects}
{...form.getInputProps("project")} value={selectedProject}
onChange={setSelectedProject}
/> />
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,39 +72,37 @@ export const CardsPage: FC = () => {
padding: 0, padding: 0,
}} }}
> >
<CardPageContextProvider <ProjectsEditorContextProvider>
defaultCardId={(dealId && parseInt(dealId)) || undefined} <CardPageContextProvider
refetchCards={async () => { defaultCardId={(dealId && parseInt(dealId)) || undefined}
await refetchSummaries(); refetchCards={async () => {
}} 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 /> <PageBlock
</ProjectsEditorContextProvider> style={{
<PageBlock display: "flex",
style={{ flexDirection: "column",
display: "flex", flex: 1,
flexDirection: "column", height: "100%",
flex: 1, }}
height: "100%", >
}} {getBody()}
> </PageBlock>
{getBody()} <CardEditDrawer />
</PageBlock> <ProjectEditDrawer />
<CardEditDrawer /> <CardPrefillDrawer />
<CardPrefillDrawer /> </PrefillCardsWithExcelContextProvider>
</PrefillCardsWithExcelContextProvider> </PrefillCardContextProvider>
</PrefillCardContextProvider> </CardPageContextProvider>
</CardPageContextProvider> </ProjectsEditorContextProvider>
</PageBlock> </PageBlock>
); );
}; };

View File

@@ -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) => {