diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 81087071e..bc2717854 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -5727,10 +5727,6 @@ "context": "section header", "string": "Product Attributes" }, - "src_dot_productTypes_dot_components_dot_ProductTypeAttributes_dot_888493112": { - "context": "section header", - "string": "Variant Attributes" - }, "src_dot_productTypes_dot_components_dot_ProductTypeDetailsPage_dot_1217376589": { "context": "switch button", "string": "Product type uses Variant Attributes" @@ -5827,6 +5823,31 @@ "src_dot_productTypes_dot_components_dot_ProductTypeShipping_dot_746695941": { "string": "Weight" }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_1192828581": { + "string": "No attributes found" + }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_1228425832": { + "string": "Attribute name" + }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_1656462109": { + "context": "button", + "string": "Assign attribute" + }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_1779771890": { + "string": "{inputType} attributes cannot be used as variant selection attributes." + }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_1835784218": { + "context": "variant attribute checkbox", + "string": "Variant Selection" + }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_3478065224": { + "context": "attribute internal name", + "string": "Slug" + }, + "src_dot_productTypes_dot_components_dot_ProductTypeVariantAttributes_dot_888493112": { + "context": "section header", + "string": "Variant Attributes" + }, "src_dot_productTypes_dot_hooks_dot_useProductTypeDelete_dot_title": { "context": "ProductTypeDeleteWarningDialog title", "string": "Delete product {selectedTypesCount,plural,one{type} other{types}}" diff --git a/schema.graphql b/schema.graphql index 232dfa87f..776352630 100644 --- a/schema.graphql +++ b/schema.graphql @@ -70,6 +70,7 @@ enum AccountErrorCode { JWT_INVALID_CSRF_TOKEN CHANNEL_INACTIVE MISSING_CHANNEL_SLUG + ACCOUNT_NOT_CONFIRMED } input AccountInput { @@ -456,6 +457,11 @@ type AssignNavigation { errors: [MenuError!]! } +type AssignedVariantAttribute { + attribute: Attribute! + variantSelection: Boolean! +} + type Attribute implements Node & ObjectWithMetadata { id: ID! productTypes(before: String, after: String, first: Int, last: Int): ProductTypeCountableConnection! @@ -3748,6 +3754,7 @@ type Mutation { shippingZoneBulkDelete(ids: [ID]!): ShippingZoneBulkDelete shippingZoneUpdate(id: ID!, input: ShippingZoneUpdateInput!): ShippingZoneUpdate productAttributeAssign(operations: [ProductAttributeAssignInput]!, productTypeId: ID!): ProductAttributeAssign + productAttributeAssignmentUpdate(operations: [ProductAttributeAssignmentUpdateInput]!, productTypeId: ID!): ProductAttributeAssignmentUpdate productAttributeUnassign(attributeIds: [ID]!, productTypeId: ID!): ProductAttributeUnassign categoryCreate(input: CategoryInput!, parent: ID): CategoryCreate categoryDelete(id: ID!): CategoryDelete @@ -5200,6 +5207,18 @@ type ProductAttributeAssign { input ProductAttributeAssignInput { id: ID! type: ProductAttributeType! + variantSelection: Boolean +} + +type ProductAttributeAssignmentUpdate { + productType: ProductType + productErrors: [ProductError!]! @deprecated(reason: "This field will be removed in Saleor 4.0. Use `errors` field instead.") + errors: [ProductError!]! +} + +input ProductAttributeAssignmentUpdateInput { + id: ID! + variantSelection: Boolean! } enum ProductAttributeType { @@ -5527,7 +5546,8 @@ type ProductType implements Node & ObjectWithMetadata { kind: ProductTypeKindEnum! products(channel: String, before: String, after: String, first: Int, last: Int): ProductCountableConnection @deprecated(reason: "This field will be removed in Saleor 4.0. Use the top-level `products` query with the `productTypes` filter.") taxType: TaxType - variantAttributes(variantSelection: VariantAttributeScope): [Attribute] + variantAttributes(variantSelection: VariantAttributeScope): [Attribute] @deprecated(reason: "This field will be removed in Saleor 4.0. use `assignedVariantAttributes` instead.") + assignedVariantAttributes(variantSelection: VariantAttributeScope): [AssignedVariantAttribute] productAttributes: [Attribute] availableAttributes(filter: AttributeFilterInput, before: String, after: String, first: Int, last: Int): AttributeCountableConnection } diff --git a/src/fragments/productTypes.ts b/src/fragments/productTypes.ts index 7163b63f1..002c5b9f5 100644 --- a/src/fragments/productTypes.ts +++ b/src/fragments/productTypes.ts @@ -30,6 +30,12 @@ export const productTypeDetailsFragment = gql` variantAttributes { ...AttributeFragment } + assignedVariantAttributes { + attribute { + ...AttributeFragment + } + variantSelection + } weight { unit value diff --git a/src/fragments/types/ProductTypeDetailsFragment.ts b/src/fragments/types/ProductTypeDetailsFragment.ts index ef929cac3..6f81a58b7 100644 --- a/src/fragments/types/ProductTypeDetailsFragment.ts +++ b/src/fragments/types/ProductTypeDetailsFragment.ts @@ -53,6 +53,25 @@ export interface ProductTypeDetailsFragment_variantAttributes { inputType: AttributeInputTypeEnum | null; } +export interface ProductTypeDetailsFragment_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductTypeDetailsFragment_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: ProductTypeDetailsFragment_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface ProductTypeDetailsFragment_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -71,5 +90,6 @@ export interface ProductTypeDetailsFragment { privateMetadata: (ProductTypeDetailsFragment_privateMetadata | null)[]; productAttributes: (ProductTypeDetailsFragment_productAttributes | null)[] | null; variantAttributes: (ProductTypeDetailsFragment_variantAttributes | null)[] | null; + assignedVariantAttributes: (ProductTypeDetailsFragment_assignedVariantAttributes | null)[] | null; weight: ProductTypeDetailsFragment_weight | null; } diff --git a/src/productTypes/components/ProductTypeAttributes/ProductTypeAttributes.tsx b/src/productTypes/components/ProductTypeAttributes/ProductTypeAttributes.tsx index a006f269a..5dbee00ea 100644 --- a/src/productTypes/components/ProductTypeAttributes/ProductTypeAttributes.tsx +++ b/src/productTypes/components/ProductTypeAttributes/ProductTypeAttributes.tsx @@ -89,25 +89,12 @@ const ProductTypeAttributes: React.FC = props => { const intl = useIntl(); return ( - + void; onHasVariantsToggle: (hasVariants: boolean) => void; onSubmit: (data: ProductTypeForm) => SubmitPromise; + setSelectedVariantAttributes: (data: string[]) => void; + selectedVariantAttributes: string[]; } function handleTaxTypeChange( @@ -98,7 +101,9 @@ const ProductTypeDetailsPage: React.FC = ({ onBack, onDelete, onHasVariantsToggle, - onSubmit + onSubmit, + setSelectedVariantAttributes, + selectedVariantAttributes }) => { const intl = useIntl(); const { @@ -156,7 +161,7 @@ const ProductTypeDetailsPage: React.FC = ({ return (
- {({ change, data, hasChanged, submit }) => { + {({ change, data, hasChanged, submit, setChanged }) => { const changeMetadata = makeMetadataChangeHandler(change); return ( @@ -217,9 +222,11 @@ const ProductTypeDetailsPage: React.FC = ({ {data.hasVariants && ( <> - productType.variantAttributes)} + assignedVariantAttributes={ + productType?.assignedVariantAttributes + } disabled={disabled} type={ProductAttributeType.VARIANT} onAttributeAssign={onAttributeAdd} @@ -228,6 +235,11 @@ const ProductTypeDetailsPage: React.FC = ({ onAttributeReorder(event, ProductAttributeType.VARIANT) } onAttributeUnassign={onAttributeUnassign} + onAttributeVariantSelection={setChanged} + setSelectedVariantAttributes={ + setSelectedVariantAttributes + } + selectedVariantAttributes={selectedVariantAttributes} {...variantAttributeList} /> diff --git a/src/productTypes/components/ProductTypeVariantAttributes/ProductTypeVariantAttributes.tsx b/src/productTypes/components/ProductTypeVariantAttributes/ProductTypeVariantAttributes.tsx new file mode 100644 index 000000000..ef2822050 --- /dev/null +++ b/src/productTypes/components/ProductTypeVariantAttributes/ProductTypeVariantAttributes.tsx @@ -0,0 +1,314 @@ +import { + Button, + Card, + IconButton, + TableCell, + TableRow, + Tooltip +} from "@material-ui/core"; +import DeleteIcon from "@material-ui/icons/Delete"; +import HelpOutline from "@material-ui/icons/HelpOutline"; +import CardTitle from "@saleor/components/CardTitle"; +import Checkbox from "@saleor/components/Checkbox"; +import ResponsiveTable from "@saleor/components/ResponsiveTable"; +import Skeleton from "@saleor/components/Skeleton"; +import { + SortableTableBody, + SortableTableRow +} from "@saleor/components/SortableTable"; +import TableHead from "@saleor/components/TableHead"; +import { makeStyles } from "@saleor/macaw-ui"; +import { maybe, renderCollection, stopPropagation } from "@saleor/misc"; +import { ListActions, ReorderAction } from "@saleor/types"; +import { ProductAttributeType } from "@saleor/types/globalTypes"; +import capitalize from "lodash/capitalize"; +import React, { useEffect } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { + ProductTypeDetails_productType_assignedVariantAttributes, + ProductTypeDetails_productType_variantAttributes +} from "../../types/ProductTypeDetails"; + +const useStyles = makeStyles( + { + colAction: { + "&:last-child": { + paddingRight: 0 + }, + width: 80 + }, + colGrab: { + width: 60 + }, + colName: { + width: 200 + }, + colSlug: { + width: 200 + }, + colVariant: { + width: 150 + }, + colVariantContent: { + display: "flex", + alignItems: "center" + }, + colVariantDisabled: { + fill: "#28234A", + fillOpacity: 0.6, + "&:hover": { + fillOpacity: 1 + } + }, + link: { + cursor: "pointer" + }, + textLeft: { + textAlign: "left" + } + }, + { name: "ProductTypeAttributes" } +); + +interface ProductTypeVariantAttributesProps extends ListActions { + assignedVariantAttributes: ProductTypeDetails_productType_assignedVariantAttributes[]; + disabled: boolean; + type: string; + testId?: string; + selectedVariantAttributes: string[]; + onAttributeAssign: (type: ProductAttributeType) => void; + onAttributeClick: (id: string) => void; + onAttributeReorder: ReorderAction; + onAttributeUnassign: (id: string) => void; + onAttributeVariantSelection?: (isActive: boolean) => void; + setSelectedVariantAttributes?: (data: string[]) => void; +} + +function handleContainerAssign( + variantID: string, + isSelected: boolean, + selectedAttributes: string[], + setSelectedAttributes: (data: string[]) => void +) { + if (isSelected) { + setSelectedAttributes( + selectedAttributes.filter( + selectedContainer => selectedContainer !== variantID + ) + ); + } else { + setSelectedAttributes([...selectedAttributes, variantID]); + } +} + +const numberOfColumns = 6; + +const ProductTypeVariantAttributes: React.FC = props => { + const { + assignedVariantAttributes, + disabled, + isChecked, + selected, + toggle, + toggleAll, + toolbar, + type, + testId, + onAttributeAssign, + onAttributeClick, + onAttributeReorder, + onAttributeUnassign, + onAttributeVariantSelection, + setSelectedVariantAttributes, + selectedVariantAttributes + } = props; + const classes = useStyles(props); + + const intl = useIntl(); + + useEffect(() => { + // Populate initial selection - populated inside this component to preserve it's state between data reloads + setSelectedVariantAttributes( + assignedVariantAttributes + .map(elem => (elem.variantSelection ? elem.attribute.id : undefined)) + .filter(Boolean) || [] + ); + }, []); + + return ( + + onAttributeAssign(ProductAttributeType[type])} + > + + + } + /> + + + + + + + + + + {assignedVariantAttributes?.length > 0 && ( + + + + + + + + + + + + + )} + + {renderCollection( + assignedVariantAttributes, + (assignedVariantAttribute, attributeIndex) => { + const { attribute } = assignedVariantAttribute; + const isVariantSelected = assignedVariantAttribute + ? isChecked(attribute.id) + : false; + const isSelected = !!selectedVariantAttributes.find( + selectedAttribute => selectedAttribute === attribute.id + ); + const variantSelectionDisabled = ![ + "DROPDOWN", + "BOOLEAN", + "SWATCH", + "NUMERIC" + ].includes(attribute.inputType); + const readableAttributeInputType = capitalize( + attribute.inputType.split("_").join(" ") + ); + + return ( + onAttributeClick(attribute.id) + : undefined + } + key={maybe(() => attribute.id)} + index={attributeIndex || 0} + data-test="id" + data-test-id={maybe(() => attribute.id)} + > + + toggle(attribute.id)} + /> + + + {attribute.name ?? } + + + {maybe(() => attribute.slug) ? ( + attribute.slug + ) : ( + + )} + + +
+ { + onAttributeVariantSelection(true); + handleContainerAssign( + attribute.id, + isSelected, + selectedVariantAttributes, + setSelectedVariantAttributes + ); + }} + /> + {!!variantSelectionDisabled && ( + + } + > + + + )} +
+
+ + + onAttributeUnassign(attribute.id) + )} + > + + + +
+ ); + }, + () => ( + + + + + + ) + )} +
+
+
+ ); +}; +ProductTypeVariantAttributes.displayName = "ProductTypeVariantAttributes"; +export default ProductTypeVariantAttributes; diff --git a/src/productTypes/components/ProductTypeVariantAttributes/index.ts b/src/productTypes/components/ProductTypeVariantAttributes/index.ts new file mode 100644 index 000000000..7abf7c963 --- /dev/null +++ b/src/productTypes/components/ProductTypeVariantAttributes/index.ts @@ -0,0 +1,2 @@ +export { default } from "./ProductTypeVariantAttributes"; +export * from "./ProductTypeVariantAttributes"; diff --git a/src/productTypes/fixtures.ts b/src/productTypes/fixtures.ts index 865a6694f..df4f9e0b8 100644 --- a/src/productTypes/fixtures.ts +++ b/src/productTypes/fixtures.ts @@ -1140,6 +1140,24 @@ export const productType: ProductTypeDetails_productType = { unit: null } ], + assignedVariantAttributes: [ + { + __typename: "AssignedVariantAttribute" as "AssignedVariantAttribute", + attribute: { + __typename: "Attribute" as "Attribute", + filterableInDashboard: true, + filterableInStorefront: false, + id: "UHJvZHVjdEF0dHJpYnV0ATo5", + name: "Author", + slug: "author", + type: AttributeTypeEnum.PRODUCT_TYPE, + inputType: AttributeInputTypeEnum.DROPDOWN, + visibleInStorefront: true, + unit: null + }, + variantSelection: true + } + ], weight: { __typename: "Weight", unit: WeightUnitsEnum.KG, diff --git a/src/productTypes/mutations.ts b/src/productTypes/mutations.ts index 6c49a1309..e9dd4f06e 100644 --- a/src/productTypes/mutations.ts +++ b/src/productTypes/mutations.ts @@ -7,6 +7,10 @@ import { AssignProductAttribute, AssignProductAttributeVariables } from "./types/AssignProductAttribute"; +import { + ProductAttributeAssignmentUpdate, + ProductAttributeAssignmentUpdateVariables +} from "./types/ProductAttributeAssignmentUpdate"; import { ProductTypeAttributeReorder, ProductTypeAttributeReorderVariables @@ -170,3 +174,30 @@ export const ProductTypeAttributeReorderMutation = TypedMutation< ProductTypeAttributeReorder, ProductTypeAttributeReorderVariables >(productTypeAttributeReorder); + +export const productAttributeAssignmentUpdate = gql` + ${productTypeDetailsFragment} + mutation ProductAttributeAssignmentUpdate( + $operations: [ProductAttributeAssignmentUpdateInput]! + $productTypeId: ID! + ) { + productAttributeAssignmentUpdate( + operations: $operations + productTypeId: $productTypeId + ) { + errors { + field + message + attributes + } + productType { + ...ProductTypeDetailsFragment + } + } + } +`; + +export const useProductAttributeAssignmentUpdateMutation = makeMutation< + ProductAttributeAssignmentUpdate, + ProductAttributeAssignmentUpdateVariables +>(productAttributeAssignmentUpdate); diff --git a/src/productTypes/types/AssignProductAttribute.ts b/src/productTypes/types/AssignProductAttribute.ts index 75c396f8b..2dd738e88 100644 --- a/src/productTypes/types/AssignProductAttribute.ts +++ b/src/productTypes/types/AssignProductAttribute.ts @@ -59,6 +59,25 @@ export interface AssignProductAttribute_productAttributeAssign_productType_varia inputType: AttributeInputTypeEnum | null; } +export interface AssignProductAttribute_productAttributeAssign_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface AssignProductAttribute_productAttributeAssign_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: AssignProductAttribute_productAttributeAssign_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface AssignProductAttribute_productAttributeAssign_productType_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -77,6 +96,7 @@ export interface AssignProductAttribute_productAttributeAssign_productType { privateMetadata: (AssignProductAttribute_productAttributeAssign_productType_privateMetadata | null)[]; productAttributes: (AssignProductAttribute_productAttributeAssign_productType_productAttributes | null)[] | null; variantAttributes: (AssignProductAttribute_productAttributeAssign_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (AssignProductAttribute_productAttributeAssign_productType_assignedVariantAttributes | null)[] | null; weight: AssignProductAttribute_productAttributeAssign_productType_weight | null; } diff --git a/src/productTypes/types/ProductAttributeAssignmentUpdate.ts b/src/productTypes/types/ProductAttributeAssignmentUpdate.ts new file mode 100644 index 000000000..56dd06ed4 --- /dev/null +++ b/src/productTypes/types/ProductAttributeAssignmentUpdate.ts @@ -0,0 +1,116 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { ProductAttributeAssignmentUpdateInput, ProductTypeKindEnum, AttributeTypeEnum, MeasurementUnitsEnum, AttributeInputTypeEnum, WeightUnitsEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ProductAttributeAssignmentUpdate +// ==================================================== + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_errors { + __typename: "ProductError"; + field: string | null; + message: string | null; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_taxType { + __typename: "TaxType"; + description: string | null; + taxCode: string | null; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_metadata { + __typename: "MetadataItem"; + key: string; + value: string; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_privateMetadata { + __typename: "MetadataItem"; + key: string; + value: string; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_productAttributes { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_variantAttributes { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_weight { + __typename: "Weight"; + unit: WeightUnitsEnum; + value: number; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType { + __typename: "ProductType"; + id: string; + name: string; + kind: ProductTypeKindEnum; + hasVariants: boolean; + isShippingRequired: boolean; + taxType: ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_taxType | null; + metadata: (ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_metadata | null)[]; + privateMetadata: (ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_privateMetadata | null)[]; + productAttributes: (ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_productAttributes | null)[] | null; + variantAttributes: (ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_assignedVariantAttributes | null)[] | null; + weight: ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType_weight | null; +} + +export interface ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate { + __typename: "ProductAttributeAssignmentUpdate"; + errors: ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_errors[]; + productType: ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate_productType | null; +} + +export interface ProductAttributeAssignmentUpdate { + productAttributeAssignmentUpdate: ProductAttributeAssignmentUpdate_productAttributeAssignmentUpdate | null; +} + +export interface ProductAttributeAssignmentUpdateVariables { + operations: (ProductAttributeAssignmentUpdateInput | null)[]; + productTypeId: string; +} diff --git a/src/productTypes/types/ProductTypeAttributeReorder.ts b/src/productTypes/types/ProductTypeAttributeReorder.ts index cbdf71139..fb7debaf4 100644 --- a/src/productTypes/types/ProductTypeAttributeReorder.ts +++ b/src/productTypes/types/ProductTypeAttributeReorder.ts @@ -59,6 +59,25 @@ export interface ProductTypeAttributeReorder_productTypeReorderAttributes_produc inputType: AttributeInputTypeEnum | null; } +export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -77,6 +96,7 @@ export interface ProductTypeAttributeReorder_productTypeReorderAttributes_produc privateMetadata: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_privateMetadata | null)[]; productAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_productAttributes | null)[] | null; variantAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_assignedVariantAttributes | null)[] | null; weight: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_weight | null; } diff --git a/src/productTypes/types/ProductTypeCreate.ts b/src/productTypes/types/ProductTypeCreate.ts index df7eb1cc9..6344b3222 100644 --- a/src/productTypes/types/ProductTypeCreate.ts +++ b/src/productTypes/types/ProductTypeCreate.ts @@ -59,6 +59,25 @@ export interface ProductTypeCreate_productTypeCreate_productType_variantAttribut inputType: AttributeInputTypeEnum | null; } +export interface ProductTypeCreate_productTypeCreate_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductTypeCreate_productTypeCreate_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: ProductTypeCreate_productTypeCreate_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface ProductTypeCreate_productTypeCreate_productType_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -77,6 +96,7 @@ export interface ProductTypeCreate_productTypeCreate_productType { privateMetadata: (ProductTypeCreate_productTypeCreate_productType_privateMetadata | null)[]; productAttributes: (ProductTypeCreate_productTypeCreate_productType_productAttributes | null)[] | null; variantAttributes: (ProductTypeCreate_productTypeCreate_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (ProductTypeCreate_productTypeCreate_productType_assignedVariantAttributes | null)[] | null; weight: ProductTypeCreate_productTypeCreate_productType_weight | null; } diff --git a/src/productTypes/types/ProductTypeDetails.ts b/src/productTypes/types/ProductTypeDetails.ts index 0aa7d0d7a..fdc258d40 100644 --- a/src/productTypes/types/ProductTypeDetails.ts +++ b/src/productTypes/types/ProductTypeDetails.ts @@ -53,6 +53,25 @@ export interface ProductTypeDetails_productType_variantAttributes { inputType: AttributeInputTypeEnum | null; } +export interface ProductTypeDetails_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductTypeDetails_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: ProductTypeDetails_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface ProductTypeDetails_productType_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -71,6 +90,7 @@ export interface ProductTypeDetails_productType { privateMetadata: (ProductTypeDetails_productType_privateMetadata | null)[]; productAttributes: (ProductTypeDetails_productType_productAttributes | null)[] | null; variantAttributes: (ProductTypeDetails_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (ProductTypeDetails_productType_assignedVariantAttributes | null)[] | null; weight: ProductTypeDetails_productType_weight | null; } diff --git a/src/productTypes/types/ProductTypeUpdate.ts b/src/productTypes/types/ProductTypeUpdate.ts index cbf6cea18..13104a0ab 100644 --- a/src/productTypes/types/ProductTypeUpdate.ts +++ b/src/productTypes/types/ProductTypeUpdate.ts @@ -59,6 +59,25 @@ export interface ProductTypeUpdate_productTypeUpdate_productType_variantAttribut inputType: AttributeInputTypeEnum | null; } +export interface ProductTypeUpdate_productTypeUpdate_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface ProductTypeUpdate_productTypeUpdate_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: ProductTypeUpdate_productTypeUpdate_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface ProductTypeUpdate_productTypeUpdate_productType_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -77,6 +96,7 @@ export interface ProductTypeUpdate_productTypeUpdate_productType { privateMetadata: (ProductTypeUpdate_productTypeUpdate_productType_privateMetadata | null)[]; productAttributes: (ProductTypeUpdate_productTypeUpdate_productType_productAttributes | null)[] | null; variantAttributes: (ProductTypeUpdate_productTypeUpdate_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (ProductTypeUpdate_productTypeUpdate_productType_assignedVariantAttributes | null)[] | null; weight: ProductTypeUpdate_productTypeUpdate_productType_weight | null; } diff --git a/src/productTypes/types/UnassignProductAttribute.ts b/src/productTypes/types/UnassignProductAttribute.ts index 81f794e9b..88beb2e85 100644 --- a/src/productTypes/types/UnassignProductAttribute.ts +++ b/src/productTypes/types/UnassignProductAttribute.ts @@ -59,6 +59,25 @@ export interface UnassignProductAttribute_productAttributeUnassign_productType_v inputType: AttributeInputTypeEnum | null; } +export interface UnassignProductAttribute_productAttributeUnassign_productType_assignedVariantAttributes_attribute { + __typename: "Attribute"; + id: string; + name: string | null; + slug: string | null; + type: AttributeTypeEnum | null; + visibleInStorefront: boolean; + filterableInDashboard: boolean; + filterableInStorefront: boolean; + unit: MeasurementUnitsEnum | null; + inputType: AttributeInputTypeEnum | null; +} + +export interface UnassignProductAttribute_productAttributeUnassign_productType_assignedVariantAttributes { + __typename: "AssignedVariantAttribute"; + attribute: UnassignProductAttribute_productAttributeUnassign_productType_assignedVariantAttributes_attribute; + variantSelection: boolean; +} + export interface UnassignProductAttribute_productAttributeUnassign_productType_weight { __typename: "Weight"; unit: WeightUnitsEnum; @@ -77,6 +96,7 @@ export interface UnassignProductAttribute_productAttributeUnassign_productType { privateMetadata: (UnassignProductAttribute_productAttributeUnassign_productType_privateMetadata | null)[]; productAttributes: (UnassignProductAttribute_productAttributeUnassign_productType_productAttributes | null)[] | null; variantAttributes: (UnassignProductAttribute_productAttributeUnassign_productType_variantAttributes | null)[] | null; + assignedVariantAttributes: (UnassignProductAttribute_productAttributeUnassign_productType_assignedVariantAttributes | null)[] | null; weight: UnassignProductAttribute_productAttributeUnassign_productType_weight | null; } diff --git a/src/productTypes/views/ProductTypeUpdate/index.tsx b/src/productTypes/views/ProductTypeUpdate/index.tsx index 0f06ba3ec..e2a93dc8f 100644 --- a/src/productTypes/views/ProductTypeUpdate/index.tsx +++ b/src/productTypes/views/ProductTypeUpdate/index.tsx @@ -13,7 +13,10 @@ import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; import { getStringOrPlaceholder, maybe } from "@saleor/misc"; import useProductTypeDelete from "@saleor/productTypes/hooks/useProductTypeDelete"; -import { useProductTypeUpdateMutation } from "@saleor/productTypes/mutations"; +import { + useProductAttributeAssignmentUpdateMutation, + useProductTypeUpdateMutation +} from "@saleor/productTypes/mutations"; import { ReorderEvent } from "@saleor/types"; import { ProductAttributeType } from "@saleor/types/globalTypes"; import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler"; @@ -90,13 +93,47 @@ export const ProductTypeUpdate: React.FC = ({ } } }); + const [ + updateProductAttributes, + updateProductAttributesOpts + ] = useProductAttributeAssignmentUpdateMutation({ + onCompleted: updateData => { + if ( + updateData.productAttributeAssignmentUpdate.errors !== null && + updateData.productAttributeAssignmentUpdate.errors.length > 0 + ) { + setErrors(prevErrors => ({ + ...prevErrors, + formErrors: updateData.productAttributeAssignmentUpdate.errors + })); + } + } + }); const [updateMetadata] = useMetadataUpdate({}); const [updatePrivateMetadata] = usePrivateMetadataUpdate({}); const handleBack = () => navigate(productTypeListUrl()); + const [ + selectedVariantAttributes, + setSelectedVariantAttributes + ] = React.useState([]); const handleProductTypeUpdate = async (formData: ProductTypeForm) => { + const operations = formData.variantAttributes.map(variantAttribute => ({ + id: variantAttribute.value, + variantSelection: selectedVariantAttributes.includes( + variantAttribute.value + ) + })); + + const productAttributeUpdateResult = await updateProductAttributes({ + variables: { + productTypeId: id, + operations + } + }); + const result = await updateProductType({ variables: { id, @@ -117,7 +154,11 @@ export const ProductTypeUpdate: React.FC = ({ } }); - return result.data.productTypeUpdate.errors; + return [ + ...result.data.productTypeUpdate.errors, + ...productAttributeUpdateResult.data.productAttributeAssignmentUpdate + .errors + ]; }; const productTypeDeleteData = useProductTypeDelete({ @@ -232,7 +273,10 @@ export const ProductTypeUpdate: React.FC = ({ ids: params.ids }); - const loading = updateProductTypeOpts.loading || dataLoading; + const loading = + updateProductTypeOpts.loading || + updateProductAttributesOpts.loading || + dataLoading; const handleAttributeReorder = ( event: ReorderEvent, @@ -262,8 +306,13 @@ export const ProductTypeUpdate: React.FC = ({ errors={errors.formErrors} pageTitle={maybe(() => data.productType.name)} productType={maybe(() => data.productType)} - saveButtonBarState={updateProductTypeOpts.status} + saveButtonBarState={ + updateProductTypeOpts.status || + updateProductAttributesOpts.status + } taxTypes={maybe(() => data.taxTypes, [])} + selectedVariantAttributes={selectedVariantAttributes} + setSelectedVariantAttributes={setSelectedVariantAttributes} onAttributeAdd={type => navigate( productTypeUrl(id, { diff --git a/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx b/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx index 7e5deeb0a..0dab482b0 100644 --- a/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx +++ b/src/storybook/stories/productTypes/ProductTypeDetailsPage.tsx @@ -28,7 +28,9 @@ const props: Omit = { productType, saveButtonBarState: "default", taxTypes: [], - variantAttributeList: listActionsProps + variantAttributeList: listActionsProps, + setSelectedVariantAttributes: () => undefined, + selectedVariantAttributes: [] }; storiesOf("Views / Product types / Product type details", module) diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index aa05f5cca..18e286d92 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -8,6 +8,7 @@ //============================================================== export enum AccountErrorCode { + ACCOUNT_NOT_CONFIRMED = "ACCOUNT_NOT_CONFIRMED", ACTIVATE_OWN_ACCOUNT = "ACTIVATE_OWN_ACCOUNT", ACTIVATE_SUPERUSER_ACCOUNT = "ACTIVATE_SUPERUSER_ACCOUNT", CHANNEL_INACTIVE = "CHANNEL_INACTIVE", @@ -2528,6 +2529,12 @@ export interface PriceRangeInput { export interface ProductAttributeAssignInput { id: string; type: ProductAttributeType; + variantSelection?: boolean | null; +} + +export interface ProductAttributeAssignmentUpdateInput { + id: string; + variantSelection: boolean; } export interface ProductChannelListingAddInput {