From d4c0eac4a008c782026713c00ee8ed79c0afdffd Mon Sep 17 00:00:00 2001 From: AlexSserb Date: Mon, 27 Oct 2025 10:05:29 +0400 Subject: [PATCH] feat: deal attributes editing --- package.json | 2 +- .../components/AttributeValueInput.tsx | 101 +++++++++++ .../components/DealEditorBody.tsx | 63 ++++--- .../components/DealEditorTabPanel.tsx | 31 ++++ .../hooks/useDealAttributeValuesActions.ts | 76 ++++++++ .../tabs/CustomTab/CustomTab.tsx | 141 +++++++++++++++ src/lib/client/@tanstack/react-query.gen.ts | 81 +++++++++ src/lib/client/sdk.gen.ts | 62 +++++++ src/lib/client/types.gen.ts | 169 ++++++++++++++++++ src/lib/client/zod.gen.ts | 76 ++++++++ src/modules/utils/isModuleInProject.ts | 9 +- src/utils/datetime.ts | 4 + yarn.lock | 10 +- 13 files changed, 794 insertions(+), 31 deletions(-) create mode 100644 src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx create mode 100644 src/app/deals/drawers/DealEditorDrawer/components/DealEditorTabPanel.tsx create mode 100644 src/app/deals/drawers/DealEditorDrawer/hooks/useDealAttributeValuesActions.ts create mode 100644 src/app/deals/drawers/DealEditorDrawer/tabs/CustomTab/CustomTab.tsx diff --git a/package.json b/package.json index b35b78d..8cde821 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "clsx": "^2.1.1", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", - "dayjs": "^1.11.15", + "dayjs": "^1.11.18", "framer-motion": "^12.23.7", "handlebars": "^4.7.8", "i18n-iso-countries": "^7.14.0", diff --git a/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx b/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx new file mode 100644 index 0000000..d6d160d --- /dev/null +++ b/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx @@ -0,0 +1,101 @@ +import { CSSProperties, FC, JSX } from "react"; +import { Checkbox, NumberInput, TextInput } from "@mantine/core"; +import { DatePickerInput, DateTimePicker } from "@mantine/dates"; +import { DealModuleAttributeSchema } from "@/lib/client"; +import { naiveDateTimeStringToUtc } from "@/utils/datetime"; + +type Props = { + attrInfo: DealModuleAttributeSchema; + value: any; + onChange: (value: any) => void; + error?: string; + style?: CSSProperties; +}; + +const AttributeValueInput: FC = ({ + attrInfo, + value, + onChange, + error, + style, +}) => { + const commonProps = { + label: attrInfo.label, + style, + error, + }; + + const renderCheckbox = () => { + const localValue = value === undefined ? false : value; + return ( + onChange(e.currentTarget.checked)} + /> + ); + }; + + const renderDatePicker = () => ( + { + onChange(val ? new Date(val) : null); + }} + clearable + locale={"ru"} + valueFormat={"DD.MM.YYYY"} + /> + ); + + const renderDateTimePicker = () => ( + { + if (!val) return; + const localDate = new Date(val.replace(" ", "T")); + const utcString = localDate + .toISOString() + .substring(0, 19) + .replace("T", " "); + onChange(utcString); + }} + clearable + locale={"ru"} + valueFormat={"DD.MM.YYYY HH:mm"} + /> + ); + + const renderTextInput = () => ( + onChange(e.currentTarget.value)} + /> + ); + + const renderNumberInput = () => ( + onChange(Number(value))} + /> + ); + + const renderingFuncMap: Record JSX.Element> = { + bool: renderCheckbox, + date: renderDatePicker, + datetime: renderDateTimePicker, + str: renderTextInput, + int: renderNumberInput, + float: renderNumberInput, + }; + + const render = renderingFuncMap[attrInfo.type.type]; + return render(); +}; + +export default AttributeValueInput; diff --git a/src/app/deals/drawers/DealEditorDrawer/components/DealEditorBody.tsx b/src/app/deals/drawers/DealEditorDrawer/components/DealEditorBody.tsx index 524c61d..6271ae1 100644 --- a/src/app/deals/drawers/DealEditorDrawer/components/DealEditorBody.tsx +++ b/src/app/deals/drawers/DealEditorDrawer/components/DealEditorBody.tsx @@ -1,8 +1,9 @@ import React, { FC, ReactNode } from "react"; -import { IconEdit, IconHistory } from "@tabler/icons-react"; -import { motion } from "framer-motion"; +import { IconBlocks, IconEdit, IconHistory } from "@tabler/icons-react"; import { Box, Tabs, Text } from "@mantine/core"; +import DealEditorTabPanel from "@/app/deals/drawers/DealEditorDrawer/components/DealEditorTabPanel"; import TabsList from "@/app/deals/drawers/DealEditorDrawer/components/TabsList"; +import CustomTab from "@/app/deals/drawers/DealEditorDrawer/tabs/CustomTab/CustomTab"; import DealStatusHistoryTab from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/DealStatusHistoryTab"; import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab"; import useIsMobile from "@/hooks/utils/useIsMobile"; @@ -20,23 +21,6 @@ type Props = { const DealEditorBody: FC = props => { const isMobile = useIsMobile(); - const getTabPanel = (value: string, component: ReactNode): ReactNode => ( - - - - {component} - - - - ); - const getTab = (key: string, label: string, icon: ReactNode) => ( = props => { const tabs: ReactNode[] = []; for (const module of props.project.modules) { + if (!module.isBuiltIn) { + for (const tab of module.tabs) { + tabs.push(getTab(tab.key, tab.label, )); + } + continue; + } + const moduleInfo = MODULES[module.key]; for (const tab of moduleInfo.tabs) { if ( @@ -76,6 +67,23 @@ const DealEditorBody: FC = props => { const tabPanels: ReactNode[] = []; for (const module of props.project.modules) { + if (!module.isBuiltIn) { + const tabPanel = ( + + + + ); + + tabPanels.push(tabPanel); + continue; + } + const moduleInfo = MODULES[module.key]; for (const tab of moduleInfo.tabs) { if ( @@ -84,7 +92,14 @@ const DealEditorBody: FC = props => { ) continue; - tabPanels.push(getTabPanel(tab.key, tab.tab(props))); + const tabPanel = ( + + {tab.tab(props)} + + ); + tabPanels.push(tabPanel); } } @@ -105,8 +120,12 @@ const DealEditorBody: FC = props => { {getModuleTabs()} - {getTabPanel("general", )} - {getTabPanel("history", )} + + + + + + {getModuleTabPanels()} ); diff --git a/src/app/deals/drawers/DealEditorDrawer/components/DealEditorTabPanel.tsx b/src/app/deals/drawers/DealEditorDrawer/components/DealEditorTabPanel.tsx new file mode 100644 index 0000000..d40153e --- /dev/null +++ b/src/app/deals/drawers/DealEditorDrawer/components/DealEditorTabPanel.tsx @@ -0,0 +1,31 @@ +import React, { FC, PropsWithChildren } from "react"; +import { motion } from "framer-motion"; +import { Box, Tabs } from "@mantine/core"; + +type Props = { + value: string; +}; + +const DealEditorTabPanel: FC> = ({ + value, + children, +}) => { + return ( + + + + {children} + + + + ); +}; + +export default DealEditorTabPanel; diff --git a/src/app/deals/drawers/DealEditorDrawer/hooks/useDealAttributeValuesActions.ts b/src/app/deals/drawers/DealEditorDrawer/hooks/useDealAttributeValuesActions.ts new file mode 100644 index 0000000..dcd4ad8 --- /dev/null +++ b/src/app/deals/drawers/DealEditorDrawer/hooks/useDealAttributeValuesActions.ts @@ -0,0 +1,76 @@ +import { useMemo } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { AxiosError } from "axios"; +import { + DealModuleAttributeSchema, + HttpValidationError, + UpdateDealModuleAttributeSchema, +} from "@/lib/client"; +import { + getDealModuleAttributesOptions, + updateDealModuleAttributesMutation, +} from "@/lib/client/@tanstack/react-query.gen"; +import { notifications } from "@/lib/notifications"; + +type Props = { + dealId: number; + moduleId: number; +}; + +const useDealAttributeValuesActions = ({ dealId, moduleId }: Props) => { + const { data, refetch: refetchAttributes } = useQuery( + getDealModuleAttributesOptions({ + path: { + dealId, + moduleId, + }, + }) + ); + + const sortedAttributes = useMemo(() => { + if (!data?.attributes) return []; + const sortedAttributes: DealModuleAttributeSchema[] = []; + for (const attr of data.attributes) { + if (attr.type.type === "bool") sortedAttributes.push(attr); + else sortedAttributes.unshift(attr); + } + return sortedAttributes; + }, [data?.attributes]); + + const onError = (error: AxiosError) => { + console.error(error); + notifications.error({ + message: error.response?.data?.detail as string | undefined, + }); + }; + + const updateAttributeValuesMutation = useMutation({ + ...updateDealModuleAttributesMutation(), + onError, + onSuccess: ({ message }) => { + notifications.success({ message }); + refetchAttributes(); + }, + }); + + const updateAttributeValues = ( + attributeValues: UpdateDealModuleAttributeSchema[] + ) => { + updateAttributeValuesMutation.mutate({ + path: { + moduleId, + dealId, + }, + body: { + attributes: attributeValues, + }, + }); + }; + + return { + dealAttributes: sortedAttributes, + updateAttributeValues, + }; +}; + +export default useDealAttributeValuesActions; diff --git a/src/app/deals/drawers/DealEditorDrawer/tabs/CustomTab/CustomTab.tsx b/src/app/deals/drawers/DealEditorDrawer/tabs/CustomTab/CustomTab.tsx new file mode 100644 index 0000000..6d547d5 --- /dev/null +++ b/src/app/deals/drawers/DealEditorDrawer/tabs/CustomTab/CustomTab.tsx @@ -0,0 +1,141 @@ +import React, { + FC, + ReactNode, + useCallback, + useEffect, + useMemo, + useState, +} from "react"; +import { Flex, Group } from "@mantine/core"; +import AttributeValueInput from "@/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput"; +import useDealAttributeValuesActions from "@/app/deals/drawers/DealEditorDrawer/hooks/useDealAttributeValuesActions"; +import FormFlexRow from "@/components/ui/FormFlexRow/FormFlexRow"; +import InlineButton from "@/components/ui/InlineButton/InlineButton"; +import { + DealModuleAttributeSchema, + DealSchema, + ModuleSchemaOutput, + UpdateDealModuleAttributeSchema, +} from "@/lib/client"; + +type Props = { + module: ModuleSchemaOutput; + deal: DealSchema; +}; + +type Value = { + value?: any; +}; + +const CustomTab: FC = ({ module, deal }) => { + const { dealAttributes, updateAttributeValues } = + useDealAttributeValuesActions({ + moduleId: module.id, + dealId: deal.id, + }); + + const [attributeValuesMap, setAttributeValuesMap] = useState< + Map + >(new Map()); + const [attributeErrorsMap, setAttributeErrorsMap] = useState< + Map + >(new Map()); + + useEffect(() => { + const values = new Map(); + for (const dealAttr of dealAttributes) { + values.set(dealAttr.attributeId, dealAttr?.value); + } + setAttributeValuesMap(values); + }, [dealAttributes]); + + const onSubmit = () => { + let isErrorFound = false; + for (const attr of dealAttributes) { + const value = attributeValuesMap.get(attr.attributeId); + if (!attr.isNullable && (value === null || value === undefined)) { + attributeErrorsMap.set(attr.attributeId, "Обязательное поле"); + isErrorFound = true; + } + } + setAttributeErrorsMap(new Map(attributeErrorsMap)); + if (isErrorFound) return; + + const attributeValues: UpdateDealModuleAttributeSchema[] = + attributeValuesMap + .entries() + .map( + ([attributeId, value]) => + ({ + attributeId, + value, + }) as UpdateDealModuleAttributeSchema + ) + .toArray(); + + updateAttributeValues(attributeValues); + }; + + const getAttributeElement = useCallback( + (attribute: DealModuleAttributeSchema) => ( + { + attributeValuesMap.set(attribute.attributeId, { value }); + setAttributeValuesMap(new Map(attributeValuesMap)); + attributeErrorsMap.delete(attribute.attributeId); + setAttributeErrorsMap(new Map(attributeErrorsMap)); + }} + style={{ flex: 1 }} + error={attributeErrorsMap.get(attribute.attributeId)} + /> + ), + [attributeValuesMap, attributeErrorsMap] + ); + + const attributesRows = useMemo(() => { + if (!dealAttributes) return []; + const boolAttributes = dealAttributes.filter( + a => a.type.type === "bool" + ); + const otherAttributes = dealAttributes.filter( + a => a.type.type !== "bool" + ); + + const rows: ReactNode[] = []; + for (let i = 0; i < otherAttributes.length; i += 2) { + const rightIdx = i + 1; + + rows.push( + + {getAttributeElement(otherAttributes[i])} + {rightIdx < otherAttributes.length && + getAttributeElement(otherAttributes[rightIdx])} + + ); + } + + for (const attr of boolAttributes) { + rows.push(getAttributeElement(attr)); + } + + return rows; + }, [dealAttributes, getAttributeElement]); + + return ( + + {attributesRows} + + Сохранить + + + ); +}; + +export default CustomTab; diff --git a/src/lib/client/@tanstack/react-query.gen.ts b/src/lib/client/@tanstack/react-query.gen.ts index 39e7b36..5a0f230 100644 --- a/src/lib/client/@tanstack/react-query.gen.ts +++ b/src/lib/client/@tanstack/react-query.gen.ts @@ -59,6 +59,7 @@ import { getBaseMarketplaces, getBoards, getClients, + getDealModuleAttributes, getDealProducts, getDeals, getDealServices, @@ -85,6 +86,7 @@ import { updateClient, updateDeal, updateDealGroup, + updateDealModuleAttributes, updateDealProduct, updateDealProductService, updateDealService, @@ -234,6 +236,7 @@ import type { GetBaseMarketplacesData, GetBoardsData, GetClientsData, + GetDealModuleAttributesData, GetDealProductsData, GetDealsData, GetDealsError, @@ -284,6 +287,9 @@ import type { UpdateDealGroupData, UpdateDealGroupError, UpdateDealGroupResponse2, + UpdateDealModuleAttributesData, + UpdateDealModuleAttributesError, + UpdateDealModuleAttributesResponse2, UpdateDealProductData, UpdateDealProductError, UpdateDealProductResponse2, @@ -569,6 +575,81 @@ export const getAttributeTypesOptions = ( }); }; +export const getDealModuleAttributesQueryKey = ( + options: Options +) => createQueryKey("getDealModuleAttributes", options); + +/** + * Get Deal Module Attributes + */ +export const getDealModuleAttributesOptions = ( + options: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getDealModuleAttributes({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getDealModuleAttributesQueryKey(options), + }); +}; + +export const updateDealModuleAttributesQueryKey = ( + options: Options +) => createQueryKey("updateDealModuleAttributes", options); + +/** + * Update Deal Module Attributes + */ +export const updateDealModuleAttributesOptions = ( + options: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await updateDealModuleAttributes({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: updateDealModuleAttributesQueryKey(options), + }); +}; + +/** + * Update Deal Module Attributes + */ +export const updateDealModuleAttributesMutation = ( + options?: Partial> +): UseMutationOptions< + UpdateDealModuleAttributesResponse2, + AxiosError, + Options +> => { + const mutationOptions: UseMutationOptions< + UpdateDealModuleAttributesResponse2, + AxiosError, + Options + > = { + mutationFn: async localOptions => { + const { data } = await updateDealModuleAttributes({ + ...options, + ...localOptions, + throwOnError: true, + }); + return data; + }, + }; + return mutationOptions; +}; + export const getBoardsQueryKey = (options: Options) => createQueryKey("getBoards", options); diff --git a/src/lib/client/sdk.gen.ts b/src/lib/client/sdk.gen.ts index 32e7b6e..2ccb4b3 100644 --- a/src/lib/client/sdk.gen.ts +++ b/src/lib/client/sdk.gen.ts @@ -149,6 +149,9 @@ import type { GetClientsData, GetClientsErrors, GetClientsResponses, + GetDealModuleAttributesData, + GetDealModuleAttributesErrors, + GetDealModuleAttributesResponses, GetDealProductsData, GetDealProductsErrors, GetDealProductsResponses, @@ -219,6 +222,9 @@ import type { UpdateDealGroupData, UpdateDealGroupErrors, UpdateDealGroupResponses, + UpdateDealModuleAttributesData, + UpdateDealModuleAttributesErrors, + UpdateDealModuleAttributesResponses, UpdateDealProductData, UpdateDealProductErrors, UpdateDealProductResponses, @@ -365,6 +371,8 @@ import { zGetBoardsResponse2, zGetClientsData, zGetClientsResponse2, + zGetDealModuleAttributesData, + zGetDealModuleAttributesResponse2, zGetDealProductsData, zGetDealProductsResponse2, zGetDealsData, @@ -416,6 +424,8 @@ import { zUpdateDealData, zUpdateDealGroupData, zUpdateDealGroupResponse2, + zUpdateDealModuleAttributesData, + zUpdateDealModuleAttributesResponse2, zUpdateDealProductData, zUpdateDealProductResponse2, zUpdateDealProductServiceData, @@ -616,6 +626,58 @@ export const getAttributeTypes = ( }); }; +/** + * Get Deal Module Attributes + */ +export const getDealModuleAttributes = ( + options: Options +) => { + return (options.client ?? _heyApiClient).get< + GetDealModuleAttributesResponses, + GetDealModuleAttributesErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zGetDealModuleAttributesData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zGetDealModuleAttributesResponse2.parseAsync(data); + }, + url: "/crm/v1/attribute/deal/{dealId}/module/{moduleId}", + ...options, + }); +}; + +/** + * Update Deal Module Attributes + */ +export const updateDealModuleAttributes = < + ThrowOnError extends boolean = false, +>( + options: Options +) => { + return (options.client ?? _heyApiClient).post< + UpdateDealModuleAttributesResponses, + UpdateDealModuleAttributesErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zUpdateDealModuleAttributesData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zUpdateDealModuleAttributesResponse2.parseAsync(data); + }, + url: "/crm/v1/attribute/deal/{dealId}/module/{moduleId}", + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + }); +}; + /** * Get Boards */ diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index 952cab2..3fb8184 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -1034,6 +1034,57 @@ export type DealGroupSchema = { lexorank: string; }; +/** + * DealModuleAttributeSchema + */ +export type DealModuleAttributeSchema = { + /** + * Attributeid + */ + attributeId: number; + /** + * Label + */ + label: string; + /** + * Originallabel + */ + originalLabel: string; + /** + * Value + */ + value: { + [key: string]: unknown; + } | null; + type: AttributeTypeSchema; + /** + * Defaultvalue + */ + defaultValue: { + [key: string]: unknown; + }; + /** + * Description + */ + description: string; + /** + * Isapplicabletogroup + */ + isApplicableToGroup: boolean; + /** + * Isshownondashboard + */ + isShownOnDashboard: boolean; + /** + * Ishighlightifexpired + */ + isHighlightIfExpired: boolean; + /** + * Isnullable + */ + isNullable: boolean; +}; + /** * DealProductAddKitRequest */ @@ -1510,6 +1561,16 @@ export type GetClientsResponse = { items: Array; }; +/** + * GetDealModuleAttributesResponse + */ +export type GetDealModuleAttributesResponse = { + /** + * Attributes + */ + attributes: Array; +}; + /** * GetDealProductsResponse */ @@ -2501,6 +2562,42 @@ export type UpdateDealGroupSchema = { statusId?: number | null; }; +/** + * UpdateDealModuleAttributeSchema + */ +export type UpdateDealModuleAttributeSchema = { + /** + * Attributeid + */ + attributeId: number; + /** + * Value + */ + value: { + [key: string]: unknown; + } | null; +}; + +/** + * UpdateDealModuleAttributesRequest + */ +export type UpdateDealModuleAttributesRequest = { + /** + * Attributes + */ + attributes: Array; +}; + +/** + * UpdateDealModuleAttributesResponse + */ +export type UpdateDealModuleAttributesResponse = { + /** + * Message + */ + message: string; +}; + /** * UpdateDealProductRequest */ @@ -3177,6 +3274,78 @@ export type GetAttributeTypesResponses = { export type GetAttributeTypesResponse = GetAttributeTypesResponses[keyof GetAttributeTypesResponses]; +export type GetDealModuleAttributesData = { + body?: never; + path: { + /** + * Dealid + */ + dealId: number; + /** + * Moduleid + */ + moduleId: number; + }; + query?: never; + url: "/crm/v1/attribute/deal/{dealId}/module/{moduleId}"; +}; + +export type GetDealModuleAttributesErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetDealModuleAttributesError = + GetDealModuleAttributesErrors[keyof GetDealModuleAttributesErrors]; + +export type GetDealModuleAttributesResponses = { + /** + * Successful Response + */ + 200: GetDealModuleAttributesResponse; +}; + +export type GetDealModuleAttributesResponse2 = + GetDealModuleAttributesResponses[keyof GetDealModuleAttributesResponses]; + +export type UpdateDealModuleAttributesData = { + body: UpdateDealModuleAttributesRequest; + path: { + /** + * Dealid + */ + dealId: number; + /** + * Moduleid + */ + moduleId: number; + }; + query?: never; + url: "/crm/v1/attribute/deal/{dealId}/module/{moduleId}"; +}; + +export type UpdateDealModuleAttributesErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UpdateDealModuleAttributesError = + UpdateDealModuleAttributesErrors[keyof UpdateDealModuleAttributesErrors]; + +export type UpdateDealModuleAttributesResponses = { + /** + * Successful Response + */ + 200: UpdateDealModuleAttributesResponse; +}; + +export type UpdateDealModuleAttributesResponse2 = + UpdateDealModuleAttributesResponses[keyof UpdateDealModuleAttributesResponses]; + export type GetBoardsData = { body?: never; path: { diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts index d5fb939..283fbfd 100644 --- a/src/lib/client/zod.gen.ts +++ b/src/lib/client/zod.gen.ts @@ -827,6 +827,23 @@ export const zDealAddKitResponse = z.object({ message: z.string(), }); +/** + * DealModuleAttributeSchema + */ +export const zDealModuleAttributeSchema = z.object({ + attributeId: z.int(), + label: z.string(), + originalLabel: z.string(), + value: z.union([z.object({}), z.null()]), + type: zAttributeTypeSchema, + defaultValue: z.object({}), + description: z.string(), + isApplicableToGroup: z.boolean(), + isShownOnDashboard: z.boolean(), + isHighlightIfExpired: z.boolean(), + isNullable: z.boolean(), +}); + /** * DealProductAddKitRequest */ @@ -1093,6 +1110,13 @@ export const zGetClientsResponse = z.object({ items: z.array(zClientSchema), }); +/** + * GetDealModuleAttributesResponse + */ +export const zGetDealModuleAttributesResponse = z.object({ + attributes: z.array(zDealModuleAttributeSchema), +}); + /** * GetDealProductsResponse */ @@ -1441,6 +1465,28 @@ export const zUpdateDealGroupResponse = z.object({ message: z.string(), }); +/** + * UpdateDealModuleAttributeSchema + */ +export const zUpdateDealModuleAttributeSchema = z.object({ + attributeId: z.int(), + value: z.union([z.object({}), z.null()]), +}); + +/** + * UpdateDealModuleAttributesRequest + */ +export const zUpdateDealModuleAttributesRequest = z.object({ + attributes: z.array(zUpdateDealModuleAttributeSchema), +}); + +/** + * UpdateDealModuleAttributesResponse + */ +export const zUpdateDealModuleAttributesResponse = z.object({ + message: z.string(), +}); + /** * UpdateDealProductSchema */ @@ -1837,6 +1883,36 @@ export const zGetAttributeTypesData = z.object({ */ export const zGetAttributeTypesResponse = zGetAllAttributeTypesResponse; +export const zGetDealModuleAttributesData = z.object({ + body: z.optional(z.never()), + path: z.object({ + dealId: z.int(), + moduleId: z.int(), + }), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zGetDealModuleAttributesResponse2 = + zGetDealModuleAttributesResponse; + +export const zUpdateDealModuleAttributesData = z.object({ + body: zUpdateDealModuleAttributesRequest, + path: z.object({ + dealId: z.int(), + moduleId: z.int(), + }), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zUpdateDealModuleAttributesResponse2 = + zUpdateDealModuleAttributesResponse; + export const zGetBoardsData = z.object({ body: z.optional(z.never()), path: z.object({ diff --git a/src/modules/utils/isModuleInProject.ts b/src/modules/utils/isModuleInProject.ts index 0808445..7a5ecb9 100644 --- a/src/modules/utils/isModuleInProject.ts +++ b/src/modules/utils/isModuleInProject.ts @@ -1,7 +1,10 @@ -import { ProjectSchema } from "../../client"; -import { ModuleNames } from "../modules.tsx"; +import { ProjectSchema } from "@/lib/client"; +import { ModuleNames } from "../modules"; -const isModuleInProject = (module: ModuleNames, project?: ProjectSchema | null) => { +const isModuleInProject = ( + module: ModuleNames, + project?: ProjectSchema | null +) => { return project?.modules.findIndex(m => m.key === module.toString()) !== -1; }; diff --git a/src/utils/datetime.ts b/src/utils/datetime.ts index 6f30ba7..ee414ee 100644 --- a/src/utils/datetime.ts +++ b/src/utils/datetime.ts @@ -1,5 +1,9 @@ import { formatInTimeZone } from "date-fns-tz"; +export const naiveDateTimeStringToUtc = (datetime: string) => { + return `${datetime.replace(" ", "T")}Z`; +}; + export const utcDateToLocal = (datetime: string | Date) => { const userTZ = Intl.DateTimeFormat().resolvedOptions().timeZone; const localTime = formatInTimeZone(datetime, userTZ, "yyyy-MM-dd HH:mm:ss"); diff --git a/yarn.lock b/yarn.lock index de3ccb4..1deca15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6185,7 +6185,7 @@ __metadata: clsx: "npm:^2.1.1" date-fns: "npm:^4.1.0" date-fns-tz: "npm:^3.2.0" - dayjs: "npm:^1.11.15" + dayjs: "npm:^1.11.18" eslint: "npm:^9.29.0" eslint-config-mantine: "npm:^4.0.3" eslint-plugin-eslint-comments: "npm:^3.2.0" @@ -6425,10 +6425,10 @@ __metadata: languageName: node linkType: hard -"dayjs@npm:^1.11.15": - version: 1.11.15 - resolution: "dayjs@npm:1.11.15" - checksum: 10c0/bb66cd5419fff017f3950b95fc27643cf4e0ce22a087cef1f67398f18126ed07bf36d6911f33b19029a1621c64090b8ecaef660477de7678287fe8c0f4e68d29 +"dayjs@npm:^1.11.18": + version: 1.11.18 + resolution: "dayjs@npm:1.11.18" + checksum: 10c0/83b67f5d977e2634edf4f5abdd91d9041a696943143638063016915d2cd8c7e57e0751e40379a07ebca8be7a48dd380bef8752d22a63670f2d15970e34f96d7a languageName: node linkType: hard