Variant selection attributes (#1463)

* Create separate table for variant attributes and handle variant selection state

* implemented most required changes

* implementation

* localize leftover string

* implement most cr changes

* implemented most of cr changes

* add additional comment and fix ci

* reorder update mutation for BE consistency

Co-authored-by: bonifacy1 <szewczyk134@gmail.com>
This commit is contained in:
Szymon Wiszczuk 2021-10-19 10:38:54 +02:00 committed by GitHub
parent 19f90d29e8
commit 39dabc8ea2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 757 additions and 32 deletions

View file

@ -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}}"

View file

@ -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
}

View file

@ -30,6 +30,12 @@ export const productTypeDetailsFragment = gql`
variantAttributes {
...AttributeFragment
}
assignedVariantAttributes {
attribute {
...AttributeFragment
}
variantSelection
}
weight {
unit
value

View file

@ -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;
}

View file

@ -89,25 +89,12 @@ const ProductTypeAttributes: React.FC<ProductTypeAttributesProps> = props => {
const intl = useIntl();
return (
<Card
data-test={
type === ProductAttributeType.PRODUCT
? "product-attributes"
: "variant-attributes"
}
>
<Card data-test="product-attributes">
<CardTitle
title={
type === ProductAttributeType.PRODUCT
? intl.formatMessage({
defaultMessage: "Product Attributes",
description: "section header"
})
: intl.formatMessage({
defaultMessage: "Variant Attributes",
description: "section header"
})
}
title={intl.formatMessage({
defaultMessage: "Product Attributes",
description: "section header"
})}
toolbar={
<Button
data-test-id={testId}

View file

@ -32,6 +32,7 @@ import ProductTypeAttributes from "../ProductTypeAttributes/ProductTypeAttribute
import ProductTypeDetails from "../ProductTypeDetails/ProductTypeDetails";
import ProductTypeShipping from "../ProductTypeShipping/ProductTypeShipping";
import ProductTypeTaxes from "../ProductTypeTaxes/ProductTypeTaxes";
import ProductTypeVariantAttributes from "../ProductTypeVariantAttributes/ProductTypeVariantAttributes";
interface ChoiceType {
label: string;
@ -67,6 +68,8 @@ export interface ProductTypeDetailsPageProps {
onDelete: () => 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<ProductTypeDetailsPageProps> = ({
onBack,
onDelete,
onHasVariantsToggle,
onSubmit
onSubmit,
setSelectedVariantAttributes,
selectedVariantAttributes
}) => {
const intl = useIntl();
const {
@ -156,7 +161,7 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
return (
<Form initial={formInitialData} onSubmit={handleSubmit} confirmLeave>
{({ change, data, hasChanged, submit }) => {
{({ change, data, hasChanged, submit, setChanged }) => {
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -217,9 +222,11 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
{data.hasVariants && (
<>
<CardSpacer />
<ProductTypeAttributes
<ProductTypeVariantAttributes
testId="assignVariantsAttributes"
attributes={maybe(() => productType.variantAttributes)}
assignedVariantAttributes={
productType?.assignedVariantAttributes
}
disabled={disabled}
type={ProductAttributeType.VARIANT}
onAttributeAssign={onAttributeAdd}
@ -228,6 +235,11 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
onAttributeReorder(event, ProductAttributeType.VARIANT)
}
onAttributeUnassign={onAttributeUnassign}
onAttributeVariantSelection={setChanged}
setSelectedVariantAttributes={
setSelectedVariantAttributes
}
selectedVariantAttributes={selectedVariantAttributes}
{...variantAttributeList}
/>
</>

View file

@ -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<ProductTypeVariantAttributesProps> = 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 (
<Card data-test="variant-attributes">
<CardTitle
title={intl.formatMessage({
defaultMessage: "Variant Attributes",
description: "section header"
})}
toolbar={
<Button
data-test-id={testId}
color="primary"
variant="text"
onClick={() => onAttributeAssign(ProductAttributeType[type])}
>
<FormattedMessage
defaultMessage="Assign attribute"
description="button"
/>
</Button>
}
/>
<ResponsiveTable>
<colgroup>
<col className={classes.colGrab} />
<col />
<col className={classes.colName} />
<col className={classes.colSlug} />
<col className={classes.colVariant} />
<col className={classes.colAction} />
</colgroup>
{assignedVariantAttributes?.length > 0 && (
<TableHead
colSpan={numberOfColumns}
disabled={disabled}
dragRows
selected={selected}
items={
(assignedVariantAttributes as unknown) as ProductTypeDetails_productType_variantAttributes[]
}
toggleAll={toggleAll}
toolbar={toolbar}
>
<TableCell className={classes.colName}>
<FormattedMessage defaultMessage="Attribute name" />
</TableCell>
<TableCell className={classes.colName}>
<FormattedMessage
defaultMessage="Slug"
description="attribute internal name"
/>
</TableCell>
<TableCell className={classes.colName}>
<FormattedMessage
defaultMessage="Variant Selection"
description="variant attribute checkbox"
/>
</TableCell>
<TableCell />
</TableHead>
)}
<SortableTableBody onSortEnd={onAttributeReorder}>
{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 (
<SortableTableRow
selected={isVariantSelected}
className={!!attribute ? classes.link : undefined}
hover={!!attribute}
onClick={
!!attribute
? () => onAttributeClick(attribute.id)
: undefined
}
key={maybe(() => attribute.id)}
index={attributeIndex || 0}
data-test="id"
data-test-id={maybe(() => attribute.id)}
>
<TableCell padding="checkbox">
<Checkbox
checked={isVariantSelected}
disabled={disabled}
disableClickPropagation
onChange={() => toggle(attribute.id)}
/>
</TableCell>
<TableCell className={classes.colName} data-test="name">
{attribute.name ?? <Skeleton />}
</TableCell>
<TableCell className={classes.colSlug} data-test="slug">
{maybe(() => attribute.slug) ? (
attribute.slug
) : (
<Skeleton />
)}
</TableCell>
<TableCell
className={classes.colVariant}
data-test="variant-selection"
>
<div className={classes.colVariantContent}>
<Checkbox
checked={isSelected}
disabled={disabled || variantSelectionDisabled}
disableClickPropagation
onChange={() => {
onAttributeVariantSelection(true);
handleContainerAssign(
attribute.id,
isSelected,
selectedVariantAttributes,
setSelectedVariantAttributes
);
}}
/>
{!!variantSelectionDisabled && (
<Tooltip
title={
<FormattedMessage
defaultMessage={
"{inputType} attributes cannot be used as variant selection attributes."
}
values={{ inputType: readableAttributeInputType }}
/>
}
>
<HelpOutline className={classes.colVariantDisabled} />
</Tooltip>
)}
</div>
</TableCell>
<TableCell className={classes.colAction}>
<IconButton
onClick={stopPropagation(() =>
onAttributeUnassign(attribute.id)
)}
>
<DeleteIcon color="primary" />
</IconButton>
</TableCell>
</SortableTableRow>
);
},
() => (
<TableRow>
<TableCell colSpan={numberOfColumns}>
<FormattedMessage defaultMessage="No attributes found" />
</TableCell>
</TableRow>
)
)}
</SortableTableBody>
</ResponsiveTable>
</Card>
);
};
ProductTypeVariantAttributes.displayName = "ProductTypeVariantAttributes";
export default ProductTypeVariantAttributes;

View file

@ -0,0 +1,2 @@
export { default } from "./ProductTypeVariantAttributes";
export * from "./ProductTypeVariantAttributes";

View file

@ -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,

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<ProductTypeUpdateProps> = ({
}
}
});
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<string[]>([]);
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<ProductTypeUpdateProps> = ({
}
});
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<ProductTypeUpdateProps> = ({
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<ProductTypeUpdateProps> = ({
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, {

View file

@ -28,7 +28,9 @@ const props: Omit<ProductTypeDetailsPageProps, "classes"> = {
productType,
saveButtonBarState: "default",
taxTypes: [],
variantAttributeList: listActionsProps
variantAttributeList: listActionsProps,
setSelectedVariantAttributes: () => undefined,
selectedVariantAttributes: []
};
storiesOf("Views / Product types / Product type details", module)

View file

@ -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 {