feat: ff deal attributes editor

This commit is contained in:
2025-10-31 20:54:04 +04:00
parent ee90ebe0f0
commit 2948189291
12 changed files with 219 additions and 142 deletions

View File

@ -74,7 +74,7 @@ const DealEditorBody: FC<Props> = props => {
value={module.key}>
<CustomTab
key={module.key}
module={module}
moduleId={module.id}
deal={props.value}
/>
</DealEditorTabPanel>

View File

@ -1,76 +0,0 @@
import { useMemo } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";
import {
DealModuleAttributeSchema,
HttpValidationError,
UpdateDealModuleAttributeSchema,
} from "@/lib/client";
import {
getDealModuleAttributesOptions,
updateDealModuleAttributesMutation,
} from "@/lib/client/@tanstack/react-query.gen";
import { notifications } from "@/lib/notifications";
type Props = {
dealId: number;
moduleId: number;
};
const useDealAttributeValuesActions = ({ dealId, moduleId }: Props) => {
const { data, refetch: refetchAttributes } = useQuery(
getDealModuleAttributesOptions({
path: {
dealId,
moduleId,
},
})
);
const sortedAttributes = useMemo(() => {
if (!data?.attributes) return [];
const sortedAttributes: DealModuleAttributeSchema[] = [];
for (const attr of data.attributes) {
if (attr.type.type === "bool") sortedAttributes.push(attr);
else sortedAttributes.unshift(attr);
}
return sortedAttributes;
}, [data?.attributes]);
const onError = (error: AxiosError<HttpValidationError>) => {
console.error(error);
notifications.error({
message: error.response?.data?.detail as string | undefined,
});
};
const updateAttributeValuesMutation = useMutation({
...updateDealModuleAttributesMutation(),
onError,
onSuccess: ({ message }) => {
notifications.success({ message });
refetchAttributes();
},
});
const updateAttributeValues = (
attributeValues: UpdateDealModuleAttributeSchema[]
) => {
updateAttributeValuesMutation.mutate({
path: {
moduleId,
dealId,
},
body: {
attributes: attributeValues,
},
});
};
return {
dealAttributes: sortedAttributes,
updateAttributeValues,
};
};
export default useDealAttributeValuesActions;

View File

@ -1,147 +1,21 @@
import React, {
FC,
ReactNode,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { Flex, Group } from "@mantine/core";
import AttributeValueInput from "@/app/deals/drawers/DealEditorDrawer/components/AttributeValueInput";
import useDealAttributeValuesActions from "@/app/deals/drawers/DealEditorDrawer/hooks/useDealAttributeValuesActions";
import FormFlexRow from "@/components/ui/FormFlexRow/FormFlexRow";
import InlineButton from "@/components/ui/InlineButton/InlineButton";
import {
DealModuleAttributeSchema,
DealSchema,
ModuleSchemaOutput,
UpdateDealModuleAttributeSchema,
} from "@/lib/client";
import React, { FC } from "react";
import AttributeEditor from "@/components/ui/AttributesEditor/AttributesEditor";
import { DealSchema } from "@/lib/client";
type Props = {
module: ModuleSchemaOutput;
moduleId: number;
deal: DealSchema;
};
type AttrInfo = {
value?: any;
isApplicableToGroup: boolean;
};
const CustomTab: FC<Props> = ({ module, deal }) => {
const { dealAttributes, updateAttributeValues } =
useDealAttributeValuesActions({
moduleId: module.id,
dealId: deal.id,
});
const [attributeValuesMap, setAttributeValuesMap] = useState<
Map<number, AttrInfo | null>
>(new Map());
const [attributeErrorsMap, setAttributeErrorsMap] = useState<
Map<number, string>
>(new Map());
useEffect(() => {
const values = new Map<number, AttrInfo | null>();
for (const dealAttr of dealAttributes) {
values.set(dealAttr.attributeId, {
...dealAttr,
value: dealAttr.value,
});
}
setAttributeValuesMap(values);
}, [dealAttributes]);
const onSubmit = () => {
let isErrorFound = false;
for (const attr of dealAttributes) {
const value = attributeValuesMap.get(attr.attributeId);
if (!attr.isNullable && (value === null || value === undefined)) {
attributeErrorsMap.set(attr.attributeId, "Обязательное поле");
isErrorFound = true;
}
}
setAttributeErrorsMap(new Map(attributeErrorsMap));
if (isErrorFound) return;
const attributeValues: UpdateDealModuleAttributeSchema[] =
attributeValuesMap
.entries()
.map(
([attributeId, info]) =>
({
attributeId,
...info,
}) as UpdateDealModuleAttributeSchema
)
.toArray();
updateAttributeValues(attributeValues);
};
const getAttributeElement = useCallback(
(attribute: DealModuleAttributeSchema) => (
<AttributeValueInput
key={attribute.attributeId}
attrInfo={attribute}
value={attributeValuesMap.get(attribute.attributeId)?.value}
onChange={value => {
attributeValuesMap.set(attribute.attributeId, {
...attribute,
value,
});
setAttributeValuesMap(new Map(attributeValuesMap));
attributeErrorsMap.delete(attribute.attributeId);
setAttributeErrorsMap(new Map(attributeErrorsMap));
}}
style={{ flex: 1 }}
error={attributeErrorsMap.get(attribute.attributeId)}
/>
),
[attributeValuesMap, attributeErrorsMap]
);
const attributesRows = useMemo(() => {
if (!dealAttributes) return [];
const boolAttributes = dealAttributes.filter(
a => a.type.type === "bool"
);
const otherAttributes = dealAttributes.filter(
a => a.type.type !== "bool"
);
const rows: ReactNode[] = [];
for (let i = 0; i < otherAttributes.length; i += 2) {
const rightIdx = i + 1;
rows.push(
<FormFlexRow key={`row${i}`}>
{getAttributeElement(otherAttributes[i])}
{rightIdx < otherAttributes.length &&
getAttributeElement(otherAttributes[rightIdx])}
</FormFlexRow>
);
}
for (const attr of boolAttributes) {
rows.push(getAttributeElement(attr));
}
return rows;
}, [dealAttributes, getAttributeElement]);
const CustomTab: FC<Props> = props => {
return (
<Flex
direction={"column"}
gap={"xs"}
py={"xs"}
px={"md"}>
{attributesRows}
<Group>
<InlineButton onClick={onSubmit}>Сохранить</InlineButton>
</Group>
</Flex>
<AttributeEditor
{...props}
containerStyle={{
paddingBlock: "var(--mantine-spacing-xs)",
paddingInline: "var(--mantine-spacing-md)",
}}
/>
);
};