feat: deal attributes editing

This commit is contained in:
2025-10-27 10:05:29 +04:00
parent e39df47520
commit d4c0eac4a0
13 changed files with 794 additions and 31 deletions

View File

@ -32,7 +32,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"dayjs": "^1.11.15", "dayjs": "^1.11.18",
"framer-motion": "^12.23.7", "framer-motion": "^12.23.7",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"i18n-iso-countries": "^7.14.0", "i18n-iso-countries": "^7.14.0",

View File

@ -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<Props> = ({
attrInfo,
value,
onChange,
error,
style,
}) => {
const commonProps = {
label: attrInfo.label,
style,
error,
};
const renderCheckbox = () => {
const localValue = value === undefined ? false : value;
return (
<Checkbox
{...commonProps}
checked={localValue}
onChange={e => onChange(e.currentTarget.checked)}
/>
);
};
const renderDatePicker = () => (
<DatePickerInput
{...commonProps}
value={value ? new Date(String(value)) : null}
onChange={val => {
onChange(val ? new Date(val) : null);
}}
clearable
locale={"ru"}
valueFormat={"DD.MM.YYYY"}
/>
);
const renderDateTimePicker = () => (
<DateTimePicker
{...commonProps}
value={value ? naiveDateTimeStringToUtc(value) : null}
onChange={val => {
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 = () => (
<TextInput
{...commonProps}
value={value ?? ""}
onChange={e => onChange(e.currentTarget.value)}
/>
);
const renderNumberInput = () => (
<NumberInput
{...commonProps}
allowDecimal={attrInfo.type.type === "float"}
value={Number(value)}
onChange={value => onChange(Number(value))}
/>
);
const renderingFuncMap: Record<string, () => 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;

View File

@ -1,8 +1,9 @@
import React, { FC, ReactNode } from "react"; import React, { FC, ReactNode } from "react";
import { IconEdit, IconHistory } from "@tabler/icons-react"; import { IconBlocks, IconEdit, IconHistory } from "@tabler/icons-react";
import { motion } from "framer-motion";
import { Box, Tabs, Text } from "@mantine/core"; 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 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 DealStatusHistoryTab from "@/app/deals/drawers/DealEditorDrawer/tabs/DealStatusHistoryTab/DealStatusHistoryTab";
import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab"; import GeneralTab from "@/app/deals/drawers/DealEditorDrawer/tabs/GeneralTab/GeneralTab";
import useIsMobile from "@/hooks/utils/useIsMobile"; import useIsMobile from "@/hooks/utils/useIsMobile";
@ -20,23 +21,6 @@ type Props = {
const DealEditorBody: FC<Props> = props => { const DealEditorBody: FC<Props> = props => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const getTabPanel = (value: string, component: ReactNode): ReactNode => (
<Tabs.Panel
key={value}
value={value}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}>
<Box
h={"100%"}
w={"100%"}>
{component}
</Box>
</motion.div>
</Tabs.Panel>
);
const getTab = (key: string, label: string, icon: ReactNode) => ( const getTab = (key: string, label: string, icon: ReactNode) => (
<Tabs.Tab <Tabs.Tab
key={key} key={key}
@ -56,6 +40,13 @@ const DealEditorBody: FC<Props> = props => {
const tabs: ReactNode[] = []; const tabs: ReactNode[] = [];
for (const module of props.project.modules) { for (const module of props.project.modules) {
if (!module.isBuiltIn) {
for (const tab of module.tabs) {
tabs.push(getTab(tab.key, tab.label, <IconBlocks />));
}
continue;
}
const moduleInfo = MODULES[module.key]; const moduleInfo = MODULES[module.key];
for (const tab of moduleInfo.tabs) { for (const tab of moduleInfo.tabs) {
if ( if (
@ -76,6 +67,23 @@ const DealEditorBody: FC<Props> = props => {
const tabPanels: ReactNode[] = []; const tabPanels: ReactNode[] = [];
for (const module of props.project.modules) { for (const module of props.project.modules) {
if (!module.isBuiltIn) {
const tabPanel = (
<DealEditorTabPanel
key={module.key}
value={module.key}>
<CustomTab
key={module.key}
module={module}
deal={props.value}
/>
</DealEditorTabPanel>
);
tabPanels.push(tabPanel);
continue;
}
const moduleInfo = MODULES[module.key]; const moduleInfo = MODULES[module.key];
for (const tab of moduleInfo.tabs) { for (const tab of moduleInfo.tabs) {
if ( if (
@ -84,7 +92,14 @@ const DealEditorBody: FC<Props> = props => {
) )
continue; continue;
tabPanels.push(getTabPanel(tab.key, tab.tab(props))); const tabPanel = (
<DealEditorTabPanel
key={tab.key}
value={tab.key}>
{tab.tab(props)}
</DealEditorTabPanel>
);
tabPanels.push(tabPanel);
} }
} }
@ -105,8 +120,12 @@ const DealEditorBody: FC<Props> = props => {
{getModuleTabs()} {getModuleTabs()}
</TabsList> </TabsList>
{getTabPanel("general", <GeneralTab {...props} />)} <DealEditorTabPanel value={"general"}>
{getTabPanel("history", <DealStatusHistoryTab {...props} />)} <GeneralTab {...props} />
</DealEditorTabPanel>
<DealEditorTabPanel value={"history"}>
<DealStatusHistoryTab {...props} />
</DealEditorTabPanel>
{getModuleTabPanels()} {getModuleTabPanels()}
</Tabs> </Tabs>
); );

View File

@ -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<PropsWithChildren<Props>> = ({
value,
children,
}) => {
return (
<Tabs.Panel
key={value}
value={value}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}>
<Box
h={"100%"}
w={"100%"}>
{children}
</Box>
</motion.div>
</Tabs.Panel>
);
};
export default DealEditorTabPanel;

View File

@ -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<HttpValidationError>) => {
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;

View File

@ -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<Props> = ({ module, deal }) => {
const { dealAttributes, updateAttributeValues } =
useDealAttributeValuesActions({
moduleId: module.id,
dealId: deal.id,
});
const [attributeValuesMap, setAttributeValuesMap] = useState<
Map<number, Value | null>
>(new Map());
const [attributeErrorsMap, setAttributeErrorsMap] = useState<
Map<number, string>
>(new Map());
useEffect(() => {
const values = new Map<number, Value | null>();
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) => (
<AttributeValueInput
key={attribute.attributeId}
attrInfo={attribute}
value={attributeValuesMap.get(attribute.attributeId)?.value}
onChange={value => {
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(
<FormFlexRow key={`row${i}`}>
{getAttributeElement(otherAttributes[i])}
{rightIdx < otherAttributes.length &&
getAttributeElement(otherAttributes[rightIdx])}
</FormFlexRow>
);
}
for (const attr of boolAttributes) {
rows.push(getAttributeElement(attr));
}
return rows;
}, [dealAttributes, getAttributeElement]);
return (
<Flex
direction={"column"}
gap={"xs"}
py={"xs"}
px={"md"}>
{attributesRows}
<Group>
<InlineButton onClick={onSubmit}>Сохранить</InlineButton>
</Group>
</Flex>
);
};
export default CustomTab;

View File

@ -59,6 +59,7 @@ import {
getBaseMarketplaces, getBaseMarketplaces,
getBoards, getBoards,
getClients, getClients,
getDealModuleAttributes,
getDealProducts, getDealProducts,
getDeals, getDeals,
getDealServices, getDealServices,
@ -85,6 +86,7 @@ import {
updateClient, updateClient,
updateDeal, updateDeal,
updateDealGroup, updateDealGroup,
updateDealModuleAttributes,
updateDealProduct, updateDealProduct,
updateDealProductService, updateDealProductService,
updateDealService, updateDealService,
@ -234,6 +236,7 @@ import type {
GetBaseMarketplacesData, GetBaseMarketplacesData,
GetBoardsData, GetBoardsData,
GetClientsData, GetClientsData,
GetDealModuleAttributesData,
GetDealProductsData, GetDealProductsData,
GetDealsData, GetDealsData,
GetDealsError, GetDealsError,
@ -284,6 +287,9 @@ import type {
UpdateDealGroupData, UpdateDealGroupData,
UpdateDealGroupError, UpdateDealGroupError,
UpdateDealGroupResponse2, UpdateDealGroupResponse2,
UpdateDealModuleAttributesData,
UpdateDealModuleAttributesError,
UpdateDealModuleAttributesResponse2,
UpdateDealProductData, UpdateDealProductData,
UpdateDealProductError, UpdateDealProductError,
UpdateDealProductResponse2, UpdateDealProductResponse2,
@ -569,6 +575,81 @@ export const getAttributeTypesOptions = (
}); });
}; };
export const getDealModuleAttributesQueryKey = (
options: Options<GetDealModuleAttributesData>
) => createQueryKey("getDealModuleAttributes", options);
/**
* Get Deal Module Attributes
*/
export const getDealModuleAttributesOptions = (
options: Options<GetDealModuleAttributesData>
) => {
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<UpdateDealModuleAttributesData>
) => createQueryKey("updateDealModuleAttributes", options);
/**
* Update Deal Module Attributes
*/
export const updateDealModuleAttributesOptions = (
options: Options<UpdateDealModuleAttributesData>
) => {
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<Options<UpdateDealModuleAttributesData>>
): UseMutationOptions<
UpdateDealModuleAttributesResponse2,
AxiosError<UpdateDealModuleAttributesError>,
Options<UpdateDealModuleAttributesData>
> => {
const mutationOptions: UseMutationOptions<
UpdateDealModuleAttributesResponse2,
AxiosError<UpdateDealModuleAttributesError>,
Options<UpdateDealModuleAttributesData>
> = {
mutationFn: async localOptions => {
const { data } = await updateDealModuleAttributes({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getBoardsQueryKey = (options: Options<GetBoardsData>) => export const getBoardsQueryKey = (options: Options<GetBoardsData>) =>
createQueryKey("getBoards", options); createQueryKey("getBoards", options);

View File

@ -149,6 +149,9 @@ import type {
GetClientsData, GetClientsData,
GetClientsErrors, GetClientsErrors,
GetClientsResponses, GetClientsResponses,
GetDealModuleAttributesData,
GetDealModuleAttributesErrors,
GetDealModuleAttributesResponses,
GetDealProductsData, GetDealProductsData,
GetDealProductsErrors, GetDealProductsErrors,
GetDealProductsResponses, GetDealProductsResponses,
@ -219,6 +222,9 @@ import type {
UpdateDealGroupData, UpdateDealGroupData,
UpdateDealGroupErrors, UpdateDealGroupErrors,
UpdateDealGroupResponses, UpdateDealGroupResponses,
UpdateDealModuleAttributesData,
UpdateDealModuleAttributesErrors,
UpdateDealModuleAttributesResponses,
UpdateDealProductData, UpdateDealProductData,
UpdateDealProductErrors, UpdateDealProductErrors,
UpdateDealProductResponses, UpdateDealProductResponses,
@ -365,6 +371,8 @@ import {
zGetBoardsResponse2, zGetBoardsResponse2,
zGetClientsData, zGetClientsData,
zGetClientsResponse2, zGetClientsResponse2,
zGetDealModuleAttributesData,
zGetDealModuleAttributesResponse2,
zGetDealProductsData, zGetDealProductsData,
zGetDealProductsResponse2, zGetDealProductsResponse2,
zGetDealsData, zGetDealsData,
@ -416,6 +424,8 @@ import {
zUpdateDealData, zUpdateDealData,
zUpdateDealGroupData, zUpdateDealGroupData,
zUpdateDealGroupResponse2, zUpdateDealGroupResponse2,
zUpdateDealModuleAttributesData,
zUpdateDealModuleAttributesResponse2,
zUpdateDealProductData, zUpdateDealProductData,
zUpdateDealProductResponse2, zUpdateDealProductResponse2,
zUpdateDealProductServiceData, zUpdateDealProductServiceData,
@ -616,6 +626,58 @@ export const getAttributeTypes = <ThrowOnError extends boolean = false>(
}); });
}; };
/**
* Get Deal Module Attributes
*/
export const getDealModuleAttributes = <ThrowOnError extends boolean = false>(
options: Options<GetDealModuleAttributesData, ThrowOnError>
) => {
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<UpdateDealModuleAttributesData, ThrowOnError>
) => {
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 * Get Boards
*/ */

View File

@ -1034,6 +1034,57 @@ export type DealGroupSchema = {
lexorank: string; 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 * DealProductAddKitRequest
*/ */
@ -1510,6 +1561,16 @@ export type GetClientsResponse = {
items: Array<ClientSchema>; items: Array<ClientSchema>;
}; };
/**
* GetDealModuleAttributesResponse
*/
export type GetDealModuleAttributesResponse = {
/**
* Attributes
*/
attributes: Array<DealModuleAttributeSchema>;
};
/** /**
* GetDealProductsResponse * GetDealProductsResponse
*/ */
@ -2501,6 +2562,42 @@ export type UpdateDealGroupSchema = {
statusId?: number | null; statusId?: number | null;
}; };
/**
* UpdateDealModuleAttributeSchema
*/
export type UpdateDealModuleAttributeSchema = {
/**
* Attributeid
*/
attributeId: number;
/**
* Value
*/
value: {
[key: string]: unknown;
} | null;
};
/**
* UpdateDealModuleAttributesRequest
*/
export type UpdateDealModuleAttributesRequest = {
/**
* Attributes
*/
attributes: Array<UpdateDealModuleAttributeSchema>;
};
/**
* UpdateDealModuleAttributesResponse
*/
export type UpdateDealModuleAttributesResponse = {
/**
* Message
*/
message: string;
};
/** /**
* UpdateDealProductRequest * UpdateDealProductRequest
*/ */
@ -3177,6 +3274,78 @@ export type GetAttributeTypesResponses = {
export type GetAttributeTypesResponse = export type GetAttributeTypesResponse =
GetAttributeTypesResponses[keyof GetAttributeTypesResponses]; 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 = { export type GetBoardsData = {
body?: never; body?: never;
path: { path: {

View File

@ -827,6 +827,23 @@ export const zDealAddKitResponse = z.object({
message: z.string(), 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 * DealProductAddKitRequest
*/ */
@ -1093,6 +1110,13 @@ export const zGetClientsResponse = z.object({
items: z.array(zClientSchema), items: z.array(zClientSchema),
}); });
/**
* GetDealModuleAttributesResponse
*/
export const zGetDealModuleAttributesResponse = z.object({
attributes: z.array(zDealModuleAttributeSchema),
});
/** /**
* GetDealProductsResponse * GetDealProductsResponse
*/ */
@ -1441,6 +1465,28 @@ export const zUpdateDealGroupResponse = z.object({
message: z.string(), 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 * UpdateDealProductSchema
*/ */
@ -1837,6 +1883,36 @@ export const zGetAttributeTypesData = z.object({
*/ */
export const zGetAttributeTypesResponse = zGetAllAttributeTypesResponse; 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({ export const zGetBoardsData = z.object({
body: z.optional(z.never()), body: z.optional(z.never()),
path: z.object({ path: z.object({

View File

@ -1,7 +1,10 @@
import { ProjectSchema } from "../../client"; import { ProjectSchema } from "@/lib/client";
import { ModuleNames } from "../modules.tsx"; 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; return project?.modules.findIndex(m => m.key === module.toString()) !== -1;
}; };

View File

@ -1,5 +1,9 @@
import { formatInTimeZone } from "date-fns-tz"; import { formatInTimeZone } from "date-fns-tz";
export const naiveDateTimeStringToUtc = (datetime: string) => {
return `${datetime.replace(" ", "T")}Z`;
};
export const utcDateToLocal = (datetime: string | Date) => { export const utcDateToLocal = (datetime: string | Date) => {
const userTZ = Intl.DateTimeFormat().resolvedOptions().timeZone; const userTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
const localTime = formatInTimeZone(datetime, userTZ, "yyyy-MM-dd HH:mm:ss"); const localTime = formatInTimeZone(datetime, userTZ, "yyyy-MM-dd HH:mm:ss");

View File

@ -6185,7 +6185,7 @@ __metadata:
clsx: "npm:^2.1.1" clsx: "npm:^2.1.1"
date-fns: "npm:^4.1.0" date-fns: "npm:^4.1.0"
date-fns-tz: "npm:^3.2.0" date-fns-tz: "npm:^3.2.0"
dayjs: "npm:^1.11.15" dayjs: "npm:^1.11.18"
eslint: "npm:^9.29.0" eslint: "npm:^9.29.0"
eslint-config-mantine: "npm:^4.0.3" eslint-config-mantine: "npm:^4.0.3"
eslint-plugin-eslint-comments: "npm:^3.2.0" eslint-plugin-eslint-comments: "npm:^3.2.0"
@ -6425,10 +6425,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"dayjs@npm:^1.11.15": "dayjs@npm:^1.11.18":
version: 1.11.15 version: 1.11.18
resolution: "dayjs@npm:1.11.15" resolution: "dayjs@npm:1.11.18"
checksum: 10c0/bb66cd5419fff017f3950b95fc27643cf4e0ce22a087cef1f67398f18126ed07bf36d6911f33b19029a1621c64090b8ecaef660477de7678287fe8c0f4e68d29 checksum: 10c0/83b67f5d977e2634edf4f5abdd91d9041a696943143638063016915d2cd8c7e57e0751e40379a07ebca8be7a48dd380bef8752d22a63670f2d15970e34f96d7a
languageName: node languageName: node
linkType: hard linkType: hard