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 { ProfitTableDataItem } from './models/ProfitTableDataItem';
|
||||
export type { ProfitTableGroupBy } from './models/ProfitTableGroupBy';
|
||||
export type { ProjectGeneralInfoSchema } from './models/ProjectGeneralInfoSchema';
|
||||
export type { ProjectSchema } from './models/ProjectSchema';
|
||||
export type { ReceiptBoxSchema } from './models/ReceiptBoxSchema';
|
||||
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 */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ProjectSchema } from './ProjectSchema';
|
||||
import type { ProjectGeneralInfoSchema } from './ProjectGeneralInfoSchema';
|
||||
export type UpdateProjectRequest = {
|
||||
project: ProjectSchema;
|
||||
project: ProjectGeneralInfoSchema;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
||||
import { BoardSchema, BoardService } from "../../../../../client";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
import { useCardPageContext } from "../../../../../pages/CardsPage/contexts/CardPageContext.tsx";
|
||||
import { useProjectsContext } from "../../../../../contexts/ProjectsContext.tsx";
|
||||
|
||||
type Props = {
|
||||
boards: BoardSchema[];
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
|
||||
const useBoards = ({ boards, refetchBoards }: Props) => {
|
||||
const [selectedBoard, setSelectedBoard] = useState<BoardSchema | null>(null);
|
||||
const { selectedProject: project } = useCardPageContext();
|
||||
const { selectedProject: project } = useProjectsContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (boards.length > 0 && selectedBoard === null) {
|
||||
@@ -24,7 +24,7 @@ const useBoards = ({ boards, refetchBoards }: Props) => {
|
||||
let newBoard = boards.find(board => board.id === selectedBoard.id);
|
||||
|
||||
if (!newBoard && boards.length > 0) {
|
||||
newBoard = boards[0]
|
||||
newBoard = boards[0];
|
||||
}
|
||||
setSelectedBoard(newBoard ?? null);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IconCheck, IconLayoutGridRemove, IconTrash } from "@tabler/icons-react"
|
||||
import { useContextMenu } from "mantine-contextmenu";
|
||||
import useCardSummaryState from "./useCardSummaryState.tsx";
|
||||
import isModuleInProject, { Modules } from "../../../../pages/CardsPage/utils/isModuleInProject.ts";
|
||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||
|
||||
type Props = {
|
||||
cardSummary: CardSummary;
|
||||
@@ -18,7 +19,8 @@ type Props = {
|
||||
|
||||
const CardSummaryItem: FC<Props> = ({ cardSummary, color }) => {
|
||||
const { showContextMenu } = useContextMenu();
|
||||
const { selectedProject, setSelectedCard } = useCardPageContext();
|
||||
const { selectedProject } = useProjectsContext();
|
||||
const { setSelectedCard } = useCardPageContext();
|
||||
const { onDelete, onComplete, onDeleteFromGroup } = useCardSummaryState();
|
||||
|
||||
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 CreateDealsFromFileButton from "../CreateCardsFromFileButton/CreateDealsFromFileButton.tsx";
|
||||
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 { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||
|
||||
type Props = {
|
||||
status: StatusSchema;
|
||||
@@ -27,7 +27,7 @@ export const CardsDndColumn: FC<Props> = ({
|
||||
dragState,
|
||||
withCreateButton = false,
|
||||
}) => {
|
||||
const { selectedProject } = useCardPageContext();
|
||||
const { selectedProject } = useProjectsContext();
|
||||
const isCreatingDealFromFileEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
|
||||
const isDropDisabled = dragState === DragState.DRAG_STATUS;
|
||||
const droppableId = status.id.toString();
|
||||
|
||||
@@ -7,8 +7,8 @@ import { CardService, StatusSchema } from "../../../../client";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { dateWithoutTimezone } from "../../../../shared/lib/date.ts";
|
||||
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 { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||
|
||||
type Props = {
|
||||
status: StatusSchema;
|
||||
@@ -20,7 +20,7 @@ const CreateCardButton = ({ status }: Props) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { prefillCard, setPrefillCard } = usePrefillCardContext();
|
||||
|
||||
const { selectedProject } = useCardPageContext();
|
||||
const { selectedProject } = useProjectsContext();
|
||||
const isPrefillingDealEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,8 +9,8 @@ import ShippingWarehouseAutocomplete
|
||||
from "../../../Selects/ShippingWarehouseAutocomplete/ShippingWarehouseAutocomplete.tsx";
|
||||
import BaseMarketplaceSelect from "../../../Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.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 { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||
|
||||
type Props = {
|
||||
onSubmit: (quickDeal: QuickCard) => void;
|
||||
@@ -18,7 +18,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const CreateCardForm: FC<Props> = ({ onSubmit, onCancel }) => {
|
||||
const { selectedProject } = useCardPageContext();
|
||||
const { selectedProject } = useProjectsContext();
|
||||
const isPrefillingEnabled = isModuleInProject(Modules.SERVICES_AND_PRODUCTS, selectedProject);
|
||||
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 { type FullProjectSchema, ProjectService } from "../../../client";
|
||||
import { type FullProjectSchema, ProjectService } from "../client";
|
||||
|
||||
|
||||
const useProjects = () => {
|
||||
12
src/main.tsx
12
src/main.tsx
@@ -23,6 +23,7 @@ import { DatesProvider } from "@mantine/dates";
|
||||
import { modals } from "./modals/modals.ts";
|
||||
import TasksProvider from "./providers/TasksProvider/TasksProvider.tsx";
|
||||
import { ContextMenuProvider } from "mantine-contextmenu";
|
||||
import { ProjectsContextProvider } from "./contexts/ProjectsContext.tsx";
|
||||
|
||||
// Configuring router
|
||||
const router = createRouter({ routeTree });
|
||||
@@ -43,22 +44,21 @@ const queryClient = new QueryClient();
|
||||
|
||||
// Configuring OpenAPI
|
||||
OpenAPI.BASE = import.meta.env.VITE_API_URL;
|
||||
OpenAPI.TOKEN = JSON.parse(localStorage.getItem("authState") || "{}")[
|
||||
"accessToken"
|
||||
];
|
||||
OpenAPI.TOKEN = JSON.parse(localStorage.getItem("authState") || "{}")["accessToken"];
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<Provider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MantineProvider defaultColorScheme={"dark"}>
|
||||
<ContextMenuProvider>
|
||||
|
||||
<ModalsProvider
|
||||
labels={{ confirm: "Да", cancel: "Нет" }}
|
||||
modals={modals}>
|
||||
<DatesProvider settings={{ locale: "ru" }}>
|
||||
<TasksProvider>
|
||||
<RouterProvider router={router} />
|
||||
<Notifications />
|
||||
<ProjectsContextProvider>
|
||||
<RouterProvider router={router} />
|
||||
<Notifications />
|
||||
</ProjectsContextProvider>
|
||||
</TasksProvider>
|
||||
</DatesProvider>
|
||||
</ModalsProvider>
|
||||
|
||||
@@ -35,9 +35,9 @@ import ReceiptModal from "../pages/ReceiptPage/components/ReceiptEditing/modals/
|
||||
import SelectScannedProductModal from "../pages/ReceiptPage/modals/SelectScannedProductModal.tsx";
|
||||
import BoardModal from "../pages/CardsPage/modals/BoardModal/BoardModal.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
|
||||
from "../pages/CardsPage/drawers/ProjectsEditorDrawer/tabs/ProjectsTab/modals/CreateProjectModal.tsx";
|
||||
from "../pages/CardsPage/drawers/ProjectEditDrawer/tabs/General/modals/CreateProjectModal.tsx";
|
||||
|
||||
export const modals = {
|
||||
enterDeadline: EnterDeadlineModal,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
IconCalendarUser,
|
||||
IconCoins,
|
||||
IconCurrencyDollar,
|
||||
IconQrcode,
|
||||
IconQrcode, IconSubtask,
|
||||
IconTopologyStar3,
|
||||
IconUser,
|
||||
} from "@tabler/icons-react";
|
||||
@@ -21,6 +21,7 @@ import { RootState } from "../../redux/store.ts";
|
||||
import OrganizationalStructureTab from "./tabs/OrganizationalStructureTab/OrganizationalStructureTab.tsx";
|
||||
import { ReactNode } from "react";
|
||||
import WorkShiftsPlanning from "./tabs/WorkShiftsPlanning/WorkShiftsPlanning.tsx";
|
||||
import Attributes from "./tabs/Attributes/Attributes.tsx";
|
||||
|
||||
const AdminPage = () => {
|
||||
const userRole = useSelector((state: RootState) => state.auth.role);
|
||||
@@ -88,6 +89,13 @@ const AdminPage = () => {
|
||||
Доходы и расходы
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<Tabs.Tab
|
||||
value={"attributes"}
|
||||
leftSection={<IconSubtask />}>
|
||||
Атрибуты карточек
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
</Tabs.List>
|
||||
{getTabPanel("users", <UsersTab />)}
|
||||
{getTabPanel("rolesAndPositions", <OrganizationalStructureTab />)}
|
||||
@@ -96,6 +104,7 @@ const AdminPage = () => {
|
||||
{getTabPanel("workShiftsPlanning", <WorkShiftsPlanning />)}
|
||||
{getTabPanel("workShifts", <WorkShiftsTab />)}
|
||||
{getTabPanel("transactions", <TransactionsTab />)}
|
||||
{getTabPanel("attributes", <Attributes />)}
|
||||
</Tabs>
|
||||
</PageBlock>
|
||||
</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 useAttributesList from "../../../../../../hooks/useAttributesList.tsx";
|
||||
import useAttributesList from "../../../../hooks/useAttributesList.tsx";
|
||||
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 { modals } from "@mantine/modals";
|
||||
import { AttributeSchema, AttributeService } from "../../../../../../client";
|
||||
import { notifications } from "../../../../../../shared/lib/notifications.ts";
|
||||
import { AttributeSchema, AttributeService } from "../../../../client";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
|
||||
const AttributesTab = () => {
|
||||
const Attributes = () => {
|
||||
const columns = useAttributesTableColumns();
|
||||
const { objects: attributes, refetch: refetchAttributes } = useAttributesList();
|
||||
|
||||
@@ -66,7 +66,7 @@ const AttributesTab = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<Group>
|
||||
<InlineButton onClick={onCreateAttributeClick}>
|
||||
<InlineButton onClick={onCreateAttributeClick} mt={"md"}>
|
||||
<IconPlus />
|
||||
Добавить атрибут
|
||||
</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 { AttributeTypeSchema } from "../../../../../../../client";
|
||||
import ObjectSelect, { ObjectSelectProps } from "../../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import { AttributeTypeSchema } from "../../../../../client";
|
||||
import useAttributeTypesList from "../hooks/useAttributeTypesList.tsx";
|
||||
|
||||
type Props = Omit<ObjectSelectProps<AttributeTypeSchema>, "data">;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Checkbox, NumberInput, TextInput } from "@mantine/core";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { DatePickerInput, DateTimePicker } from "@mantine/dates";
|
||||
import { AttributeSchema } from "../../../../../../../client";
|
||||
import { AttributeSchema } from "../../../../../client";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturnType<Partial<AttributeSchema>>;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { AttributeSchema } from "../../../../../../../client";
|
||||
import { AttributeSchema } from "../../../../../client";
|
||||
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||
import { formatDate, formatDateTime } from "../../../../../../../types/utils.ts";
|
||||
import { formatDate, formatDateTime } from "../../../../../types/utils.ts";
|
||||
|
||||
|
||||
const useAttributesTableColumns = () => {
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AttributeService } from "../../../../../../../client";
|
||||
import ObjectList from "../../../../../../../hooks/objectList.tsx";
|
||||
import { AttributeService } from "../../../../../client";
|
||||
import ObjectList from "../../../../../hooks/objectList.tsx";
|
||||
|
||||
const useAttributeTypesList = () =>
|
||||
ObjectList({
|
||||
@@ -2,11 +2,11 @@ import { ContextModalProps } from "@mantine/modals";
|
||||
import { Button, Checkbox, Stack, Textarea, TextInput } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import AttributeTypeSelect from "../components/AttributeTypeSelect.tsx";
|
||||
import { AttributeSchema, AttributeService } from "../../../../../../../client";
|
||||
import { convertRussianToSnakeCase } from "../../../utils/stringConverting.ts";
|
||||
import { AttributeSchema, AttributeService } from "../../../../../client";
|
||||
import { convertRussianToSnakeCase } from "../../../../CardsPage/drawers/ProjectEditDrawer/utils/stringConverting.ts";
|
||||
import { useForm } from "@mantine/form";
|
||||
import DefaultAttributeValueInput from "../components/DefaultAttributeValueInput.tsx";
|
||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
|
||||
import { notifications } from "../../../../../shared/lib/notifications.ts";
|
||||
|
||||
|
||||
type Props = {
|
||||
@@ -1,5 +1,5 @@
|
||||
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 styles from "../../ui/CardsPage.module.css";
|
||||
import PageBlock from "../../../../components/PageBlock/PageBlock.tsx";
|
||||
@@ -7,30 +7,41 @@ import DisplayMode from "../../enums/DisplayMode.ts";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { CardsPageState } from "../../hooks/useCardsPageState.tsx";
|
||||
import React from "react";
|
||||
import { ProjectSchema } from "../../../../client";
|
||||
import ObjectSelect from "../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import CardsTableFiltersModal from "../../modals/CardsTableFiltersModal.tsx";
|
||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "../../../../redux/store.ts";
|
||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||
import { modals } from "@mantine/modals";
|
||||
|
||||
type Props = {
|
||||
displayMode: DisplayMode;
|
||||
setDisplayMode: React.Dispatch<React.SetStateAction<DisplayMode>>;
|
||||
form: UseFormReturnType<CardsPageState>;
|
||||
projects: ProjectSchema[];
|
||||
}
|
||||
|
||||
const CardsPageHeader = ({
|
||||
displayMode,
|
||||
setDisplayMode,
|
||||
form,
|
||||
projects,
|
||||
}: Props) => {
|
||||
const { openProjectsEditor } = useProjectsEditorContext();
|
||||
const { selectedProject, setSelectedProject, projects, refetchProjects } = useProjectsContext();
|
||||
const userRole = useSelector((state: RootState) => state.auth.role);
|
||||
const isAdmin = userRole === "admin";
|
||||
|
||||
const handleCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "createProjectModal",
|
||||
title: "Создание проекта",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
refetchProjects,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const getHeaderInputsBoard = () => {
|
||||
return (
|
||||
<div
|
||||
@@ -40,17 +51,26 @@ const CardsPageHeader = ({
|
||||
}}
|
||||
>
|
||||
{isAdmin && (
|
||||
<ActionIcon
|
||||
size={"lg"}
|
||||
onClick={openProjectsEditor}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
<>
|
||||
<ActionIcon
|
||||
size={"lg"}
|
||||
onClick={handleCreateClick}
|
||||
variant={"default"}>
|
||||
<IconPlus />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
size={"lg"}
|
||||
onClick={openProjectsEditor}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</>
|
||||
)}
|
||||
<ObjectSelect
|
||||
placeholder={"Выберите проект"}
|
||||
data={projects}
|
||||
{...form.getInputProps("project")}
|
||||
value={selectedProject}
|
||||
onChange={setSelectedProject}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { createContext, FC, useContext, useEffect, useState } from "react";
|
||||
import { CardSchema, CardService, ProjectSchema } from "../../../client";
|
||||
import { CardSchema, CardService } from "../../../client";
|
||||
|
||||
type CardPageContextState = {
|
||||
selectedCard?: CardSchema;
|
||||
setSelectedCard: (card: CardSchema | undefined) => void;
|
||||
refetchCards: () => Promise<void>;
|
||||
refetchCard: () => void;
|
||||
selectedProject?: ProjectSchema | null;
|
||||
};
|
||||
|
||||
const CardPageContext = createContext<CardPageContextState | undefined>(
|
||||
@@ -16,7 +15,6 @@ const CardPageContext = createContext<CardPageContextState | undefined>(
|
||||
type CardPageContextStateProps = {
|
||||
refetchCards: () => Promise<void>;
|
||||
defaultCardId?: number;
|
||||
selectedProject?: ProjectSchema | null;
|
||||
}
|
||||
|
||||
const useCardPageContextState = (props: CardPageContextStateProps) => {
|
||||
@@ -41,7 +39,6 @@ const useCardPageContextState = (props: CardPageContextStateProps) => {
|
||||
return {
|
||||
selectedCard,
|
||||
setSelectedCard,
|
||||
selectedProject: props.selectedProject,
|
||||
refetchCards,
|
||||
refetchCard,
|
||||
};
|
||||
|
||||
@@ -5,37 +5,28 @@ type ProjectsEditorContextState = {
|
||||
openedProjectsEditor: boolean;
|
||||
openProjectsEditor: () => void;
|
||||
closeProjectsEditor: () => void;
|
||||
onUpdate: () => void;
|
||||
};
|
||||
|
||||
const ProjectsEditorContext = createContext<ProjectsEditorContextState | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
type ProjectsEditorContextStateProps = {
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
const useProjectsEditorContextState = ({ onUpdate }: ProjectsEditorContextStateProps) => {
|
||||
const useProjectsEditorContextState = () => {
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
return {
|
||||
openedProjectsEditor: opened,
|
||||
openProjectsEditor: open,
|
||||
closeProjectsEditor: close,
|
||||
onUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
type ProjectsEditorContextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
} & ProjectsEditorContextStateProps;
|
||||
};
|
||||
|
||||
export const ProjectsEditorContextProvider: FC<ProjectsEditorContextProviderProps> = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const state = useProjectsEditorContextState(props);
|
||||
export const ProjectsEditorContextProvider: FC<ProjectsEditorContextProviderProps> = ({ children }) => {
|
||||
const state = useProjectsEditorContextState();
|
||||
|
||||
return (
|
||||
<ProjectsEditorContext.Provider value={state}>
|
||||
@@ -48,7 +39,7 @@ export const useProjectsEditorContext = () => {
|
||||
const context = useContext(ProjectsEditorContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useProjectsEditorContext must be used within a ProjectsEditorContextProvider",
|
||||
"useProjectEditorContext must be used within a ProjectEditorContextProvider",
|
||||
);
|
||||
}
|
||||
return context;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
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 { motion } from "framer-motion";
|
||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
|
||||
import ProjectsTab from "./tabs/ProjectsTab/ProjectsTab.tsx";
|
||||
import AttributesTab from "./tabs/AttributesTab/AttributesTab.tsx";
|
||||
import General from "./tabs/General/General.tsx";
|
||||
import Attributes from "./tabs/Attributes/Attributes.tsx";
|
||||
import Modules from "./tabs/Modules/Modules.tsx";
|
||||
|
||||
|
||||
const ProjectsEditorDrawer = () => {
|
||||
const ProjectEditDrawer = () => {
|
||||
const { closeProjectsEditor, openedProjectsEditor } = useProjectsEditorContext();
|
||||
|
||||
const getTabPanel = (value: string, component: ReactNode) => {
|
||||
@@ -45,16 +46,21 @@ const ProjectsEditorDrawer = () => {
|
||||
},
|
||||
}}>
|
||||
<Tabs
|
||||
defaultValue={"projects"}
|
||||
defaultValue={"general"}
|
||||
flex={1}
|
||||
variant={"outline"}
|
||||
orientation={"vertical"}
|
||||
keepMounted={false}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab
|
||||
value={"projects"}
|
||||
value={"general"}
|
||||
leftSection={<IconSettings />}>
|
||||
Проекты
|
||||
Общее
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value={"modules"}
|
||||
leftSection={<IconHexagons />}>
|
||||
Модули
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value={"attributes"}
|
||||
@@ -63,11 +69,12 @@ const ProjectsEditorDrawer = () => {
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
{getTabPanel("projects", <ProjectsTab />)}
|
||||
{getTabPanel("attributes", <AttributesTab />)}
|
||||
{getTabPanel("general", <General/>)}
|
||||
{getTabPanel("attributes", <Attributes/>)}
|
||||
{getTabPanel("modules", <Modules />)}
|
||||
</Tabs>
|
||||
</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 { 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 refetchBoards = () => {
|
||||
if (!projectId) return;
|
||||
if (!selectedProject) return;
|
||||
|
||||
BoardService.getBoards({
|
||||
projectId,
|
||||
projectId: selectedProject.id,
|
||||
})
|
||||
.then(data => {
|
||||
setBoards(data.boards);
|
||||
@@ -22,7 +21,7 @@ const useBoards = ({ projectId }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
refetchBoards();
|
||||
}, [projectId]);
|
||||
}, [selectedProject]);
|
||||
|
||||
return {
|
||||
boards,
|
||||
|
||||
@@ -3,28 +3,21 @@ import { useForm } from "@mantine/form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BaseMarketplaceSchema, BoardSchema, ClientSchema, ProjectSchema, StatusSchema } from "../../../client";
|
||||
|
||||
|
||||
type Props = {
|
||||
projects: ProjectSchema[];
|
||||
}
|
||||
|
||||
export type CardsPageState = {
|
||||
id: number | null;
|
||||
marketplace: BaseMarketplaceSchema | null;
|
||||
client: ClientSchema | null;
|
||||
project: ProjectSchema | null;
|
||||
|
||||
projectForTable: ProjectSchema | null;
|
||||
board: BoardSchema | null;
|
||||
status: StatusSchema | null;
|
||||
};
|
||||
|
||||
const useCardsPageState = ({ projects }: Props) => {
|
||||
const useCardsPageState = () => {
|
||||
const { objects } = useCardSummariesFull();
|
||||
|
||||
const form = useForm<CardsPageState>({
|
||||
initialValues: {
|
||||
project: null,
|
||||
id: null,
|
||||
marketplace: null,
|
||||
client: null,
|
||||
@@ -51,7 +44,7 @@ const useCardsPageState = ({ projects }: Props) => {
|
||||
}
|
||||
if (form.values.projectForTable) {
|
||||
result = result.filter(
|
||||
obj => obj.board.projectId === form.values.project?.id,
|
||||
obj => obj.board.projectId === form.values.projectForTable?.id,
|
||||
);
|
||||
|
||||
if (form.values.board) {
|
||||
@@ -78,12 +71,6 @@ const useCardsPageState = ({ projects }: Props) => {
|
||||
applyFilters();
|
||||
}, [form.values, objects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (projects.length > 0 && form.values.project === null) {
|
||||
form.setFieldValue("project", projects[0]);
|
||||
}
|
||||
}, [projects]);
|
||||
|
||||
return { data, form };
|
||||
};
|
||||
|
||||
|
||||
@@ -13,16 +13,14 @@ import { useParams } from "@tanstack/react-router";
|
||||
import { PrefillCardsWithExcelContextProvider } from "../contexts/PrefillDealsWithExcelContext.tsx";
|
||||
import DisplayMode from "../enums/DisplayMode.ts";
|
||||
import CardsPageHeader from "../components/CardsPageHeader/CardsPageHeader.tsx";
|
||||
import useProjects from "../hooks/useProjects.tsx";
|
||||
import Boards from "../../../components/Dnd/Boards/Boards/Boards.tsx";
|
||||
import useBoards from "../hooks/useBoards.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 = () => {
|
||||
const { projects, refetchProjects } = useProjects();
|
||||
const { data, form } = useCardsPageState({ projects });
|
||||
const { boards, refetchBoards } = useBoards({ projectId: form.values.project?.id });
|
||||
const { data, form } = useCardsPageState();
|
||||
const { boards, refetchBoards } = useBoards();
|
||||
const { dealId } = useParams({ strict: false });
|
||||
const { summariesRaw, refetch: refetchSummaries } = useCardSummaries();
|
||||
|
||||
@@ -74,39 +72,37 @@ export const CardsPage: FC = () => {
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<CardPageContextProvider
|
||||
defaultCardId={(dealId && parseInt(dealId)) || undefined}
|
||||
refetchCards={async () => {
|
||||
await refetchSummaries();
|
||||
}}
|
||||
selectedProject={form.values.project}
|
||||
>
|
||||
<PrefillCardContextProvider>
|
||||
<PrefillCardsWithExcelContextProvider>
|
||||
<ProjectsEditorContextProvider onUpdate={refetchProjects}>
|
||||
<ProjectsEditorContextProvider>
|
||||
<CardPageContextProvider
|
||||
defaultCardId={(dealId && parseInt(dealId)) || undefined}
|
||||
refetchCards={async () => {
|
||||
await refetchSummaries();
|
||||
}}
|
||||
>
|
||||
<PrefillCardContextProvider>
|
||||
<PrefillCardsWithExcelContextProvider>
|
||||
<CardsPageHeader
|
||||
form={form}
|
||||
displayMode={displayMode}
|
||||
setDisplayMode={setDisplayMode}
|
||||
projects={projects}
|
||||
/>
|
||||
<ProjectsEditorDrawer />
|
||||
</ProjectsEditorContextProvider>
|
||||
<PageBlock
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{getBody()}
|
||||
</PageBlock>
|
||||
<CardEditDrawer />
|
||||
<CardPrefillDrawer />
|
||||
</PrefillCardsWithExcelContextProvider>
|
||||
</PrefillCardContextProvider>
|
||||
</CardPageContextProvider>
|
||||
<PageBlock
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{getBody()}
|
||||
</PageBlock>
|
||||
<CardEditDrawer />
|
||||
<ProjectEditDrawer />
|
||||
<CardPrefillDrawer />
|
||||
</PrefillCardsWithExcelContextProvider>
|
||||
</PrefillCardContextProvider>
|
||||
</CardPageContextProvider>
|
||||
</ProjectsEditorContextProvider>
|
||||
</PageBlock>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ export enum Modules {
|
||||
EMPLOYEES = "employees",
|
||||
CLIENTS = "clients",
|
||||
MANAGERS = "managers",
|
||||
MEGA_MODULE = "hui",
|
||||
}
|
||||
|
||||
const isModuleInProject = (module: Modules, project?: ProjectSchema | null) => {
|
||||
|
||||
Reference in New Issue
Block a user