diff --git a/.changeset/brown-readers-hang.md b/.changeset/brown-readers-hang.md new file mode 100644 index 00000000000..5c652277efd --- /dev/null +++ b/.changeset/brown-readers-hang.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Fix sending attibutes on variant create/update in datagrid on product details page diff --git a/src/attributes/utils/handlers.test.ts b/src/attributes/utils/handlers.test.ts index 6ed3fb84abd..db41b50c203 100644 --- a/src/attributes/utils/handlers.test.ts +++ b/src/attributes/utils/handlers.test.ts @@ -431,8 +431,8 @@ describe("Sending only changed attributes", () => { newAttr | oldAttr | expected ${null} | ${null} | ${null} ${"my value"} | ${"my value"} | ${null} - ${"my value"} | ${null} | ${["my value"]} - ${null} | ${"my value"} | ${[]} + ${"my value"} | ${null} | ${"my value"} + ${null} | ${"my value"} | ${""} `( "$oldAttr -> $newAttr returns $expected", ({ newAttr, oldAttr, expected }) => { @@ -446,7 +446,9 @@ describe("Sending only changed attributes", () => { }); const expectedResult = - expected !== null ? [{ id: ATTR_ID, values: expected }] : []; + expected !== null + ? [{ id: ATTR_ID, swatch: { value: expected } }] + : []; expect(result).toEqual(expectedResult); }, ); diff --git a/src/attributes/utils/handlers.ts b/src/attributes/utils/handlers.ts index bcf1f5752d6..228e5bc542e 100644 --- a/src/attributes/utils/handlers.ts +++ b/src/attributes/utils/handlers.ts @@ -314,6 +314,15 @@ export const prepareAttributesInput = ({ }); return attrInput; } + if (inputType === AttributeInputTypeEnum.SWATCH) { + attrInput.push({ + id: attr.id, + swatch: { + value: attr.value[0] ?? "", + }, + }); + return attrInput; + } attrInput.push({ id: attr.id, diff --git a/src/fragments/products.ts b/src/fragments/products.ts index afb2dac8979..f74154513e6 100644 --- a/src/fragments/products.ts +++ b/src/fragments/products.ts @@ -149,19 +149,7 @@ export const productVariantAttributesFragment = gql` productType { id variantAttributes { - id - name - inputType - valueRequired - unit - choices( - first: $firstValues - after: $afterValues - last: $lastValues - before: $beforeValues - ) { - ...AttributeValueList - } + ...VariantAttribute } } channelListings { @@ -185,8 +173,7 @@ export const productDetailsVariant = gql` name } values { - id - name + ...AttributeValueDetails } } media { diff --git a/src/graphql/hooks.generated.ts b/src/graphql/hooks.generated.ts index 7f476947647..a3ee1eb5c69 100644 --- a/src/graphql/hooks.generated.ts +++ b/src/graphql/hooks.generated.ts @@ -2336,6 +2336,25 @@ export const ProductWithChannelListingsFragmentDoc = gql` } ${ChannelListingProductWithoutPricingFragmentDoc} ${PriceRangeFragmentDoc}`; +export const VariantAttributeFragmentDoc = gql` + fragment VariantAttribute on Attribute { + id + name + slug + inputType + entityType + valueRequired + unit + choices( + first: $firstValues + after: $afterValues + last: $lastValues + before: $beforeValues + ) { + ...AttributeValueList + } +} + ${AttributeValueListFragmentDoc}`; export const ProductVariantAttributesFragmentDoc = gql` fragment ProductVariantAttributes on Product { id @@ -2364,19 +2383,7 @@ export const ProductVariantAttributesFragmentDoc = gql` productType { id variantAttributes { - id - name - inputType - valueRequired - unit - choices( - first: $firstValues - after: $afterValues - last: $lastValues - before: $beforeValues - ) { - ...AttributeValueList - } + ...VariantAttribute } } channelListings { @@ -2388,7 +2395,8 @@ export const ProductVariantAttributesFragmentDoc = gql` } } ${AttributeValueListFragmentDoc} -${AttributeValueDetailsFragmentDoc}`; +${AttributeValueDetailsFragmentDoc} +${VariantAttributeFragmentDoc}`; export const ProductMediaFragmentDoc = gql` fragment ProductMedia on ProductMedia { id @@ -2437,8 +2445,7 @@ export const ProductDetailsVariantFragmentDoc = gql` name } values { - id - name + ...AttributeValueDetails } } media { @@ -2456,7 +2463,8 @@ export const ProductDetailsVariantFragmentDoc = gql` } quantityLimitPerCustomer } - ${StockFragmentDoc} + ${AttributeValueDetailsFragmentDoc} +${StockFragmentDoc} ${PreorderFragmentDoc} ${ChannelListingProductVariantFragmentDoc}`; export const WeightFragmentDoc = gql` @@ -2515,25 +2523,6 @@ ${ChannelListingProductWithoutPricingFragmentDoc} ${ProductMediaFragmentDoc} ${ProductDetailsVariantFragmentDoc} ${WeightFragmentDoc}`; -export const VariantAttributeFragmentDoc = gql` - fragment VariantAttribute on Attribute { - id - name - slug - inputType - entityType - valueRequired - unit - choices( - first: $firstValues - after: $afterValues - last: $lastValues - before: $beforeValues - ) { - ...AttributeValueList - } -} - ${AttributeValueListFragmentDoc}`; export const SelectedVariantAttributeFragmentDoc = gql` fragment SelectedVariantAttribute on SelectedAttribute { attribute { diff --git a/src/graphql/types.generated.ts b/src/graphql/types.generated.ts index 269d323efa1..62b608f2fb2 100644 --- a/src/graphql/types.generated.ts +++ b/src/graphql/types.generated.ts @@ -9933,11 +9933,11 @@ export type ChannelListingProductVariantFragment = { __typename: 'ProductVariant export type ProductWithChannelListingsFragment = { __typename: 'Product', id: string, name: string, thumbnail: { __typename: 'Image', url: string } | null, productType: { __typename: 'ProductType', id: string, name: string, hasVariants: boolean }, channelListings: Array<{ __typename: 'ProductChannelListing', isPublished: boolean, publicationDate: any | null, isAvailableForPurchase: boolean | null, availableForPurchase: any | null, visibleInListings: boolean, pricing?: { __typename: 'ProductPricingInfo', priceRange: { __typename: 'TaxedMoneyRange', start: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } } | null, stop: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } } | null } | null } | null, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null }; -export type ProductVariantAttributesFragment = { __typename: 'Product', id: string, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, slug: string | null, name: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, productType: { __typename: 'ProductType', id: string, variantAttributes: Array<{ __typename: 'Attribute', id: string, name: string | null, inputType: AttributeInputTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }> | null }, channelListings: Array<{ __typename: 'ProductChannelListing', channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null }; +export type ProductVariantAttributesFragment = { __typename: 'Product', id: string, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, slug: string | null, name: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, productType: { __typename: 'ProductType', id: string, variantAttributes: Array<{ __typename: 'Attribute', id: string, name: string | null, slug: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }> | null }, channelListings: Array<{ __typename: 'ProductChannelListing', channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null }; -export type ProductDetailsVariantFragment = { __typename: 'ProductVariant', id: string, sku: string | null, name: string, trackInventory: boolean, quantityLimitPerCustomer: number | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, name: string | null }, values: Array<{ __typename: 'AttributeValue', id: string, name: string | null }> }>, media: Array<{ __typename: 'ProductMedia', url: string }> | null, stocks: Array<{ __typename: 'Stock', id: string, quantity: number, quantityAllocated: number, warehouse: { __typename: 'Warehouse', id: string, name: string } }> | null, preorder: { __typename: 'PreorderData', globalThreshold: number | null, globalSoldUnits: number, endDate: any | null } | null, channelListings: Array<{ __typename: 'ProductVariantChannelListing', id: string, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string }, price: { __typename: 'Money', amount: number, currency: string } | null, costPrice: { __typename: 'Money', amount: number, currency: string } | null, preorderThreshold: { __typename: 'PreorderThreshold', quantity: number | null, soldUnits: number } | null }> | null }; +export type ProductDetailsVariantFragment = { __typename: 'ProductVariant', id: string, sku: string | null, name: string, trackInventory: boolean, quantityLimitPerCustomer: number | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, name: string | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, media: Array<{ __typename: 'ProductMedia', url: string }> | null, stocks: Array<{ __typename: 'Stock', id: string, quantity: number, quantityAllocated: number, warehouse: { __typename: 'Warehouse', id: string, name: string } }> | null, preorder: { __typename: 'PreorderData', globalThreshold: number | null, globalSoldUnits: number, endDate: any | null } | null, channelListings: Array<{ __typename: 'ProductVariantChannelListing', id: string, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string }, price: { __typename: 'Money', amount: number, currency: string } | null, costPrice: { __typename: 'Money', amount: number, currency: string } | null, preorderThreshold: { __typename: 'PreorderThreshold', quantity: number | null, soldUnits: number } | null }> | null }; -export type ProductFragment = { __typename: 'Product', name: string, slug: string, description: any | null, seoTitle: string | null, seoDescription: string | null, rating: number | null, isAvailable: boolean | null, id: string, defaultVariant: { __typename: 'ProductVariant', id: string } | null, category: { __typename: 'Category', id: string, name: string } | null, collections: Array<{ __typename: 'Collection', id: string, name: string }> | null, channelListings: Array<{ __typename: 'ProductChannelListing', isPublished: boolean, publicationDate: any | null, isAvailableForPurchase: boolean | null, availableForPurchase: any | null, visibleInListings: boolean, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null, media: Array<{ __typename: 'ProductMedia', id: string, alt: string, sortOrder: number | null, url: string, type: ProductMediaType, oembedData: any }> | null, variants: Array<{ __typename: 'ProductVariant', id: string, sku: string | null, name: string, trackInventory: boolean, quantityLimitPerCustomer: number | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, name: string | null }, values: Array<{ __typename: 'AttributeValue', id: string, name: string | null }> }>, media: Array<{ __typename: 'ProductMedia', url: string }> | null, stocks: Array<{ __typename: 'Stock', id: string, quantity: number, quantityAllocated: number, warehouse: { __typename: 'Warehouse', id: string, name: string } }> | null, preorder: { __typename: 'PreorderData', globalThreshold: number | null, globalSoldUnits: number, endDate: any | null } | null, channelListings: Array<{ __typename: 'ProductVariantChannelListing', id: string, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string }, price: { __typename: 'Money', amount: number, currency: string } | null, costPrice: { __typename: 'Money', amount: number, currency: string } | null, preorderThreshold: { __typename: 'PreorderThreshold', quantity: number | null, soldUnits: number } | null }> | null }> | null, productType: { __typename: 'ProductType', id: string, name: string, hasVariants: boolean, variantAttributes: Array<{ __typename: 'Attribute', id: string, name: string | null, inputType: AttributeInputTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }> | null }, weight: { __typename: 'Weight', unit: WeightUnitsEnum, value: number } | null, taxClass: { __typename: 'TaxClass', id: string, name: string } | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, slug: string | null, name: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> }; +export type ProductFragment = { __typename: 'Product', name: string, slug: string, description: any | null, seoTitle: string | null, seoDescription: string | null, rating: number | null, isAvailable: boolean | null, id: string, defaultVariant: { __typename: 'ProductVariant', id: string } | null, category: { __typename: 'Category', id: string, name: string } | null, collections: Array<{ __typename: 'Collection', id: string, name: string }> | null, channelListings: Array<{ __typename: 'ProductChannelListing', isPublished: boolean, publicationDate: any | null, isAvailableForPurchase: boolean | null, availableForPurchase: any | null, visibleInListings: boolean, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null, media: Array<{ __typename: 'ProductMedia', id: string, alt: string, sortOrder: number | null, url: string, type: ProductMediaType, oembedData: any }> | null, variants: Array<{ __typename: 'ProductVariant', id: string, sku: string | null, name: string, trackInventory: boolean, quantityLimitPerCustomer: number | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, name: string | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, media: Array<{ __typename: 'ProductMedia', url: string }> | null, stocks: Array<{ __typename: 'Stock', id: string, quantity: number, quantityAllocated: number, warehouse: { __typename: 'Warehouse', id: string, name: string } }> | null, preorder: { __typename: 'PreorderData', globalThreshold: number | null, globalSoldUnits: number, endDate: any | null } | null, channelListings: Array<{ __typename: 'ProductVariantChannelListing', id: string, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string }, price: { __typename: 'Money', amount: number, currency: string } | null, costPrice: { __typename: 'Money', amount: number, currency: string } | null, preorderThreshold: { __typename: 'PreorderThreshold', quantity: number | null, soldUnits: number } | null }> | null }> | null, productType: { __typename: 'ProductType', id: string, name: string, hasVariants: boolean, variantAttributes: Array<{ __typename: 'Attribute', id: string, name: string | null, slug: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }> | null }, weight: { __typename: 'Weight', unit: WeightUnitsEnum, value: number } | null, taxClass: { __typename: 'TaxClass', id: string, name: string } | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, slug: string | null, name: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> }; export type VariantAttributeFragment = { __typename: 'Attribute', id: string, name: string | null, slug: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }; @@ -11203,7 +11203,7 @@ export type ProductDetailsQueryVariables = Exact<{ }>; -export type ProductDetailsQuery = { __typename: 'Query', product: { __typename: 'Product', name: string, slug: string, description: any | null, seoTitle: string | null, seoDescription: string | null, rating: number | null, isAvailable: boolean | null, id: string, defaultVariant: { __typename: 'ProductVariant', id: string } | null, category: { __typename: 'Category', id: string, name: string } | null, collections: Array<{ __typename: 'Collection', id: string, name: string }> | null, channelListings: Array<{ __typename: 'ProductChannelListing', isPublished: boolean, publicationDate: any | null, isAvailableForPurchase: boolean | null, availableForPurchase: any | null, visibleInListings: boolean, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null, media: Array<{ __typename: 'ProductMedia', id: string, alt: string, sortOrder: number | null, url: string, type: ProductMediaType, oembedData: any }> | null, variants: Array<{ __typename: 'ProductVariant', id: string, sku: string | null, name: string, trackInventory: boolean, quantityLimitPerCustomer: number | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, name: string | null }, values: Array<{ __typename: 'AttributeValue', id: string, name: string | null }> }>, media: Array<{ __typename: 'ProductMedia', url: string }> | null, stocks: Array<{ __typename: 'Stock', id: string, quantity: number, quantityAllocated: number, warehouse: { __typename: 'Warehouse', id: string, name: string } }> | null, preorder: { __typename: 'PreorderData', globalThreshold: number | null, globalSoldUnits: number, endDate: any | null } | null, channelListings: Array<{ __typename: 'ProductVariantChannelListing', id: string, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string }, price: { __typename: 'Money', amount: number, currency: string } | null, costPrice: { __typename: 'Money', amount: number, currency: string } | null, preorderThreshold: { __typename: 'PreorderThreshold', quantity: number | null, soldUnits: number } | null }> | null }> | null, productType: { __typename: 'ProductType', id: string, name: string, hasVariants: boolean, variantAttributes: Array<{ __typename: 'Attribute', id: string, name: string | null, inputType: AttributeInputTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }> | null }, weight: { __typename: 'Weight', unit: WeightUnitsEnum, value: number } | null, taxClass: { __typename: 'TaxClass', id: string, name: string } | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, slug: string | null, name: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> } | null }; +export type ProductDetailsQuery = { __typename: 'Query', product: { __typename: 'Product', name: string, slug: string, description: any | null, seoTitle: string | null, seoDescription: string | null, rating: number | null, isAvailable: boolean | null, id: string, defaultVariant: { __typename: 'ProductVariant', id: string } | null, category: { __typename: 'Category', id: string, name: string } | null, collections: Array<{ __typename: 'Collection', id: string, name: string }> | null, channelListings: Array<{ __typename: 'ProductChannelListing', isPublished: boolean, publicationDate: any | null, isAvailableForPurchase: boolean | null, availableForPurchase: any | null, visibleInListings: boolean, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string } }> | null, media: Array<{ __typename: 'ProductMedia', id: string, alt: string, sortOrder: number | null, url: string, type: ProductMediaType, oembedData: any }> | null, variants: Array<{ __typename: 'ProductVariant', id: string, sku: string | null, name: string, trackInventory: boolean, quantityLimitPerCustomer: number | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, name: string | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, media: Array<{ __typename: 'ProductMedia', url: string }> | null, stocks: Array<{ __typename: 'Stock', id: string, quantity: number, quantityAllocated: number, warehouse: { __typename: 'Warehouse', id: string, name: string } }> | null, preorder: { __typename: 'PreorderData', globalThreshold: number | null, globalSoldUnits: number, endDate: any | null } | null, channelListings: Array<{ __typename: 'ProductVariantChannelListing', id: string, channel: { __typename: 'Channel', id: string, name: string, currencyCode: string }, price: { __typename: 'Money', amount: number, currency: string } | null, costPrice: { __typename: 'Money', amount: number, currency: string } | null, preorderThreshold: { __typename: 'PreorderThreshold', quantity: number | null, soldUnits: number } | null }> | null }> | null, productType: { __typename: 'ProductType', id: string, name: string, hasVariants: boolean, variantAttributes: Array<{ __typename: 'Attribute', id: string, name: string | null, slug: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }> | null }, weight: { __typename: 'Weight', unit: WeightUnitsEnum, value: number } | null, taxClass: { __typename: 'TaxClass', id: string, name: string } | null, attributes: Array<{ __typename: 'SelectedAttribute', attribute: { __typename: 'Attribute', id: string, slug: string | null, name: string | null, inputType: AttributeInputTypeEnum | null, entityType: AttributeEntityTypeEnum | null, valueRequired: boolean, unit: MeasurementUnitsEnum | null, choices: { __typename: 'AttributeValueCountableConnection', pageInfo: { __typename: 'PageInfo', endCursor: string | null, hasNextPage: boolean, hasPreviousPage: boolean, startCursor: string | null }, edges: Array<{ __typename: 'AttributeValueCountableEdge', cursor: string, node: { __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null } }> } | null }, values: Array<{ __typename: 'AttributeValue', plainText: string | null, richText: any | null, id: string, name: string | null, slug: string | null, reference: string | null, boolean: boolean | null, date: any | null, dateTime: any | null, value: string | null, file: { __typename: 'File', url: string, contentType: string | null } | null }> }>, metadata: Array<{ __typename: 'MetadataItem', key: string, value: string }>, privateMetadata: Array<{ __typename: 'MetadataItem', key: string, value: string }> } | null }; export type ProductTypeQueryVariables = Exact<{ id: Scalars['ID']; diff --git a/src/products/fixtures.ts b/src/products/fixtures.ts index 289df6d5b2c..fcd15c92926 100644 --- a/src/products/fixtures.ts +++ b/src/products/fixtures.ts @@ -1,6 +1,7 @@ // @ts-strict-ignore import { channelsList } from "@dashboard/channels/fixtures"; import { + AttributeEntityTypeEnum, AttributeInputTypeEnum, GridAttributesQuery, ProductFragment, @@ -8,6 +9,7 @@ import { ProductMediaType, ProductVariantCreateDataQuery, ProductVariantFragment, + VariantAttributeFragment, WeightUnitsEnum, } from "@dashboard/graphql"; import { RelayToFlat } from "@dashboard/types"; @@ -479,6 +481,8 @@ export const product: ( inputType: AttributeInputTypeEnum.DROPDOWN, valueRequired: false, unit: null, + slug: "attachment", + entityType: AttributeEntityTypeEnum.PRODUCT, choices: { __typename: "AttributeValueCountableConnection", pageInfo: { @@ -518,6 +522,8 @@ export const product: ( __typename: "Attribute", id: "pta18161", name: "Color", + slug: "color", + entityType: AttributeEntityTypeEnum.PRODUCT, inputType: AttributeInputTypeEnum.DROPDOWN, valueRequired: false, unit: null, @@ -617,6 +623,19 @@ export const product: ( { id: "QXR0cmlidXRlVmFsdWU6NDc=", name: "1l", + plainText: "", + richText: "", + slug: "", + reference: "", + boolean: false, + date: "", + dateTime: "", + value: "", + file: { + __typename: "File", + url: "", + contentType: "", + }, __typename: "AttributeValue", }, ], @@ -772,6 +791,19 @@ export const product: ( { id: "QXR0cmlidXRlVmFsdWU6NDg=", name: "2l", + plainText: "", + richText: "", + slug: "", + reference: "", + boolean: false, + date: "", + dateTime: "", + value: "", + file: { + __typename: "File", + url: "", + contentType: "", + }, __typename: "AttributeValue", }, ], @@ -860,6 +892,19 @@ export const product: ( { id: "QXR0cmlidXRlVmFsdWU6NDY=", name: "500ml", + plainText: "", + richText: "", + slug: "", + reference: "", + boolean: false, + date: "", + dateTime: "", + value: "", + file: { + __typename: "File", + url: "", + contentType: "", + }, __typename: "AttributeValue", }, ], @@ -3997,3 +4042,48 @@ export const gridAttributesResult: GridAttributesQuery = { ], }, }; + +export const variantAttributes: VariantAttributeFragment[] = [ + { + __typename: "Attribute", + id: "QXR0cmlidXRlOjE1", + name: "Bottle size", + slug: "bottle-size", + inputType: AttributeInputTypeEnum.DROPDOWN, + entityType: null, + valueRequired: false, + unit: null, + choices: { + __typename: "AttributeValueCountableConnection", + pageInfo: { + __typename: "PageInfo", + endCursor: "WyI1IiwgIjg1Il0=", + hasNextPage: false, + hasPreviousPage: false, + startCursor: "WyIwIiwgIjYzIl0=", + }, + edges: [], + }, + }, + { + __typename: "Attribute", + id: "QXR0cmlidXRlOjY4MQ==", + name: "Plain text", + slug: "plain-text", + inputType: AttributeInputTypeEnum.PLAIN_TEXT, + entityType: null, + valueRequired: false, + unit: null, + choices: { + __typename: "AttributeValueCountableConnection", + pageInfo: { + __typename: "PageInfo", + endCursor: "WyI1IiwgIjg1Il0=", + hasNextPage: false, + hasPreviousPage: false, + startCursor: "WyIwIiwgIjYzIl0=", + }, + edges: [], + }, + }, +]; diff --git a/src/products/views/ProductUpdate/handlers/data/attributes.test.ts b/src/products/views/ProductUpdate/handlers/data/attributes.test.ts index f2f23bd7ff8..d999f9a3b12 100644 --- a/src/products/views/ProductUpdate/handlers/data/attributes.test.ts +++ b/src/products/views/ProductUpdate/handlers/data/attributes.test.ts @@ -1,4 +1,5 @@ import { DatagridChange } from "@dashboard/components/Datagrid/hooks/useDatagridChange"; +import { variantAttributes } from "@dashboard/products/fixtures"; import { getAttributeData } from "./attributes"; @@ -6,17 +7,30 @@ describe("getAttributeData", () => { test("should filter and map data to attribute format", () => { // Arrage const changeData: DatagridChange[] = [ - { column: "attribute:1", row: 1, data: { value: { value: "test" } } }, - { column: "attribute:2", row: 1, data: { value: { value: "test2" } } }, + { + column: "attribute:QXR0cmlidXRlOjE1", + row: 1, + data: { value: { value: "test" } }, + }, + { + column: "attribute:QXR0cmlidXRlOjY4MQ==", + row: 1, + data: { value: { value: "test2" } }, + }, ]; // Act - const attributes = getAttributeData(changeData, 1, []); + const attributes = getAttributeData(changeData, 1, [], variantAttributes); // Assert expect(attributes).toEqual([ - { id: "1", values: ["test"] }, - { id: "2", values: ["test2"] }, + { + id: "QXR0cmlidXRlOjE1", + dropdown: { + value: "test", + }, + }, + { id: "QXR0cmlidXRlOjY4MQ==", plainText: "test2" }, ]); }); @@ -28,7 +42,7 @@ describe("getAttributeData", () => { ]; // Act - const attributes = getAttributeData(changeData, 2, []); + const attributes = getAttributeData(changeData, 2, [], variantAttributes); // Assert expect(attributes).toEqual([]); @@ -42,7 +56,7 @@ describe("getAttributeData", () => { ]; // Act - const attributes = getAttributeData(changeData, 1, []); + const attributes = getAttributeData(changeData, 1, [], variantAttributes); // Assert expect(attributes).toEqual([]); diff --git a/src/products/views/ProductUpdate/handlers/data/attributes.ts b/src/products/views/ProductUpdate/handlers/data/attributes.ts index c58e5d9a2cd..4a2169803b5 100644 --- a/src/products/views/ProductUpdate/handlers/data/attributes.ts +++ b/src/products/views/ProductUpdate/handlers/data/attributes.ts @@ -1,29 +1,190 @@ import { DatagridChange } from "@dashboard/components/Datagrid/hooks/useDatagridChange"; +import { + AttributeInputTypeEnum, + AttributeValueDetailsFragment, + AttributeValueInput, + BulkAttributeValueInput, + VariantAttributeFragment, +} from "@dashboard/graphql"; import { getColumnAttribute, isCurrentRow, } from "@dashboard/products/utils/datagrid"; +import { byAttributeName } from "../utils"; + export function getAttributeData( data: DatagridChange[], currentIndex: number, removedIds: number[], + variantAttributes: VariantAttributeFragment[], ) { return data .filter(change => isCurrentRow(change.row, currentIndex, removedIds)) .filter(byHavingAnyAttribute) - .map(toAttributeData); + .map(toAttributeData(variantAttributes)); } function byHavingAnyAttribute(change: DatagridChange): boolean { return !!getColumnAttribute(change.column); } -function toAttributeData(change: DatagridChange) { - const attributeId = getColumnAttribute(change.column); +function toAttributeData(variantAttributes: VariantAttributeFragment[]) { + return (change: DatagridChange): AttributeValueInput | undefined => { + const attributeId = getColumnAttribute(change.column); + const attributeType = getAttributeType( + variantAttributes, + attributeId ?? "", + ); + + if (!attributeType) { + return undefined; + } + + return { + id: attributeId, + ...getDatagridAttributeInput(attributeType, change.data.value.value), + }; + }; +} + +export function getAttributeType( + source: VariantAttributeFragment[], + attributeId: string, +): AttributeInputTypeEnum | undefined | null { + const attributeVariant = source.find(attr => attr.id === attributeId); + return attributeVariant?.inputType; +} + +// Datagrid only support PLAIN_TEXT and DROPDOWN attribute types +export function getDatagridAttributeInput( + inputType: AttributeInputTypeEnum, + value: string = "", +): BulkAttributeValueInput { + if (inputType === AttributeInputTypeEnum.DROPDOWN) { + return { + dropdown: { + value, + }, + }; + } + + if (inputType === AttributeInputTypeEnum.PLAIN_TEXT) { + return { + plainText: value, + }; + } return { - id: attributeId, - values: change.data.value.value ? [change.data.value.value] : [], + values: [value], }; } + +// ProductUpdateForm supports all attribute types +export function getAttributeInput( + inputType: AttributeInputTypeEnum, + values: AttributeValueDetailsFragment[], +): BulkAttributeValueInput { + if (inputType === AttributeInputTypeEnum.FILE) { + return { + file: values[0]?.file?.url ?? null, + }; + } + + if (inputType === AttributeInputTypeEnum.NUMERIC) { + return { + numeric: getAttributeValueOrNull(values[0], "name"), + }; + } + + if (inputType === AttributeInputTypeEnum.DROPDOWN) { + return { + dropdown: { + value: getAttributeValueOrNull(values[0], "name"), + }, + }; + } + + if (inputType === AttributeInputTypeEnum.MULTISELECT) { + return { + multiselect: values.map(({ id }) => ({ id })), + }; + } + + if (inputType === AttributeInputTypeEnum.SWATCH) { + return { + swatch: { + value: getAttributeValueOrNull(values[0], "name"), + }, + }; + } + + if (inputType === AttributeInputTypeEnum.BOOLEAN) { + return { + boolean: getBooleanValue(values[0]), + }; + } + + if (inputType === AttributeInputTypeEnum.PLAIN_TEXT) { + return { + plainText: getAttributeValueOrNull(values[0], "name"), + }; + } + + if (inputType === AttributeInputTypeEnum.RICH_TEXT) { + return { + richText: getAttributeValueOrNull(values[0], "richText"), + }; + } + + if (inputType === AttributeInputTypeEnum.REFERENCE) { + return { + references: values + .map(({ reference }) => reference) + .filter(byAttributeName), + }; + } + + if (inputType === AttributeInputTypeEnum.DATE) { + return { + date: getAttributeValueOrNull(values[0], "date"), + }; + } + + if (inputType === AttributeInputTypeEnum.DATE_TIME) { + return { + dateTime: getAttributeValueOrNull(values[0], "dateTime"), + }; + } + + return { + values: values.map(({ name }) => name).filter(byAttributeName), + }; +} + +function getAttributeValueOrNull( + value: AttributeValueDetailsFragment | undefined, + field: keyof AttributeValueDetailsFragment, +): string | null { + if (value?.[field]) { + return value[field]; + } + + return null; +} + +function getBooleanValue( + value: AttributeValueDetailsFragment | undefined, +): boolean { + const booleanValue = getAttributeValueOrNull(value, "name") || ""; + + if (booleanValue.includes("true") || booleanValue.includes("false")) { + return booleanValue === "true"; + } + + if (booleanValue.includes("Yes") || booleanValue.includes("No")) { + return booleanValue.includes("Yes"); + } + + return false; +} diff --git a/src/products/views/ProductUpdate/handlers/useProductUpdateHandler.ts b/src/products/views/ProductUpdate/handlers/useProductUpdateHandler.ts index f6ae659e127..fee425c8adf 100644 --- a/src/products/views/ProductUpdate/handlers/useProductUpdateHandler.ts +++ b/src/products/views/ProductUpdate/handlers/useProductUpdateHandler.ts @@ -159,7 +159,11 @@ export function useProductUpdateHandler( variables: { id: product.id, inputs: data.variants.added.map(index => ({ - ...getCreateVariantInput(data.variants, index), + ...getCreateVariantInput( + data.variants, + index, + product?.productType?.variantAttributes ?? [], + ), })), }, }); @@ -176,6 +180,7 @@ export function useProductUpdateHandler( const updateInputdData = getBulkVariantUpdateInputs( product.variants, data.variants, + product?.productType?.variantAttributes ?? [], ); if (updateInputdData.length) { diff --git a/src/products/views/ProductUpdate/handlers/utils.test.ts b/src/products/views/ProductUpdate/handlers/utils.test.ts index aa77227c4b8..1e3e309f0df 100644 --- a/src/products/views/ProductUpdate/handlers/utils.test.ts +++ b/src/products/views/ProductUpdate/handlers/utils.test.ts @@ -3,7 +3,7 @@ import { DatagridChangeOpts } from "@dashboard/components/Datagrid/hooks/useData import { ProductFragment } from "@dashboard/graphql"; import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types"; -import { product } from "../../../fixtures"; +import { product, variantAttributes } from "../../../fixtures"; import { getBulkVariantUpdateInputs, getCreateVariantInput, @@ -152,14 +152,20 @@ describe("getCreateVariantInput", () => { added: [1], }; // Act - const createDataInput = getCreateVariantInput(inputData, 1); + const createDataInput = getCreateVariantInput( + inputData, + 1, + variantAttributes, + ); // Assert expect(createDataInput).toEqual({ attributes: [ { id: "QXR0cmlidXRlOjE1", - values: ["1l"], + dropdown: { + value: "1l", + }, }, ], sku: "23423", @@ -204,7 +210,11 @@ describe("getCreateVariantInput", () => { }; // Act - const createDataInput = getCreateVariantInput(inputData, 1); + const createDataInput = getCreateVariantInput( + inputData, + 1, + variantAttributes, + ); // Assert expect(createDataInput).toEqual({ @@ -288,6 +298,7 @@ describe("getBulkVariantUpdateInputs", () => { const bulkVariantUpdateInput = getBulkVariantUpdateInputs( variants, inputData, + variantAttributes, ); // Assert @@ -312,8 +323,10 @@ describe("getBulkVariantUpdateInputs", () => { id: variants[2].id, attributes: [ { - id: variants[2].attributes[0].attribute.id, - values: ["2l"], + id: "QXR0cmlidXRlOjE1", + dropdown: { + value: "2l", + }, }, ], sku: "2345555", diff --git a/src/products/views/ProductUpdate/handlers/utils.ts b/src/products/views/ProductUpdate/handlers/utils.ts index 7750d0d0696..de3e5a802f6 100644 --- a/src/products/views/ProductUpdate/handlers/utils.ts +++ b/src/products/views/ProductUpdate/handlers/utils.ts @@ -11,6 +11,7 @@ import { ProductChannelListingUpdateMutationVariables, ProductFragment, ProductVariantBulkUpdateInput, + VariantAttributeFragment, } from "@dashboard/graphql"; import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types"; import { getAttributeInputFromProduct } from "@dashboard/products/utils/data"; @@ -18,7 +19,11 @@ import { getParsedDataForJsonStringField } from "@dashboard/utils/richText/misc" import pick from "lodash/pick"; import uniq from "lodash/uniq"; -import { getAttributeData } from "./data/attributes"; +import { + getAttributeData, + getAttributeInput, + getAttributeType, +} from "./data/attributes"; import { getUpdateVariantChannelInputs, getVariantChannelsInputs, @@ -61,9 +66,18 @@ export function getProductUpdateVariables( }; } -export function getCreateVariantInput(data: DatagridChangeOpts, index: number) { +export function getCreateVariantInput( + data: DatagridChangeOpts, + index: number, + variantAttributes: VariantAttributeFragment[], +) { return { - attributes: getAttributeData(data.updates, index, data.removed), + attributes: getAttributeData( + data.updates, + index, + data.removed, + variantAttributes, + ), sku: getSkuData(data.updates, index, data.removed), name: getNameData(data.updates, index, data.removed), channelListings: getVariantChannelsInputs(data, index), @@ -134,19 +148,25 @@ export function hasProductChannelsUpdate( export function getBulkVariantUpdateInputs( variants: ProductFragment["variants"], data: DatagridChangeOpts, + variantsAttributes: VariantAttributeFragment[], ): ProductVariantBulkUpdateInput[] { - const toUpdateInput = createToUpdateInput(data); + const toUpdateInput = createToUpdateInput(data, variantsAttributes); return variants.map(toUpdateInput).filter(byAvailability); } const createToUpdateInput = - (data: DatagridChangeOpts) => + (data: DatagridChangeOpts, variantsAttributes: VariantAttributeFragment[]) => ( variant: ProductFragment["variants"][number], variantIndex: number, ): ProductVariantBulkUpdateInput => ({ id: variant.id, - attributes: getVariantAttributesForUpdate(data, variantIndex, variant), + attributes: getVariantAttributesForUpdate( + data, + variantIndex, + variant, + variantsAttributes, + ), sku: getSkuData(data.updates, variantIndex, data.removed), name: getNameData(data.updates, variantIndex, data.removed), stocks: getVaraintUpdateStockData( @@ -162,24 +182,43 @@ const getVariantAttributesForUpdate = ( data: DatagridChangeOpts, variantIndex: number, variant: ProductFragment["variants"][number], + variantsAttributes: VariantAttributeFragment[], ) => { const updatedAttributes = getAttributeData( data.updates, variantIndex, data.removed, + variantsAttributes, ); + + if (!updatedAttributes.length) { + return []; + } + // Re-send current values for all not-updated attributes, in case some of them were required const notUpdatedAttributes: ReturnType = variant.attributes - .filter(attribute => - updatedAttributes.find( - updatedAttribute => updatedAttribute.id !== attribute.attribute.id, - ), + .filter( + attribute => + !updatedAttributes.find( + updatedAttribute => updatedAttribute.id === attribute.attribute.id, + ), ) - .map(attribute => ({ - id: attribute.attribute.id, - values: attribute.values.map(({ name }) => name), - })); + .map(attribute => { + const attributeType = getAttributeType( + variantsAttributes, + attribute.attribute.id, + ); + + if (!attributeType) { + return undefined; + } + + return { + id: attribute.attribute.id, + ...getAttributeInput(attributeType, attribute.values), + }; + }); return [...updatedAttributes, ...notUpdatedAttributes]; }; @@ -212,3 +251,7 @@ export function inferProductChannelsAfterUpdate( ...updatedChannelsIds, ]); } + +export function byAttributeName(x: string | null | undefined): x is string { + return typeof x === "string" && x.length > 0; +}