diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 96c23fd..e69e359 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -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) {
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
diff --git a/src/app/module-editor/[moduleId]/ModuleEditor.module.css b/src/app/module-editor/[moduleId]/ModuleEditor.module.css
new file mode 100644
index 0000000..c88fd39
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/ModuleEditor.module.css
@@ -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);
+ }
+}
diff --git a/src/app/module-editor/[moduleId]/components/shared/AttributeTableActions/AttributeTableActions.tsx b/src/app/module-editor/[moduleId]/components/shared/AttributeTableActions/AttributeTableActions.tsx
new file mode 100644
index 0000000..53a466d
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/AttributeTableActions/AttributeTableActions.tsx
@@ -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 = ({ 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 (
+
+
+ attributeActions.onUpdate(attribute)}
+ disabled={attribute.isBuiltIn}
+ tipLabel={"Редактировать"}>
+
+
+ attributeActions.onDelete(attribute)}
+ disabled={attribute.isBuiltIn}
+ tipLabel={"Удалить"}>
+
+
+ toggleAttributeInModule(attribute)}
+ tipLabel={
+ usedAttributeIds.has(attribute.id)
+ ? "Удалить из модуля"
+ : "Добавить в модуль"
+ }>
+ {usedAttributeIds.has(attribute.id) ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default AttributeTableActions;
diff --git a/src/app/module-editor/[moduleId]/components/shared/AttributeTypeSelect/AttributeTypeSelect.tsx b/src/app/module-editor/[moduleId]/components/shared/AttributeTypeSelect/AttributeTypeSelect.tsx
new file mode 100644
index 0000000..042033b
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/AttributeTypeSelect/AttributeTypeSelect.tsx
@@ -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, "data">;
+
+const AttributeTypeSelect = (props: Props) => {
+ const { attributeTypes } = useAttributeTypesList();
+
+ return (
+
+ );
+};
+
+export default AttributeTypeSelect;
diff --git a/src/app/module-editor/[moduleId]/components/shared/AttributesTable/AttributesTable.tsx b/src/app/module-editor/[moduleId]/components/shared/AttributesTable/AttributesTable.tsx
new file mode 100644
index 0000000..e833d5c
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/AttributesTable/AttributesTable.tsx
@@ -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 (
+
+ Нет атрибутов
+
+ );
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default AttributesTable;
diff --git a/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx b/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx
new file mode 100644
index 0000000..30a26b4
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx
@@ -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: Действия,
+ width: "0%",
+ render: attribute => (
+
+ ),
+ },
+ ] as DataTableColumn[],
+ [isMobile]
+ );
+};
+
+export default useAttributesTableColumns;
diff --git a/src/app/module-editor/[moduleId]/components/shared/CreateAttributeButton/CreateAttributeButton.tsx b/src/app/module-editor/[moduleId]/components/shared/CreateAttributeButton/CreateAttributeButton.tsx
new file mode 100644
index 0000000..6597918
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/CreateAttributeButton/CreateAttributeButton.tsx
@@ -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 (
+
+ Создать атрибут
+
+ );
+};
+
+export default CreateAttributeButton;
diff --git a/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx b/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx
new file mode 100644
index 0000000..d067e36
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx
@@ -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>;
+};
+
+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 (
+
+ form.setFieldValue("defaultValue", {
+ value: e.currentTarget.checked,
+ })
+ }
+ />
+ );
+ } else if (type === "date") {
+ return (
+
+ form.setFieldValue("defaultValue", { value })
+ }
+ clearable
+ locale={"ru"}
+ valueFormat={"DD.MM.YYYY"}
+ />
+ );
+ } else if (type === "datetime") {
+ return (
+
+ form.setFieldValue("defaultValue", { value })
+ }
+ clearable
+ locale={"ru"}
+ valueFormat={"DD.MM.YYYY HH:mm"}
+ />
+ );
+ } else if (type === "str") {
+ return (
+
+ );
+ } else if (type === "int" || type === "float") {
+ return (
+
+ form.setFieldValue("defaultValue", { value: Number(value) })
+ }
+ />
+ );
+ }
+
+ return <>>;
+};
+
+export default DefaultAttributeValueInput;
diff --git a/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx b/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx
new file mode 100644
index 0000000..ef5cea2
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx
@@ -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 = ({ attribute }) => {
+ const {
+ attributeActions: { removeAttributeFromModule, onEditAttributeLabel },
+ } = useModuleEditorContext();
+
+ const getAttrLabelText = () => {
+ const order = 6;
+
+ if (attribute.label === attribute.originalLabel) {
+ return Название: {attribute.label};
+ }
+ return (
+
+ Название: {attribute.label}{" "}
+ (Ориг. {attribute.originalLabel})
+
+ );
+ };
+
+ return (
+
+
+
+ <>{getAttrLabelText()}>
+ Тип: {attribute.type.name}
+
+
+ onEditAttributeLabel(attribute)}>
+
+
+ removeAttributeFromModule(attribute)}>
+
+
+
+
+
+ );
+};
+
+export default ModuleAttribute;
diff --git a/src/app/module-editor/[moduleId]/components/shared/ModuleAttributesEditor/ModuleAttributesEditor.tsx b/src/app/module-editor/[moduleId]/components/shared/ModuleAttributesEditor/ModuleAttributesEditor.tsx
new file mode 100644
index 0000000..4089945
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/ModuleAttributesEditor/ModuleAttributesEditor.tsx
@@ -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(
+
+
+ {rightIdx < module.attributes.length && (
+
+ )}
+
+ );
+ }
+
+ return rows;
+ };
+
+ return (
+
+ {getAttributesRows()}
+
+ );
+};
+
+export default ModuleAttributesEditor;
diff --git a/src/app/module-editor/[moduleId]/components/shared/ModuleCommonInfoEditor/ModuleCommonInfoEditor.tsx b/src/app/module-editor/[moduleId]/components/shared/ModuleCommonInfoEditor/ModuleCommonInfoEditor.tsx
new file mode 100644
index 0000000..0ddff1d
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/ModuleCommonInfoEditor/ModuleCommonInfoEditor.tsx
@@ -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({
+ initialValues: module ?? {
+ label: "",
+ description: "",
+ },
+ validate: {
+ label: label => !label && "Введите название модуля",
+ },
+ });
+
+ const onSubmit = (values: ModuleInfo) => {
+ updateCommonInfo(values, form.resetDirty);
+ };
+
+ return (
+
+ );
+};
+
+export default ModuleCommonInfoEditor;
diff --git a/src/app/module-editor/[moduleId]/components/shared/PageBody/PageBody.tsx b/src/app/module-editor/[moduleId]/components/shared/PageBody/PageBody.tsx
new file mode 100644
index 0000000..17b6134
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/components/shared/PageBody/PageBody.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default PageBody;
diff --git a/src/app/module-editor/[moduleId]/contexts/ModuleEditorContext.tsx b/src/app/module-editor/[moduleId]/contexts/ModuleEditorContext.tsx
new file mode 100644
index 0000000..1793ca5
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/contexts/ModuleEditorContext.tsx
@@ -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(
+ useModuleEditorContextState,
+ "ModuleEditor"
+ );
diff --git a/src/app/module-editor/[moduleId]/hooks/useAttributeTypesList.ts b/src/app/module-editor/[moduleId]/hooks/useAttributeTypesList.ts
new file mode 100644
index 0000000..ab56903
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/hooks/useAttributeTypesList.ts
@@ -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;
diff --git a/src/app/module-editor/[moduleId]/hooks/useAttributesActions.tsx b/src/app/module-editor/[moduleId]/hooks/useAttributesActions.tsx
new file mode 100644
index 0000000..48f376c
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/hooks/useAttributesActions.tsx
@@ -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) => {
+ 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: (
+
+ Вы уверены, что хотите удалить атрибут "{attribute.label}"
+ из модуля?
+
+ ),
+ 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: (
+
+ Вы уверены, что хотите удалить атрибут "{attribute.label}"?
+
+ ),
+ confirmProps: { color: "red" },
+ onConfirm: () =>
+ deleteAttribute.mutate({ path: { pk: attribute.id } }),
+ });
+ };
+
+ return {
+ addAttributeToModule,
+ removeAttributeFromModule,
+ onEditAttributeLabel,
+ onCreate,
+ onUpdate,
+ onDelete,
+ };
+};
+
+export default useAttributesActions;
diff --git a/src/app/module-editor/[moduleId]/hooks/useAttributesList.ts b/src/app/module-editor/[moduleId]/hooks/useAttributesList.ts
new file mode 100644
index 0000000..1662ee1
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/hooks/useAttributesList.ts
@@ -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;
diff --git a/src/app/module-editor/[moduleId]/hooks/useModulesActions.tsx b/src/app/module-editor/[moduleId]/hooks/useModulesActions.tsx
new file mode 100644
index 0000000..1259bf7
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/hooks/useModulesActions.tsx
@@ -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;
diff --git a/src/app/module-editor/[moduleId]/modals/AttributeEditorModal.tsx b/src/app/module-editor/[moduleId]/modals/AttributeEditorModal.tsx
new file mode 100644
index 0000000..30d5b5e
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/modals/AttributeEditorModal.tsx
@@ -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) => {
+ const [isInitial, setIsInitial] = useState(true);
+ const [isNullableInputShown, setIsNullableInputShown] = useState(true);
+ const [copyTypeId, setCopyTypeId] = useState();
+
+ const form = useForm>({
+ initialValues: innerProps.isEditing
+ ? innerProps.entity
+ : ({
+ label: "",
+ name: "",
+ typeId: undefined,
+ type: undefined,
+ isApplicableToGroup: false,
+ isShownOnDashboard: false,
+ isHighlightIfExpired: false,
+ isNullable: false,
+ defaultValue: null,
+ description: "",
+ } as Partial),
+ 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 (
+ context.closeContextModal(id)}>
+
+
+ {
+ form.setFieldValue("type", type);
+ form.setFieldValue("typeId", type.id);
+ }}
+ />
+
+
+ {(form.values.type?.type === "datetime" ||
+ form.values.type?.type === "date") && (
+
+ )}
+ {isNullableInputShown && (
+
+ )}
+ {form.values.type && copyTypeId === form.values.type.id && (
+
+ )}
+
+
+
+ );
+};
+
+export default AttributeEditorModal;
diff --git a/src/app/module-editor/[moduleId]/page.tsx b/src/app/module-editor/[moduleId]/page.tsx
new file mode 100644
index 0000000..e1e0e0f
--- /dev/null
+++ b/src/app/module-editor/[moduleId]/page.tsx
@@ -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 (
+
+
+
+ }>
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/modules/components/InnerAttributesTable.tsx b/src/app/modules/components/InnerAttributesTable.tsx
new file mode 100644
index 0000000..6f8580e
--- /dev/null
+++ b/src/app/modules/components/InnerAttributesTable.tsx
@@ -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 = ({ attributes, moduleId }) => {
+ const innerColumns = useAttributesInnerTableColumns();
+
+ if (attributes.length === 0) {
+ return (
+
+ В модуле нет атрибутов
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+export default InnerAttributesTable;
diff --git a/src/app/modules/components/ModulesHeader.tsx b/src/app/modules/components/ModulesHeader.tsx
new file mode 100644
index 0000000..d47823a
--- /dev/null
+++ b/src/app/modules/components/ModulesHeader.tsx
@@ -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 (
+
+
+ Создать модуль
+
+
+ );
+};
+
+export default ModulesHeader;
diff --git a/src/app/modules/components/ModulesTable.tsx b/src/app/modules/components/ModulesTable.tsx
new file mode 100644
index 0000000..cd0519e
--- /dev/null
+++ b/src/app/modules/components/ModulesTable.tsx
@@ -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([]);
+
+ const outerColumns = useModulesTableColumns({
+ expandedModuleIds,
+ setExpandedModuleIds,
+ });
+
+ return (
+ (
+
+ ),
+ }}
+ mx={isMobile ? "xs" : 0}
+ />
+ );
+};
+
+export default ModulesTable;
diff --git a/src/app/modules/components/PageBody.tsx b/src/app/modules/components/PageBody.tsx
new file mode 100644
index 0000000..17218bb
--- /dev/null
+++ b/src/app/modules/components/PageBody.tsx
@@ -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 = () => (
+
+
+
+);
+
+export default PageBody;
diff --git a/src/app/modules/contexts/ModulesContext.tsx b/src/app/modules/contexts/ModulesContext.tsx
new file mode 100644
index 0000000..1d48b4e
--- /dev/null
+++ b/src/app/modules/contexts/ModulesContext.tsx
@@ -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(useModulesContextState, "Modules");
diff --git a/src/app/modules/hooks/useAttributesInnerTableColumns.tsx b/src/app/modules/hooks/useAttributesInnerTableColumns.tsx
new file mode 100644
index 0000000..2950122
--- /dev/null
+++ b/src/app/modules/hooks/useAttributesInnerTableColumns.tsx
@@ -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 ? : );
+
+ 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 ? : ;
+ }
+
+ 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 => {attr.description},
+ },
+ ] as DataTableColumn[],
+ [isMobile]
+ );
+};
+
+export default useAttributesInnerTableColumns;
diff --git a/src/app/modules/hooks/useModulesTableActions.tsx b/src/app/modules/hooks/useModulesTableActions.tsx
new file mode 100644
index 0000000..cdbbeff
--- /dev/null
+++ b/src/app/modules/hooks/useModulesTableActions.tsx
@@ -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, _: 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: (
+
+ Вы уверены, что хотите удалить модуль "{module.label}"?
+
+ ),
+ confirmProps: { color: "red" },
+ onConfirm: () => deleteMutation.mutate({ path: { pk: module.id } }),
+ });
+ };
+
+ return {
+ onUpdate,
+ onDelete,
+ };
+};
+
+export default useModulesActions;
diff --git a/src/app/modules/hooks/useModulesTableColumns.tsx b/src/app/modules/hooks/useModulesTableColumns.tsx
new file mode 100644
index 0000000..6ce90eb
--- /dev/null
+++ b/src/app/modules/hooks/useModulesTableColumns.tsx
@@ -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 ;
+ if (expandedModuleIds.length === 0) return ;
+ return ;
+ };
+
+ return useMemo(
+ () =>
+ [
+ {
+ accessor: "",
+ title: (
+
+
+ {getExpandAllIcon()}
+
+ Модуль
+
+ ),
+ render: ({ id, label }) => (
+
+ {expandedModuleIds.includes(id) ? (
+
+ ) : (
+
+ )}
+ {label}
+
+ ),
+ },
+ {
+ title: "Описание",
+ accessor: "description",
+ },
+ {
+ title: "Зависит от модулей",
+ accessor: "dependsOn",
+ render: module => (
+
+ {module.dependsOn?.map(m => m.label).join(", ")}
+
+ ),
+ },
+ {
+ accessor: "actions",
+ title: isMobile ? "" : "Действия",
+ sortable: false,
+ textAlign: "center",
+ width: "0%",
+ render: module => (
+
+ onDelete(module)}
+ onChange={() => onUpdate(module)}
+ disabled={module.isBuiltIn}
+ />
+
+ ),
+ },
+ ] as DataTableColumn[],
+ [expandedModuleIds, modules, isMobile]
+ );
+};
+
+export default useModulesTableColumns;
diff --git a/src/app/modules/hooks/useModulesWithAttrsList.ts b/src/app/modules/hooks/useModulesWithAttrsList.ts
new file mode 100644
index 0000000..2f3efd0
--- /dev/null
+++ b/src/app/modules/hooks/useModulesWithAttrsList.ts
@@ -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;
diff --git a/src/app/modules/page.tsx b/src/app/modules/page.tsx
new file mode 100644
index 0000000..1d037ce
--- /dev/null
+++ b/src/app/modules/page.tsx
@@ -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 (
+
+
+
+ }>
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/layout/Navbar/data/linksData.ts b/src/components/layout/Navbar/data/linksData.ts
index 37688f2..a38455a 100644
--- a/src/components/layout/Navbar/data/linksData.ts
+++ b/src/components/layout/Navbar/data/linksData.ts
@@ -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: "Клиенты",
diff --git a/src/components/ui/BaseTable/components/UpdateDeleteTableActions.tsx b/src/components/ui/BaseTable/components/UpdateDeleteTableActions.tsx
index 3762a98..2e9d60a 100644
--- a/src/components/ui/BaseTable/components/UpdateDeleteTableActions.tsx
+++ b/src/components/ui/BaseTable/components/UpdateDeleteTableActions.tsx
@@ -19,6 +19,7 @@ type Props = {
otherActions?: ActionData[];
dotsForMobile?: boolean;
style?: CSSProperties;
+ disabled?: boolean;
};
const UpdateDeleteTableActions: FC = ({
@@ -27,6 +28,7 @@ const UpdateDeleteTableActions: FC = ({
otherActions,
style,
dotsForMobile = false,
+ disabled = false,
}) => {
const isMobile = useIsMobile();
@@ -45,6 +47,7 @@ const UpdateDeleteTableActions: FC = ({
onClick={onChange}
icon={}
label={"Редактировать"}
+ disabled={disabled}
/>
{otherActions?.map(
action =>
@@ -54,6 +57,7 @@ const UpdateDeleteTableActions: FC = ({
icon={action.icon}
label={action.label}
key={action.label}
+ disabled={disabled}
/>
)
)}
@@ -61,6 +65,7 @@ const UpdateDeleteTableActions: FC = ({
onClick={onDelete}
icon={}
label={"Удалить"}
+ disabled={disabled}
/>
@@ -76,6 +81,7 @@ const UpdateDeleteTableActions: FC = ({
e.stopPropagation();
onChange();
}}
+ disabled={disabled}
tipLabel={"Редактировать"}>
@@ -87,6 +93,7 @@ const UpdateDeleteTableActions: FC = ({
e.stopPropagation();
action.onClick();
}}
+ disabled={disabled}
key={action.label}
tipLabel={action.label}>
{action.icon}
@@ -99,6 +106,7 @@ const UpdateDeleteTableActions: FC = ({
e.stopPropagation();
onDelete();
}}
+ disabled={disabled}
tipLabel={"Удалить"}>
diff --git a/src/components/ui/DropdownMenuItem/DropdownMenuItem.tsx b/src/components/ui/DropdownMenuItem/DropdownMenuItem.tsx
index 0f33cb6..a5e254c 100644
--- a/src/components/ui/DropdownMenuItem/DropdownMenuItem.tsx
+++ b/src/components/ui/DropdownMenuItem/DropdownMenuItem.tsx
@@ -5,16 +5,25 @@ type Props = {
onClick: MouseEventHandler;
icon: ReactNode;
label: string;
+ disabled?: boolean;
};
-const DropdownMenuItem: FC = ({ icon, label, onClick }) => {
+const DropdownMenuItem: FC = ({
+ icon,
+ label,
+ onClick,
+ disabled = false,
+}) => {
const onClickWrapper: MouseEventHandler = e => {
e.stopPropagation();
+ if (disabled) return;
onClick(e);
};
return (
-
+
{icon}
{label}
diff --git a/src/lib/client/@tanstack/react-query.gen.ts b/src/lib/client/@tanstack/react-query.gen.ts
index f168c55..6fa2f61 100644
--- a/src/lib/client/@tanstack/react-query.gen.ts
+++ b/src/lib/client/@tanstack/react-query.gen.ts
@@ -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 = (
return [params];
};
+export const getAttributesQueryKey = (options?: Options) =>
+ createQueryKey("getAttributes", options);
+
+/**
+ * Get Attributes
+ */
+export const getAttributesOptions = (options?: Options) => {
+ 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
+) => createQueryKey("createAttribute", options);
+
+/**
+ * Create Attribute
+ */
+export const createAttributeOptions = (
+ options: Options
+) => {
+ 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>
+): UseMutationOptions<
+ CreateAttributeResponse2,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ CreateAttributeResponse2,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await createAttribute({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+/**
+ * Delete Attribute
+ */
+export const deleteAttributeMutation = (
+ options?: Partial>
+): UseMutationOptions<
+ DeleteAttributeResponse2,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ DeleteAttributeResponse2,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await deleteAttribute({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+/**
+ * Update Attribute
+ */
+export const updateAttributeMutation = (
+ options?: Partial>
+): UseMutationOptions<
+ UpdateAttributeResponse2,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ UpdateAttributeResponse2,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await updateAttribute({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+export const updateAttributeLabelQueryKey = (
+ options: Options
+) => createQueryKey("updateAttributeLabel", options);
+
+/**
+ * Update Attribute Label
+ */
+export const updateAttributeLabelOptions = (
+ options: Options
+) => {
+ 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>
+): UseMutationOptions<
+ UpdateAttributeLabelResponse2,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ UpdateAttributeLabelResponse2,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await updateAttributeLabel({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+export const getAttributeTypesQueryKey = (
+ options?: Options
+) => createQueryKey("getAttributeTypes", options);
+
+/**
+ * Get Attribute Types
+ */
+export const getAttributeTypesOptions = (
+ options?: Options
+) => {
+ 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) =>
createQueryKey("getBoards", options);
@@ -1054,6 +1295,186 @@ export const getModulesOptions = (options?: Options) => {
});
};
+export const getModulesWithAttributesQueryKey = (
+ options?: Options
+) => createQueryKey("getModulesWithAttributes", options);
+
+/**
+ * Get Modules With Attributes
+ */
+export const getModulesWithAttributesOptions = (
+ options?: Options
+) => {
+ 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
+) => createQueryKey("getModuleWithAttributes", options);
+
+/**
+ * Get Module With Attributes
+ */
+export const getModuleWithAttributesOptions = (
+ options: Options
+) => {
+ 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>
+): UseMutationOptions<
+ UpdateModuleResponse,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ UpdateModuleResponse,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await updateModule({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+/**
+ * Delete Module
+ */
+export const deleteModuleMutation = (
+ options?: Partial>
+): UseMutationOptions<
+ DeleteModuleResponse2,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ DeleteModuleResponse2,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await deleteModule({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+/**
+ * Remove Attribute From Module
+ */
+export const removeAttributeFromModuleMutation = (
+ options?: Partial>
+): UseMutationOptions<
+ RemoveAttributeFromModuleResponse,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ RemoveAttributeFromModuleResponse,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await removeAttributeFromModule({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
+export const addAttributeToModuleQueryKey = (
+ options: Options
+) => createQueryKey("addAttributeToModule", options);
+
+/**
+ * Add Attribute To Module
+ */
+export const addAttributeToModuleOptions = (
+ options: Options
+) => {
+ 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>
+): UseMutationOptions<
+ AddAttributeToModuleResponse,
+ AxiosError,
+ Options
+> => {
+ const mutationOptions: UseMutationOptions<
+ AddAttributeToModuleResponse,
+ AxiosError,
+ Options
+ > = {
+ mutationFn: async localOptions => {
+ const { data } = await addAttributeToModule({
+ ...options,
+ ...localOptions,
+ throwOnError: true,
+ });
+ return data;
+ },
+ };
+ return mutationOptions;
+};
+
export const getClientsQueryKey = (options?: Options) =>
createQueryKey("getClients", options);
diff --git a/src/lib/client/sdk.gen.ts b/src/lib/client/sdk.gen.ts
index 1af52c7..26e8d28 100644
--- a/src/lib/client/sdk.gen.ts
+++ b/src/lib/client/sdk.gen.ts
@@ -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;
};
+/**
+ * Get Attributes
+ */
+export const getAttributes = (
+ options?: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options?: Options
+) => {
+ 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 = (
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 = (
+ options?: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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 = (
+ options: Options
+) => {
+ 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
*/
diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts
index 4efdadf..756f3eb 100644
--- a/src/lib/client/types.gen.ts
+++ b/src/lib/client/types.gen.ts
@@ -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;
+};
+
+/**
+ * GetAllAttributesResponse
+ */
+export type GetAllAttributesResponse = {
+ /**
+ * Items
+ */
+ items: Array;
+};
+
/**
* GetAllModulesResponse
*/
@@ -1200,6 +1402,16 @@ export type GetAllModulesResponse = {
items: Array;
};
+/**
+ * GetAllWithAttributesResponse
+ */
+export type GetAllWithAttributesResponse = {
+ /**
+ * Items
+ */
+ items: Array;
+};
+
/**
* GetBarcodeAttributesResponse
*/
@@ -1250,6 +1462,13 @@ export type GetBoardsResponse = {
items: Array;
};
+/**
+ * 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;
+ /**
+ * Tabs
+ */
+ tabs: Array;
+ /**
+ * Attributes
+ */
+ attributes: Array;
+};
+
/**
* 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;
diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts
index 58a7f3e..096a305 100644
--- a/src/lib/client/zod.gen.ts
+++ b/src/lib/client/zod.gen.ts
@@ -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()),
diff --git a/src/modals/modals.ts b/src/modals/modals.ts
index c5ac8a7..b91f9bf 100644
--- a/src/modals/modals.ts
+++ b/src/modals/modals.ts
@@ -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,
};
diff --git a/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/ProductView.tsx b/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/ProductView.tsx
index 799d0c0..91faeef 100644
--- a/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/ProductView.tsx
+++ b/src/modules/dealModularEditorTabs/FulfillmentBase/desktop/components/ProductView/ProductView.tsx
@@ -117,10 +117,12 @@ const ProductView: FC = ({ dealProduct }) => {
)}
{dealProduct.product.name}
-
- Штрихкоды:
- {dealProduct.product.barcodes.join(", ")}
-
+ {dealProduct.product.barcodes && (
+
+ Штрихкоды:
+ {dealProduct.product.barcodes.join(", ")}
+
+ )}
{
+ const res = localDateTimeToString(utcDateToLocal(datetime));
+ return res.substring(0, 11);
+};