feat: modules and module-editor pages

This commit is contained in:
2025-10-25 12:11:14 +04:00
parent 57a7ab0871
commit 2bdbebc453
40 changed files with 3485 additions and 38 deletions

View File

@ -16,6 +16,7 @@ import {
import { theme } from "@/theme";
import "@/app/global.css";
import { ContextMenuProvider } from "mantine-contextmenu";
import { DatesProvider } from "@mantine/dates";
import { ModalsProvider } from "@mantine/modals";
import { Notifications } from "@mantine/notifications";
import { ProjectsContextProvider } from "@/app/deals/contexts/ProjectsContext";
@ -71,31 +72,33 @@ export default function RootLayout({ children }: Props) {
<ModalsProvider
labels={{ confirm: "Да", cancel: "Нет" }}
modals={modals}>
<DrawersContextProvider>
<ProjectsContextProvider>
<AppShell
layout={"alt"}
withBorder={false}
navbar={{
width: 220,
breakpoint: "sm",
collapsed: {
desktop: false,
mobile: true,
},
}}>
<AppShellNavbarWrapper>
<Navbar />
</AppShellNavbarWrapper>
<AppShellMainWrapper>
{children}
</AppShellMainWrapper>
<AppShellFooterWrapper>
<Footer />
</AppShellFooterWrapper>
</AppShell>
</ProjectsContextProvider>
</DrawersContextProvider>
<DatesProvider settings={{ locale: "ru" }}>
<DrawersContextProvider>
<ProjectsContextProvider>
<AppShell
layout={"alt"}
withBorder={false}
navbar={{
width: 220,
breakpoint: "sm",
collapsed: {
desktop: false,
mobile: true,
},
}}>
<AppShellNavbarWrapper>
<Navbar />
</AppShellNavbarWrapper>
<AppShellMainWrapper>
{children}
</AppShellMainWrapper>
<AppShellFooterWrapper>
<Footer />
</AppShellFooterWrapper>
</AppShell>
</ProjectsContextProvider>
</DrawersContextProvider>
</DatesProvider>
</ModalsProvider>
</ReduxProvider>
<Notifications position="bottom-right" />

View File

@ -0,0 +1,12 @@
.container {
padding: var(--mantine-spacing-sm);
border: dashed 1px var(--mantine-color-default-border);
border-radius: var(--mantine-radius-lg);
}
.module-attr-container {
@mixin light {
background-color: var(--color-light-gray-blue);
border: 1px dashed var(--color-light-aqua);
}
}

View File

@ -0,0 +1,68 @@
import { FC, useCallback, useMemo } from "react";
import {
IconArrowRight,
IconEdit,
IconTrash,
IconX,
} from "@tabler/icons-react";
import { Center, Flex } from "@mantine/core";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import { AttributeSchema } from "@/lib/client";
type Props = {
attribute: AttributeSchema;
};
const AttributeTableActions: FC<Props> = ({ attribute }) => {
const { attributeActions, module } = useModuleEditorContext();
const usedAttributeIds = useMemo(
() => new Set(module?.attributes.map(a => a.id)),
[module]
);
const toggleAttributeInModule = useCallback(
(attribute: AttributeSchema) => {
if (usedAttributeIds.has(attribute.id)) {
attributeActions.removeAttributeFromModule(attribute);
} else {
attributeActions.addAttributeToModule(attribute);
}
},
[usedAttributeIds]
);
return (
<Center>
<Flex gap={"xs"}>
<ActionIconWithTip
onClick={() => attributeActions.onUpdate(attribute)}
disabled={attribute.isBuiltIn}
tipLabel={"Редактировать"}>
<IconEdit />
</ActionIconWithTip>
<ActionIconWithTip
onClick={() => attributeActions.onDelete(attribute)}
disabled={attribute.isBuiltIn}
tipLabel={"Удалить"}>
<IconTrash />
</ActionIconWithTip>
<ActionIconWithTip
onClick={() => toggleAttributeInModule(attribute)}
tipLabel={
usedAttributeIds.has(attribute.id)
? "Удалить из модуля"
: "Добавить в модуль"
}>
{usedAttributeIds.has(attribute.id) ? (
<IconX />
) : (
<IconArrowRight />
)}
</ActionIconWithTip>
</Flex>
</Center>
);
};
export default AttributeTableActions;

View File

@ -0,0 +1,21 @@
import useAttributeTypesList from "@/app/module-editor/[moduleId]/hooks/useAttributeTypesList";
import ObjectSelect, {
ObjectSelectProps,
} from "@/components/selects/ObjectSelect/ObjectSelect";
import { AttributeTypeSchema } from "@/lib/client";
type Props = Omit<ObjectSelectProps<AttributeTypeSchema>, "data">;
const AttributeTypeSelect = (props: Props) => {
const { attributeTypes } = useAttributeTypesList();
return (
<ObjectSelect
label={"Тип атрибута"}
data={attributeTypes}
{...props}
/>
);
};
export default AttributeTypeSelect;

View File

@ -0,0 +1,34 @@
import { Box, Center, Text } from "@mantine/core";
import useAttributesTableColumns from "@/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
const AttributesTable = () => {
const { attributes } = useModuleEditorContext();
const columns = useAttributesTableColumns();
if (attributes.length === 0) {
return (
<Center my={"md"}>
<Text>Нет атрибутов</Text>
</Center>
);
}
return (
<Box
h="100%"
style={{ overflow: "auto" }}>
<BaseTable
withTableBorder
columns={columns}
records={attributes}
verticalSpacing={"md"}
groups={undefined}
styles={{ table: { width: "100%" } }}
/>
</Box>
);
};
export default AttributesTable;

View File

@ -0,0 +1,37 @@
"use client";
import { useMemo } from "react";
import { DataTableColumn } from "mantine-datatable";
import { Center } from "@mantine/core";
import AttributeTableActions from "@/app/module-editor/[moduleId]/components/shared/AttributeTableActions/AttributeTableActions";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { AttributeSchema } from "@/lib/client";
const useAttributesTableColumns = () => {
const isMobile = useIsMobile();
return useMemo(
() =>
[
{
title: "Название",
accessor: "label",
},
{
title: "Тип",
accessor: "type.name",
},
{
accessor: "actions",
title: <Center>Действия</Center>,
width: "0%",
render: attribute => (
<AttributeTableActions attribute={attribute} />
),
},
] as DataTableColumn<AttributeSchema>[],
[isMobile]
);
};
export default useAttributesTableColumns;

View File

@ -0,0 +1,18 @@
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
const CreateAttributeButton = () => {
const {
attributeActions: { onCreate },
} = useModuleEditorContext();
return (
<InlineButton
onClick={onCreate}
w={"100%"}>
Создать атрибут
</InlineButton>
);
};
export default CreateAttributeButton;

View File

@ -0,0 +1,90 @@
import { Checkbox, NumberInput, TextInput } from "@mantine/core";
import { DatePickerInput, DateTimePicker } from "@mantine/dates";
import { UseFormReturnType } from "@mantine/form";
import { UpdateAttributeSchema } from "@/lib/client";
type Props = {
form: UseFormReturnType<Partial<UpdateAttributeSchema>>;
};
const DefaultAttributeValueInput = ({ form }: Props) => {
const type = form.values.type?.type;
const label = "Значение по умолчанию";
const inputName = "defaultValue";
const value = form.getValues().defaultValue?.value;
if (type === "bool") {
return (
<Checkbox
label={label}
{...form.getInputProps(inputName, { type: "checkbox" })}
checked={value as boolean}
onChange={e =>
form.setFieldValue("defaultValue", {
value: e.currentTarget.checked,
})
}
/>
);
} else if (type === "date") {
return (
<DatePickerInput
label={label}
{...form.getInputProps(inputName)}
value={
form.values.defaultValue?.value
? new Date(String(form.values.defaultValue.value))
: null
}
onChange={value =>
form.setFieldValue("defaultValue", { value })
}
clearable
locale={"ru"}
valueFormat={"DD.MM.YYYY"}
/>
);
} else if (type === "datetime") {
return (
<DateTimePicker
label={label}
{...form.getInputProps(inputName)}
value={
form.values.defaultValue?.value
? new Date(String(form.values.defaultValue.value))
: null
}
onChange={value =>
form.setFieldValue("defaultValue", { value })
}
clearable
locale={"ru"}
valueFormat={"DD.MM.YYYY HH:mm"}
/>
);
} else if (type === "str") {
return (
<TextInput
label={label}
{...form.getInputProps(inputName)}
/>
);
} else if (type === "int" || type === "float") {
return (
<NumberInput
allowDecimal={type === "float"}
label={label}
{...form.getInputProps(inputName)}
value={Number(form.values.defaultValue?.value)}
onChange={value =>
form.setFieldValue("defaultValue", { value: Number(value) })
}
/>
);
}
return <></>;
};
export default DefaultAttributeValueInput;

View File

@ -0,0 +1,68 @@
import { FC } from "react";
import { IconEdit, IconX } from "@tabler/icons-react";
import { Card, Group, Stack, Text, Title } from "@mantine/core";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import styles from "@/app/module-editor/[moduleId]/ModuleEditor.module.css";
import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import { ModuleAttributeSchema } from "@/lib/client";
type Props = {
attribute: ModuleAttributeSchema;
};
const ModuleAttribute: FC<Props> = ({ attribute }) => {
const {
attributeActions: { removeAttributeFromModule, onEditAttributeLabel },
} = useModuleEditorContext();
const getAttrLabelText = () => {
const order = 6;
if (attribute.label === attribute.originalLabel) {
return <Title order={order}>Название: {attribute.label}</Title>;
}
return (
<Group gap={"xs"}>
<Title order={order}>Название: {attribute.label}</Title>{" "}
<Text>(Ориг. {attribute.originalLabel})</Text>
</Group>
);
};
return (
<Card
flex={1}
className={styles["module-attr-container"]}>
<Group
w={"100%"}
h={"100%"}
justify={"space-between"}
align={"center"}>
<Stack gap={7}>
<>{getAttrLabelText()}</>
<Text>Тип: {attribute.type.name}</Text>
</Stack>
<Group
justify={"end"}
wrap={"nowrap"}
w={"100%"}
gap={"xs"}>
<ActionIconWithTip
tipLabel={
"Редактировать название (только для данного модуля)"
}
onClick={() => onEditAttributeLabel(attribute)}>
<IconEdit />
</ActionIconWithTip>
<ActionIconWithTip
tipLabel={"Удалить из модуля"}
onClick={() => removeAttributeFromModule(attribute)}>
<IconX />
</ActionIconWithTip>
</Group>
</Group>
</Card>
);
};
export default ModuleAttribute;

View File

@ -0,0 +1,41 @@
import React, { ReactNode } from "react";
import { Flex } from "@mantine/core";
import ModuleAttribute from "@/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import FormFlexRow from "@/components/ui/FormFlexRow/FormFlexRow";
const ModuleAttributesEditor = () => {
const { module } = useModuleEditorContext();
const getAttributesRows = () => {
if (!module?.attributes) return [];
const rows: ReactNode[] = [];
for (let i = 0; i < module.attributes.length; i += 2) {
const rightIdx = i + 1;
rows.push(
<FormFlexRow key={i}>
<ModuleAttribute attribute={module.attributes[i]} />
{rightIdx < module.attributes.length && (
<ModuleAttribute
attribute={module.attributes[rightIdx]}
/>
)}
</FormFlexRow>
);
}
return rows;
};
return (
<Flex
gap={"xs"}
direction={"column"}>
{getAttributesRows()}
</Flex>
);
};
export default ModuleAttributesEditor;

View File

@ -0,0 +1,52 @@
import { Button, Flex, Group, Textarea, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import { ModuleInfo } from "../../../hooks/useModulesActions";
const ModuleCommonInfoEditor = () => {
const {
module,
moduleActions: { updateCommonInfo },
} = useModuleEditorContext();
const form = useForm<ModuleInfo>({
initialValues: module ?? {
label: "",
description: "",
},
validate: {
label: label => !label && "Введите название модуля",
},
});
const onSubmit = (values: ModuleInfo) => {
updateCommonInfo(values, form.resetDirty);
};
return (
<form onSubmit={form.onSubmit(onSubmit)}>
<Flex
gap={"xs"}
direction={"column"}>
<TextInput
label={"Название"}
{...form.getInputProps("label")}
/>
<Textarea
label={"Описание"}
{...form.getInputProps("description")}
/>
<Group>
<Button
variant={"default"}
disabled={!form.isDirty()}
type={"submit"}>
Сохранить
</Button>
</Group>
</Flex>
</form>
);
};
export default ModuleCommonInfoEditor;

View File

@ -0,0 +1,59 @@
"use client";
import { Box, Fieldset, Flex, Stack } from "@mantine/core";
import AttributesTable from "@/app/module-editor/[moduleId]/components/shared/AttributesTable/AttributesTable";
import CreateAttributeButton from "@/app/module-editor/[moduleId]/components/shared/CreateAttributeButton/CreateAttributeButton";
import ModuleAttributesEditor from "@/app/module-editor/[moduleId]/components/shared/ModuleAttributesEditor/ModuleAttributesEditor";
import ModuleCommonInfoEditor from "@/app/module-editor/[moduleId]/components/shared/ModuleCommonInfoEditor/ModuleCommonInfoEditor";
import { useModuleEditorContext } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
const PageBody = () => {
const { module } = useModuleEditorContext();
return (
<Stack h={"100%"}>
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
<Flex
w={"100%"}
h={"100%"}
flex={3}
style={{ overflow: "auto" }}
gap={"md"}>
<Fieldset
flex={1}
legend={"Атрибуты"}>
<Flex
direction={"column"}
h={"100%"}
gap={"xs"}>
<Box style={{ flex: 1, minHeight: 0 }}>
<AttributesTable />
</Box>
<CreateAttributeButton />
</Flex>
</Fieldset>
<Flex
gap={"xs"}
flex={2}
direction={"column"}>
<Fieldset
flex={1}
legend={"Общие данные модуля"}>
{module && <ModuleCommonInfoEditor />}
</Fieldset>
<Fieldset
flex={3}
legend={"Атрибуты модуля"}>
<ModuleAttributesEditor />
</Fieldset>
</Flex>
</Flex>
</PageBlock>
</Stack>
);
};
export default PageBody;

View File

@ -0,0 +1,71 @@
"use client";
import { useEffect } from "react";
import { redirect } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import useAttributesActions, {
AttributesActions,
} from "@/app/module-editor/[moduleId]/hooks/useAttributesActions";
import useAttributesList from "@/app/module-editor/[moduleId]/hooks/useAttributesList";
import useModulesActions, {
ModulesActions,
} from "@/app/module-editor/[moduleId]/hooks/useModulesActions";
import { AttributeSchema, ModuleWithAttributesSchema } from "@/lib/client";
import { getModuleWithAttributesOptions } from "@/lib/client/@tanstack/react-query.gen";
import makeContext from "@/lib/contextFactory/contextFactory";
type ModuleEditorContextState = {
module?: ModuleWithAttributesSchema;
refetchModule: () => void;
attributes: AttributeSchema[];
refetchAttributes: () => void;
attributeActions: AttributesActions;
moduleActions: ModulesActions;
};
type Props = {
moduleId: number;
};
const useModuleEditorContextState = ({
moduleId,
}: Props): ModuleEditorContextState => {
const { data, refetch: refetchModule } = useQuery(
getModuleWithAttributesOptions({ path: { pk: moduleId } })
);
const module = data?.entity;
const { attributes, refetch: refetchAttributes } = useAttributesList();
useEffect(() => {
if (module?.isBuiltIn) {
redirect("modules");
}
}, [module]);
const attributeActions = useAttributesActions({
module,
refetchModule,
refetchAttributes,
});
const moduleActions = useModulesActions({
module,
refetchModule,
});
return {
module,
refetchModule,
attributes,
refetchAttributes,
attributeActions,
moduleActions,
};
};
export const [ModuleEditorContextProvider, useModuleEditorContext] =
makeContext<ModuleEditorContextState, Props>(
useModuleEditorContextState,
"ModuleEditor"
);

View File

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

View File

@ -0,0 +1,214 @@
import React from "react";
import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import {
AttributeSchema,
HttpValidationError,
ModuleSchemaOutput,
} from "@/lib/client";
import {
addAttributeToModuleMutation,
createAttributeMutation,
deleteAttributeMutation,
removeAttributeFromModuleMutation,
updateAttributeLabelMutation,
updateAttributeMutation,
} from "@/lib/client/@tanstack/react-query.gen";
import { notifications } from "@/lib/notifications";
export type AttributesActions = {
addAttributeToModule: (attribute: AttributeSchema) => void;
removeAttributeFromModule: (attribute: AttributeSchema) => void;
onEditAttributeLabel: (attribute: AttributeSchema) => void;
onCreate: () => void;
onUpdate: (attribute: AttributeSchema) => void;
onDelete: (attribute: AttributeSchema) => void;
};
type Props = {
module?: ModuleSchemaOutput;
refetchModule: () => void;
refetchAttributes: () => void;
};
const useAttributesActions = ({
module,
refetchModule,
refetchAttributes,
}: Props): AttributesActions => {
const onError = (error: AxiosError<HttpValidationError>) => {
console.error(error);
notifications.error({
message: error.response?.data?.detail as string | undefined,
});
};
const addAttrToModuleMutation = useMutation({
...addAttributeToModuleMutation(),
onError,
});
const removeAttrFromModuleMutation = useMutation({
...removeAttributeFromModuleMutation(),
onError,
});
const toggleAttributeInModule = (
attribute: AttributeSchema,
isAdding: boolean
) => {
if (!module) return;
const mutation = isAdding
? addAttrToModuleMutation
: removeAttrFromModuleMutation;
mutation.mutate(
{
body: {
moduleId: module.id,
attributeId: attribute.id,
},
},
{
onSuccess: ({ message }) => {
notifications.success({ message });
refetchModule();
refetchAttributes();
},
}
);
};
const addAttributeToModule = (attribute: AttributeSchema) =>
toggleAttributeInModule(attribute, true);
const removeAttributeFromModule = (attribute: AttributeSchema) => {
modals.openConfirmModal({
title: "Удаление атрибута из модуля",
children: (
<Text>
Вы уверены, что хотите удалить атрибут "{attribute.label}"
из модуля?
</Text>
),
confirmProps: { color: "red" },
onConfirm: () => toggleAttributeInModule(attribute, false),
});
};
const updateAttributeLabel = useMutation({
...updateAttributeLabelMutation(),
onError,
onSuccess: refetchModule,
});
const onEditAttributeLabel = (attribute: AttributeSchema) => {
if (!module) return;
modals.openContextModal({
modal: "enterNameModal",
title: "Редактирование имени атрибута в модуле",
withCloseButton: true,
innerProps: {
onChange: values =>
updateAttributeLabel.mutate({
body: {
moduleId: module.id,
attributeId: attribute.id,
label: values.name,
},
}),
value: { name: attribute.label },
},
});
};
const createAttribute = useMutation({
...createAttributeMutation(),
onError,
onSuccess: refetchAttributes,
});
const onCreate = () => {
modals.openContextModal({
modal: "attributeEditorModal",
title: "Создание атрибута",
withCloseButton: true,
innerProps: {
onCreate: values =>
createAttribute.mutate({
body: {
entity: values,
},
}),
isEditing: false,
},
});
};
const updateAttribute = useMutation({
...updateAttributeMutation(),
onError,
onSuccess: () => {
refetchAttributes();
refetchModule();
},
});
const onUpdate = (attribute: AttributeSchema) => {
modals.openContextModal({
modal: "attributeEditorModal",
title: "Редактирование атрибута",
withCloseButton: true,
innerProps: {
onChange: values =>
updateAttribute.mutate({
path: {
pk: attribute.id,
},
body: {
entity: values,
},
}),
entity: attribute,
isEditing: true,
},
});
};
const deleteAttribute = useMutation({
...deleteAttributeMutation(),
onError,
onSuccess: () => {
refetchModule();
refetchAttributes();
},
});
const onDelete = (attribute: AttributeSchema) => {
modals.openConfirmModal({
title: "Удаление атрибута",
children: (
<Text>
Вы уверены, что хотите удалить атрибут "{attribute.label}"?
</Text>
),
confirmProps: { color: "red" },
onConfirm: () =>
deleteAttribute.mutate({ path: { pk: attribute.id } }),
});
};
return {
addAttributeToModule,
removeAttributeFromModule,
onEditAttributeLabel,
onCreate,
onUpdate,
onDelete,
};
};
export default useAttributesActions;

View File

@ -0,0 +1,32 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { AttributeSchema } from "@/lib/client";
import {
getAttributesOptions,
getProjectsQueryKey,
} from "@/lib/client/@tanstack/react-query.gen";
const useAttributesList = () => {
const queryClient = useQueryClient();
const { data, refetch } = useQuery(getAttributesOptions());
const queryKey = getProjectsQueryKey();
const setAttributes = (attributes: AttributeSchema[]) => {
queryClient.setQueryData(
queryKey,
(old: { items: AttributeSchema[] }) => ({
...old,
items: attributes,
})
);
};
return {
attributes: data?.items ?? [],
setAttributes,
refetch,
queryKey,
};
};
export default useAttributesList;

View File

@ -0,0 +1,51 @@
import { useMutation } from "@tanstack/react-query";
import { ModuleSchemaOutput } from "@/lib/client";
import { updateModuleMutation } from "@/lib/client/@tanstack/react-query.gen";
export type ModuleInfo = {
label: string;
description: string | null;
};
type Props = {
module?: ModuleSchemaOutput;
refetchModule: () => void;
};
export type ModulesActions = {
updateCommonInfo: (values: ModuleInfo, onSuccess?: () => void) => void;
};
const useModulesActions = ({
module,
refetchModule,
}: Props): ModulesActions => {
const updateCommonInfoMutation = useMutation(updateModuleMutation());
const updateCommonInfo = (values: ModuleInfo, onSuccess?: () => void) => {
if (!module) return;
updateCommonInfoMutation.mutate(
{
path: {
pk: module.id,
},
body: {
entity: values,
},
},
{
onSuccess: () => {
refetchModule();
onSuccess && onSuccess();
},
}
);
};
return {
updateCommonInfo,
};
};
export default useModulesActions;

View File

@ -0,0 +1,138 @@
"use client";
import { useEffect, useState } from "react";
import { Checkbox, Stack, Textarea, TextInput } from "@mantine/core";
import { useForm } from "@mantine/form";
import { ContextModalProps } from "@mantine/modals";
import AttributeTypeSelect from "@/app/module-editor/[moduleId]/components/shared/AttributeTypeSelect/AttributeTypeSelect";
import DefaultAttributeValueInput from "@/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput";
import {
AttributeSchema,
CreateAttributeSchema,
UpdateAttributeSchema,
} from "@/lib/client";
import BaseFormModal, {
CreateEditFormProps,
} from "@/modals/base/BaseFormModal/BaseFormModal";
type Props = CreateEditFormProps<
AttributeSchema,
CreateAttributeSchema,
UpdateAttributeSchema
>;
const AttributeEditorModal = ({
context,
id,
innerProps,
}: ContextModalProps<Props>) => {
const [isInitial, setIsInitial] = useState(true);
const [isNullableInputShown, setIsNullableInputShown] = useState(true);
const [copyTypeId, setCopyTypeId] = useState<number>();
const form = useForm<Partial<UpdateAttributeSchema>>({
initialValues: innerProps.isEditing
? innerProps.entity
: ({
label: "",
name: "",
typeId: undefined,
type: undefined,
isApplicableToGroup: false,
isShownOnDashboard: false,
isHighlightIfExpired: false,
isNullable: false,
defaultValue: null,
description: "",
} as Partial<CreateAttributeSchema>),
validate: {
label: label => !label?.trim() && "Название не заполнено",
type: type => !type && "Тип атрибута не выбран",
defaultValue: (defaultValue, values) => {
if (defaultValue === null && !values.isNullable) {
return "Укажите значение по умолчанию или разрешите пустое значение.";
}
return false;
},
},
});
useEffect(() => {
const type = form.values.type?.type;
setIsNullableInputShown(type !== "bool");
if (!isInitial) {
if (type === "bool") {
form.setFieldValue("isNullable", false);
form.setFieldValue("defaultValue", { value: false });
} else {
form.setFieldValue("defaultValue", null);
}
}
setIsInitial(false);
setCopyTypeId(form.values.type?.id);
}, [form.values.type?.id]);
return (
<BaseFormModal
{...innerProps}
closeOnSubmit
form={form}
onClose={() => context.closeContextModal(id)}>
<Stack gap={"md"}>
<TextInput
withAsterisk
label={"Название"}
{...form.getInputProps("label")}
/>
<AttributeTypeSelect
withAsterisk
disabled={innerProps.isEditing}
{...form.getInputProps("type")}
onChange={type => {
form.setFieldValue("type", type);
form.setFieldValue("typeId", type.id);
}}
/>
<Checkbox
label={"Значение синхронизировано в группе"}
{...form.getInputProps("isApplicableToGroup", {
type: "checkbox",
})}
/>
<Checkbox
label={"Значение выводится на дашборде"}
{...form.getInputProps("isShownOnDashboard", {
type: "checkbox",
})}
/>
{(form.values.type?.type === "datetime" ||
form.values.type?.type === "date") && (
<Checkbox
label={"Подсветка, если просрочено"}
{...form.getInputProps("isHighlightIfExpired", {
type: "checkbox",
})}
/>
)}
{isNullableInputShown && (
<Checkbox
label={"Может быть пустым"}
{...form.getInputProps("isNullable", {
type: "checkbox",
})}
/>
)}
{form.values.type && copyTypeId === form.values.type.id && (
<DefaultAttributeValueInput form={form} />
)}
<Textarea
label={"Описание"}
{...form.getInputProps("description")}
/>
</Stack>
</BaseFormModal>
);
};
export default AttributeEditorModal;

View File

@ -0,0 +1,39 @@
import { Suspense } from "react";
import { NextResponse } from "next/server";
import { Center, Loader } from "@mantine/core";
import PageBody from "@/app/module-editor/[moduleId]/components/shared/PageBody/PageBody";
import { ModuleEditorContextProvider } from "@/app/module-editor/[moduleId]/contexts/ModuleEditorContext";
import PageContainer from "@/components/layout/PageContainer/PageContainer";
type Params = {
params: Promise<{
moduleId: string;
}>;
};
export default async function ModuleEditorPage({ params }: Params) {
const { moduleId } = await params;
const id = Number(moduleId);
if (isNaN(id)) {
return NextResponse.json(
{ error: "Некорректный ID модуля. ID должен быть числом." },
{ status: 400 }
);
}
return (
<Suspense
fallback={
<Center h="50vh">
<Loader size="lg" />
</Center>
}>
<PageContainer>
<ModuleEditorContextProvider moduleId={id}>
<PageBody />
</ModuleEditorContextProvider>
</PageContainer>
</Suspense>
);
}

View File

@ -0,0 +1,38 @@
"use client";
import { FC } from "react";
import { Center, Text } from "@mantine/core";
import useAttributesInnerTableColumns from "@/app/modules/hooks/useAttributesInnerTableColumns";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import { ModuleAttributeSchema } from "@/lib/client";
type Props = {
attributes: ModuleAttributeSchema[];
moduleId: number;
};
const InnerAttributesTable: FC<Props> = ({ attributes, moduleId }) => {
const innerColumns = useAttributesInnerTableColumns();
if (attributes.length === 0) {
return (
<Center my={"md"}>
<Text>В модуле нет атрибутов</Text>
</Center>
);
}
return (
<BaseTable
key={moduleId}
withTableBorder
columns={innerColumns}
records={attributes}
verticalSpacing={"md"}
groups={undefined}
styles={{ table: { width: "100%" } }}
/>
);
};
export default InnerAttributesTable;

View File

@ -0,0 +1,24 @@
"use client";
import { FC } from "react";
import { Group } from "@mantine/core";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import useIsMobile from "@/hooks/utils/useIsMobile";
const ModulesHeader: FC = () => {
const isMobile = useIsMobile();
return (
<Group
wrap={"nowrap"}
justify={"space-between"}
mt={isMobile ? "xs" : ""}
mx={isMobile ? "xs" : ""}>
<InlineButton w={isMobile ? "100%" : ""}>
Создать модуль
</InlineButton>
</Group>
);
};
export default ModulesHeader;

View File

@ -0,0 +1,46 @@
"use client";
import { FC, useState } from "react";
import { useModulesContext } from "@/app/modules/contexts/ModulesContext";
import useModulesTableColumns from "@/app/modules/hooks/useModulesTableColumns";
import BaseTable from "@/components/ui/BaseTable/BaseTable";
import useIsMobile from "@/hooks/utils/useIsMobile";
import InnerAttributesTable from "@/app/modules/components/InnerAttributesTable";
const ModulesTable: FC = () => {
const isMobile = useIsMobile();
const { modules } = useModulesContext();
const [expandedModuleIds, setExpandedModuleIds] = useState<number[]>([]);
const outerColumns = useModulesTableColumns({
expandedModuleIds,
setExpandedModuleIds,
});
return (
<BaseTable
columns={outerColumns}
groups={undefined}
records={modules}
withTableBorder
verticalSpacing={"md"}
rowExpansion={{
allowMultiple: true,
expanded: {
recordIds: expandedModuleIds,
onRecordIdsChange: setExpandedModuleIds,
},
content: ({ record }) => (
<InnerAttributesTable
attributes={record.attributes}
moduleId={record.id}
/>
),
}}
mx={isMobile ? "xs" : 0}
/>
);
};
export default ModulesTable;

View File

@ -0,0 +1,26 @@
"use client";
import ModulesTable from "@/app/modules/components/ModulesTable";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
import ModulesHeader from "./ModulesHeader";
const PageBody = () => (
<PageBlock
style={{ flex: 1, minHeight: 0 }}
fullScreenMobile>
<div
style={{
height: "100%",
display: "flex",
flexDirection: "column",
gap: "var(--mantine-spacing-md)",
}}>
<ModulesHeader />
<div style={{ flex: 1, overflow: "auto" }}>
<ModulesTable />
</div>
</div>
</PageBlock>
);
export default PageBody;

View File

@ -0,0 +1,22 @@
"use client";
import useModulesWithAttrsList from "@/app/modules/hooks/useModulesWithAttrsList";
import { ModuleWithAttributesSchema } from "@/lib/client";
import makeContext from "@/lib/contextFactory/contextFactory";
type ModulesContextState = {
modules: ModuleWithAttributesSchema[];
refetchModules: () => void;
};
const useModulesContextState = (): ModulesContextState => {
const { modules, refetch } = useModulesWithAttrsList();
return {
modules,
refetchModules: refetch,
};
};
export const [ModulesContextProvider, useModulesContext] =
makeContext<ModulesContextState>(useModulesContextState, "Modules");

View File

@ -0,0 +1,82 @@
"use client";
import { useMemo } from "react";
import { IconCheck, IconX } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { Box } from "@mantine/core";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { ModuleAttributeSchema } from "@/lib/client";
import {
utcDateTimeToLocalString,
utcDateToLocalString,
} from "@/utils/datetime";
const useAttributesInnerTableColumns = () => {
const isMobile = useIsMobile();
const renderCheck = (value: boolean) => (value ? <IconCheck /> : <IconX />);
return useMemo(
() =>
[
{
title: "Название атрибута",
accessor: "label",
},
{
title: "Тип",
accessor: "type.name",
},
{
title: "Значение по умолчанию",
accessor: "defaultValue",
render: attr => {
if (!attr.defaultValue) return <>-</>;
const value = attr.defaultValue.value;
if (value === null) return <>-</>;
const type = attr.type.type;
if (type === "datetime") {
return utcDateTimeToLocalString(value as string);
}
if (type === "date") {
return utcDateToLocalString(value as string);
}
if (type === "bool") {
return value ? <IconCheck /> : <IconX />;
}
return <>{value}</>;
},
},
{
title: "Синхронизировано в группе",
accessor: "isApplicableToGroup",
render: attr => renderCheck(attr.isApplicableToGroup),
},
{
title: "Вывод на дашборде",
accessor: "isShownOnDashboard",
render: attr => renderCheck(attr.isShownOnDashboard),
},
{
title: "Подсветка, если просрочен",
accessor: "isHighlightIfExpired",
render: attr => renderCheck(attr.isHighlightIfExpired),
},
{
title: "Может быть пустым",
accessor: "isNullable",
render: attr => renderCheck(attr.isNullable),
},
{
title: "Описаниие",
accessor: "description",
render: attr => <Box>{attr.description}</Box>,
},
] as DataTableColumn<ModuleAttributeSchema>[],
[isMobile]
);
};
export default useAttributesInnerTableColumns;

View File

@ -0,0 +1,57 @@
import React from "react";
import { redirect } from "next/navigation";
import { useMutation } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { Text } from "@mantine/core";
import { modals } from "@mantine/modals";
import { HttpValidationError, ModuleSchemaOutput } from "@/lib/client";
import { deleteModuleMutation } from "@/lib/client/@tanstack/react-query.gen";
import { notifications } from "@/lib/notifications";
export type ModulesActions = {
onUpdate: (module: ModuleSchemaOutput) => void;
onDelete: (module: ModuleSchemaOutput) => void;
};
type Props = {
refetchModules: () => void;
};
const useModulesActions = ({ refetchModules }: Props): ModulesActions => {
const onUpdate = (module: ModuleSchemaOutput) => {
redirect(`/module-editor/${module.id}`);
};
const onError = (error: AxiosError<HttpValidationError>, _: any) => {
console.error(error);
notifications.error({
message: error.response?.data?.detail as string | undefined,
});
};
const deleteMutation = useMutation({
...deleteModuleMutation(),
onError,
onSuccess: refetchModules,
});
const onDelete = (module: ModuleSchemaOutput) => {
modals.openConfirmModal({
title: "Удаление услуги из сделки",
children: (
<Text>
Вы уверены, что хотите удалить модуль "{module.label}"?
</Text>
),
confirmProps: { color: "red" },
onConfirm: () => deleteMutation.mutate({ path: { pk: module.id } }),
});
};
return {
onUpdate,
onDelete,
};
};
export default useModulesActions;

View File

@ -0,0 +1,112 @@
import { useMemo } from "react";
import {
IconChevronDown,
IconChevronsDown,
IconChevronsRight,
IconChevronsUp,
IconChevronUp,
} from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable";
import { Box, Group, Text, Tooltip } from "@mantine/core";
import useModulesActions from "@/app/modules/hooks/useModulesTableActions";
import UpdateDeleteTableActions from "@/components/ui/BaseTable/components/UpdateDeleteTableActions";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { ModuleWithAttributesSchema } from "@/lib/client";
import { useModulesContext } from "../contexts/ModulesContext";
type Props = {
expandedModuleIds: number[];
setExpandedModuleIds: (ids: number[]) => void;
};
const useModulesTableColumns = ({
expandedModuleIds,
setExpandedModuleIds,
}: Props) => {
const isMobile = useIsMobile();
const { modules, refetchModules } = useModulesContext();
const { onUpdate, onDelete } = useModulesActions({ refetchModules });
const onExpandAllClick = () => {
if (expandedModuleIds.length !== modules.length) {
setExpandedModuleIds(modules.map(c => c.id));
return;
}
setExpandedModuleIds([]);
};
const getExpandAllIcon = () => {
if (expandedModuleIds.length === modules.length)
return <IconChevronsUp />;
if (expandedModuleIds.length === 0) return <IconChevronsDown />;
return <IconChevronsRight />;
};
return useMemo(
() =>
[
{
accessor: "",
title: (
<Group>
<Box
style={{ cursor: "pointer" }}
onClick={onExpandAllClick}>
{getExpandAllIcon()}
</Box>
Модуль
</Group>
),
render: ({ id, label }) => (
<Group
key={id}
wrap={"nowrap"}>
{expandedModuleIds.includes(id) ? (
<IconChevronUp />
) : (
<IconChevronDown />
)}
<Text>{label}</Text>
</Group>
),
},
{
title: "Описание",
accessor: "description",
},
{
title: "Зависит от модулей",
accessor: "dependsOn",
render: module => (
<Text>
{module.dependsOn?.map(m => m.label).join(", ")}
</Text>
),
},
{
accessor: "actions",
title: isMobile ? "" : "Действия",
sortable: false,
textAlign: "center",
width: "0%",
render: module => (
<Tooltip
label={
module.isBuiltIn
? "Нельзя изменять встроенные модули"
: null
}>
<UpdateDeleteTableActions
onDelete={() => onDelete(module)}
onChange={() => onUpdate(module)}
disabled={module.isBuiltIn}
/>
</Tooltip>
),
},
] as DataTableColumn<ModuleWithAttributesSchema>[],
[expandedModuleIds, modules, isMobile]
);
};
export default useModulesTableColumns;

View File

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

22
src/app/modules/page.tsx Normal file
View File

@ -0,0 +1,22 @@
import { Suspense } from "react";
import { Center, Loader } from "@mantine/core";
import PageBody from "@/app/modules/components/PageBody";
import { ModulesContextProvider } from "@/app/modules/contexts/ModulesContext";
import PageContainer from "@/components/layout/PageContainer/PageContainer";
export default async function ModulesPage() {
return (
<Suspense
fallback={
<Center h="50vh">
<Loader size="lg" />
</Center>
}>
<PageContainer>
<ModulesContextProvider>
<PageBody />
</ModulesContextProvider>
</PageContainer>
</Suspense>
);
}

View File

@ -1,4 +1,5 @@
import {
IconApps,
IconBox,
IconColumns,
IconFileBarcode,
@ -15,6 +16,12 @@ const linksData: LinkData[] = [
href: "/deals",
moduleName: undefined,
},
{
icon: IconApps,
label: "Модули",
href: "/modules",
moduleName: undefined,
},
{
icon: IconUsers,
label: "Клиенты",

View File

@ -19,6 +19,7 @@ type Props = {
otherActions?: ActionData[];
dotsForMobile?: boolean;
style?: CSSProperties;
disabled?: boolean;
};
const UpdateDeleteTableActions: FC<Props> = ({
@ -27,6 +28,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
otherActions,
style,
dotsForMobile = false,
disabled = false,
}) => {
const isMobile = useIsMobile();
@ -45,6 +47,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
onClick={onChange}
icon={<IconEdit />}
label={"Редактировать"}
disabled={disabled}
/>
{otherActions?.map(
action =>
@ -54,6 +57,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
icon={action.icon}
label={action.label}
key={action.label}
disabled={disabled}
/>
)
)}
@ -61,6 +65,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
onClick={onDelete}
icon={<IconTrash />}
label={"Удалить"}
disabled={disabled}
/>
</Menu.Dropdown>
</Menu>
@ -76,6 +81,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
e.stopPropagation();
onChange();
}}
disabled={disabled}
tipLabel={"Редактировать"}>
<IconEdit />
</ActionIconWithTip>
@ -87,6 +93,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
e.stopPropagation();
action.onClick();
}}
disabled={disabled}
key={action.label}
tipLabel={action.label}>
{action.icon}
@ -99,6 +106,7 @@ const UpdateDeleteTableActions: FC<Props> = ({
e.stopPropagation();
onDelete();
}}
disabled={disabled}
tipLabel={"Удалить"}>
<IconTrash />
</ActionIconWithTip>

View File

@ -5,16 +5,25 @@ type Props = {
onClick: MouseEventHandler<HTMLButtonElement>;
icon: ReactNode;
label: string;
disabled?: boolean;
};
const DropdownMenuItem: FC<Props> = ({ icon, label, onClick }) => {
const DropdownMenuItem: FC<Props> = ({
icon,
label,
onClick,
disabled = false,
}) => {
const onClickWrapper: MouseEventHandler<HTMLButtonElement> = e => {
e.stopPropagation();
if (disabled) return;
onClick(e);
};
return (
<Menu.Item onClick={onClickWrapper}>
<Menu.Item
onClick={onClickWrapper}
disabled={disabled}>
<Group wrap={"nowrap"}>
{icon}
<Text>{label}</Text>

View File

@ -10,8 +10,10 @@ import {
import type { AxiosError } from "axios";
import { client as _heyApiClient } from "../client.gen";
import {
addAttributeToModule,
addKitToDeal,
addKitToDealProduct,
createAttribute,
createBarcodeTemplate,
createBoard,
createClient,
@ -28,6 +30,7 @@ import {
createServiceCategory,
createServicesKit,
createStatus,
deleteAttribute,
deleteBarcodeTemplate,
deleteBoard,
deleteClient,
@ -38,6 +41,7 @@ import {
deleteDealService,
deleteDealTag,
deleteMarketplace,
deleteModule,
deleteProduct,
deleteProductBarcodeImage,
deleteProject,
@ -46,6 +50,8 @@ import {
deleteServicesKit,
deleteStatus,
duplicateProductServices,
getAttributes,
getAttributeTypes,
getBarcodeTemplateAttributes,
getBarcodeTemplates,
getBarcodeTemplateSizes,
@ -59,6 +65,8 @@ import {
getDealTags,
getMarketplaces,
getModules,
getModulesWithAttributes,
getModuleWithAttributes,
getProductBarcodePdf,
getProducts,
getProjects,
@ -67,7 +75,10 @@ import {
getServicesKits,
getStatuses,
getStatusHistory,
removeAttributeFromModule,
switchDealTag,
updateAttribute,
updateAttributeLabel,
updateBarcodeTemplate,
updateBoard,
updateClient,
@ -79,6 +90,7 @@ import {
updateDealsInGroup,
updateDealTag,
updateMarketplace,
updateModule,
updateProduct,
updateProject,
updateService,
@ -90,12 +102,18 @@ import {
type Options,
} from "../sdk.gen";
import type {
AddAttributeToModuleData,
AddAttributeToModuleError,
AddAttributeToModuleResponse,
AddKitToDealData,
AddKitToDealError,
AddKitToDealProductData,
AddKitToDealProductError,
AddKitToDealProductResponse,
AddKitToDealResponse,
CreateAttributeData,
CreateAttributeError,
CreateAttributeResponse2,
CreateBarcodeTemplateData,
CreateBarcodeTemplateError,
CreateBarcodeTemplateResponse2,
@ -144,6 +162,9 @@ import type {
CreateStatusData,
CreateStatusError,
CreateStatusResponse2,
DeleteAttributeData,
DeleteAttributeError,
DeleteAttributeResponse2,
DeleteBarcodeTemplateData,
DeleteBarcodeTemplateError,
DeleteBarcodeTemplateResponse2,
@ -174,6 +195,9 @@ import type {
DeleteMarketplaceData,
DeleteMarketplaceError,
DeleteMarketplaceResponse2,
DeleteModuleData,
DeleteModuleError,
DeleteModuleResponse2,
DeleteProductBarcodeImageData,
DeleteProductBarcodeImageError,
DeleteProductBarcodeImageResponse,
@ -198,6 +222,8 @@ import type {
DuplicateProductServicesData,
DuplicateProductServicesError,
DuplicateProductServicesResponse,
GetAttributesData,
GetAttributeTypesData,
GetBarcodeTemplateAttributesData,
GetBarcodeTemplatesData,
GetBarcodeTemplateSizesData,
@ -214,6 +240,8 @@ import type {
GetDealTagsData,
GetMarketplacesData,
GetModulesData,
GetModulesWithAttributesData,
GetModuleWithAttributesData,
GetProductBarcodePdfData,
GetProductBarcodePdfError,
GetProductBarcodePdfResponse2,
@ -226,9 +254,18 @@ import type {
GetServicesKitsData,
GetStatusesData,
GetStatusHistoryData,
RemoveAttributeFromModuleData,
RemoveAttributeFromModuleError,
RemoveAttributeFromModuleResponse,
SwitchDealTagData,
SwitchDealTagError,
SwitchDealTagResponse2,
UpdateAttributeData,
UpdateAttributeError,
UpdateAttributeLabelData,
UpdateAttributeLabelError,
UpdateAttributeLabelResponse2,
UpdateAttributeResponse2,
UpdateBarcodeTemplateData,
UpdateBarcodeTemplateError,
UpdateBarcodeTemplateResponse2,
@ -262,6 +299,9 @@ import type {
UpdateMarketplaceData,
UpdateMarketplaceError,
UpdateMarketplaceResponse2,
UpdateModuleData,
UpdateModuleError,
UpdateModuleResponse,
UpdateProductData,
UpdateProductError,
UpdateProductResponse2,
@ -324,6 +364,207 @@ const createQueryKey = <TOptions extends Options>(
return [params];
};
export const getAttributesQueryKey = (options?: Options<GetAttributesData>) =>
createQueryKey("getAttributes", options);
/**
* Get Attributes
*/
export const getAttributesOptions = (options?: Options<GetAttributesData>) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getAttributes({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getAttributesQueryKey(options),
});
};
export const createAttributeQueryKey = (
options: Options<CreateAttributeData>
) => createQueryKey("createAttribute", options);
/**
* Create Attribute
*/
export const createAttributeOptions = (
options: Options<CreateAttributeData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await createAttribute({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: createAttributeQueryKey(options),
});
};
/**
* Create Attribute
*/
export const createAttributeMutation = (
options?: Partial<Options<CreateAttributeData>>
): UseMutationOptions<
CreateAttributeResponse2,
AxiosError<CreateAttributeError>,
Options<CreateAttributeData>
> => {
const mutationOptions: UseMutationOptions<
CreateAttributeResponse2,
AxiosError<CreateAttributeError>,
Options<CreateAttributeData>
> = {
mutationFn: async localOptions => {
const { data } = await createAttribute({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Delete Attribute
*/
export const deleteAttributeMutation = (
options?: Partial<Options<DeleteAttributeData>>
): UseMutationOptions<
DeleteAttributeResponse2,
AxiosError<DeleteAttributeError>,
Options<DeleteAttributeData>
> => {
const mutationOptions: UseMutationOptions<
DeleteAttributeResponse2,
AxiosError<DeleteAttributeError>,
Options<DeleteAttributeData>
> = {
mutationFn: async localOptions => {
const { data } = await deleteAttribute({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Update Attribute
*/
export const updateAttributeMutation = (
options?: Partial<Options<UpdateAttributeData>>
): UseMutationOptions<
UpdateAttributeResponse2,
AxiosError<UpdateAttributeError>,
Options<UpdateAttributeData>
> => {
const mutationOptions: UseMutationOptions<
UpdateAttributeResponse2,
AxiosError<UpdateAttributeError>,
Options<UpdateAttributeData>
> = {
mutationFn: async localOptions => {
const { data } = await updateAttribute({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const updateAttributeLabelQueryKey = (
options: Options<UpdateAttributeLabelData>
) => createQueryKey("updateAttributeLabel", options);
/**
* Update Attribute Label
*/
export const updateAttributeLabelOptions = (
options: Options<UpdateAttributeLabelData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await updateAttributeLabel({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: updateAttributeLabelQueryKey(options),
});
};
/**
* Update Attribute Label
*/
export const updateAttributeLabelMutation = (
options?: Partial<Options<UpdateAttributeLabelData>>
): UseMutationOptions<
UpdateAttributeLabelResponse2,
AxiosError<UpdateAttributeLabelError>,
Options<UpdateAttributeLabelData>
> => {
const mutationOptions: UseMutationOptions<
UpdateAttributeLabelResponse2,
AxiosError<UpdateAttributeLabelError>,
Options<UpdateAttributeLabelData>
> = {
mutationFn: async localOptions => {
const { data } = await updateAttributeLabel({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getAttributeTypesQueryKey = (
options?: Options<GetAttributeTypesData>
) => createQueryKey("getAttributeTypes", options);
/**
* Get Attribute Types
*/
export const getAttributeTypesOptions = (
options?: Options<GetAttributeTypesData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getAttributeTypes({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getAttributeTypesQueryKey(options),
});
};
export const getBoardsQueryKey = (options: Options<GetBoardsData>) =>
createQueryKey("getBoards", options);
@ -1054,6 +1295,186 @@ export const getModulesOptions = (options?: Options<GetModulesData>) => {
});
};
export const getModulesWithAttributesQueryKey = (
options?: Options<GetModulesWithAttributesData>
) => createQueryKey("getModulesWithAttributes", options);
/**
* Get Modules With Attributes
*/
export const getModulesWithAttributesOptions = (
options?: Options<GetModulesWithAttributesData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getModulesWithAttributes({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getModulesWithAttributesQueryKey(options),
});
};
export const getModuleWithAttributesQueryKey = (
options: Options<GetModuleWithAttributesData>
) => createQueryKey("getModuleWithAttributes", options);
/**
* Get Module With Attributes
*/
export const getModuleWithAttributesOptions = (
options: Options<GetModuleWithAttributesData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await getModuleWithAttributes({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: getModuleWithAttributesQueryKey(options),
});
};
/**
* Update Module Common Info
*/
export const updateModuleMutation = (
options?: Partial<Options<UpdateModuleData>>
): UseMutationOptions<
UpdateModuleResponse,
AxiosError<UpdateModuleError>,
Options<UpdateModuleData>
> => {
const mutationOptions: UseMutationOptions<
UpdateModuleResponse,
AxiosError<UpdateModuleError>,
Options<UpdateModuleData>
> = {
mutationFn: async localOptions => {
const { data } = await updateModule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Delete Module
*/
export const deleteModuleMutation = (
options?: Partial<Options<DeleteModuleData>>
): UseMutationOptions<
DeleteModuleResponse2,
AxiosError<DeleteModuleError>,
Options<DeleteModuleData>
> => {
const mutationOptions: UseMutationOptions<
DeleteModuleResponse2,
AxiosError<DeleteModuleError>,
Options<DeleteModuleData>
> = {
mutationFn: async localOptions => {
const { data } = await deleteModule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
/**
* Remove Attribute From Module
*/
export const removeAttributeFromModuleMutation = (
options?: Partial<Options<RemoveAttributeFromModuleData>>
): UseMutationOptions<
RemoveAttributeFromModuleResponse,
AxiosError<RemoveAttributeFromModuleError>,
Options<RemoveAttributeFromModuleData>
> => {
const mutationOptions: UseMutationOptions<
RemoveAttributeFromModuleResponse,
AxiosError<RemoveAttributeFromModuleError>,
Options<RemoveAttributeFromModuleData>
> = {
mutationFn: async localOptions => {
const { data } = await removeAttributeFromModule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const addAttributeToModuleQueryKey = (
options: Options<AddAttributeToModuleData>
) => createQueryKey("addAttributeToModule", options);
/**
* Add Attribute To Module
*/
export const addAttributeToModuleOptions = (
options: Options<AddAttributeToModuleData>
) => {
return queryOptions({
queryFn: async ({ queryKey, signal }) => {
const { data } = await addAttributeToModule({
...options,
...queryKey[0],
signal,
throwOnError: true,
});
return data;
},
queryKey: addAttributeToModuleQueryKey(options),
});
};
/**
* Add Attribute To Module
*/
export const addAttributeToModuleMutation = (
options?: Partial<Options<AddAttributeToModuleData>>
): UseMutationOptions<
AddAttributeToModuleResponse,
AxiosError<AddAttributeToModuleError>,
Options<AddAttributeToModuleData>
> => {
const mutationOptions: UseMutationOptions<
AddAttributeToModuleResponse,
AxiosError<AddAttributeToModuleError>,
Options<AddAttributeToModuleData>
> = {
mutationFn: async localOptions => {
const { data } = await addAttributeToModule({
...options,
...localOptions,
throwOnError: true,
});
return data;
},
};
return mutationOptions;
};
export const getClientsQueryKey = (options?: Options<GetClientsData>) =>
createQueryKey("getClients", options);

View File

@ -8,12 +8,18 @@ import {
} from "./client";
import { client as _heyApiClient } from "./client.gen";
import type {
AddAttributeToModuleData,
AddAttributeToModuleErrors,
AddAttributeToModuleResponses,
AddKitToDealData,
AddKitToDealErrors,
AddKitToDealProductData,
AddKitToDealProductErrors,
AddKitToDealProductResponses,
AddKitToDealResponses,
CreateAttributeData,
CreateAttributeErrors,
CreateAttributeResponses,
CreateBarcodeTemplateData,
CreateBarcodeTemplateErrors,
CreateBarcodeTemplateResponses,
@ -62,6 +68,9 @@ import type {
CreateStatusData,
CreateStatusErrors,
CreateStatusResponses,
DeleteAttributeData,
DeleteAttributeErrors,
DeleteAttributeResponses,
DeleteBarcodeTemplateData,
DeleteBarcodeTemplateErrors,
DeleteBarcodeTemplateResponses,
@ -92,6 +101,9 @@ import type {
DeleteMarketplaceData,
DeleteMarketplaceErrors,
DeleteMarketplaceResponses,
DeleteModuleData,
DeleteModuleErrors,
DeleteModuleResponses,
DeleteProductBarcodeImageData,
DeleteProductBarcodeImageErrors,
DeleteProductBarcodeImageResponses,
@ -116,6 +128,10 @@ import type {
DuplicateProductServicesData,
DuplicateProductServicesErrors,
DuplicateProductServicesResponses,
GetAttributesData,
GetAttributesResponses,
GetAttributeTypesData,
GetAttributeTypesResponses,
GetBarcodeTemplateAttributesData,
GetBarcodeTemplateAttributesResponses,
GetBarcodeTemplatesData,
@ -149,6 +165,11 @@ import type {
GetMarketplacesResponses,
GetModulesData,
GetModulesResponses,
GetModulesWithAttributesData,
GetModulesWithAttributesResponses,
GetModuleWithAttributesData,
GetModuleWithAttributesErrors,
GetModuleWithAttributesResponses,
GetProductBarcodePdfData,
GetProductBarcodePdfErrors,
GetProductBarcodePdfResponses,
@ -169,9 +190,18 @@ import type {
GetStatusHistoryData,
GetStatusHistoryErrors,
GetStatusHistoryResponses,
RemoveAttributeFromModuleData,
RemoveAttributeFromModuleErrors,
RemoveAttributeFromModuleResponses,
SwitchDealTagData,
SwitchDealTagErrors,
SwitchDealTagResponses,
UpdateAttributeData,
UpdateAttributeErrors,
UpdateAttributeLabelData,
UpdateAttributeLabelErrors,
UpdateAttributeLabelResponses,
UpdateAttributeResponses,
UpdateBarcodeTemplateData,
UpdateBarcodeTemplateErrors,
UpdateBarcodeTemplateResponses,
@ -205,6 +235,9 @@ import type {
UpdateMarketplaceData,
UpdateMarketplaceErrors,
UpdateMarketplaceResponses,
UpdateModuleData,
UpdateModuleErrors,
UpdateModuleResponses,
UpdateProductData,
UpdateProductErrors,
UpdateProductResponses,
@ -231,10 +264,14 @@ import type {
UploadProductImageResponses,
} from "./types.gen";
import {
zAddAttributeToModuleData,
zAddAttributeToModuleResponse,
zAddKitToDealData,
zAddKitToDealProductData,
zAddKitToDealProductResponse,
zAddKitToDealResponse,
zCreateAttributeData,
zCreateAttributeResponse2,
zCreateBarcodeTemplateData,
zCreateBarcodeTemplateResponse2,
zCreateBoardData,
@ -267,6 +304,8 @@ import {
zCreateServicesKitResponse2,
zCreateStatusData,
zCreateStatusResponse2,
zDeleteAttributeData,
zDeleteAttributeResponse2,
zDeleteBarcodeTemplateData,
zDeleteBarcodeTemplateResponse2,
zDeleteBoardData,
@ -287,6 +326,8 @@ import {
zDeleteDealTagResponse2,
zDeleteMarketplaceData,
zDeleteMarketplaceResponse2,
zDeleteModuleData,
zDeleteModuleResponse2,
zDeleteProductBarcodeImageData,
zDeleteProductBarcodeImageResponse,
zDeleteProductData,
@ -303,6 +344,10 @@ import {
zDeleteStatusResponse2,
zDuplicateProductServicesData,
zDuplicateProductServicesResponse,
zGetAttributesData,
zGetAttributesResponse,
zGetAttributeTypesData,
zGetAttributeTypesResponse,
zGetBarcodeTemplateAttributesData,
zGetBarcodeTemplateAttributesResponse,
zGetBarcodeTemplatesData,
@ -329,6 +374,10 @@ import {
zGetMarketplacesResponse2,
zGetModulesData,
zGetModulesResponse,
zGetModulesWithAttributesData,
zGetModulesWithAttributesResponse,
zGetModuleWithAttributesData,
zGetModuleWithAttributesResponse,
zGetProductBarcodePdfData,
zGetProductBarcodePdfResponse2,
zGetProductsData,
@ -345,8 +394,14 @@ import {
zGetStatusesResponse2,
zGetStatusHistoryData,
zGetStatusHistoryResponse2,
zRemoveAttributeFromModuleData,
zRemoveAttributeFromModuleResponse,
zSwitchDealTagData,
zSwitchDealTagResponse2,
zUpdateAttributeData,
zUpdateAttributeLabelData,
zUpdateAttributeLabelResponse2,
zUpdateAttributeResponse2,
zUpdateBarcodeTemplateData,
zUpdateBarcodeTemplateResponse2,
zUpdateBoardData,
@ -369,6 +424,8 @@ import {
zUpdateDealTagResponse2,
zUpdateMarketplaceData,
zUpdateMarketplaceResponse2,
zUpdateModuleData,
zUpdateModuleResponse,
zUpdateProductData,
zUpdateProductResponse2,
zUpdateProjectData,
@ -404,6 +461,156 @@ export type Options<
meta?: Record<string, unknown>;
};
/**
* Get Attributes
*/
export const getAttributes = <ThrowOnError extends boolean = false>(
options?: Options<GetAttributesData, ThrowOnError>
) => {
return (options?.client ?? _heyApiClient).get<
GetAttributesResponses,
unknown,
ThrowOnError
>({
requestValidator: async data => {
return await zGetAttributesData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetAttributesResponse.parseAsync(data);
},
url: "/crm/v1/attribute/",
...options,
});
};
/**
* Create Attribute
*/
export const createAttribute = <ThrowOnError extends boolean = false>(
options: Options<CreateAttributeData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).post<
CreateAttributeResponses,
CreateAttributeErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zCreateAttributeData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zCreateAttributeResponse2.parseAsync(data);
},
url: "/crm/v1/attribute/",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Delete Attribute
*/
export const deleteAttribute = <ThrowOnError extends boolean = false>(
options: Options<DeleteAttributeData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).delete<
DeleteAttributeResponses,
DeleteAttributeErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zDeleteAttributeData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zDeleteAttributeResponse2.parseAsync(data);
},
url: "/crm/v1/attribute/{pk}",
...options,
});
};
/**
* Update Attribute
*/
export const updateAttribute = <ThrowOnError extends boolean = false>(
options: Options<UpdateAttributeData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).patch<
UpdateAttributeResponses,
UpdateAttributeErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zUpdateAttributeData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUpdateAttributeResponse2.parseAsync(data);
},
url: "/crm/v1/attribute/{pk}",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Update Attribute Label
*/
export const updateAttributeLabel = <ThrowOnError extends boolean = false>(
options: Options<UpdateAttributeLabelData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).post<
UpdateAttributeLabelResponses,
UpdateAttributeLabelErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zUpdateAttributeLabelData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUpdateAttributeLabelResponse2.parseAsync(data);
},
url: "/crm/v1/attribute/label",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Get Attribute Types
*/
export const getAttributeTypes = <ThrowOnError extends boolean = false>(
options?: Options<GetAttributeTypesData, ThrowOnError>
) => {
return (options?.client ?? _heyApiClient).get<
GetAttributeTypesResponses,
unknown,
ThrowOnError
>({
requestValidator: async data => {
return await zGetAttributeTypesData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetAttributeTypesResponse.parseAsync(data);
},
url: "/crm/v1/attribute/type",
...options,
});
};
/**
* Get Boards
*/
@ -876,11 +1083,161 @@ export const getModules = <ThrowOnError extends boolean = false>(
responseValidator: async data => {
return await zGetModulesResponse.parseAsync(data);
},
url: "/crm/v1/module/built-in/",
url: "/crm/v1/module/",
...options,
});
};
/**
* Get Modules With Attributes
*/
export const getModulesWithAttributes = <ThrowOnError extends boolean = false>(
options?: Options<GetModulesWithAttributesData, ThrowOnError>
) => {
return (options?.client ?? _heyApiClient).get<
GetModulesWithAttributesResponses,
unknown,
ThrowOnError
>({
requestValidator: async data => {
return await zGetModulesWithAttributesData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetModulesWithAttributesResponse.parseAsync(data);
},
url: "/crm/v1/module/with-attributes",
...options,
});
};
/**
* Get Module With Attributes
*/
export const getModuleWithAttributes = <ThrowOnError extends boolean = false>(
options: Options<GetModuleWithAttributesData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).get<
GetModuleWithAttributesResponses,
GetModuleWithAttributesErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zGetModuleWithAttributesData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zGetModuleWithAttributesResponse.parseAsync(data);
},
url: "/crm/v1/module/{pk}/with-attributes",
...options,
});
};
/**
* Update Module Common Info
*/
export const updateModule = <ThrowOnError extends boolean = false>(
options: Options<UpdateModuleData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).patch<
UpdateModuleResponses,
UpdateModuleErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zUpdateModuleData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zUpdateModuleResponse.parseAsync(data);
},
url: "/crm/v1/module/{pk}/common-info",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Delete Module
*/
export const deleteModule = <ThrowOnError extends boolean = false>(
options: Options<DeleteModuleData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).delete<
DeleteModuleResponses,
DeleteModuleErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zDeleteModuleData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zDeleteModuleResponse2.parseAsync(data);
},
url: "/crm/v1/module/{pk}",
...options,
});
};
/**
* Remove Attribute From Module
*/
export const removeAttributeFromModule = <ThrowOnError extends boolean = false>(
options: Options<RemoveAttributeFromModuleData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).delete<
RemoveAttributeFromModuleResponses,
RemoveAttributeFromModuleErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zRemoveAttributeFromModuleData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zRemoveAttributeFromModuleResponse.parseAsync(data);
},
url: "/crm/v1/module/attribute",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Add Attribute To Module
*/
export const addAttributeToModule = <ThrowOnError extends boolean = false>(
options: Options<AddAttributeToModuleData, ThrowOnError>
) => {
return (options.client ?? _heyApiClient).post<
AddAttributeToModuleResponses,
AddAttributeToModuleErrors,
ThrowOnError
>({
requestValidator: async data => {
return await zAddAttributeToModuleData.parseAsync(data);
},
responseType: "json",
responseValidator: async data => {
return await zAddAttributeToModuleResponse.parseAsync(data);
},
url: "/crm/v1/module/attribute",
...options,
headers: {
"Content-Type": "application/json",
...options.headers,
},
});
};
/**
* Get Clients
*/

View File

@ -1,5 +1,96 @@
// This file is auto-generated by @hey-api/openapi-ts
/**
* AddAttributeRequest
*/
export type AddAttributeRequest = {
/**
* Attributeid
*/
attributeId: number;
/**
* Moduleid
*/
moduleId: number;
};
/**
* AddAttributeResponse
*/
export type AddAttributeResponse = {
/**
* Message
*/
message: string;
};
/**
* AttributeSchema
*/
export type AttributeSchema = {
/**
* Label
*/
label: string;
/**
* Isapplicabletogroup
*/
isApplicableToGroup: boolean;
/**
* Isshownondashboard
*/
isShownOnDashboard: boolean;
/**
* Ishighlightifexpired
*/
isHighlightIfExpired: boolean;
/**
* Isnullable
*/
isNullable: boolean;
/**
* Defaultvalue
*/
defaultValue: {
[key: string]: unknown;
} | null;
/**
* Description
*/
description: string;
/**
* Typeid
*/
typeId: number;
/**
* Id
*/
id: number;
/**
* Isbuiltin
*/
isBuiltIn: boolean;
type: AttributeTypeSchema;
};
/**
* AttributeTypeSchema
*/
export type AttributeTypeSchema = {
/**
* Id
*/
id: number;
/**
* Type
*/
type: string;
/**
* Name
*/
name: string;
};
/**
* BarcodeTemplateAttributeSchema
*/
@ -186,6 +277,63 @@ export type ClientSchema = {
isDeleted?: boolean;
};
/**
* CreateAttributeRequest
*/
export type CreateAttributeRequest = {
entity: CreateAttributeSchema;
};
/**
* CreateAttributeResponse
*/
export type CreateAttributeResponse = {
/**
* Message
*/
message: string;
};
/**
* CreateAttributeSchema
*/
export type CreateAttributeSchema = {
/**
* Label
*/
label: string;
/**
* Isapplicabletogroup
*/
isApplicableToGroup: boolean;
/**
* Isshownondashboard
*/
isShownOnDashboard: boolean;
/**
* Ishighlightifexpired
*/
isHighlightIfExpired: boolean;
/**
* Isnullable
*/
isNullable: boolean;
/**
* Defaultvalue
*/
defaultValue: {
[key: string]: unknown;
} | null;
/**
* Description
*/
description: string;
/**
* Typeid
*/
typeId: number;
};
/**
* CreateBarcodeTemplateRequest
*/
@ -1020,6 +1168,30 @@ export type DealTagSchema = {
tagColor: DealTagColorSchema;
};
/**
* DeleteAttributeRequest
*/
export type DeleteAttributeRequest = {
/**
* Attributeid
*/
attributeId: number;
/**
* Moduleid
*/
moduleId: number;
};
/**
* DeleteAttributeResponse
*/
export type DeleteAttributeResponse = {
/**
* Message
*/
message: string;
};
/**
* DeleteBarcodeImageResponse
*/
@ -1120,6 +1292,16 @@ export type DeleteMarketplaceResponse = {
message: string;
};
/**
* DeleteModuleResponse
*/
export type DeleteModuleResponse = {
/**
* Message
*/
message: string;
};
/**
* DeleteProductResponse
*/
@ -1190,6 +1372,26 @@ export type DeleteStatusResponse = {
message: string;
};
/**
* GetAllAttributeTypesResponse
*/
export type GetAllAttributeTypesResponse = {
/**
* Items
*/
items: Array<AttributeTypeSchema>;
};
/**
* GetAllAttributesResponse
*/
export type GetAllAttributesResponse = {
/**
* Items
*/
items: Array<AttributeSchema>;
};
/**
* GetAllModulesResponse
*/
@ -1200,6 +1402,16 @@ export type GetAllModulesResponse = {
items: Array<ModuleSchemaOutput>;
};
/**
* GetAllWithAttributesResponse
*/
export type GetAllWithAttributesResponse = {
/**
* Items
*/
items: Array<ModuleWithAttributesSchema>;
};
/**
* GetBarcodeAttributesResponse
*/
@ -1250,6 +1462,13 @@ export type GetBoardsResponse = {
items: Array<BoardSchema>;
};
/**
* GetByIdWithAttributesResponse
*/
export type GetByIdWithAttributesResponse = {
entity: ModuleWithAttributesSchema;
};
/**
* GetClientsResponse
*/
@ -1464,6 +1683,59 @@ export type MarketplaceSchema = {
};
};
/**
* ModuleAttributeSchema
*/
export type ModuleAttributeSchema = {
/**
* Label
*/
label: string;
/**
* Isapplicabletogroup
*/
isApplicableToGroup: boolean;
/**
* Isshownondashboard
*/
isShownOnDashboard: boolean;
/**
* Ishighlightifexpired
*/
isHighlightIfExpired: boolean;
/**
* Isnullable
*/
isNullable: boolean;
/**
* Defaultvalue
*/
defaultValue: {
[key: string]: unknown;
} | null;
/**
* Description
*/
description: string;
/**
* Typeid
*/
typeId: number;
/**
* Id
*/
id: number;
/**
* Isbuiltin
*/
isBuiltIn: boolean;
type: AttributeTypeSchema;
/**
* Originallabel
*/
originalLabel: string;
};
/**
* ModuleSchema
*/
@ -1483,7 +1755,11 @@ export type ModuleSchemaInput = {
/**
* Description
*/
description: string;
description: string | null;
/**
* Isbuiltin
*/
isBuiltIn: boolean;
/**
* Dependson
*/
@ -1513,7 +1789,11 @@ export type ModuleSchemaOutput = {
/**
* Description
*/
description: string;
description: string | null;
/**
* Isbuiltin
*/
isBuiltIn: boolean;
/**
* Dependson
*/
@ -1550,6 +1830,44 @@ export type ModuleTabSchema = {
device: string;
};
/**
* ModuleWithAttributesSchema
*/
export type ModuleWithAttributesSchema = {
/**
* Id
*/
id: number;
/**
* Key
*/
key: string;
/**
* Label
*/
label: string;
/**
* Description
*/
description: string | null;
/**
* Isbuiltin
*/
isBuiltIn: boolean;
/**
* Dependson
*/
dependsOn: Array<ModuleSchemaOutput>;
/**
* Tabs
*/
tabs: Array<ModuleTabSchema>;
/**
* Attributes
*/
attributes: Array<ModuleAttributeSchema>;
};
/**
* PaginationInfoSchema
*/
@ -1932,6 +2250,88 @@ export type SwitchDealTagResponse = {
message: string;
};
/**
* UpdateAttributeLabelRequest
*/
export type UpdateAttributeLabelRequest = {
/**
* Moduleid
*/
moduleId: number;
/**
* Attributeid
*/
attributeId: number;
/**
* Label
*/
label: string;
};
/**
* UpdateAttributeLabelResponse
*/
export type UpdateAttributeLabelResponse = {
/**
* Message
*/
message: string;
};
/**
* UpdateAttributeRequest
*/
export type UpdateAttributeRequest = {
entity: UpdateAttributeSchema;
};
/**
* UpdateAttributeResponse
*/
export type UpdateAttributeResponse = {
/**
* Message
*/
message: string;
};
/**
* UpdateAttributeSchema
*/
export type UpdateAttributeSchema = {
/**
* Label
*/
label?: string | null;
/**
* Isapplicabletogroup
*/
isApplicableToGroup?: boolean | null;
/**
* Isshownondashboard
*/
isShownOnDashboard?: boolean | null;
/**
* Ishighlightifexpired
*/
isHighlightIfExpired?: boolean | null;
/**
* Isnullable
*/
isNullable?: boolean | null;
/**
* Defaultvalue
*/
defaultValue?: {
[key: string]: unknown;
} | null;
/**
* Description
*/
description?: string | null;
type?: AttributeTypeSchema | null;
};
/**
* UpdateBarcodeTemplateRequest
*/
@ -2259,6 +2659,37 @@ export type UpdateMarketplaceSchema = {
} | null;
};
/**
* UpdateModuleCommonInfoRequest
*/
export type UpdateModuleCommonInfoRequest = {
entity: UpdateModuleCommonInfoSchema;
};
/**
* UpdateModuleCommonInfoResponse
*/
export type UpdateModuleCommonInfoResponse = {
/**
* Message
*/
message: string;
};
/**
* UpdateModuleCommonInfoSchema
*/
export type UpdateModuleCommonInfoSchema = {
/**
* Label
*/
label: string;
/**
* Description
*/
description: string | null;
};
/**
* UpdateProductRequest
*/
@ -2563,6 +2994,158 @@ export type ValidationError = {
type: string;
};
export type GetAttributesData = {
body?: never;
path?: never;
query?: never;
url: "/crm/v1/attribute/";
};
export type GetAttributesResponses = {
/**
* Successful Response
*/
200: GetAllAttributesResponse;
};
export type GetAttributesResponse =
GetAttributesResponses[keyof GetAttributesResponses];
export type CreateAttributeData = {
body: CreateAttributeRequest;
path?: never;
query?: never;
url: "/crm/v1/attribute/";
};
export type CreateAttributeErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type CreateAttributeError =
CreateAttributeErrors[keyof CreateAttributeErrors];
export type CreateAttributeResponses = {
/**
* Successful Response
*/
200: CreateAttributeResponse;
};
export type CreateAttributeResponse2 =
CreateAttributeResponses[keyof CreateAttributeResponses];
export type DeleteAttributeData = {
body?: never;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/attribute/{pk}";
};
export type DeleteAttributeErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type DeleteAttributeError =
DeleteAttributeErrors[keyof DeleteAttributeErrors];
export type DeleteAttributeResponses = {
/**
* Successful Response
*/
200: DeleteAttributeResponse;
};
export type DeleteAttributeResponse2 =
DeleteAttributeResponses[keyof DeleteAttributeResponses];
export type UpdateAttributeData = {
body: UpdateAttributeRequest;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/attribute/{pk}";
};
export type UpdateAttributeErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UpdateAttributeError =
UpdateAttributeErrors[keyof UpdateAttributeErrors];
export type UpdateAttributeResponses = {
/**
* Successful Response
*/
200: UpdateAttributeResponse;
};
export type UpdateAttributeResponse2 =
UpdateAttributeResponses[keyof UpdateAttributeResponses];
export type UpdateAttributeLabelData = {
body: UpdateAttributeLabelRequest;
path?: never;
query?: never;
url: "/crm/v1/attribute/label";
};
export type UpdateAttributeLabelErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UpdateAttributeLabelError =
UpdateAttributeLabelErrors[keyof UpdateAttributeLabelErrors];
export type UpdateAttributeLabelResponses = {
/**
* Successful Response
*/
200: UpdateAttributeLabelResponse;
};
export type UpdateAttributeLabelResponse2 =
UpdateAttributeLabelResponses[keyof UpdateAttributeLabelResponses];
export type GetAttributeTypesData = {
body?: never;
path?: never;
query?: never;
url: "/crm/v1/attribute/type";
};
export type GetAttributeTypesResponses = {
/**
* Successful Response
*/
200: GetAllAttributeTypesResponse;
};
export type GetAttributeTypesResponse =
GetAttributeTypesResponses[keyof GetAttributeTypesResponses];
export type GetBoardsData = {
body?: never;
path: {
@ -3120,7 +3703,7 @@ export type GetModulesData = {
body?: never;
path?: never;
query?: never;
url: "/crm/v1/module/built-in/";
url: "/crm/v1/module/";
};
export type GetModulesResponses = {
@ -3132,6 +3715,171 @@ export type GetModulesResponses = {
export type GetModulesResponse = GetModulesResponses[keyof GetModulesResponses];
export type GetModulesWithAttributesData = {
body?: never;
path?: never;
query?: never;
url: "/crm/v1/module/with-attributes";
};
export type GetModulesWithAttributesResponses = {
/**
* Successful Response
*/
200: GetAllWithAttributesResponse;
};
export type GetModulesWithAttributesResponse =
GetModulesWithAttributesResponses[keyof GetModulesWithAttributesResponses];
export type GetModuleWithAttributesData = {
body?: never;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/module/{pk}/with-attributes";
};
export type GetModuleWithAttributesErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type GetModuleWithAttributesError =
GetModuleWithAttributesErrors[keyof GetModuleWithAttributesErrors];
export type GetModuleWithAttributesResponses = {
/**
* Successful Response
*/
200: GetByIdWithAttributesResponse;
};
export type GetModuleWithAttributesResponse =
GetModuleWithAttributesResponses[keyof GetModuleWithAttributesResponses];
export type UpdateModuleData = {
body: UpdateModuleCommonInfoRequest;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/module/{pk}/common-info";
};
export type UpdateModuleErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type UpdateModuleError = UpdateModuleErrors[keyof UpdateModuleErrors];
export type UpdateModuleResponses = {
/**
* Successful Response
*/
200: UpdateModuleCommonInfoResponse;
};
export type UpdateModuleResponse =
UpdateModuleResponses[keyof UpdateModuleResponses];
export type DeleteModuleData = {
body?: never;
path: {
/**
* Pk
*/
pk: number;
};
query?: never;
url: "/crm/v1/module/{pk}";
};
export type DeleteModuleErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type DeleteModuleError = DeleteModuleErrors[keyof DeleteModuleErrors];
export type DeleteModuleResponses = {
/**
* Successful Response
*/
200: DeleteModuleResponse;
};
export type DeleteModuleResponse2 =
DeleteModuleResponses[keyof DeleteModuleResponses];
export type RemoveAttributeFromModuleData = {
body: DeleteAttributeRequest;
path?: never;
query?: never;
url: "/crm/v1/module/attribute";
};
export type RemoveAttributeFromModuleErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type RemoveAttributeFromModuleError =
RemoveAttributeFromModuleErrors[keyof RemoveAttributeFromModuleErrors];
export type RemoveAttributeFromModuleResponses = {
/**
* Successful Response
*/
200: DeleteAttributeResponse;
};
export type RemoveAttributeFromModuleResponse =
RemoveAttributeFromModuleResponses[keyof RemoveAttributeFromModuleResponses];
export type AddAttributeToModuleData = {
body: AddAttributeRequest;
path?: never;
query?: never;
url: "/crm/v1/module/attribute";
};
export type AddAttributeToModuleErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type AddAttributeToModuleError =
AddAttributeToModuleErrors[keyof AddAttributeToModuleErrors];
export type AddAttributeToModuleResponses = {
/**
* Successful Response
*/
200: AddAttributeResponse;
};
export type AddAttributeToModuleResponse =
AddAttributeToModuleResponses[keyof AddAttributeToModuleResponses];
export type GetClientsData = {
body?: never;
path?: never;

View File

@ -2,6 +2,47 @@
import { z } from "zod";
/**
* AddAttributeRequest
*/
export const zAddAttributeRequest = z.object({
attributeId: z.int(),
moduleId: z.int(),
});
/**
* AddAttributeResponse
*/
export const zAddAttributeResponse = z.object({
message: z.string(),
});
/**
* AttributeTypeSchema
*/
export const zAttributeTypeSchema = z.object({
id: z.int(),
type: z.string(),
name: z.string(),
});
/**
* AttributeSchema
*/
export const zAttributeSchema = z.object({
label: z.string(),
isApplicableToGroup: z.boolean(),
isShownOnDashboard: z.boolean(),
isHighlightIfExpired: z.boolean(),
isNullable: z.boolean(),
defaultValue: z.union([z.object({}), z.null()]),
description: z.string(),
typeId: z.int(),
id: z.int(),
isBuiltIn: z.boolean(),
type: zAttributeTypeSchema,
});
/**
* BarcodeTemplateAttributeSchema
*/
@ -95,6 +136,34 @@ export const zClientSchema = z.object({
isDeleted: z.optional(z.boolean()).default(false),
});
/**
* CreateAttributeSchema
*/
export const zCreateAttributeSchema = z.object({
label: z.string(),
isApplicableToGroup: z.boolean(),
isShownOnDashboard: z.boolean(),
isHighlightIfExpired: z.boolean(),
isNullable: z.boolean(),
defaultValue: z.union([z.object({}), z.null()]),
description: z.string(),
typeId: z.int(),
});
/**
* CreateAttributeRequest
*/
export const zCreateAttributeRequest = z.object({
entity: zCreateAttributeSchema,
});
/**
* CreateAttributeResponse
*/
export const zCreateAttributeResponse = z.object({
message: z.string(),
});
/**
* CreateBarcodeTemplateSchema
*/
@ -579,7 +648,8 @@ export const zModuleSchemaOutput = z.object({
id: z.int(),
key: z.string(),
label: z.string(),
description: z.string(),
description: z.union([z.string(), z.null()]),
isBuiltIn: z.boolean(),
get dependsOn() {
return z.array(
z.lazy((): any => {
@ -751,6 +821,21 @@ export const zDealProductAddKitResponse = z.object({
message: z.string(),
});
/**
* DeleteAttributeRequest
*/
export const zDeleteAttributeRequest = z.object({
attributeId: z.int(),
moduleId: z.int(),
});
/**
* DeleteAttributeResponse
*/
export const zDeleteAttributeResponse = z.object({
message: z.string(),
});
/**
* DeleteBarcodeImageResponse
*/
@ -821,6 +906,13 @@ export const zDeleteMarketplaceResponse = z.object({
message: z.string(),
});
/**
* DeleteModuleResponse
*/
export const zDeleteModuleResponse = z.object({
message: z.string(),
});
/**
* DeleteProductResponse
*/
@ -870,6 +962,20 @@ export const zDeleteStatusResponse = z.object({
message: z.string(),
});
/**
* GetAllAttributeTypesResponse
*/
export const zGetAllAttributeTypesResponse = z.object({
items: z.array(zAttributeTypeSchema),
});
/**
* GetAllAttributesResponse
*/
export const zGetAllAttributesResponse = z.object({
items: z.array(zAttributeSchema),
});
/**
* GetAllModulesResponse
*/
@ -877,6 +983,45 @@ export const zGetAllModulesResponse = z.object({
items: z.array(zModuleSchemaOutput),
});
/**
* ModuleAttributeSchema
*/
export const zModuleAttributeSchema = z.object({
label: z.string(),
isApplicableToGroup: z.boolean(),
isShownOnDashboard: z.boolean(),
isHighlightIfExpired: z.boolean(),
isNullable: z.boolean(),
defaultValue: z.union([z.object({}), z.null()]),
description: z.string(),
typeId: z.int(),
id: z.int(),
isBuiltIn: z.boolean(),
type: zAttributeTypeSchema,
originalLabel: z.string(),
});
/**
* ModuleWithAttributesSchema
*/
export const zModuleWithAttributesSchema = z.object({
id: z.int(),
key: z.string(),
label: z.string(),
description: z.union([z.string(), z.null()]),
isBuiltIn: z.boolean(),
dependsOn: z.array(zModuleSchemaOutput),
tabs: z.array(zModuleTabSchema),
attributes: z.array(zModuleAttributeSchema),
});
/**
* GetAllWithAttributesResponse
*/
export const zGetAllWithAttributesResponse = z.object({
items: z.array(zModuleWithAttributesSchema),
});
/**
* GetBarcodeAttributesResponse
*/
@ -912,6 +1057,13 @@ export const zGetBoardsResponse = z.object({
items: z.array(zBoardSchema),
});
/**
* GetByIdWithAttributesResponse
*/
export const zGetByIdWithAttributesResponse = z.object({
entity: zModuleWithAttributesSchema,
});
/**
* GetClientsResponse
*/
@ -1074,7 +1226,8 @@ export const zModuleSchemaInput = z.object({
id: z.int(),
key: z.string(),
label: z.string(),
description: z.string(),
description: z.union([z.string(), z.null()]),
isBuiltIn: z.boolean(),
get dependsOn() {
return z.array(
z.lazy((): any => {
@ -1127,6 +1280,50 @@ export const zSwitchDealTagResponse = z.object({
message: z.string(),
});
/**
* UpdateAttributeLabelRequest
*/
export const zUpdateAttributeLabelRequest = z.object({
moduleId: z.int(),
attributeId: z.int(),
label: z.string(),
});
/**
* UpdateAttributeLabelResponse
*/
export const zUpdateAttributeLabelResponse = z.object({
message: z.string(),
});
/**
* UpdateAttributeSchema
*/
export const zUpdateAttributeSchema = z.object({
label: z.optional(z.union([z.string(), z.null()])),
isApplicableToGroup: z.optional(z.union([z.boolean(), z.null()])),
isShownOnDashboard: z.optional(z.union([z.boolean(), z.null()])),
isHighlightIfExpired: z.optional(z.union([z.boolean(), z.null()])),
isNullable: z.optional(z.union([z.boolean(), z.null()])),
defaultValue: z.optional(z.union([z.object({}), z.null()])),
description: z.optional(z.union([z.string(), z.null()])),
type: z.optional(z.union([zAttributeTypeSchema, z.null()])),
});
/**
* UpdateAttributeRequest
*/
export const zUpdateAttributeRequest = z.object({
entity: zUpdateAttributeSchema,
});
/**
* UpdateAttributeResponse
*/
export const zUpdateAttributeResponse = z.object({
message: z.string(),
});
/**
* UpdateBarcodeTemplateSchema
*/
@ -1352,6 +1549,28 @@ export const zUpdateMarketplaceResponse = z.object({
message: z.string(),
});
/**
* UpdateModuleCommonInfoSchema
*/
export const zUpdateModuleCommonInfoSchema = z.object({
label: z.string(),
description: z.union([z.string(), z.null()]),
});
/**
* UpdateModuleCommonInfoRequest
*/
export const zUpdateModuleCommonInfoRequest = z.object({
entity: zUpdateModuleCommonInfoSchema,
});
/**
* UpdateModuleCommonInfoResponse
*/
export const zUpdateModuleCommonInfoResponse = z.object({
message: z.string(),
});
/**
* UpdateProductSchema
*/
@ -1526,6 +1745,76 @@ export const zUpdateStatusResponse = z.object({
message: z.string(),
});
export const zGetAttributesData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetAttributesResponse = zGetAllAttributesResponse;
export const zCreateAttributeData = z.object({
body: zCreateAttributeRequest,
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zCreateAttributeResponse2 = zCreateAttributeResponse;
export const zDeleteAttributeData = z.object({
body: z.optional(z.never()),
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zDeleteAttributeResponse2 = zDeleteAttributeResponse;
export const zUpdateAttributeData = z.object({
body: zUpdateAttributeRequest,
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUpdateAttributeResponse2 = zUpdateAttributeResponse;
export const zUpdateAttributeLabelData = z.object({
body: zUpdateAttributeLabelRequest,
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUpdateAttributeLabelResponse2 = zUpdateAttributeLabelResponse;
export const zGetAttributeTypesData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetAttributeTypesResponse = zGetAllAttributeTypesResponse;
export const zGetBoardsData = z.object({
body: z.optional(z.never()),
path: z.object({
@ -1769,6 +2058,78 @@ export const zGetModulesData = z.object({
*/
export const zGetModulesResponse = zGetAllModulesResponse;
export const zGetModulesWithAttributesData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetModulesWithAttributesResponse = zGetAllWithAttributesResponse;
export const zGetModuleWithAttributesData = z.object({
body: z.optional(z.never()),
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zGetModuleWithAttributesResponse = zGetByIdWithAttributesResponse;
export const zUpdateModuleData = z.object({
body: zUpdateModuleCommonInfoRequest,
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zUpdateModuleResponse = zUpdateModuleCommonInfoResponse;
export const zDeleteModuleData = z.object({
body: z.optional(z.never()),
path: z.object({
pk: z.int(),
}),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zDeleteModuleResponse2 = zDeleteModuleResponse;
export const zRemoveAttributeFromModuleData = z.object({
body: zDeleteAttributeRequest,
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zRemoveAttributeFromModuleResponse = zDeleteAttributeResponse;
export const zAddAttributeToModuleData = z.object({
body: zAddAttributeRequest,
path: z.optional(z.never()),
query: z.optional(z.never()),
});
/**
* Successful Response
*/
export const zAddAttributeToModuleResponse = zAddAttributeResponse;
export const zGetClientsData = z.object({
body: z.optional(z.never()),
path: z.optional(z.never()),

View File

@ -5,11 +5,13 @@ import ColorPickerModal from "@/app/deals/modals/ColorPickerModal/ColorPickerMod
import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/DealsBoardFiltersModal";
import DealsScheduleFiltersModal from "@/app/deals/modals/DealsScheduleFiltersModal/DealsScheduleFiltersModal";
import DealsTableFiltersModal from "@/app/deals/modals/DealsTableFiltersModal/DealsTableFiltersModal";
import AttributeEditorModal from "@/app/module-editor/[moduleId]/modals/AttributeEditorModal";
import {
ServiceCategoryEditorModal,
ServiceEditorModal,
ServicesKitEditorModal,
} from "@/app/services/modals";
import DealTagModal from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/modals/DealTagModal";
import EnterNameModal from "@/modals/EnterNameModal/EnterNameModal";
import {
DealProductEditorModal,
@ -20,7 +22,6 @@ import {
ProductServiceEditorModal,
ServicesKitSelectModal,
} from "@/modules/dealModularEditorTabs/FulfillmentBase/shared/modals";
import DealTagModal from "@/drawers/common/ProjectEditorDrawer/tabs/TagsTab/modals/DealTagModal";
export const modals = {
enterNameModal: EnterNameModal,
@ -42,4 +43,5 @@ export const modals = {
statusColorPickerModal: ColorPickerModal,
marketplaceEditorModal: MarketplaceEditorModal,
dealTagModal: DealTagModal,
attributeEditorModal: AttributeEditorModal,
};

View File

@ -117,10 +117,12 @@ const ProductView: FC<Props> = ({ dealProduct }) => {
)}
<Title order={3}>{dealProduct.product.name}</Title>
<ProductFieldsList product={dealProduct.product} />
<Text>
Штрихкоды:
{dealProduct.product.barcodes.join(", ")}
</Text>
{dealProduct.product.barcodes && (
<Text>
Штрихкоды:
{dealProduct.product.barcodes.join(", ")}
</Text>
)}
<NumberInput
suffix={" шт."}
value={dealProduct.quantity}

View File

@ -82,5 +82,27 @@ export const theme = createTheme({
radius,
},
},
Fieldset: {
defaultProps: {
radius: "xl",
bg: "transparent",
bd: "dashed 1px var(--mantine-color-default-border)",
},
},
DateTimePicker: {
defaultProps: {
radius,
},
},
DatePickerInput: {
defaultProps: {
radius,
},
},
ActionIcon: {
defaultProps: {
bg: "light-dark(var(--mantine-color-gray-1), var(--mantine-color-default))",
},
},
},
});

View File

@ -18,3 +18,10 @@ export const utcDateTimeToLocalString = (
const res = localDateTimeToString(utcDateToLocal(datetime));
return withSeconds ? res : res.substring(0, 17);
};
export const utcDateToLocalString = (
datetime: string | Date,
) => {
const res = localDateTimeToString(utcDateToLocal(datetime));
return res.substring(0, 11);
};