feat: deal tags

This commit is contained in:
2025-10-19 12:12:28 +04:00
parent 9023b07c65
commit 3a1d8e23e3
25 changed files with 978 additions and 166 deletions

View File

@ -9,6 +9,7 @@ import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema } from "@/lib/client";
import { ModuleNames } from "@/modules/modules";
import styles from "./DealCard.module.css";
import DealTags from "@/components/ui/DealTags/DealTags";
type Props = {
deal: DealSchema;
@ -100,6 +101,7 @@ const DealCard = ({ deal, isInGroup = false }: Props) => {
</>
)}
</Stack>
{!deal.group && <DealTags dealId={deal.id} tags={deal.tags} />}
</Stack>
</Card>
);

View File

@ -7,6 +7,7 @@ import { useDebouncedValue } from "@mantine/hooks";
import GroupMenu from "@/app/deals/components/mobile/GroupMenu/GroupMenu";
import DealCard from "@/app/deals/components/shared/DealCard/DealCard";
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
import DealTags from "@/components/ui/DealTags/DealTags";
import useIsMobile from "@/hooks/utils/useIsMobile";
import GroupWithDealsSchema from "@/types/GroupWithDealsSchema";
import styles from "./DealsGroup.module.css";
@ -87,6 +88,12 @@ const DealsGroup: FC<Props> = ({ group }) => {
key={deal.id}
/>
))}
{group.items.length > 0 && (
<DealTags
groupId={group.id}
tags={group.items[0].tags}
/>
)}
</Stack>
);
};

View File

