feat: added tags for cards, aligned status headers
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { TagsInput, TagsInputProps } from "@mantine/core";
|
||||
import { useProjectsContext } from "../../../../contexts/ProjectsContext.tsx";
|
||||
|
||||
type Props = Omit<TagsInputProps, "data">
|
||||
|
||||
const CardTagsInput = (props: Props) => {
|
||||
const { selectedProject } = useProjectsContext();
|
||||
|
||||
return (
|
||||
<TagsInput
|
||||
{...props}
|
||||
data={selectedProject?.tags.map(tag => tag.name)}
|
||||
label={"Теги"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardTagsInput;
|
||||
@@ -42,7 +42,7 @@ const CardEditDrawer: FC = () => {
|
||||
|
||||
const getTabPanel = (value: string, component: ReactNode) => {
|
||||
return (
|
||||
<Tabs.Panel value={value}>
|
||||
<Tabs.Panel key={value} value={value}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
@@ -61,6 +61,7 @@ const CardEditDrawer: FC = () => {
|
||||
const getTabs = () => {
|
||||
const moduleTabs = modules.map(module => (
|
||||
<Tabs.Tab
|
||||
key={module.info.key}
|
||||
value={module.info.key}
|
||||
leftSection={module.info.icon}
|
||||
>
|
||||
|
||||
@@ -22,9 +22,10 @@ import { ButtonCopyControlled } from "../../../../../../components/ButtonCopyCon
|
||||
import { useClipboard } from "@mantine/hooks";
|
||||
import ProjectSelect from "../../../../../../components/ProjectSelect/ProjectSelect.tsx";
|
||||
import BoardSelect from "../../../../../../components/BoardSelect/BoardSelect.tsx";
|
||||
import CardStatusSelect from "../../../../../../components/DealStatusSelect/CardStatusSelect.tsx";
|
||||
import CardStatusSelect from "../../../../../../components/CardStatusSelect/CardStatusSelect.tsx";
|
||||
import CardAttributeFields from "../../../../../../components/CardAttributeFields/CardAttributeFields.tsx";
|
||||
import getAttributesFromCard from "../../../../../../components/CardAttributeFields/utils/getAttributesFromCard.ts";
|
||||
import CardTagsInput from "../../../../components/CardTagsInput/CardTagsInput.tsx";
|
||||
|
||||
type Props = {
|
||||
card: CardSchema;
|
||||
@@ -41,6 +42,7 @@ const Content: FC<Props> = ({ card }) => {
|
||||
const clipboard = useClipboard();
|
||||
const queryClient = useQueryClient();
|
||||
const [project, setProject] = useState<ProjectSchema | null>(card.board.project);
|
||||
const [cardTags, setCardTags] = useState<string[]>(card.tags?.map(tag => tag.name) ?? []);
|
||||
|
||||
const getInitialValues = (card: CardSchema): CardGeneralFormType => {
|
||||
return {
|
||||
@@ -82,6 +84,7 @@ const Content: FC<Props> = ({ card }) => {
|
||||
boardId: values.board.id,
|
||||
clientId: values.client?.id ?? null,
|
||||
attributes,
|
||||
tags: cardTags,
|
||||
},
|
||||
},
|
||||
}).then(({ ok, message }) => {
|
||||
@@ -125,6 +128,20 @@ const Content: FC<Props> = ({ card }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const cancelChanges = () => {
|
||||
form.reset();
|
||||
setCardTags(card.tags?.map(tag => tag.name) ?? []);
|
||||
};
|
||||
|
||||
const isEqualValues = () => {
|
||||
const initialCardTagsSet = new Set(card.tags?.map(tag => tag.name) ?? []);
|
||||
|
||||
const tagsEqual = initialCardTagsSet.size === cardTags.length &&
|
||||
cardTags.every(element => initialCardTagsSet.has(element));
|
||||
|
||||
return isEqual(initialValues, form.values) && tagsEqual;
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={form.onSubmit(values => handleSubmit(values))}>
|
||||
<Flex
|
||||
@@ -177,6 +194,12 @@ const Content: FC<Props> = ({ card }) => {
|
||||
placeholder={"Введите коментарий"}
|
||||
{...form.getInputProps("comment")}
|
||||
/>
|
||||
{project && project?.tags.length > 0 && (
|
||||
<CardTagsInput
|
||||
value={cardTags}
|
||||
onChange={setCardTags}
|
||||
/>
|
||||
)}
|
||||
{project && (
|
||||
<CardAttributeFields
|
||||
project={project}
|
||||
@@ -223,14 +246,14 @@ const Content: FC<Props> = ({ card }) => {
|
||||
<Button
|
||||
color={"red"}
|
||||
type={"reset"}
|
||||
disabled={isEqual(initialValues, form.values)}
|
||||
onClick={() => form.reset()}>
|
||||
disabled={isEqualValues()}
|
||||
onClick={cancelChanges}>
|
||||
Отменить изменения
|
||||
</Button>
|
||||
<Button
|
||||
variant={"default"}
|
||||
type={"submit"}
|
||||
disabled={isEqual(initialValues, form.values)}>
|
||||
disabled={isEqualValues()}>
|
||||
Сохранить изменения
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Box, Drawer, rem, Tabs } from "@mantine/core";
|
||||
import { IconHexagons, IconSettings, IconSubtask } from "@tabler/icons-react";
|
||||
import { IconHexagons, IconSettings, IconSubtask, IconTags } from "@tabler/icons-react";
|
||||
import { ReactNode } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useProjectsEditorContext } from "../../contexts/ProjectsEditorContext.tsx";
|
||||
import General from "./tabs/General/General.tsx";
|
||||
import Attributes from "./tabs/Attributes/Attributes.tsx";
|
||||
import Modules from "./tabs/Modules/Modules.tsx";
|
||||
import Tags from "./tabs/Tags/Tags.tsx";
|
||||
|
||||
|
||||
const ProjectEditDrawer = () => {
|
||||
@@ -67,11 +68,17 @@ const ProjectEditDrawer = () => {
|
||||
leftSection={<IconSubtask />}>
|
||||
Атрибуты
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
value={"tags"}
|
||||
leftSection={<IconTags />}>
|
||||
Теги
|
||||
</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
{getTabPanel("general", <General/>)}
|
||||
{getTabPanel("attributes", <Attributes/>)}
|
||||
{getTabPanel("general", <General />)}
|
||||
{getTabPanel("attributes", <Attributes />)}
|
||||
{getTabPanel("modules", <Modules />)}
|
||||
{getTabPanel("tags", <Tags />)}
|
||||
</Tabs>
|
||||
</Drawer>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { ActionIcon, Flex, Group, rem, Stack, Tooltip } from "@mantine/core";
|
||||
import { BaseTable } from "../../../../../../components/BaseTable/BaseTable.tsx";
|
||||
import tagsTableColumns from "./hooks/tagsTableColumns.tsx";
|
||||
import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||
import { CardTagSchema } from "../../../../../../client";
|
||||
import { MRT_TableOptions } from "mantine-react-table";
|
||||
import useTags from "./hooks/useTags.tsx";
|
||||
import InlineButton from "../../../../../../components/InlineButton/InlineButton.tsx";
|
||||
|
||||
|
||||
const Tags = () => {
|
||||
const columns = tagsTableColumns();
|
||||
|
||||
const {
|
||||
project,
|
||||
onDeleteClick,
|
||||
onChangeClick,
|
||||
onCreateClick,
|
||||
} = useTags();
|
||||
|
||||
return (
|
||||
<Stack gap={rem(10)}>
|
||||
<Group>
|
||||
<InlineButton onClick={onCreateClick}>
|
||||
<IconPlus />
|
||||
Создать
|
||||
</InlineButton>
|
||||
</Group>
|
||||
<BaseTable
|
||||
data={project?.tags}
|
||||
columns={columns}
|
||||
|
||||
restProps={
|
||||
{
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableRowActions: true,
|
||||
|
||||
renderRowActions: ({ row }) => {
|
||||
return (
|
||||
<Flex gap="md">
|
||||
<Tooltip label="Удалить">
|
||||
<ActionIcon
|
||||
onClick={() => onDeleteClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconTrash />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Редактировать">
|
||||
<ActionIcon
|
||||
onClick={() => onChangeClick(row.original)}
|
||||
variant={"default"}>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
);
|
||||
},
|
||||
} as MRT_TableOptions<CardTagSchema>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tags;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useMemo } from "react";
|
||||
import { MRT_ColumnDef } from "mantine-react-table";
|
||||
import { CardTagSchema } from "../../../../../../../client";
|
||||
|
||||
const useTagsTableColumns = () => {
|
||||
return useMemo<MRT_ColumnDef<CardTagSchema>[]>(
|
||||
() => [
|
||||
{
|
||||
header: "Название",
|
||||
accessorKey: "name",
|
||||
size: 1000,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export default useTagsTableColumns;
|
||||
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
CancelablePromise,
|
||||
CardTagSchema,
|
||||
CardTagService,
|
||||
CreateTagResponse,
|
||||
DeleteTagResponse,
|
||||
UpdateTagResponse,
|
||||
} from "../../../../../../../client";
|
||||
import { notifications } from "../../../../../../../shared/lib/notifications.ts";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { Text } from "@mantine/core";
|
||||
import { useProjectsContext } from "../../../../../../../contexts/ProjectsContext.tsx";
|
||||
import { useCardPageContext } from "../../../../../contexts/CardPageContext.tsx";
|
||||
|
||||
|
||||
const useTags = () => {
|
||||
const { selectedProject: project, refetchProjects } = useProjectsContext();
|
||||
const { refetchCards } = useCardPageContext();
|
||||
|
||||
const processResponse = (
|
||||
response: CancelablePromise<DeleteTagResponse | UpdateTagResponse | CreateTagResponse>,
|
||||
) => {
|
||||
response
|
||||
.then(({ ok, message }) => {
|
||||
if (!ok) {
|
||||
notifications.error({ message });
|
||||
return;
|
||||
}
|
||||
refetchProjects();
|
||||
refetchCards();
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
const onDelete = (tag: CardTagSchema) => {
|
||||
const response = CardTagService.deleteTag({
|
||||
cardTagId: tag.id,
|
||||
});
|
||||
|
||||
processResponse(response);
|
||||
};
|
||||
|
||||
const onDeleteClick = (tag: CardTagSchema) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление тега",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Вы уверены что хотите удалить тег {tag.name}
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Да", cancel: "Нет" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => onDelete(tag),
|
||||
});
|
||||
};
|
||||
|
||||
const onChange = (tag: CardTagSchema) => {
|
||||
const response = CardTagService.updateTag({
|
||||
requestBody: { tag },
|
||||
});
|
||||
|
||||
processResponse(response);
|
||||
};
|
||||
|
||||
const onChangeClick = (tag: CardTagSchema) => {
|
||||
modals.openContextModal({
|
||||
modal: "cardTagModal",
|
||||
innerProps: {
|
||||
element: tag,
|
||||
onChange,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
const onCreate = (tag: CardTagSchema) => {
|
||||
if (!project) return;
|
||||
|
||||
const response = CardTagService.createTag({
|
||||
requestBody: {
|
||||
tag: {
|
||||
name: tag.name,
|
||||
projectId: project.id,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
processResponse(response);
|
||||
};
|
||||
|
||||
const onCreateClick = () => {
|
||||
modals.openContextModal({
|
||||
modal: "cardTagModal",
|
||||
innerProps: {
|
||||
onCreate,
|
||||
},
|
||||
withCloseButton: false,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
project,
|
||||
onDeleteClick,
|
||||
onChangeClick,
|
||||
onCreateClick,
|
||||
};
|
||||
};
|
||||
|
||||
export default useTags;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import BaseFormModal, {
|
||||
CreateEditFormProps,
|
||||
} from "../../../../../../ClientsPage/modals/BaseFormModal/BaseFormModal.tsx";
|
||||
import { CardTagSchema } from "../../../../../../../client";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { TextInput } from "@mantine/core";
|
||||
|
||||
type Props = CreateEditFormProps<CardTagSchema>;
|
||||
|
||||
const CardTagModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const isEditing = "element" in innerProps;
|
||||
const initialValue: Partial<CardTagSchema> = isEditing
|
||||
? innerProps.element
|
||||
: {
|
||||
name: "",
|
||||
};
|
||||
|
||||
const form = useForm<Partial<CardTagSchema>>({
|
||||
initialValues: initialValue,
|
||||
validate: {
|
||||
name: name => !name && "Необходимо указать название тега",
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseFormModal
|
||||
form={form}
|
||||
closeOnSubmit
|
||||
onClose={() => context.closeContextModal(id)}
|
||||
{...innerProps}>
|
||||
<BaseFormModal.Body>
|
||||
<TextInput
|
||||
label={"Название"}
|
||||
placeholder={"Введите название тега"}
|
||||
{...form.getInputProps("name")}
|
||||
/>
|
||||
</BaseFormModal.Body>
|
||||
</BaseFormModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardTagModal;
|
||||
@@ -3,7 +3,7 @@ import { Flex, Modal, NumberInput, rem } from "@mantine/core";
|
||||
import { UseFormReturnType } from "@mantine/form";
|
||||
import { CardsPageState } from "../hooks/useCardsPageState.tsx";
|
||||
import ObjectSelect from "../../../components/ObjectSelect/ObjectSelect.tsx";
|
||||
import CardStatusSelect from "../../../components/DealStatusSelect/CardStatusSelect.tsx";
|
||||
import CardStatusSelect from "../../../components/CardStatusSelect/CardStatusSelect.tsx";
|
||||
import BaseMarketplaceSelect from "../../../components/Selects/BaseMarketplaceSelect/BaseMarketplaceSelect.tsx";
|
||||
import ClientSelectNew from "../../../components/Selects/ClientSelectNew/ClientSelectNew.tsx";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
align-items: stretch;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.delete {
|
||||
|
||||
Reference in New Issue
Block a user