diff --git a/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx b/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx index 5a9f68b..9f8e2a9 100644 --- a/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx +++ b/src/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput.tsx @@ -1,6 +1,7 @@ import { CSSProperties, FC, JSX } from "react"; import { Checkbox, NumberInput, TextInput } from "@mantine/core"; import { DatePickerInput, DateTimePicker } from "@mantine/dates"; +import AttrOptionSelect from "@/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/AttrOptionSelect"; import { DealModuleAttributeSchema } from "@/lib/client"; import { naiveDateTimeStringToUtc } from "@/utils/datetime"; @@ -25,15 +26,13 @@ const AttributeValueInput: FC = ({ error, }; - const renderCheckbox = () => { - return ( - onChange(e.currentTarget.checked)} - /> - ); - }; + const renderCheckbox = () => ( + onChange(e.currentTarget.checked)} + /> + ); const renderDatePicker = () => ( = ({ /> ); + const renderSelect = () => { + if (!attrInfo.select?.id) return <>; + return ( + + ); + }; + const renderingFuncMap: Record JSX.Element> = { bool: renderCheckbox, date: renderDatePicker, @@ -91,6 +102,7 @@ const AttributeValueInput: FC = ({ str: renderTextInput, int: renderNumberInput, float: renderNumberInput, + select: renderSelect, }; const render = renderingFuncMap[attrInfo.type.type]; diff --git a/src/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/AttrOptionSelect.tsx b/src/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/AttrOptionSelect.tsx new file mode 100644 index 0000000..4772b72 --- /dev/null +++ b/src/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/AttrOptionSelect.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import { omit } from "lodash"; +import useAttributeOptionsList from "@/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/useAttributeOptionsList"; +import ObjectSelect from "@/components/selects/ObjectSelect/ObjectSelect"; +import { AttrOptionSchema } from "@/lib/client"; + +type Props = { + value: any; + onChange: (val: any) => void; + selectId: number; + error?: string; + label?: string; +}; + +const AttrOptionSelect = (props: Props) => { + const { options } = useAttributeOptionsList(props); + + const [selectedOption, setSelectedOption] = useState(); + + useEffect(() => { + if (!props.value) { + setSelectedOption(undefined); + return; + } + setSelectedOption(options.find(option => option.value === props.value)); + }, [props.value, options]); + + const restProps = omit(props, ["value, onChange", "selectId"]); + + return ( + { + setSelectedOption(option); + props.onChange(option.value); + }} + onClear={() => { + setSelectedOption(undefined); + props.onChange(null); + }} + getLabelFn={option => option.label} + clearable + searchable + /> + ); +}; + +export default AttrOptionSelect; diff --git a/src/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/useAttributeOptionsList.ts b/src/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/useAttributeOptionsList.ts new file mode 100644 index 0000000..a530a26 --- /dev/null +++ b/src/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/useAttributeOptionsList.ts @@ -0,0 +1,19 @@ +import { useQuery } from "@tanstack/react-query"; +import { getAttrSelectOptionsOptions } from "@/lib/client/@tanstack/react-query.gen"; + +type Props = { + selectId: number; +}; + +const useAttributeSelectsList = ({ selectId }: Props) => { + const { data, refetch } = useQuery( + getAttrSelectOptionsOptions({ path: { selectId } }) + ); + + return { + options: data?.items ?? [], + refetch, + }; +}; + +export default useAttributeSelectsList; diff --git a/src/app/module-editor/[moduleId]/components/shared/AttrSelectSelect/AttrSelectSelect.tsx b/src/app/module-editor/[moduleId]/components/shared/AttrSelectSelect/AttrSelectSelect.tsx new file mode 100644 index 0000000..4769f90 --- /dev/null +++ b/src/app/module-editor/[moduleId]/components/shared/AttrSelectSelect/AttrSelectSelect.tsx @@ -0,0 +1,24 @@ +import ObjectSelect, { ObjectSelectProps } from "@/components/selects/ObjectSelect/ObjectSelect"; +import { AttributeSelectSchema } from "@/lib/client"; +import useAttributeSelectsList from "./useAttributeSelectsList"; + + +type Props = Omit< + ObjectSelectProps, + "data" | "getLabelFn" +>; + +const AttrSelectSelect = (props: Props) => { + const { selects } = useAttributeSelectsList(); + + return ( + select.label} + data={selects} + {...props} + /> + ); +}; + +export default AttrSelectSelect; diff --git a/src/app/module-editor/[moduleId]/components/shared/AttrSelectSelect/useAttributeSelectsList.ts b/src/app/module-editor/[moduleId]/components/shared/AttrSelectSelect/useAttributeSelectsList.ts new file mode 100644 index 0000000..6fc5505 --- /dev/null +++ b/src/app/module-editor/[moduleId]/components/shared/AttrSelectSelect/useAttributeSelectsList.ts @@ -0,0 +1,13 @@ +import { useQuery } from "@tanstack/react-query"; +import { getAttrSelectsOptions } from "@/lib/client/@tanstack/react-query.gen"; + +const useAttributeSelectsList = () => { + const { data, refetch } = useQuery(getAttrSelectsOptions()); + + return { + selects: data?.items ?? [], + refetch, + }; +}; + +export default useAttributeSelectsList; diff --git a/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx b/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx index 30a26b4..16d1faa 100644 --- a/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx +++ b/src/app/module-editor/[moduleId]/components/shared/AttributesTable/useAttributesTableColumns.tsx @@ -20,6 +20,10 @@ const useAttributesTableColumns = () => { { title: "Тип", accessor: "type.name", + render: attr => + attr.type.type === "select" + ? `Выбор "${attr.label}"` + : attr.type.name, }, { accessor: "actions", diff --git a/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx b/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx index 70a3930..b86cfff 100644 --- a/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx +++ b/src/app/module-editor/[moduleId]/components/shared/DefaultAttributeValueInput/DefaultAttributeValueInput.tsx @@ -1,11 +1,12 @@ import { Checkbox, NumberInput, TextInput } from "@mantine/core"; import { DatePickerInput, DateTimePicker } from "@mantine/dates"; import { UseFormReturnType } from "@mantine/form"; -import { UpdateAttributeSchema } from "@/lib/client"; +import AttrOptionSelect from "@/app/module-editor/[moduleId]/components/shared/AttrOptionSelect/AttrOptionSelect"; +import { AttributeSchema } from "@/lib/client"; import { naiveDateTimeStringToUtc } from "@/utils/datetime"; type Props = { - form: UseFormReturnType>; + form: UseFormReturnType>; }; const DefaultAttributeValueInput = ({ form }: Props) => { @@ -87,8 +88,18 @@ const DefaultAttributeValueInput = ({ form }: Props) => { } /> ); + } else if (type === "select") { + if (!form.values.select?.id) return <>; + return ( + form.setFieldValue("defaultValue", value)} + selectId={form.values.select?.id} + /> + ); } - return <>; }; diff --git a/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx b/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx index ef5cea2..279d145 100644 --- a/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx +++ b/src/app/module-editor/[moduleId]/components/shared/ModuleAttribute/ModuleAttribute.tsx @@ -40,7 +40,10 @@ const ModuleAttribute: FC = ({ attribute }) => { align={"center"}> <>{getAttrLabelText()} - Тип: {attribute.type.name} + + Тип: {attribute.type.name}{" "} + {attribute.select && `"${attribute.select.label}"`} + (); - const form = useForm>({ + const form = useForm({ initialValues: innerProps.isEditing ? innerProps.entity : ({ @@ -38,11 +39,13 @@ const AttributeEditorModal = ({ name: "", typeId: undefined, type: undefined, + selectId: undefined, + select: undefined, isApplicableToGroup: false, isNullable: false, defaultValue: null, description: "", - } as Partial), + } as Partial), validate: { label: label => !label?.trim() && "Название не заполнено", type: type => !type && "Тип атрибута не выбран", @@ -62,7 +65,7 @@ const AttributeEditorModal = ({ if (!isInitial) { if (type === "bool") { form.setFieldValue("isNullable", false); - form.setFieldValue("defaultValue", { value: false }); + form.setFieldValue("defaultValue", false); } else { form.setFieldValue("defaultValue", null); } @@ -88,10 +91,27 @@ const AttributeEditorModal = ({ disabled={innerProps.isEditing} {...form.getInputProps("type")} onChange={type => { + if (type.type !== "select") { + form.setFieldValue("select", undefined); + form.setFieldValue("selectId", undefined); + } form.setFieldValue("type", type); form.setFieldValue("typeId", type.id); }} /> + {form.values.type?.type === "select" && ( + { + form.setFieldValue("select", select); + form.setFieldValue("selectId", select.id); + form.setFieldValue("defaultValue", null); + }} + /> + )} { { title: "Тип", accessor: "type.name", + render: attr => + attr.type.type === "select" + ? `Выбор "${attr.label}"` + : attr.type.name, }, { title: "Значение по умолчанию", diff --git a/src/lib/client/@tanstack/react-query.gen.ts b/src/lib/client/@tanstack/react-query.gen.ts index 5a0f230..92a0b55 100644 --- a/src/lib/client/@tanstack/react-query.gen.ts +++ b/src/lib/client/@tanstack/react-query.gen.ts @@ -53,6 +53,8 @@ import { duplicateProductServices, getAttributes, getAttributeTypes, + getAttrSelectOptions, + getAttrSelects, getBarcodeTemplateAttributes, getBarcodeTemplates, getBarcodeTemplateSizes, @@ -230,6 +232,8 @@ import type { DuplicateProductServicesResponse, GetAttributesData, GetAttributeTypesData, + GetAttrSelectOptionsData, + GetAttrSelectsData, GetBarcodeTemplateAttributesData, GetBarcodeTemplatesData, GetBarcodeTemplateSizesData, @@ -374,6 +378,53 @@ const createQueryKey = ( return [params]; }; +export const getAttrSelectsQueryKey = (options?: Options) => + createQueryKey("getAttrSelects", options); + +/** + * Get Attr Selects + */ +export const getAttrSelectsOptions = ( + options?: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getAttrSelects({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getAttrSelectsQueryKey(options), + }); +}; + +export const getAttrSelectOptionsQueryKey = ( + options: Options +) => createQueryKey("getAttrSelectOptions", options); + +/** + * Get Attr Select Options + */ +export const getAttrSelectOptionsOptions = ( + options: Options +) => { + return queryOptions({ + queryFn: async ({ queryKey, signal }) => { + const { data } = await getAttrSelectOptions({ + ...options, + ...queryKey[0], + signal, + throwOnError: true, + }); + return data; + }, + queryKey: getAttrSelectOptionsQueryKey(options), + }); +}; + export const getAttributesQueryKey = (options?: Options) => createQueryKey("getAttributes", options); diff --git a/src/lib/client/sdk.gen.ts b/src/lib/client/sdk.gen.ts index 2ccb4b3..e3a186e 100644 --- a/src/lib/client/sdk.gen.ts +++ b/src/lib/client/sdk.gen.ts @@ -135,6 +135,11 @@ import type { GetAttributesResponses, GetAttributeTypesData, GetAttributeTypesResponses, + GetAttrSelectOptionsData, + GetAttrSelectOptionsErrors, + GetAttrSelectOptionsResponses, + GetAttrSelectsData, + GetAttrSelectsResponses, GetBarcodeTemplateAttributesData, GetBarcodeTemplateAttributesResponses, GetBarcodeTemplatesData, @@ -359,6 +364,10 @@ import { zGetAttributesResponse, zGetAttributeTypesData, zGetAttributeTypesResponse, + zGetAttrSelectOptionsData, + zGetAttrSelectOptionsResponse, + zGetAttrSelectsData, + zGetAttrSelectsResponse, zGetBarcodeTemplateAttributesData, zGetBarcodeTemplateAttributesResponse, zGetBarcodeTemplatesData, @@ -476,6 +485,52 @@ export type Options< meta?: Record; }; +/** + * Get Attr Selects + */ +export const getAttrSelects = ( + options?: Options +) => { + return (options?.client ?? _heyApiClient).get< + GetAttrSelectsResponses, + unknown, + ThrowOnError + >({ + requestValidator: async data => { + return await zGetAttrSelectsData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zGetAttrSelectsResponse.parseAsync(data); + }, + url: "/crm/v1/attr-select/", + ...options, + }); +}; + +/** + * Get Attr Select Options + */ +export const getAttrSelectOptions = ( + options: Options +) => { + return (options.client ?? _heyApiClient).get< + GetAttrSelectOptionsResponses, + GetAttrSelectOptionsErrors, + ThrowOnError + >({ + requestValidator: async data => { + return await zGetAttrSelectOptionsData.parseAsync(data); + }, + responseType: "json", + responseValidator: async data => { + return await zGetAttrSelectOptionsResponse.parseAsync(data); + }, + url: "/crm/v1/attr-select/{selectId}", + ...options, + }); +}; + /** * Get Attributes */ diff --git a/src/lib/client/types.gen.ts b/src/lib/client/types.gen.ts index 53d0087..507b142 100644 --- a/src/lib/client/types.gen.ts +++ b/src/lib/client/types.gen.ts @@ -24,6 +24,42 @@ export type AddAttributeResponse = { message: string; }; +/** + * AttrOptionSchema + */ +export type AttrOptionSchema = { + /** + * Id + */ + id: number; + /** + * Label + */ + label: string; + /** + * Value + */ + value: unknown; +}; + +/** + * AttrSelectSchema + */ +export type AttrSelectSchema = { + /** + * Id + */ + id: number; + /** + * Label + */ + label: string; + /** + * Isbuiltin + */ + isBuiltIn: boolean; +}; + /** * AttributeSchema */ @@ -52,6 +88,10 @@ export type AttributeSchema = { * Typeid */ typeId: number; + /** + * Selectid + */ + selectId: number | null; /** * Id */ @@ -61,6 +101,21 @@ export type AttributeSchema = { */ isBuiltIn: boolean; type: AttributeTypeSchema; + select: AttributeSelectSchema | null; +}; + +/** + * AttributeSelectSchema + */ +export type AttributeSelectSchema = { + /** + * Id + */ + id: number; + /** + * Label + */ + label: string; }; /** @@ -312,6 +367,10 @@ export type CreateAttributeSchema = { * Typeid */ typeId: number; + /** + * Selectid + */ + selectId: number | null; }; /** @@ -1039,6 +1098,7 @@ export type DealModuleAttributeSchema = { */ value: unknown | null; type: AttributeTypeSchema; + select: AttributeSelectSchema | null; /** * Defaultvalue */ @@ -1426,6 +1486,26 @@ export type DeleteStatusResponse = { message: string; }; +/** + * GetAllAttrSelectOptionsResponse + */ +export type GetAllAttrSelectOptionsResponse = { + /** + * Items + */ + items: Array; +}; + +/** + * GetAllAttrSelectsResponse + */ +export type GetAllAttrSelectsResponse = { + /** + * Items + */ + items: Array; +}; + /** * GetAllAttributeTypesResponse */ @@ -1775,6 +1855,10 @@ export type ModuleAttributeSchema = { * Typeid */ typeId: number; + /** + * Selectid + */ + selectId: number | null; /** * Id */ @@ -1784,6 +1868,7 @@ export type ModuleAttributeSchema = { */ isBuiltIn: boolean; type: AttributeTypeSchema; + select: AttributeSelectSchema | null; /** * Originallabel */ @@ -2373,7 +2458,6 @@ export type UpdateAttributeSchema = { * Description */ description?: string | null; - type?: AttributeTypeSchema | null; }; /** @@ -3076,6 +3160,55 @@ export type ValidationError = { type: string; }; +export type GetAttrSelectsData = { + body?: never; + path?: never; + query?: never; + url: "/crm/v1/attr-select/"; +}; + +export type GetAttrSelectsResponses = { + /** + * Successful Response + */ + 200: GetAllAttrSelectsResponse; +}; + +export type GetAttrSelectsResponse = + GetAttrSelectsResponses[keyof GetAttrSelectsResponses]; + +export type GetAttrSelectOptionsData = { + body?: never; + path: { + /** + * Selectid + */ + selectId: number; + }; + query?: never; + url: "/crm/v1/attr-select/{selectId}"; +}; + +export type GetAttrSelectOptionsErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetAttrSelectOptionsError = + GetAttrSelectOptionsErrors[keyof GetAttrSelectOptionsErrors]; + +export type GetAttrSelectOptionsResponses = { + /** + * Successful Response + */ + 200: GetAllAttrSelectOptionsResponse; +}; + +export type GetAttrSelectOptionsResponse = + GetAttrSelectOptionsResponses[keyof GetAttrSelectOptionsResponses]; + export type GetAttributesData = { body?: never; path?: never; diff --git a/src/lib/client/zod.gen.ts b/src/lib/client/zod.gen.ts index ce8e482..9b7b869 100644 --- a/src/lib/client/zod.gen.ts +++ b/src/lib/client/zod.gen.ts @@ -17,6 +17,24 @@ export const zAddAttributeResponse = z.object({ message: z.string(), }); +/** + * AttrOptionSchema + */ +export const zAttrOptionSchema = z.object({ + id: z.int(), + label: z.string(), + value: z.unknown(), +}); + +/** + * AttrSelectSchema + */ +export const zAttrSelectSchema = z.object({ + id: z.int(), + label: z.string(), + isBuiltIn: z.boolean(), +}); + /** * AttributeTypeSchema */ @@ -26,6 +44,14 @@ export const zAttributeTypeSchema = z.object({ name: z.string(), }); +/** + * AttributeSelectSchema + */ +export const zAttributeSelectSchema = z.object({ + id: z.int(), + label: z.string(), +}); + /** * AttributeSchema */ @@ -36,9 +62,11 @@ export const zAttributeSchema = z.object({ defaultValue: z.union([z.unknown(), z.null()]), description: z.string(), typeId: z.int(), + selectId: z.union([z.int(), z.null()]), id: z.int(), isBuiltIn: z.boolean(), type: zAttributeTypeSchema, + select: z.union([zAttributeSelectSchema, z.null()]), }); /** @@ -102,14 +130,14 @@ export const zBoardSchema = z.object({ * Body_upload_product_barcode_image */ export const zBodyUploadProductBarcodeImage = z.object({ - upload_file: z.string(), + upload_file: z.any(), }); /** * Body_upload_product_image */ export const zBodyUploadProductImage = z.object({ - upload_file: z.string(), + upload_file: z.any(), }); /** @@ -144,6 +172,7 @@ export const zCreateAttributeSchema = z.object({ defaultValue: z.union([z.unknown(), z.null()]), description: z.string(), typeId: z.int(), + selectId: z.union([z.int(), z.null()]), }); /** @@ -833,6 +862,7 @@ export const zDealModuleAttributeSchema = z.object({ originalLabel: z.string(), value: z.union([z.unknown(), z.null()]), type: zAttributeTypeSchema, + select: z.union([zAttributeSelectSchema, z.null()]), defaultValue: z.unknown(), description: z.string(), isApplicableToGroup: z.boolean(), @@ -996,6 +1026,20 @@ export const zDeleteStatusResponse = z.object({ message: z.string(), }); +/** + * GetAllAttrSelectOptionsResponse + */ +export const zGetAllAttrSelectOptionsResponse = z.object({ + items: z.array(zAttrOptionSchema), +}); + +/** + * GetAllAttrSelectsResponse + */ +export const zGetAllAttrSelectsResponse = z.object({ + items: z.array(zAttrSelectSchema), +}); + /** * GetAllAttributeTypesResponse */ @@ -1027,9 +1071,11 @@ export const zModuleAttributeSchema = z.object({ defaultValue: z.union([z.unknown(), z.null()]), description: z.string(), typeId: z.int(), + selectId: z.union([z.int(), z.null()]), id: z.int(), isBuiltIn: z.boolean(), type: zAttributeTypeSchema, + select: z.union([zAttributeSelectSchema, z.null()]), originalLabel: z.string(), }); @@ -1344,7 +1390,6 @@ export const zUpdateAttributeSchema = z.object({ isNullable: z.optional(z.union([z.boolean(), z.null()])), defaultValue: z.optional(z.union([z.unknown(), z.null()])), description: z.optional(z.union([z.string(), z.null()])), - type: z.optional(z.union([zAttributeTypeSchema, z.null()])), }); /** @@ -1805,6 +1850,30 @@ export const zUpdateStatusResponse = z.object({ message: z.string(), }); +export const zGetAttrSelectsData = z.object({ + body: z.optional(z.never()), + path: z.optional(z.never()), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zGetAttrSelectsResponse = zGetAllAttrSelectsResponse; + +export const zGetAttrSelectOptionsData = z.object({ + body: z.optional(z.never()), + path: z.object({ + selectId: z.int(), + }), + query: z.optional(z.never()), +}); + +/** + * Successful Response + */ +export const zGetAttrSelectOptionsResponse = zGetAllAttrSelectOptionsResponse; + export const zGetAttributesData = z.object({ body: z.optional(z.never()), path: z.optional(z.never()),