@ -33,6 +33,8 @@ const useProjectsContextState = (): ProjectsContextState => {
[projects, selectedProjectId]
);
console.log(selectedProject);
const modulesSet = useMemo(
() =>
new Set(

View File

@ -3,7 +3,8 @@ import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import { useDrawersContext } from "@/drawers/DrawersContext";
const useProjectActions = () => {
const { selectedProject, projectsCrud } = useProjectsContext();
const { selectedProject, projectsCrud, refetchProjects } =
useProjectsContext();
const { openDrawer } = useDrawersContext();
const onCreateClick = () => {
@ -27,6 +28,7 @@ const useProjectActions = () => {
onChange: value => projectsCrud.onUpdate(value.id, value),
onDelete: projectsCrud.onDelete,
},
onClose: refetchProjects,
});
};

View File

@ -0,0 +1,34 @@
import { lighten, Pill, useMantineColorScheme } from "@mantine/core";
import { DealTagSchema } from "@/lib/client";
type Props = {
tag: Partial<DealTagSchema>;
};
const DealTag = ({ tag }: Props) => {
const theme = useMantineColorScheme();
const isInherit = tag.tagColor!.backgroundColor === "inherit";
let color = tag.tagColor!.color;
const backgroundColor = tag.tagColor!.backgroundColor;
if (!(theme.colorScheme === "dark" || isInherit)) {
color = lighten(color, 0.95);
}
return (
<Pill
key={tag.id}
style={{
opacity: 0.7,
color,
backgroundColor,
border: "1px solid",
borderColor: color,
}}>
{tag.name}
</Pill>
);
};
export default DealTag;

View File

@ -0,0 +1,30 @@
.add-tag-button {
@mixin light {
background-color: var(--mantine-color-gray-1);
}
@mixin dark {
background-color: var(--mantine-color-dark-6);
}
color: gray;
border: 1px gray dashed;
border-radius: 50%;
width: 1.5rem;
height: 1.5rem;
padding: 0;
cursor: pointer;
}
.add-tag-button:hover {
@mixin light {
border-color: black;
color: black;
}
@mixin dark {
border-color: white;
color: white;
}
}
.add-tag-button-icon {
color: inherit !important;
}

View File

@ -0,0 +1,93 @@
import React, { useMemo } from "react";
import { IconPlus } from "@tabler/icons-react";
import classNames from "classnames";
import { Button, Center, Checkbox, Group, Menu, Stack } from "@mantine/core";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import DealTag from "@/components/ui/DealTag/DealTag";
import useDealTags from "@/components/ui/DealTags/hooks/useDealTags";
import { DealTagSchema } from "@/lib/client";
import styles from "./DealTags.module.css";
type Props = {
dealId?: number;
groupId?: number;
tags: DealTagSchema[];
};
const DealTags = ({ tags, dealId, groupId }: Props) => {
const { selectedProject } = useProjectsContext();
const { switchTag } = useDealTags();
const tagIdsSet = useMemo(() => new Set(tags.map(t => t.id)), [tags]);
if (selectedProject?.tags.length === 0) return;
const onTagClick = (tagId: number, event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
switchTag({ dealId, groupId, tagId });
};
const addTagButton = useMemo(
() => (
<Menu withArrow>
<Menu.Target>
<Button
onClick={e => e.stopPropagation()}
unstyled
className={classNames(styles["add-tag-button"])}>
<Center>
<IconPlus
size={"1.2em"}
className={classNames(
styles["add-tag-button-icon"]
)}
/>
</Center>
</Button>
</Menu.Target>
<Menu.Dropdown>
<Stack
p={"xs"}
gap={"sm"}
onClick={e => e.stopPropagation()}>
{selectedProject?.tags.map(tag => (
<Group
key={tag.id}
wrap={"nowrap"}>
<Checkbox
checked={tagIdsSet.has(tag.id)}
onChange={event =>
onTagClick(
tag.id,
event as unknown as any
)
}
label={tag.name}
/>
</Group>
))}
</Stack>
</Menu.Dropdown>
</Menu>
),
[selectedProject?.tags, tags]
);
return (
<Group gap={"xs"}>
{addTagButton}
{selectedProject?.tags.map(
tag =>
tagIdsSet.has(tag.id) && (
<DealTag
key={tag.id}
tag={tag}
/>
)
)}
</Group>
);
};
export default DealTags;

View File

@ -0,0 +1,25 @@
import { useMutation } from "@tanstack/react-query";
import { useDealsContext } from "@/app/deals/contexts/DealsContext";
import { SwitchDealTagRequest } from "@/lib/client";
import { switchDealTagMutation } from "@/lib/client/@tanstack/react-query.gen";
const useDealTags = () => {
const { refetchDeals } = useDealsContext();
const switchTagMutation = useMutation({
...switchDealTagMutation(),
onSettled: refetchDeals,
});
const switchTag = (data: SwitchDealTagRequest) => {
switchTagMutation.mutate({
body: data,
});
};
return {
switchTag,
};
};
export default useDealTags;

View File

@ -1,9 +1,13 @@
import { FC } from "react";
import { IconBlocks, IconEdit } from "@tabler/icons-react";
import { IconBlocks, IconEdit, IconTags } from "@tabler/icons-react";
import { Tabs } from "@mantine/core";
import {
GeneralTab,
ModulesTab,
} from "@/drawers/common/ProjectEditorDrawer/tabs";
import TagsTab from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/TagsTab";
import { ProjectSchema } from "@/lib/client";
import styles from "../ProjectEditorDrawer.module.css";
import { GeneralTab, ModulesTab } from "@/drawers/common/ProjectEditorDrawer/tabs";
type Props = {
value: ProjectSchema;
@ -27,13 +31,21 @@ const ProjectEditorBody: FC<Props> = props => {
leftSection={<IconBlocks />}>
Модули
</Tabs.Tab>
<Tabs.Tab
value={"tags"}
leftSection={<IconTags />}>
Теги
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="general">
<Tabs.Panel value={"general"}>
<GeneralTab {...props} />
</Tabs.Panel>
<Tabs.Panel value="modules">
<Tabs.Panel value={"modules"}>
<ModulesTab {...props} />
</Tabs.Panel>
<Tabs.Panel value={"tags"}>
<TagsTab {...props} />
</Tabs.Panel>
</Tabs>
);
};

View File

@ -0,0 +1,26 @@
import { FC } from "react";
import { Flex } from "@mantine/core";
import TagsTabHeader from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/components/TagsTabHeader";
import TagsTable from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/components/TagsTable";
import { DealTagsContextProvider } from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/contexts/DealTagsContext";
import { ProjectSchema } from "@/lib/client";
type Props = {
value: ProjectSchema;
};
const TagsTab: FC<Props> = ({ value }) => {
return (
<Flex
h={"100%"}
direction={"column"}
gap={"xs"}>
<DealTagsContextProvider project={value}>
<TagsTabHeader />
<TagsTable />
</DealTagsContextProvider>
</Flex>
);
};
export default TagsTab;

View File

@ -0,0 +1,60 @@
"use client";
import { IconCheck } from "@tabler/icons-react";
import { Group, SelectProps } from "@mantine/core";
import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import DealTag from "@/components/ui/DealTag/DealTag";
import { DealTagColorSchema } from "@/lib/client";
import useTagColorList from "../hooks/useTagColorList";
type Props = Omit<
ObjectSelectProps<DealTagColorSchema>,
"data" | "getValueFn" | "getLabelFn"
>;
const TagColorInput = (props: Props) => {
const { colors } = useTagColorList();
const colorsMap = new Map<string, DealTagColorSchema>(
colors.map(
color =>
[color.id.toString(), color] as [string, DealTagColorSchema]
)
);
const renderSelectOption: SelectProps["renderOption"] = ({
option,
checked,
}) => {
const tag = {
id: Number(option.value),
name: "Тег-пример",
tagColor: colorsMap.get(option.value),
};
return (
<Group
flex="1"
gap="md">
<DealTag tag={tag} />
{option.label}
{checked && <IconCheck style={{ marginInlineStart: "auto" }} />}
</Group>
);
};
return (
<ObjectSelect
label={"Цвет"}
renderOption={renderSelectOption}
data={colors}
getValueFn={color => color.id.toString()}
getLabelFn={color => color.label}
searchable
{...props}
/>
);
};
export default TagColorInput;

View File

@ -0,0 +1,21 @@
import { IconPlus } from "@tabler/icons-react";
import { Group } from "@mantine/core";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import useDealTagActions from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/hooks/useDealTagActions";
const TagsTabHeader = () => {
const { onCreateClick } = useDealTagActions();
return (
<Group
pt={"xs"}
px={"xs"}>
<InlineButton onClick={onCreateClick}>
<IconPlus />
Создать
</InlineButton>
</Group>
);
};
export default TagsTabHeader;

View File

@ -0,0 +1,38 @@
import { IconMoodSad } from "@tabler/icons-react";
import { Group, Text } from "@mantine/core";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import { useDealTagsContext } from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/contexts/DealTagsContext";
import tagsTableColumns from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/hooks/tagsTableColumns";
import useDealTagActions from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/hooks/useDealTagActions";
const TagsTable = () => {
const { dealTags, dealTagsCrud } = useDealTagsContext();
const { onChangeClick } = useDealTagActions();
const columns = tagsTableColumns({
onDelete: dealTagsCrud.onDelete,
onChange: onChangeClick,
});
return (
<BaseTable
withTableBorder
records={dealTags}
columns={columns}
groups={undefined}
style={{
marginInline: "var(--mantine-spacing-xs)",
minHeight: 200,
}}
verticalSpacing={"xs"}
emptyState={
<Group mt={dealTags.length === 0 ? "xl" : 0}>
<Text>Нет тегов</Text>
<IconMoodSad />
</Group>
}
/>
);
};
export default TagsTable;

View File

@ -0,0 +1,38 @@
"use dealTag";
import { DealTagsCrud, useDealTagsCrud } from "@/hooks/cruds/useDealTagsCrud";
import useDealTagsList from "@/hooks/lists/useDealTagsList";
import { DealTagSchema, ProjectSchema } from "@/lib/client";
import makeContext from "@/lib/contextFactory/contextFactory";
type DealTagsContextState = {
dealTags: DealTagSchema[];
refetchDealTags: () => void;
project: ProjectSchema;
dealTagsCrud: DealTagsCrud;
};
type Props = {
project: ProjectSchema;
};
const useDealTagsContextState = ({ project }: Props): DealTagsContextState => {
const dealTagsList = useDealTagsList({ projectId: project.id });
const dealTagsCrud = useDealTagsCrud({
...dealTagsList,
projectId: project.id,
});
return {
dealTags: dealTagsList.dealTags,
refetchDealTags: dealTagsList.refetch,
project,
dealTagsCrud,
};
};
export const [DealTagsContextProvider, useDealTagsContext] = makeContext<
DealTagsContextState,
Props
>(useDealTagsContextState, "DealTags");

View File

@ -0,0 +1,49 @@
import { useMemo } from "react";
import { DataTableColumn } from "mantine-datatable";
import { Center } from "@mantine/core";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import DealTag from "@/components/ui/DealTag/DealTag";
import { DealTagSchema } from "@/lib/client";
type Props = {
onDelete: (tag: DealTagSchema) => void;
onChange: (tag: DealTagSchema) => void;
};
const useTagsTableColumns = ({ onDelete, onChange }: Props) => {
return useMemo(
() =>
[
{
accessor: "actions",
title: <Center>Действия</Center>,
width: "0%",
render: tag => (
<UpdateDeleteTableActions
onDelete={() => onDelete(tag)}
onChange={() => onChange(tag)}
/>
),
},
{
title: "Название",
accessor: "name",
width: 2,
},
{
title: "Цвет",
accessor: "tagColor.label",
width: 2,
},
{
title: "Пример",
accessor: "tagColor",
width: 3,
render: tag => <DealTag tag={tag} />,
},
] as DataTableColumn<DealTagSchema>[],
[]
);
};
export default useTagsTableColumns;

View File

@ -0,0 +1,37 @@
import { modals } from "@mantine/modals";
import { useDealTagsContext } from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/contexts/DealTagsContext";
import { DealTagSchema } from "@/lib/client";
const useDealTagActions = () => {
const { dealTagsCrud } = useDealTagsContext();
const onChangeClick = (tag: DealTagSchema) => {
modals.openContextModal({
modal: "dealTagModal",
innerProps: {
entity: tag,
onChange: data => dealTagsCrud.onUpdate(tag.id, data),
isEditing: true,
},
withCloseButton: false,
});
};
const onCreateClick = () => {
modals.openContextModal({
modal: "dealTagModal",
innerProps: {
onCreate: dealTagsCrud.onCreate,
isEditing: false,
},
withCloseButton: false,
});
};
return {
onChangeClick,
onCreateClick,
};
};
export default useDealTagActions;

View File

@ -0,0 +1,13 @@
import { useQuery } from "@tanstack/react-query";
import { getDealTagColorsOptions } from "@/lib/client/@tanstack/react-query.gen";
const useTagColorList = () => {
const { data, refetch } = useQuery(getDealTagColorsOptions());
return {
colors: data?.items ?? [],
refetch,
};
};
export default useTagColorList;

View File

@ -0,0 +1,68 @@
"use client";
import { Stack, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import TagColorInput from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/components/TagColorInput";
import {
CreateDealTagSchema,
DealTagSchema,
UpdateDealTagSchema,
} from "@/lib/client";
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
type Props = CreateEditFormProps<
CreateDealTagSchema,
UpdateDealTagSchema,
DealTagSchema
>;
const DealTagModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const initialValues: Partial<DealTagSchema> = innerProps.isEditing
? innerProps.entity
: {
name: "",
tagColor: undefined,
tagColorId: undefined,
};
const form = useForm<Partial<DealTagSchema>>({
initialValues,
validate: {
name: name => !name && "Необходимо указать название тега",
tagColor: tagColor => !tagColor && "Необходимо указать цвет тега",
},
});
return (
<BaseFormModal
form={form}
closeOnSubmit
onClose={() => context.closeContextModal(id)}
{...innerProps}>
<Stack>
<TextInput
label={"Название"}
placeholder={"Введите название тега"}
{...form.getInputProps("name")}
/>
<TagColorInput
placeholder={"Укажите цвет"}
{...form.getInputProps("tagColor")}
onChange={tag => {
form.setFieldValue("tagColor", tag);
form.setFieldValue("tagColorId", tag.id);
}}
/>
</Stack>
</BaseFormModal>
);
};
export default DealTagModal;

View File

@ -0,0 +1,53 @@
import { useCrudOperations } from "@/hooks/cruds/baseCrud";
import {
CreateDealTagSchema,
DealTagSchema,
UpdateDealTagSchema,
} from "@/lib/client";
import {
createDealTagMutation,
deleteDealTagMutation,
updateDealTagMutation,
} from "@/lib/client/@tanstack/react-query.gen";
type UseDealTagsOperationsProps = {
queryKey: any[];
projectId: number;
};
export type DealTagsCrud = {
onCreate: (data: CreateDealTagSchema) => void;
onUpdate: (dealTagId: number, dealTag: UpdateDealTagSchema) => void;
onDelete: (dealTag: DealTagSchema) => void;
};
export const useDealTagsCrud = ({
queryKey,
projectId,
}: UseDealTagsOperationsProps): DealTagsCrud => {
return useCrudOperations<
DealTagSchema,
UpdateDealTagSchema,
CreateDealTagSchema
>({
key: "getDealTags",
queryKey,
mutations: {
create: createDealTagMutation(),
update: updateDealTagMutation(),
delete: deleteDealTagMutation(),
},
getCreateEntity: data => ({
tagColorId: data.tagColorId!,
name: data.name!,
projectId,
}),
getUpdateEntity: (old, update) => ({
...old,
name: update.name ?? old.name,
tagColor: update.tagColor ?? old.tagColor,
tagColorId: update.tagColor?.id ?? old.tagColorId,
}),
getDeleteConfirmTitle: () => "Удаление доски",
});
};

View File

@ -0,0 +1,39 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { DealTagSchema } from "@/lib/client";
import {
getDealTagsOptions,
getDealTagsQueryKey,
} from "@/lib/client/@tanstack/react-query.gen";
type Props = {
projectId: number;
};
const useDealTagsList = ({ projectId }: Props) => {
const queryClient = useQueryClient();
const options = {
path: { projectId },
};
const { data, refetch } = useQuery(getDealTagsOptions(options));
const queryKey = getDealTagsQueryKey(options);
const setDealTags = (dealTags: DealTagSchema[]) => {
queryClient.setQueryData(
queryKey,
(old: { items: DealTagSchema[] }) => ({
...old,
items: dealTags,
})
);
};
return {
dealTags: data?.items ?? [],
setDealTags,
refetch,
queryKey,
};
};
export default useDealTagsList;

View File

@ -3,6 +3,7 @@
import {
infiniteQueryOptions,
queryOptions,
type DefaultError,
type InfiniteData,
type UseMutationOptions,
} from "@tanstack/react-query";
@ -19,6 +20,7 @@ import {
createDealProduct,
createDealProductService,
createDealService,
createDealTag,
createMarketplace,
createProduct,
createProject,
@ -26,7 +28,6 @@ import {
createServiceCategory,
createServicesKit,
createStatus,
createTag,
deleteBarcodeTemplate,
deleteBoard,
deleteClient,
@ -35,6 +36,7 @@ import {
deleteDealProduct,
deleteDealProductService,
deleteDealService,
deleteDealTag,
deleteMarketplace,
deleteProduct,
deleteProject,
@ -42,7 +44,6 @@ import {
deleteServiceCategory,
deleteServicesKit,
deleteStatus,
deleteTag,
duplicateProductServices,
getBarcodeTemplateAttributes,
getBarcodeTemplates,
@ -55,6 +56,7 @@ import {
getDeals,
getDealServices,
getDealTagColors,
getDealTags,
getMarketplaces,
getProductBarcodePdf,
getProducts,
@ -74,6 +76,7 @@ import {
updateDealProductService,
updateDealService,
updateDealsInGroup,
updateDealTag,
updateMarketplace,
updateProduct,
updateProject,
@ -81,7 +84,6 @@ import {
updateServiceCategory,
updateServicesKit,
updateStatus,
updateTag,
type Options,
} from "../sdk.gen";
import type {
@ -115,6 +117,9 @@ import type {
CreateDealServiceData,
CreateDealServiceError,
CreateDealServiceResponse2,
CreateDealTagData,
CreateDealTagError,
CreateDealTagResponse2,
CreateMarketplaceData,
CreateMarketplaceError,
CreateMarketplaceResponse2,
@ -136,9 +141,6 @@ import type {
CreateStatusData,
CreateStatusError,
CreateStatusResponse2,
CreateTagData,
CreateTagError,
CreateTagResponse,
DeleteBarcodeTemplateData,
DeleteBarcodeTemplateError,
DeleteBarcodeTemplateResponse2,
@ -163,6 +165,9 @@ import type {
DeleteDealServiceData,
DeleteDealServiceError,
DeleteDealServiceResponse2,
DeleteDealTagData,
DeleteDealTagError,
DeleteDealTagResponse2,
DeleteMarketplaceData,
DeleteMarketplaceError,
DeleteMarketplaceResponse2,
@ -184,9 +189,6 @@ import type {
DeleteStatusData,
DeleteStatusError,
DeleteStatusResponse2,
DeleteTagData,
DeleteTagError,
DeleteTagResponse,
DuplicateProductServicesData,
DuplicateProductServicesError,
DuplicateProductServicesResponse,
@ -203,6 +205,8 @@ import type {
GetDealServicesData,
GetDealsResponse2,
GetDealTagColorsData,
GetDealTagColorsResponse,
GetDealTagsData,
GetMarketplacesData,
GetProductBarcodePdfData,
GetProductBarcodePdfError,
@ -246,6 +250,9 @@ import type {
UpdateDealsInGroupData,
UpdateDealsInGroupError,
UpdateDealsInGroupResponse2,
UpdateDealTagData,
UpdateDealTagError,
UpdateDealTagResponse2,
UpdateMarketplaceData,
UpdateMarketplaceError,
UpdateMarketplaceResponse2,
@ -267,9 +274,6 @@ import type {
UpdateStatusData,
UpdateStatusError,
UpdateStatusResponse2,
UpdateTagData,
UpdateTagError,
UpdateTagResponse,
} from "../types.gen";
export type QueryKey<TOptions extends Options> = [
@ -795,43 +799,16 @@ export const updateDealsInGroupMutation = (
return mutationOptions;
};
/**
* Update Tag
*/
export const updateTagMutation = (
options?: Partial<Options<UpdateTagData>>
): UseMutationOptions<
UpdateTagResponse,
AxiosError<UpdateTagError>,
Options<UpdateTagData>
> => {
const mutationOptions: UseMutationOptions<
UpdateTagResponse,
AxiosError<UpdateTagError>,
Options<UpdateTagData>
> = {
mutationFn: async localOptions => {
const { data } = await updateTag({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const createTagQueryKey = (options: Options<CreateTagData>) =>
createQueryKey("createTag", options);
export const getDealTagsQueryKey = (options: Options<GetDealTagsData>) =>
createQueryKey("getDealTags", options);
/**
* Create Tag
* Get Deal Tags
*/
export const createTagOptions = (options: Options<CreateTagData>) => {
export const getDealTagsOptions = (options: Options<GetDealTagsData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await createTag({
const { data } = await getDealTags({
...options,
...queryKey[0],
signal,
@ -839,27 +816,48 @@ export const createTagOptions = (options: Options<CreateTagData>) => {
});
return data;
},
queryKey: createTagQueryKey(options),
queryKey: getDealTagsQueryKey(options),
});
};
export const createDealTagQueryKey = (options: Options<CreateDealTagData>) =>
createQueryKey("createDealTag", options);
/**
* Create Deal Tag
*/
export const createDealTagOptions = (options: Options<CreateDealTagData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await createDealTag({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: createDealTagQueryKey(options),
});
};
/**
* Create Tag
* Create Deal Tag
*/
export const createTagMutation = (
options?: Partial<Options<CreateTagData>>
export const createDealTagMutation = (
options?: Partial<Options<CreateDealTagData>>
): UseMutationOptions<
CreateTagResponse,
AxiosError<CreateTagError>,
Options<CreateTagData>
CreateDealTagResponse2,
AxiosError<CreateDealTagError>,
Options<CreateDealTagData>
> => {
const mutationOptions: UseMutationOptions<
CreateTagResponse,
AxiosError<CreateTagError>,
Options<CreateTagData>
CreateDealTagResponse2,
AxiosError<CreateDealTagError>,
Options<CreateDealTagData>
> = {
mutationFn: async localOptions => {
const { data } = await createTag({
const { data } = await createDealTag({
...options,
...localOptions,
throwOnError: true,
@ -871,22 +869,49 @@ export const createTagMutation = (
};
/**
* Delete Tag
* Delete Deal Tag
*/
export const deleteTagMutation = (
options?: Partial<Options<DeleteTagData>>
export const deleteDealTagMutation = (
options?: Partial<Options<DeleteDealTagData>>
): UseMutationOptions<
DeleteTagResponse,
AxiosError<DeleteTagError>,
Options<DeleteTagData>
DeleteDealTagResponse2,
AxiosError<DeleteDealTagError>,
Options<DeleteDealTagData>
> => {
const mutationOptions: UseMutationOptions<
DeleteTagResponse,
AxiosError<DeleteTagError>,
Options<DeleteTagData>
DeleteDealTagResponse2,
AxiosError<DeleteDealTagError>,
Options<DeleteDealTagData>
> = {
mutationFn: async localOptions => {
const { data } = await deleteTag({
const { data } = await deleteDealTag({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Update Deal Tag
*/
export const updateDealTagMutation = (
options?: Partial<Options<UpdateDealTagData>>
): UseMutationOptions<
UpdateDealTagResponse2,
AxiosError<UpdateDealTagError>,
Options<UpdateDealTagData>
> => {
const mutationOptions: UseMutationOptions<
UpdateDealTagResponse2,
AxiosError<UpdateDealTagError>,
Options<UpdateDealTagData>
> = {
mutationFn: async localOptions => {
const { data } = await updateDealTag({
...options,
...localOptions,
throwOnError: true,
@ -969,6 +994,33 @@ export const getDealTagColorsOptions = (
});
};
/**
* Get Deal Tag Colors
*/
export const getDealTagColorsMutation = (
options?: Partial<Options<GetDealTagColorsData>>
): UseMutationOptions<
GetDealTagColorsResponse,
AxiosError<DefaultError>,
Options<GetDealTagColorsData>
> => {
const mutationOptions: UseMutationOptions<
GetDealTagColorsResponse,
AxiosError<DefaultError>,
Options<GetDealTagColorsData>
> = {
mutationFn: async localOptions => {
const { data } = await getDealTagColors({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getBuiltInModulesQueryKey = (
options?: Options<GetBuiltInModulesData>
) => createQueryKey("getBuiltInModules", options);

View File

@ -33,6 +33,9 @@ import type {
CreateDealServiceData,
CreateDealServiceErrors,
CreateDealServiceResponses,
CreateDealTagData,
CreateDealTagErrors,
CreateDealTagResponses,
CreateMarketplaceData,
CreateMarketplaceErrors,
CreateMarketplaceResponses,
@ -54,9 +57,6 @@ import type {
CreateStatusData,
CreateStatusErrors,
CreateStatusResponses,
CreateTagData,
CreateTagErrors,
CreateTagResponses,
DeleteBarcodeTemplateData,
DeleteBarcodeTemplateErrors,
DeleteBarcodeTemplateResponses,
@ -81,6 +81,9 @@ import type {
DeleteDealServiceData,
DeleteDealServiceErrors,
DeleteDealServiceResponses,
DeleteDealTagData,
DeleteDealTagErrors,
DeleteDealTagResponses,
DeleteMarketplaceData,
DeleteMarketplaceErrors,
DeleteMarketplaceResponses,
@ -102,9 +105,6 @@ import type {
DeleteStatusData,
DeleteStatusErrors,
DeleteStatusResponses,
DeleteTagData,
DeleteTagErrors,
DeleteTagResponses,
DuplicateProductServicesData,
DuplicateProductServicesErrors,
DuplicateProductServicesResponses,
@ -135,6 +135,9 @@ import type {
GetDealsResponses,
GetDealTagColorsData,
GetDealTagColorsResponses,
GetDealTagsData,
GetDealTagsErrors,
GetDealTagsResponses,
GetMarketplacesData,
GetMarketplacesErrors,
GetMarketplacesResponses,
@ -188,6 +191,9 @@ import type {
UpdateDealsInGroupData,
UpdateDealsInGroupErrors,
UpdateDealsInGroupResponses,
UpdateDealTagData,
UpdateDealTagErrors,
UpdateDealTagResponses,
UpdateMarketplaceData,
UpdateMarketplaceErrors,
UpdateMarketplaceResponses,
@ -209,9 +215,6 @@ import type {
UpdateStatusData,
UpdateStatusErrors,
UpdateStatusResponses,
UpdateTagData,
UpdateTagErrors,
UpdateTagResponses,
} from "./types.gen";
import {
zAddKitToDealData,
@ -234,6 +237,8 @@ import {
zCreateDealResponse2,
zCreateDealServiceData,
zCreateDealServiceResponse2,
zCreateDealTagData,
zCreateDealTagResponse2,
zCreateMarketplaceData,
zCreateMarketplaceResponse2,
zCreateProductData,
@ -248,8 +253,6 @@ import {
zCreateServicesKitResponse2,
zCreateStatusData,
zCreateStatusResponse2,
zCreateTagData,
zCreateTagResponse,
zDeleteBarcodeTemplateData,
zDeleteBarcodeTemplateResponse2,
zDeleteBoardData,
@ -266,6 +269,8 @@ import {
zDeleteDealResponse2,
zDeleteDealServiceData,
zDeleteDealServiceResponse2,
zDeleteDealTagData,
zDeleteDealTagResponse2,
zDeleteMarketplaceData,
zDeleteMarketplaceResponse2,
zDeleteProductData,
@ -280,8 +285,6 @@ import {
zDeleteServicesKitResponse2,
zDeleteStatusData,
zDeleteStatusResponse2,
zDeleteTagData,
zDeleteTagResponse,
zDuplicateProductServicesData,
zDuplicateProductServicesResponse,
zGetBarcodeTemplateAttributesData,
@ -306,6 +309,8 @@ import {
zGetDealsResponse2,
zGetDealTagColorsData,
zGetDealTagColorsResponse,
zGetDealTagsData,
zGetDealTagsResponse2,
zGetMarketplacesData,
zGetMarketplacesResponse2,
zGetProductBarcodePdfData,
@ -344,6 +349,8 @@ import {
zUpdateDealServiceResponse2,
zUpdateDealsInGroupData,
zUpdateDealsInGroupResponse2,
zUpdateDealTagData,
zUpdateDealTagResponse2,
zUpdateMarketplaceData,
zUpdateMarketplaceResponse2,
zUpdateProductData,
@ -358,8 +365,6 @@ import {
zUpdateServicesKitResponse2,
zUpdateStatusData,
zUpdateStatusResponse2,
zUpdateTagData,
zUpdateTagResponse,
} from "./zod.gen";
export type Options<
@ -684,49 +689,45 @@ export const updateDealsInGroup = <ThrowOnError extends boolean = false>(
};
/**
* Update Tag
* Get Deal Tags
*/
export const updateTag = <ThrowOnError extends boolean = false>(
options: Options<UpdateTagData, ThrowOnError>
export const getDealTags = <ThrowOnError extends boolean = false>(
options: Options<GetDealTagsData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).patch<
UpdateTagResponses,
UpdateTagErrors,
return (options.client ?? _heyApiClient).get<
GetDealTagsResponses,
GetDealTagsErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zUpdateTagData.parseAsync(data);
return await zGetDealTagsData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUpdateTagResponse.parseAsync(data);
return await zGetDealTagsResponse2.parseAsync(data);
},
url: "/crm/v1/deal-tag/",
url: "/crm/v1/deal-tag/{projectId}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Create Tag
* Create Deal Tag
*/
export const createTag = <ThrowOnError extends boolean = false>(
options: Options<CreateTagData, ThrowOnError>
export const createDealTag = <ThrowOnError extends boolean = false>(
options: Options<CreateDealTagData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).post<
CreateTagResponses,
CreateTagErrors,
CreateDealTagResponses,
CreateDealTagErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zCreateTagData.parseAsync(data);
return await zCreateDealTagData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zCreateTagResponse.parseAsync(data);
return await zCreateDealTagResponse2.parseAsync(data);
},
url: "/crm/v1/deal-tag/",
...options,
@ -738,28 +739,55 @@ export const createTag = <ThrowOnError extends boolean = false>(
};
/**
* Delete Tag
* Delete Deal Tag
*/
export const deleteTag = <ThrowOnError extends boolean = false>(
options: Options<DeleteTagData, ThrowOnError>
export const deleteDealTag = <ThrowOnError extends boolean = false>(
options: Options<DeleteDealTagData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).delete<
DeleteTagResponses,
DeleteTagErrors,
DeleteDealTagResponses,
DeleteDealTagErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zDeleteTagData.parseAsync(data);
return await zDeleteDealTagData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zDeleteTagResponse.parseAsync(data);
return await zDeleteDealTagResponse2.parseAsync(data);
},
url: "/crm/v1/deal-tag/{deal_tag_id}",
url: "/crm/v1/deal-tag/{pk}",
...options,
});
};
/**
* Update Deal Tag
*/
export const updateDealTag = <ThrowOnError extends boolean = false>(
options: Options<UpdateDealTagData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).patch<
UpdateDealTagResponses,
UpdateDealTagErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zUpdateDealTagData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUpdateDealTagResponse2.parseAsync(data);
},
url: "/crm/v1/deal-tag/{pk}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Switch Deal Tag
*/
@ -793,7 +821,7 @@ export const switchDealTag = <ThrowOnError extends boolean = false>(
export const getDealTagColors = <ThrowOnError extends boolean = false>(
options?: Options<GetDealTagColorsData, ThrowOnError>
) => {
return (options?.client ?? _heyApiClient).get<
return (options?.client ?? _heyApiClient).post<
GetDealTagColorsResponses,
unknown,
ThrowOnError

View File

@ -985,6 +985,10 @@ export type DealSchema = {
*/
createdAt: string;
group: DealGroupSchema | null;
/**
* Tags
*/
tags: Array<DealTagSchema>;
/**
* Productsquantity
*/
@ -1318,6 +1322,16 @@ export type GetDealServicesResponse = {
items: Array<DealServiceSchema>;
};
/**
* GetDealTagsResponse
*/
export type GetDealTagsResponse = {
/**
* Items
*/
items: Array<DealTagSchema>;
};
/**
* GetDealsResponse
*/
@ -1650,6 +1664,10 @@ export type ProjectSchema = {
* Builtinmodules
*/
builtInModules: Array<BuiltInModuleSchemaOutput>;
/**
* Tags
*/
tags: Array<DealTagSchema>;
};
/**
@ -2851,85 +2869,124 @@ export type UpdateDealsInGroupResponses = {
export type UpdateDealsInGroupResponse2 =
UpdateDealsInGroupResponses[keyof UpdateDealsInGroupResponses];
export type UpdateTagData = {
body: UpdateDealTagRequest;
path?: never;
export type GetDealTagsData = {
body?: never;
path: {
/**
* Projectid
*/
projectId: number;
};
query?: never;
url: "/crm/v1/deal-tag/";
url: "/crm/v1/deal-tag/{projectId}";
};
export type UpdateTagErrors = {
export type GetDealTagsErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UpdateTagError = UpdateTagErrors[keyof UpdateTagErrors];
export type GetDealTagsError = GetDealTagsErrors[keyof GetDealTagsErrors];
export type UpdateTagResponses = {
export type GetDealTagsResponses = {
/**
* Successful Response
*/
200: UpdateDealTagResponse;
200: GetDealTagsResponse;
};
export type UpdateTagResponse = UpdateTagResponses[keyof UpdateTagResponses];
export type GetDealTagsResponse2 =
GetDealTagsResponses[keyof GetDealTagsResponses];
export type CreateTagData = {
export type CreateDealTagData = {
body: CreateDealTagRequest;
path?: never;
query?: never;
url: "/crm/v1/deal-tag/";
};
export type CreateTagErrors = {
export type CreateDealTagErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type CreateTagError = CreateTagErrors[keyof CreateTagErrors];
export type CreateDealTagError = CreateDealTagErrors[keyof CreateDealTagErrors];
export type CreateTagResponses = {
export type CreateDealTagResponses = {
/**
* Successful Response
*/
200: CreateDealTagResponse;
};
export type CreateTagResponse = CreateTagResponses[keyof CreateTagResponses];
export type CreateDealTagResponse2 =
CreateDealTagResponses[keyof CreateDealTagResponses];
export type DeleteTagData = {
export type DeleteDealTagData = {
body?: never;
path: {
/**
* Deal Tag Id
* Pk
*/
deal_tag_id: number;
pk: number;
};
query?: never;
url: "/crm/v1/deal-tag/{deal_tag_id}";
url: "/crm/v1/deal-tag/{pk}";
};
export type DeleteTagErrors = {
export type DeleteDealTagErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type DeleteTagError = DeleteTagErrors[keyof DeleteTagErrors];
export type DeleteDealTagError = DeleteDealTagErrors[keyof DeleteDealTagErrors];
export type DeleteTagResponses = {
export type DeleteDealTagResponses = {
/**
* Successful Response
*/
200: DeleteDealTagResponse;
};
export type DeleteTagResponse = DeleteTagResponses[keyof DeleteTagResponses];
export type DeleteDealTagResponse2 =
DeleteDealTagResponses[keyof DeleteDealTagResponses];
export type UpdateDealTagData = {
body: UpdateDealTagRequest;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/deal-tag/{pk}";
};
export type UpdateDealTagErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UpdateDealTagError = UpdateDealTagErrors[keyof UpdateDealTagErrors];
export type UpdateDealTagResponses = {
/**
* Successful Response
*/
200: UpdateDealTagResponse;
};
export type UpdateDealTagResponse2 =
UpdateDealTagResponses[keyof UpdateDealTagResponses];
export type SwitchDealTagData = {
body: SwitchDealTagRequest;

View File

@ -348,6 +348,27 @@ export const zStatusSchema = z.object({
color: z.string(),
});
/**
* DealTagColorSchema
*/
export const zDealTagColorSchema = z.object({
id: z.int(),
color: z.string(),
backgroundColor: z.string(),
label: z.string(),
});
/**
* DealTagSchema
*/
export const zDealTagSchema = z.object({
name: z.string(),
projectId: z.int(),
tagColorId: z.int(),
id: z.int(),
tagColor: zDealTagColorSchema,
});
/**
* DealSchema
*/
@ -361,6 +382,7 @@ export const zDealSchema = z.object({
offset: true,
}),
group: z.union([zDealGroupSchema, z.null()]),
tags: z.array(zDealTagSchema),
productsQuantity: z.optional(z.int()).default(0),
totalPrice: z.optional(z.number()).default(0),
client: z.optional(z.union([zClientSchema, z.null()])),
@ -427,27 +449,6 @@ export const zCreateDealTagRequest = z.object({
entity: zCreateDealTagSchema,
});
/**
* DealTagColorSchema
*/
export const zDealTagColorSchema = z.object({
id: z.int(),
color: z.string(),
backgroundColor: z.string(),
label: z.string(),
});
/**
* DealTagSchema
*/
export const zDealTagSchema = z.object({
name: z.string(),
projectId: z.int(),
tagColorId: z.int(),
id: z.int(),
tagColor: zDealTagColorSchema,
});
/**
* CreateDealTagResponse
*/
@ -571,6 +572,7 @@ export const zProjectSchema = z.object({
id: z.int(),
name: z.string(),
builtInModules: z.array(zBuiltInModuleSchemaOutput),
tags: z.array(zDealTagSchema),
});
/**
@ -899,6 +901,13 @@ export const zGetDealServicesResponse = z.object({
items: z.array(zDealServiceSchema),
});
/**
* GetDealTagsResponse
*/
export const zGetDealTagsResponse = z.object({
items: z.array(zDealTagSchema),
});
/**
* PaginationInfoSchema
*/
@ -1628,18 +1637,20 @@ export const zUpdateDealsInGroupData = z.object({
*/
export const zUpdateDealsInGroupResponse2 = zUpdateDealsInGroupResponse;
export const zUpdateTagData = z.object({
body: zUpdateDealTagRequest,
path: z.optional(z.never()),
export const zGetDealTagsData = z.object({
body: z.optional(z.never()),
path: z.object({
projectId: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUpdateTagResponse = zUpdateDealTagResponse;
export const zGetDealTagsResponse2 = zGetDealTagsResponse;
export const zCreateTagData = z.object({
export const zCreateDealTagData = z.object({
body: zCreateDealTagRequest,
path: z.optional(z.never()),
query: z.optional(z.never()),
@ -1648,12 +1659,12 @@ export const zCreateTagData = z.object({
/**
* Successful Response
*/
export const zCreateTagResponse = zCreateDealTagResponse;
export const zCreateDealTagResponse2 = zCreateDealTagResponse;
export const zDeleteTagData = z.object({
export const zDeleteDealTagData = z.object({
body: z.optional(z.never()),
path: z.object({
deal_tag_id: z.int(),
pk: z.int(),
}),
query: z.optional(z.never()),
});
@ -1661,7 +1672,20 @@ export const zDeleteTagData = z.object({
/**
* Successful Response
*/
export const zDeleteTagResponse = zDeleteDealTagResponse;
export const zDeleteDealTagResponse2 = zDeleteDealTagResponse;
export const zUpdateDealTagData = z.object({
body: zUpdateDealTagRequest,
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUpdateDealTagResponse2 = zUpdateDealTagResponse;
export const zSwitchDealTagData = z.object({
body: zSwitchDealTagRequest,

View File

@ -20,6 +20,7 @@ import {
ProductServiceEditorModal,
ServicesKitSelectModal,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals";
import DealTagModal from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/modals/DealTagModal";
export const modals = {
enterNameModal: EnterNameModal,
@ -40,4 +41,5 @@ export const modals = {
printBarcodeModal: PrintBarcodeModal,
statusColorPickerModal: ColorPickerModal,
marketplaceEditorModal: MarketplaceEditorModal,
dealTagModal: DealTagModal,
};