fix: projects editor to selected project editor, moved attributes editor
This commit is contained in:
@@ -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,112 +0,0 @@
|
||||
import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import useAttributesTableColumns from "./hooks/attributesTableColumns.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 { 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 { MRT_TableOptions } from "mantine-react-table";
|
||||
|
||||
const AttributesTab = () => {
|
||||
const columns = useAttributesTableColumns();
|
||||
const { objects: attributes, refetch: refetchAttributes } = useAttributesList();
|
||||
|
||||
const onCreateAttributeClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "attributeModal",
|
||||
title: "Создание атрибута",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
refetchAttributes,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onEditAttributeClick = (attribute: AttributeSchema) => {
|
||||
modals.openContextModal({
|
||||
modal: "attributeModal",
|
||||
title: "Редактирование атрибута",
|
||||
withCloseButton: false,
|
||||
innerProps: {
|
||||
refetchAttributes,
|
||||
attribute,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAttribute = (attribute: AttributeSchema) => {
|
||||
AttributeService.delete({
|
||||
attributeId: attribute.id,
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
}
|
||||
refetchAttributes();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDeleteAttributeClick = (attribute: AttributeSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление атрибута",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить атрибут "{attribute.label}"?
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => deleteAttribute(attribute),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Group>
|
||||
<InlineButton onClick={onCreateAttributeClick}>
|
||||
<IconPlus />
|
||||
Добавить атрибут
|
||||
</InlineButton>
|
||||
</Group>
|
||||
<BaseTable
|
||||
data={attributes}
|
||||
columns={columns}
|
||||
|
||||
restProps={
|
||||
{
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableBottomToolbar: false,
|
||||
enableRowActions: true,
|
||||
enableRowVirtualization: true,
|
||||
mantineTableContainerProps: { style: { maxHeight: "86vh" } },
|
||||
|
||||
renderRowActions: ({ row }) => (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onEditAttributeClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon
|
||||
onClick={() => onDeleteAttributeClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
),
|
||||
} as MRT_TableOptions<AttributeSchema>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributesTab;
|
||||
@@ -1,21 +0,0 @@
|
||||
import ObjectSelect, { ObjectSelectProps } from "../../../../../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import { AttributeTypeSchema } from "../../../../../../../client";
|
||||
import useAttributeTypesList from "../hooks/useAttributeTypesList.tsx";
|
||||
|
||||
type Props = Omit<ObjectSelectProps<AttributeTypeSchema>, "data">;
|
||||
|
||||
const AttributeTypeSelect = (props: Props) => {
|
||||
const { objects: attributeTypes } = useAttributeTypesList();
|
||||
|
||||
return (
|
||||
<ObjectSelect
|
||||
label={"Тип атрибута"}
|
||||
getLabelFn={type => type.name}
|
||||
getValueFn={type => type.id.toString()}
|
||||
data={attributeTypes}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributeTypeSelect;
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Checkbox, NumberInput, TextInput } from "@mantine/core";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { DatePickerInput, DateTimePicker } from "@mantine/dates";
|
||||
import { AttributeSchema } from "../../../../../../../client";
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturnType<Partial<AttributeSchema>>;
|
||||
}
|
||||
|
||||
const DefaultAttributeValueInput = ({ form }: Props) => {
|
||||
const type = form.values.type?.type;
|
||||
const label = "Значение по умолчанию";
|
||||
const inputName = "defaultValue";
|
||||
|
||||
if (type === "bool") {
|
||||
return (
|
||||
<Checkbox
|
||||
label={label}
|
||||
{...form.getInputProps(inputName, { type: "checkbox" })}
|
||||
/>
|
||||
);
|
||||
} else if (type === "date") {
|
||||
return (
|
||||
<DatePickerInput
|
||||
label={label}
|
||||
{...form.getInputProps(inputName)}
|
||||
value={form.values.defaultValue ? new Date(String(form.values.defaultValue)) : null}
|
||||
clearable
|
||||
locale={"ru-RU"}
|
||||
valueFormat="DD.MM.YYYY"
|
||||
/>
|
||||
);
|
||||
} else if (type === "datetime") {
|
||||
return (
|
||||
<DateTimePicker
|
||||
label={label}
|
||||
{...form.getInputProps(inputName)}
|
||||
value={form.values.defaultValue ? new Date(String(form.values.defaultValue)) : null}
|
||||
clearable
|
||||
locale={"ru-RU"}
|
||||
valueFormat="DD.MM.YYYY HH:mm"
|
||||
/>
|
||||
);
|
||||
} else if (type === "str") {
|
||||
return (
|
||||
<TextInput
|
||||
label={label}
|
||||
{...form.getInputProps(inputName)}
|
||||
value={form.getInputProps(inputName).value ?? ""}
|
||||
/>
|
||||
);
|
||||
} else if (type === "int" || type === "float") {
|
||||
return (
|
||||
<NumberInput
|
||||
allowDecimal={type === "float"}
|
||||
label={label}
|
||||
{...form.getInputProps(inputName)}
|
||||
value={Number(form.values.defaultValue)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default DefaultAttributeValueInput;
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { AttributeSchema } from "../../../../../../../client";
|
||||
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||
import { formatDate, formatDateTime } from "../../../../../../../types/utils.ts";
|
||||
|
||||
|
||||
const useAttributesTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<AttributeSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Название",
|
||||
accessorKey: "label",
|
||||
},
|
||||
{
|
||||
header: "Тип",
|
||||
accessorKey: "type.name",
|
||||
},
|
||||
{
|
||||
header: "Значение по умолчанию",
|
||||
accessorKey: "defaultValue",
|
||||
Cell: ({ cell, row }) => {
|
||||
const value = cell.getValue();
|
||||
if (value === null) return <>-</>;
|
||||
|
||||
const type = row.original.type.type;
|
||||
if (type === "datetime") {
|
||||
return formatDateTime(value as string);
|
||||
}
|
||||
if (type === "date") {
|
||||
return formatDate(value as string);
|
||||
}
|
||||
if (type === "bool") {
|
||||
return value ? <IconCheck /> : <IconX />;
|
||||
}
|
||||
|
||||
return <>{value}</>;
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Синхронизировано в группе",
|
||||
accessorKey: "isApplicableToGroup",
|
||||
Cell: ({ cell }) => cell.getValue() ? (
|
||||
<IconCheck />
|
||||
) : (
|
||||
<IconX />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Может быть пустым",
|
||||
accessorKey: "isNullable",
|
||||
Cell: ({ cell }) => cell.getValue() ? (
|
||||
<IconCheck />
|
||||
) : (
|
||||
<IconX />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "Описаниие",
|
||||
accessorKey: "description",
|
||||
}
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default useAttributesTableColumns;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { AttributeService } from "../../../../../../../client";
|
||||
import ObjectList from "../../../../../../../hooks/objectList.tsx";
|
||||
|
||||
const useAttributeTypesList = () =>
|
||||
ObjectList({
|
||||
queryFn: AttributeService.getTypes,
|
||||
getObjectsFn: response => response.types,
|
||||
queryKey: "getAllAttributeTypes",
|
||||
});
|
||||
|
||||
export default useAttributeTypesList;
|
||||
@@ -1,171 +0,0 @@
|
||||
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 { useForm } from "@mantine/form";
|
||||
import DefaultAttributeValueInput from "../components/DefaultAttributeValueInput.tsx";
|
||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
|
||||
|
||||
|
||||
type Props = {
|
||||
refetchAttributes: () => void;
|
||||
attribute?: AttributeSchema;
|
||||
};
|
||||
|
||||
const AttributeModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = "attribute" in innerProps;
|
||||
const [isInitial, setIsInitial] = useState(true);
|
||||
const [isNullableInputShown, setIsNullableInputShown] = useState(true);
|
||||
const [isDefaultValueInputShown, setIsDefaultValueInputShown] = useState(true);
|
||||
const closeModal = () => context.closeContextModal(id);
|
||||
|
||||
const form = useForm<Partial<AttributeSchema>>({
|
||||
initialValues: isEditing
|
||||
? innerProps.attribute
|
||||
: {
|
||||
label: "",
|
||||
name: "",
|
||||
type: undefined,
|
||||
isApplicableToGroup: false,
|
||||
isNullable: false,
|
||||
defaultValue: null,
|
||||
description: "",
|
||||
},
|
||||
validate: {
|
||||
label: label => !label?.trim() && "Название не заполнено",
|
||||
name: name => !name?.trim() && "Название генерирует некорректный уникальный ключ. Измените название",
|
||||
type: type => !type && "Тип атрибута не выбран",
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldValue("name", convertRussianToSnakeCase(form.values.label ?? ""));
|
||||
}, [form.values.label]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsDefaultValueInputShown(false);
|
||||
const type = form.values.type?.type;
|
||||
setIsNullableInputShown(type !== "bool");
|
||||
|
||||
if (!isInitial) {
|
||||
if (type === "bool") {
|
||||
form.setFieldValue("isNullable", false);
|
||||
form.setFieldValue("defaultValue", false);
|
||||
} else {
|
||||
form.setFieldValue("defaultValue", null);
|
||||
}
|
||||
}
|
||||
setIsInitial(false);
|
||||
setIsDefaultValueInputShown(true);
|
||||
}, [form.values.type?.id]);
|
||||
|
||||
const validate = (): boolean => {
|
||||
if (form.values.defaultValue === null && !form.values.isNullable) {
|
||||
notifications.error({ message: "Укажите значение по умолчанию или разрешите пустое значение." });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const onCreate = (attribute: AttributeSchema) => {
|
||||
AttributeService.create({
|
||||
requestBody: {
|
||||
attribute: {
|
||||
...attribute,
|
||||
typeId: attribute.type.id,
|
||||
name: attribute.name.trim(),
|
||||
label: attribute.label.trim(),
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
innerProps.refetchAttributes();
|
||||
closeModal();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onChange = (attribute: AttributeSchema) => {
|
||||
AttributeService.update({
|
||||
requestBody: {
|
||||
attribute: {
|
||||
...attribute,
|
||||
typeId: attribute.type.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
innerProps.refetchAttributes();
|
||||
closeModal();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onSubmit = (values: AttributeSchema) => {
|
||||
if (!validate()) return;
|
||||
if (isEditing) {
|
||||
onChange(values);
|
||||
} else {
|
||||
onCreate(values);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(values => onSubmit(values as AttributeSchema))}>
|
||||
<Stack gap={"md"}>
|
||||
<TextInput
|
||||
label={"Название"}
|
||||
{...form.getInputProps("label")}
|
||||
/>
|
||||
<TextInput
|
||||
label={"Уникальный ключ"}
|
||||
disabled
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
<AttributeTypeSelect
|
||||
disabled={isEditing}
|
||||
{...form.getInputProps("type")}
|
||||
/>
|
||||
<Checkbox
|
||||
label={"Значение синхронизировано в группе"}
|
||||
{...form.getInputProps("isApplicableToGroup", { type: "checkbox" })}
|
||||
/>
|
||||
{isNullableInputShown && (
|
||||
<Checkbox
|
||||
label={"Может быть пустым"}
|
||||
{...form.getInputProps("isNullable", { type: "checkbox" })}
|
||||
/>
|
||||
)}
|
||||
{form.values.type && isDefaultValueInputShown && (
|
||||
<DefaultAttributeValueInput form={form} />
|
||||
)}
|
||||
<Textarea
|
||||
label={"Описание"}
|
||||
{...form.getInputProps("description")}
|
||||
/>
|
||||
<Button
|
||||
variant={"default"}
|
||||
type={"submit"}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttributeModal;
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { type FullProjectSchema, ProjectService } from "../../../client";
|
||||
|
||||
|
||||
const useProjects = () => {
|
||||
const [projects, setProjects] = useState<FullProjectSchema[]>([]);
|
||||
|
||||
const refetchProjects = () => {
|
||||
ProjectService.getProjects()
|
||||
.then(data => {
|
||||
setProjects(data.projects);
|
||||
})
|
||||
.catch(e => console.log(e));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refetchProjects();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
projects,
|
||||
refetchProjects,
|
||||
};
|
||||
};
|
||||
|
||||
export default useProjects;
|
||||
@@ -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