feat: modules creation
This commit is contained in:
@ -2,10 +2,14 @@
|
||||
|
||||
import { FC } from "react";
|
||||
import { Group } from "@mantine/core";
|
||||
import { useModulesContext } from "@/app/modules/contexts/ModulesContext";
|
||||
import InlineButton from "@/components/ui/InlineButton/InlineButton";
|
||||
import useIsMobile from "@/hooks/utils/useIsMobile";
|
||||
|
||||
const ModulesHeader: FC = () => {
|
||||
const {
|
||||
modulesActions: { onCreate },
|
||||
} = useModulesContext();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
@ -14,7 +18,9 @@ const ModulesHeader: FC = () => {
|
||||
justify={"space-between"}
|
||||
mt={isMobile ? "xs" : ""}
|
||||
mx={isMobile ? "xs" : ""}>
|
||||
<InlineButton w={isMobile ? "100%" : ""}>
|
||||
<InlineButton
|
||||
onClick={onCreate}
|
||||
w={isMobile ? "100%" : ""}>
|
||||
Создать модуль
|
||||
</InlineButton>
|
||||
</Group>
|
||||
|
||||
@ -3,18 +3,22 @@
|
||||
import useModulesWithAttrsList from "@/app/modules/hooks/useModulesWithAttrsList";
|
||||
import { ModuleWithAttributesSchema } from "@/lib/client";
|
||||
import makeContext from "@/lib/contextFactory/contextFactory";
|
||||
import useModulesActions, { ModulesActions } from "@/app/modules/hooks/useModulesActions";
|
||||
|
||||
type ModulesContextState = {
|
||||
modules: ModuleWithAttributesSchema[];
|
||||
refetchModules: () => void;
|
||||
modulesActions: ModulesActions;
|
||||
};
|
||||
|
||||
const useModulesContextState = (): ModulesContextState => {
|
||||
const { modules, refetch } = useModulesWithAttrsList();
|
||||
const modulesActions = useModulesActions({ refetchModules: refetch });
|
||||
|
||||
return {
|
||||
modules,
|
||||
refetchModules: refetch,
|
||||
modulesActions,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -5,10 +5,14 @@ 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 {
|
||||
createModuleMutation,
|
||||
deleteModuleMutation,
|
||||
} from "@/lib/client/@tanstack/react-query.gen";
|
||||
import { notifications } from "@/lib/notifications";
|
||||
|
||||
export type ModulesActions = {
|
||||
onCreate: () => void;
|
||||
onUpdate: (module: ModuleSchemaOutput) => void;
|
||||
onDelete: (module: ModuleSchemaOutput) => void;
|
||||
};
|
||||
@ -18,10 +22,6 @@ type Props = {
|
||||
};
|
||||
|
||||
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({
|
||||
@ -29,6 +29,34 @@ const useModulesActions = ({ refetchModules }: Props): ModulesActions => {
|
||||
});
|
||||
};
|
||||
|
||||
const createMutation = useMutation({
|
||||
...createModuleMutation(),
|
||||
onError,
|
||||
});
|
||||
|
||||
const onCreate = () => {
|
||||
modals.openContextModal({
|
||||
modal: "moduleCreatorModal",
|
||||
title: "Создание модуля",
|
||||
innerProps: {
|
||||
onCreate: (data, onSuccess) =>
|
||||
createMutation.mutate(
|
||||
{ body: { entity: data } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
refetchModules();
|
||||
onSuccess && onSuccess();
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdate = (module: ModuleSchemaOutput) => {
|
||||
redirect(`/module-editor/${module.id}`);
|
||||
};
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
...deleteModuleMutation(),
|
||||
onError,
|
||||
@ -37,7 +65,7 @@ const useModulesActions = ({ refetchModules }: Props): ModulesActions => {
|
||||
|
||||
const onDelete = (module: ModuleSchemaOutput) => {
|
||||
modals.openConfirmModal({
|
||||
title: "Удаление услуги из сделки",
|
||||
title: "Удаление модуля",
|
||||
children: (
|
||||
<Text>
|
||||
Вы уверены, что хотите удалить модуль "{module.label}"?
|
||||
@ -49,6 +77,7 @@ const useModulesActions = ({ refetchModules }: Props): ModulesActions => {
|
||||
};
|
||||
|
||||
return {
|
||||
onCreate,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
};
|
||||
@ -8,7 +8,6 @@ import {
|
||||
} 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";
|
||||
@ -24,8 +23,10 @@ const useModulesTableColumns = ({
|
||||
setExpandedModuleIds,
|
||||
}: Props) => {
|
||||
const isMobile = useIsMobile();
|
||||
const { modules, refetchModules } = useModulesContext();
|
||||
const { onUpdate, onDelete } = useModulesActions({ refetchModules });
|
||||
const {
|
||||
modules,
|
||||
modulesActions: { onUpdate, onDelete },
|
||||
} = useModulesContext();
|
||||
|
||||
const onExpandAllClick = () => {
|
||||
if (expandedModuleIds.length !== modules.length) {
|
||||
|
||||
54
src/app/modules/modals/ModuleCreatorModal.tsx
Normal file
54
src/app/modules/modals/ModuleCreatorModal.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Stack, Textarea, TextInput } from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { ContextModalProps } from "@mantine/modals";
|
||||
import { CreateModuleSchema } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
onCreate: (data: CreateModuleSchema, onSuccess?: () => void) => void;
|
||||
};
|
||||
|
||||
const ModuleCreatorModal = ({
|
||||
context,
|
||||
id,
|
||||
innerProps,
|
||||
}: ContextModalProps<Props>) => {
|
||||
const form = useForm<CreateModuleSchema>({
|
||||
initialValues: {
|
||||
label: "",
|
||||
description: "",
|
||||
},
|
||||
validate: {
|
||||
label: label => !label?.trim() && "Название не заполнено",
|
||||
},
|
||||
});
|
||||
|
||||
const close = () => context.closeContextModal(id);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={form.onSubmit(values =>
|
||||
innerProps.onCreate(values, close)
|
||||
)}>
|
||||
<Stack gap={"md"}>
|
||||
<TextInput
|
||||
withAsterisk
|
||||
label={"Название"}
|
||||
{...form.getInputProps("label")}
|
||||
/>
|
||||
<Textarea
|
||||
label={"Описание"}
|
||||
{...form.getInputProps("description")}
|
||||
/>
|
||||
<Button
|
||||
type={"submit"}
|
||||
variant={"default"}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModuleCreatorModal;
|
||||
@ -71,7 +71,7 @@ export const useProductsTableColumns = ({
|
||||
}
|
||||
showLabel={"Показать все"}
|
||||
hideLabel={"Скрыть"}>
|
||||
{product.barcodes.map(barcode => (
|
||||
{product.barcodes?.map(barcode => (
|
||||
<List.Item key={barcode}>
|
||||
{barcode}
|
||||
</List.Item>
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
createDealService,
|
||||
createDealTag,
|
||||
createMarketplace,
|
||||
createModule,
|
||||
createProduct,
|
||||
createProject,
|
||||
createService,
|
||||
@ -144,6 +145,9 @@ import type {
|
||||
CreateMarketplaceData,
|
||||
CreateMarketplaceError,
|
||||
CreateMarketplaceResponse2,
|
||||
CreateModuleData,
|
||||
CreateModuleError,
|
||||
CreateModuleResponse2,
|
||||
CreateProductData,
|
||||
CreateProductError,
|
||||
CreateProductResponse2,
|
||||
@ -1295,6 +1299,54 @@ export const getModulesOptions = (options?: Options<GetModulesData>) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const createModuleQueryKey = (options: Options<CreateModuleData>) =>
|
||||
createQueryKey("createModule", options);
|
||||
|
||||
/**
|
||||
* Create Module
|
||||
*/
|
||||
export const createModuleOptions = (options: Options<CreateModuleData>) => {
|
||||
return queryOptions({
|
||||
queryFn: async ({ queryKey, signal }) => {
|
||||
const { data } = await createModule({
|
||||
...options,
|
||||
...queryKey[0],
|
||||
signal,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
queryKey: createModuleQueryKey(options),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Module
|
||||
*/
|
||||
export const createModuleMutation = (
|
||||
options?: Partial<Options<CreateModuleData>>
|
||||
): UseMutationOptions<
|
||||
CreateModuleResponse2,
|
||||
AxiosError<CreateModuleError>,
|
||||
Options<CreateModuleData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
CreateModuleResponse2,
|
||||
AxiosError<CreateModuleError>,
|
||||
Options<CreateModuleData>
|
||||
> = {
|
||||
mutationFn: async localOptions => {
|
||||
const { data } = await createModule({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
export const getModulesWithAttributesQueryKey = (
|
||||
options?: Options<GetModulesWithAttributesData>
|
||||
) => createQueryKey("getModulesWithAttributes", options);
|
||||
|
||||
@ -50,6 +50,9 @@ import type {
|
||||
CreateMarketplaceData,
|
||||
CreateMarketplaceErrors,
|
||||
CreateMarketplaceResponses,
|
||||
CreateModuleData,
|
||||
CreateModuleErrors,
|
||||
CreateModuleResponses,
|
||||
CreateProductData,
|
||||
CreateProductErrors,
|
||||
CreateProductResponses,
|
||||
@ -292,6 +295,8 @@ import {
|
||||
zCreateDealTagResponse2,
|
||||
zCreateMarketplaceData,
|
||||
zCreateMarketplaceResponse2,
|
||||
zCreateModuleData,
|
||||
zCreateModuleResponse2,
|
||||
zCreateProductData,
|
||||
zCreateProductResponse2,
|
||||
zCreateProjectData,
|
||||
@ -1088,6 +1093,33 @@ export const getModules = <ThrowOnError extends boolean = false>(
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create Module
|
||||
*/
|
||||
export const createModule = <ThrowOnError extends boolean = false>(
|
||||
options: Options<CreateModuleData, ThrowOnError>
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).post<
|
||||
CreateModuleResponses,
|
||||
CreateModuleErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
requestValidator: async data => {
|
||||
return await zCreateModuleData.parseAsync(data);
|
||||
},
|
||||
responseType: "json",
|
||||
responseValidator: async data => {
|
||||
return await zCreateModuleResponse2.parseAsync(data);
|
||||
},
|
||||
url: "/crm/v1/module/",
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get Modules With Attributes
|
||||
*/
|
||||
|
||||
@ -660,6 +660,37 @@ export type CreateMarketplaceSchema = {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateModuleRequest
|
||||
*/
|
||||
export type CreateModuleRequest = {
|
||||
entity: CreateModuleSchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateModuleResponse
|
||||
*/
|
||||
export type CreateModuleResponse = {
|
||||
/**
|
||||
* Message
|
||||
*/
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateModuleSchema
|
||||
*/
|
||||
export type CreateModuleSchema = {
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* CreateProductRequest
|
||||
*/
|
||||
@ -1823,7 +1854,7 @@ export type ModuleTabSchema = {
|
||||
/**
|
||||
* Iconname
|
||||
*/
|
||||
iconName: string;
|
||||
iconName: string | null;
|
||||
/**
|
||||
* Device
|
||||
*/
|
||||
@ -3715,6 +3746,32 @@ export type GetModulesResponses = {
|
||||
|
||||
export type GetModulesResponse = GetModulesResponses[keyof GetModulesResponses];
|
||||
|
||||
export type CreateModuleData = {
|
||||
body: CreateModuleRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: "/crm/v1/module/";
|
||||
};
|
||||
|
||||
export type CreateModuleErrors = {
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type CreateModuleError = CreateModuleErrors[keyof CreateModuleErrors];
|
||||
|
||||
export type CreateModuleResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: CreateModuleResponse;
|
||||
};
|
||||
|
||||
export type CreateModuleResponse2 =
|
||||
CreateModuleResponses[keyof CreateModuleResponses];
|
||||
|
||||
export type GetModulesWithAttributesData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@ -559,6 +559,28 @@ export const zCreateMarketplaceResponse = z.object({
|
||||
entity: zMarketplaceSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateModuleSchema
|
||||
*/
|
||||
export const zCreateModuleSchema = z.object({
|
||||
label: z.string(),
|
||||
description: z.union([z.string(), z.null()]),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateModuleRequest
|
||||
*/
|
||||
export const zCreateModuleRequest = z.object({
|
||||
entity: zCreateModuleSchema,
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateModuleResponse
|
||||
*/
|
||||
export const zCreateModuleResponse = z.object({
|
||||
message: z.string(),
|
||||
});
|
||||
|
||||
/**
|
||||
* CreateProductSchema
|
||||
*/
|
||||
@ -637,7 +659,7 @@ export const zModuleTabSchema = z.object({
|
||||
id: z.int(),
|
||||
key: z.string(),
|
||||
label: z.string(),
|
||||
iconName: z.string(),
|
||||
iconName: z.union([z.string(), z.null()]),
|
||||
device: z.string(),
|
||||
});
|
||||
|
||||
@ -2058,6 +2080,17 @@ export const zGetModulesData = z.object({
|
||||
*/
|
||||
export const zGetModulesResponse = zGetAllModulesResponse;
|
||||
|
||||
export const zCreateModuleData = z.object({
|
||||
body: zCreateModuleRequest,
|
||||
path: z.optional(z.never()),
|
||||
query: z.optional(z.never()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
export const zCreateModuleResponse2 = zCreateModuleResponse;
|
||||
|
||||
export const zGetModulesWithAttributesData = z.object({
|
||||
body: z.optional(z.never()),
|
||||
path: z.optional(z.never()),
|
||||
|
||||
@ -6,6 +6,7 @@ import DealsBoardFiltersModal from "@/app/deals/modals/DealsBoardFiltersModal/De
|
||||
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 ModuleCreatorModal from "@/app/modules/modals/ModuleCreatorModal";
|
||||
import {
|
||||
ServiceCategoryEditorModal,
|
||||
ServiceEditorModal,
|
||||
@ -44,4 +45,5 @@ export const modals = {
|
||||
marketplaceEditorModal: MarketplaceEditorModal,
|
||||
dealTagModal: DealTagModal,
|
||||
attributeEditorModal: AttributeEditorModal,
|
||||
moduleCreatorModal: ModuleCreatorModal,
|
||||
};
|
||||
|
||||
@ -8,7 +8,7 @@ type ModuleTab = {
|
||||
id: number;
|
||||
key: string;
|
||||
label: string;
|
||||
iconName: string;
|
||||
iconName?: string;
|
||||
moduleId: number;
|
||||
};
|
||||
|
||||
@ -16,6 +16,7 @@ type Module = {
|
||||
id: number;
|
||||
label: string;
|
||||
description: string;
|
||||
isBuiltIn: boolean;
|
||||
tabs: ModuleTab[];
|
||||
};
|
||||
|
||||
@ -77,6 +78,7 @@ const generateRows = (modules: Module[]) => {
|
||||
const iconsToImport = new Set<string>();
|
||||
for (const module of modules) {
|
||||
for (const tab of module.tabs) {
|
||||
if (!tab.iconName) continue;
|
||||
iconsToImport.add(tab.iconName);
|
||||
}
|
||||
}
|
||||
@ -98,7 +100,7 @@ const modulesFileGen = () => {
|
||||
axios
|
||||
.get(ENDPOINT)
|
||||
.then((response: AxiosResponse<ModulesResponse>) => {
|
||||
generateRows(response.data.items);
|
||||
generateRows(response.data.items.filter(item => item.isBuiltIn));
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user