From 88b8950408733eb94ef93964912dbfe999377ce0 Mon Sep 17 00:00:00 2001 From: Dawid Tarasiuk Date: Thu, 17 Sep 2020 13:31:09 +0200 Subject: [PATCH 1/4] Add drag-and-drop to allow variants reordering --- src/components/ImageUpload/ImageUpload.tsx | 38 ++++++------ .../ProductImages/ProductImages.tsx | 5 +- .../ProductUpdatePage/ProductUpdatePage.tsx | 5 +- .../ProductVariantCreatePage.tsx | 6 +- .../ProductVariantNavigation.tsx | 58 ++++++++++++------- .../ProductVariantPage/ProductVariantPage.tsx | 6 +- .../ProductVariants/ProductVariants.tsx | 24 +++++--- .../views/ProductUpdate/ProductUpdate.tsx | 1 + src/products/views/ProductVariant.tsx | 1 + src/products/views/ProductVariantCreate.tsx | 1 + .../stories/products/ProductUpdatePage.tsx | 1 + .../products/ProductVariantCreatePage.tsx | 4 ++ .../stories/products/ProductVariantPage.tsx | 3 + 13 files changed, 98 insertions(+), 55 deletions(-) diff --git a/src/components/ImageUpload/ImageUpload.tsx b/src/components/ImageUpload/ImageUpload.tsx index f0b1d18a2..65530d2fe 100644 --- a/src/components/ImageUpload/ImageUpload.tsx +++ b/src/components/ImageUpload/ImageUpload.tsx @@ -15,6 +15,7 @@ interface ImageUploadProps { isActiveClassName?: string; iconContainerClassName?: string; iconContainerActiveClassName?: string; + hideUploadIcon?: boolean; onImageUpload: (file: FileList) => void; } @@ -66,6 +67,7 @@ export const ImageUpload: React.FC = props => { iconContainerActiveClassName, iconContainerClassName, isActiveClassName, + hideUploadIcon, onImageUpload } = props; @@ -82,24 +84,26 @@ export const ImageUpload: React.FC = props => { [isActiveClassName]: isDragActive })} > -
- - - - + - -
+ + + + + + )} {children && children({ isDragActive })} diff --git a/src/products/components/ProductImages/ProductImages.tsx b/src/products/components/ProductImages/ProductImages.tsx index bc3c750ca..2890342cd 100644 --- a/src/products/components/ProductImages/ProductImages.tsx +++ b/src/products/components/ProductImages/ProductImages.tsx @@ -83,9 +83,6 @@ const useStyles = makeStyles( imageUploadActive: { zIndex: 1 }, - imageUploadIcon: { - display: "none" - }, imageUploadIconActive: { display: "block" }, @@ -253,7 +250,7 @@ const ProductImages: React.FC = props => { className={classes.imageUpload} isActiveClassName={classes.imageUploadActive} disableClick={true} - iconContainerClassName={classes.imageUploadIcon} + hideUploadIcon={true} iconContainerActiveClassName={classes.imageUploadIconActive} onImageUpload={handleImageUpload} > diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index d3be9d79e..ad8f27270 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -18,7 +18,7 @@ import { sectionNames } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories"; import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections"; -import { FetchMoreProps, ListActions } from "@saleor/types"; +import { FetchMoreProps, ListActions, ReorderAction } from "@saleor/types"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; @@ -73,6 +73,7 @@ export interface ProductUpdatePageProps extends ListActions { fetchCollections: (query: string) => void; onVariantsAdd: () => void; onVariantShow: (id: string) => () => void; + onVariantReorder: ReorderAction; onImageDelete: (id: string) => () => void; onBack?(); onDelete(); @@ -120,6 +121,7 @@ export const ProductUpdatePage: React.FC = ({ onVariantAdd, onVariantsAdd, onVariantShow, + onVariantReorder, isChecked, selected, toggle, @@ -302,6 +304,7 @@ export const ProductUpdatePage: React.FC = ({ onRowClick={onVariantShow} onVariantAdd={onVariantAdd} onVariantsAdd={onVariantsAdd} + onVariantReorder={onVariantReorder} toolbar={toolbar} isChecked={isChecked} selected={selected} diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index ac79fad42..1237d8b98 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -14,6 +14,7 @@ import useFormset, { } from "@saleor/hooks/useFormset"; import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; +import { ReorderAction } from "@saleor/types"; import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; import React from "react"; import { useIntl } from "react-intl"; @@ -56,6 +57,7 @@ interface ProductVariantCreatePageProps { onBack: () => void; onSubmit: (data: ProductVariantCreatePageSubmitData) => void; onVariantClick: (variantId: string) => void; + onVariantReorder: ReorderAction; } const ProductVariantCreatePage: React.FC = ({ @@ -69,7 +71,8 @@ const ProductVariantCreatePage: React.FC = ({ weightUnit, onBack, onSubmit, - onVariantClick + onVariantClick, + onVariantReorder }) => { const intl = useIntl(); const attributeInput = React.useMemo( @@ -131,6 +134,7 @@ const ProductVariantCreatePage: React.FC = ({ return onVariantClick(variantId); } }} + onReorder={onVariantReorder} />
diff --git a/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx b/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx index 89f0e7d55..249f03af6 100644 --- a/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx +++ b/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx @@ -1,13 +1,17 @@ import Button from "@material-ui/core/Button"; import Card from "@material-ui/core/Card"; import { makeStyles } from "@material-ui/core/styles"; -import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; import TableRow from "@material-ui/core/TableRow"; import CardTitle from "@saleor/components/CardTitle"; import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; +import { + SortableTableBody, + SortableTableRow +} from "@saleor/components/SortableTable"; import TableCellAvatar from "@saleor/components/TableCellAvatar"; +import { ReorderAction } from "@saleor/types"; import classNames from "classnames"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -26,16 +30,18 @@ const useStyles = makeStyles( cursor: "pointer" }, tabActive: { - "&:before": { - background: theme.palette.primary.main, - content: '""', - height: "100%", - left: 0, - position: "absolute", - top: 0, - width: 2 - }, - position: "relative" + "& > td:first-child": { + "&:before": { + background: theme.palette.primary.main, + content: '""', + height: "100%", + left: 0, + position: "absolute", + top: 0, + width: 2 + }, + position: "relative" + } } }), { name: "ProductVariantNavigation" } @@ -49,10 +55,18 @@ interface ProductVariantNavigationProps { | ProductVariantCreateData_product_variants[]; onAdd?: () => void; onRowClick: (variantId: string) => void; + onReorder: ReorderAction; } const ProductVariantNavigation: React.FC = props => { - const { current, fallbackThumbnail, variants, onAdd, onRowClick } = props; + const { + current, + fallbackThumbnail, + variants, + onAdd, + onRowClick, + onReorder + } = props; const classes = useStyles(props); const intl = useIntl(); @@ -66,18 +80,18 @@ const ProductVariantNavigation: React.FC = props })} /> - - {renderCollection(variants, variant => ( - + {renderCollection(variants, (variant, variantIndex) => ( + onRowClick(variant.id) : undefined} > variant.images[0].url, fallbackThumbnail @@ -86,11 +100,11 @@ const ProductVariantNavigation: React.FC = props {variant ? variant.name || variant.sku : } - + ))} {onAdd ? ( - +
diff --git a/src/products/components/ProductVariants/ProductVariants.tsx b/src/products/components/ProductVariants/ProductVariants.tsx index 86ee81aa4..90c798629 100644 --- a/src/products/components/ProductVariants/ProductVariants.tsx +++ b/src/products/components/ProductVariants/ProductVariants.tsx @@ -3,9 +3,7 @@ import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; import Hidden from "@material-ui/core/Hidden"; import { makeStyles } from "@material-ui/core/styles"; -import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; -import TableRow from "@material-ui/core/TableRow"; import Typography from "@material-ui/core/Typography"; import CardTitle from "@saleor/components/CardTitle"; import Checkbox from "@saleor/components/Checkbox"; @@ -14,13 +12,17 @@ import Money from "@saleor/components/Money"; import ResponsiveTable from "@saleor/components/ResponsiveTable"; import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; import Skeleton from "@saleor/components/Skeleton"; +import { + SortableTableBody, + SortableTableRow +} from "@saleor/components/SortableTable"; import TableHead from "@saleor/components/TableHead"; import { ProductVariant_costPrice } from "@saleor/fragments/types/ProductVariant"; import React from "react"; import { FormattedMessage, IntlShape, useIntl } from "react-intl"; import { maybe, renderCollection } from "../../../misc"; -import { ListActions } from "../../../types"; +import { ListActions, ReorderAction } from "../../../types"; import { ProductDetails_product_variants, ProductDetails_product_variants_stocks_warehouse @@ -171,12 +173,13 @@ interface ProductVariantsProps extends ListActions { disabled: boolean; variants: ProductDetails_product_variants[]; fallbackPrice?: ProductVariant_costPrice; + onVariantReorder: ReorderAction; onRowClick: (id: string) => () => void; onVariantAdd?(); onVariantsAdd?(); } -const numberOfColumns = 5; +const numberOfColumns = 6; export const ProductVariants: React.FC = props => { const { @@ -186,6 +189,7 @@ export const ProductVariants: React.FC = props => { onRowClick, onVariantAdd, onVariantsAdd, + onVariantReorder, isChecked, selected, toggle, @@ -266,6 +270,7 @@ export const ProductVariants: React.FC = props => { items={variants} toggleAll={toggleAll} toolbar={toolbar} + dragRows > = props => { /> - - {renderCollection(variants, variant => { + + {renderCollection(variants, (variant, variantIndex) => { const isSelected = variant ? isChecked(variant.id) : false; const numAvailable = variant && variant.stocks @@ -303,11 +308,12 @@ export const ProductVariants: React.FC = props => { : null; return ( - @@ -354,10 +360,10 @@ export const ProductVariants: React.FC = props => { ) )} - + ); })} - + )} diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index a5f9c4e57..33b12df25 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -289,6 +289,7 @@ export const ProductUpdate: React.FC = ({ id, params }) => { onVariantsAdd={() => navigate(productVariantCreatorUrl(id))} onVariantShow={variantId => () => navigate(productVariantEditUrl(product.id, variantId))} + onVariantReorder={() => undefined} // TODO: ... onImageUpload={handleImageUpload} onImageEdit={handleImageEdit} onImageDelete={handleImageDelete} diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index bce7b327b..36fac92c9 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -202,6 +202,7 @@ export const ProductVariant: React.FC = ({ onVariantClick={variantId => { navigate(productVariantEditUrl(productId, variantId)); }} + onVariantReorder={() => undefined} // TODO: ... /> = ({ onBack={handleBack} onSubmit={handleSubmit} onVariantClick={handleVariantClick} + onVariantReorder={() => undefined} // TODO: ... saveButtonBarState={variantCreateResult.status} warehouses={ warehouses.data?.warehouses.edges.map(edge => edge.node) || [] diff --git a/src/storybook/stories/products/ProductUpdatePage.tsx b/src/storybook/stories/products/ProductUpdatePage.tsx index 7c00bcf01..7682f8369 100644 --- a/src/storybook/stories/products/ProductUpdatePage.tsx +++ b/src/storybook/stories/products/ProductUpdatePage.tsx @@ -34,6 +34,7 @@ const props: ProductUpdatePageProps = { onImageUpload: () => undefined, onSubmit: () => undefined, onVariantAdd: () => undefined, + onVariantReorder: () => undefined, onVariantShow: () => undefined, onVariantsAdd: () => undefined, placeholderImage, diff --git a/src/storybook/stories/products/ProductVariantCreatePage.tsx b/src/storybook/stories/products/ProductVariantCreatePage.tsx index fee28f4a7..d8578724d 100644 --- a/src/storybook/stories/products/ProductVariantCreatePage.tsx +++ b/src/storybook/stories/products/ProductVariantCreatePage.tsx @@ -23,6 +23,7 @@ storiesOf("Views / Products / Create product variant", module) onBack={() => undefined} onSubmit={() => undefined} onVariantClick={undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" warehouses={warehouseList} /> @@ -54,6 +55,7 @@ storiesOf("Views / Products / Create product variant", module) onBack={() => undefined} onSubmit={() => undefined} onVariantClick={undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" warehouses={warehouseList} /> @@ -69,6 +71,7 @@ storiesOf("Views / Products / Create product variant", module) onBack={() => undefined} onSubmit={() => undefined} onVariantClick={undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" warehouses={warehouseList} /> @@ -87,6 +90,7 @@ storiesOf("Views / Products / Create product variant", module) onBack={() => undefined} onSubmit={() => undefined} onVariantClick={undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" warehouses={warehouseList} /> diff --git a/src/storybook/stories/products/ProductVariantPage.tsx b/src/storybook/stories/products/ProductVariantPage.tsx index f4d50e4ca..cca477a73 100644 --- a/src/storybook/stories/products/ProductVariantPage.tsx +++ b/src/storybook/stories/products/ProductVariantPage.tsx @@ -24,6 +24,7 @@ storiesOf("Views / Products / Product variant details", module) onImageSelect={() => undefined} onSubmit={() => undefined} onVariantClick={() => undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" warehouses={warehouseList} /> @@ -41,6 +42,7 @@ storiesOf("Views / Products / Product variant details", module) onImageSelect={() => undefined} onSubmit={() => undefined} onVariantClick={() => undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" warehouses={warehouseList} /> @@ -56,6 +58,7 @@ storiesOf("Views / Products / Product variant details", module) onImageSelect={() => undefined} onSubmit={() => undefined} onVariantClick={() => undefined} + onVariantReorder={() => undefined} saveButtonBarState="default" errors={[ { From ffaf56a51b7cb27ab431ce69f29418e55213388e Mon Sep 17 00:00:00 2001 From: Dawid Tarasiuk Date: Thu, 17 Sep 2020 16:37:33 +0200 Subject: [PATCH 2/4] Handle variants reordering --- locale/defaultMessages.json | 4 + schema.graphql | 10 + .../ProductVariantNavigation.tsx | 2 +- src/products/mutations.ts | 23 ++ src/products/types/ProductVariantReorder.ts | 236 ++++++++++++++++++ .../views/ProductUpdate/ProductUpdate.tsx | 16 +- src/products/views/ProductUpdate/handlers.ts | 21 ++ src/products/views/ProductVariant.tsx | 17 +- src/products/views/ProductVariantCreate.tsx | 22 +- src/types/globalTypes.ts | 2 + .../WebhookEvents/WebhookEvents.tsx | 4 + 11 files changed, 349 insertions(+), 8 deletions(-) create mode 100644 src/products/types/ProductVariantReorder.ts diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 105c9bf12..5c00097a1 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -5897,6 +5897,10 @@ "context": "webhook events", "string": "Expand or restrict webhooks permissions to register certain events in Saleor system." }, + "src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3316426878": { + "context": "event", + "string": "Product updated" + }, "src_dot_webhooks_dot_components_dot_WebhookEvents_dot_3345061702": { "context": "event", "string": "Order fully paid" diff --git a/schema.graphql b/schema.graphql index e2953571b..abe05671b 100644 --- a/schema.graphql +++ b/schema.graphql @@ -2640,6 +2640,7 @@ type Mutation { productClearPrivateMetadata(id: ID!, input: MetaPath!): ProductClearPrivateMeta @deprecated(reason: "Use the `deletePrivateMetadata` mutation instead. This field will be removed after 2020-07-31.") productSetAvailabilityForPurchase(isAvailable: Boolean!, productId: ID!, startDate: Date): ProductSetAvailabilityForPurchase productImageCreate(input: ProductImageCreateInput!): ProductImageCreate + productVariantReorder(moves: [ReorderInput]!, productId: ID!): ProductVariantReorder productImageDelete(id: ID!): ProductImageDelete productImageBulkDelete(ids: [ID]!): ProductImageBulkDelete productImageReorder(imagesIds: [ID]!, productId: ID!): ProductImageReorder @@ -2999,6 +3000,7 @@ enum OrderErrorCode { REQUIRED SHIPPING_METHOD_NOT_APPLICABLE SHIPPING_METHOD_REQUIRED + TAX_ERROR UNIQUE VOID_INACTIVE_PAYMENT ZERO_QUANTITY @@ -4174,6 +4176,12 @@ input ProductVariantInput { weight: WeightScalar } +type ProductVariantReorder { + errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") + product: Product + productErrors: [ProductError!]! +} + type ProductVariantStocksCreate { errors: [Error!]! @deprecated(reason: "Use typed errors with error codes. This field will be removed after 2020-07-31.") productVariant: ProductVariant @@ -5600,6 +5608,7 @@ enum WebhookEventTypeEnum { INVOICE_SENT CUSTOMER_CREATED PRODUCT_CREATED + PRODUCT_UPDATED CHECKOUT_QUANTITY_CHANGED CHECKOUT_CREATED CHECKOUT_UPDATED @@ -5622,6 +5631,7 @@ enum WebhookSampleEventTypeEnum { INVOICE_SENT CUSTOMER_CREATED PRODUCT_CREATED + PRODUCT_UPDATED CHECKOUT_QUANTITY_CHANGED CHECKOUT_CREATED CHECKOUT_UPDATED diff --git a/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx b/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx index 249f03af6..39ac8e6b2 100644 --- a/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx +++ b/src/products/components/ProductVariantNavigation/ProductVariantNavigation.tsx @@ -116,7 +116,7 @@ const ProductVariantNavigation: React.FC = props ) : ( - + (productSetAvailabilityForPurchase); + +const productVariantReorder = gql` + ${productErrorFragment} + ${productFragmentDetails} + mutation ProductVariantReorder($move: ReorderInput!, $productId: ID!) { + productVariantReorder(moves: [$move], productId: $productId) { + errors: productErrors { + ...ProductErrorFragment + } + product { + ...Product + } + } + } +`; +export const useProductVariantReorderMutation = makeMutation< + ProductVariantReorder, + ProductVariantReorderVariables +>(productVariantReorder); diff --git a/src/products/types/ProductVariantReorder.ts b/src/products/types/ProductVariantReorder.ts new file mode 100644 index 000000000..8c5f1741f --- /dev/null +++ b/src/products/types/ProductVariantReorder.ts @@ -0,0 +1,236 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ReorderInput, ProductErrorCode, AttributeInputTypeEnum, WeightUnitsEnum } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ProductVariantReorder +// ==================================================== + +export interface ProductVariantReorder_productVariantReorder_errors { + __typename: "ProductError"; + code: ProductErrorCode; + field: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes_attribute_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes_attribute { + __typename: "Attribute"; + id: string; + slug: string | null; + name: string | null; + inputType: AttributeInputTypeEnum | null; + valueRequired: boolean; + values: (ProductVariantReorder_productVariantReorder_product_attributes_attribute_values | null)[] | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_attributes { + __typename: "SelectedAttribute"; + attribute: ProductVariantReorder_productVariantReorder_product_attributes_attribute; + values: (ProductVariantReorder_productVariantReorder_product_attributes_values | null)[]; +} + +export interface ProductVariantReorder_productVariantReorder_product_productType_variantAttributes_values { + __typename: "AttributeValue"; + id: string; + name: string | null; + slug: string | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_productType_variantAttributes { + __typename: "Attribute"; + id: string; + name: string | null; + values: (ProductVariantReorder_productVariantReorder_product_productType_variantAttributes_values | null)[] | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_productType { + __typename: "ProductType"; + id: string; + variantAttributes: (ProductVariantReorder_productVariantReorder_product_productType_variantAttributes | null)[] | null; + name: string; + hasVariants: boolean; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start_gross { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start { + __typename: "TaxedMoney"; + gross: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start_gross; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop_gross { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop { + __typename: "TaxedMoney"; + gross: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop_gross; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted { + __typename: "TaxedMoneyRange"; + start: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_start | null; + stop: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted_stop | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_pricing { + __typename: "ProductPricingInfo"; + priceRangeUndiscounted: ProductVariantReorder_productVariantReorder_product_pricing_priceRangeUndiscounted | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_metadata { + __typename: "MetadataItem"; + key: string; + value: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_privateMetadata { + __typename: "MetadataItem"; + key: string; + value: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_category { + __typename: "Category"; + id: string; + name: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_collections { + __typename: "Collection"; + id: string; + name: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_margin { + __typename: "Margin"; + start: number | null; + stop: number | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_purchaseCost_start { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_purchaseCost_stop { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_purchaseCost { + __typename: "MoneyRange"; + start: ProductVariantReorder_productVariantReorder_product_purchaseCost_start | null; + stop: ProductVariantReorder_productVariantReorder_product_purchaseCost_stop | null; +} + +export interface ProductVariantReorder_productVariantReorder_product_images { + __typename: "ProductImage"; + id: string; + alt: string; + sortOrder: number | null; + url: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants_price { + __typename: "Money"; + amount: number; + currency: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants_stocks_warehouse { + __typename: "Warehouse"; + id: string; + name: string; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants_stocks { + __typename: "Stock"; + id: string; + quantity: number; + quantityAllocated: number; + warehouse: ProductVariantReorder_productVariantReorder_product_variants_stocks_warehouse; +} + +export interface ProductVariantReorder_productVariantReorder_product_variants { + __typename: "ProductVariant"; + id: string; + sku: string; + name: string; + price: ProductVariantReorder_productVariantReorder_product_variants_price | null; + margin: number | null; + stocks: (ProductVariantReorder_productVariantReorder_product_variants_stocks | null)[] | null; + trackInventory: boolean; +} + +export interface ProductVariantReorder_productVariantReorder_product_weight { + __typename: "Weight"; + unit: WeightUnitsEnum; + value: number; +} + +export interface ProductVariantReorder_productVariantReorder_product { + __typename: "Product"; + id: string; + attributes: ProductVariantReorder_productVariantReorder_product_attributes[]; + productType: ProductVariantReorder_productVariantReorder_product_productType; + pricing: ProductVariantReorder_productVariantReorder_product_pricing | null; + metadata: (ProductVariantReorder_productVariantReorder_product_metadata | null)[]; + privateMetadata: (ProductVariantReorder_productVariantReorder_product_privateMetadata | null)[]; + name: string; + descriptionJson: any; + seoTitle: string | null; + seoDescription: string | null; + category: ProductVariantReorder_productVariantReorder_product_category | null; + collections: (ProductVariantReorder_productVariantReorder_product_collections | null)[] | null; + margin: ProductVariantReorder_productVariantReorder_product_margin | null; + purchaseCost: ProductVariantReorder_productVariantReorder_product_purchaseCost | null; + isAvailableForPurchase: boolean | null; + isAvailable: boolean | null; + isPublished: boolean; + chargeTaxes: boolean; + publicationDate: any | null; + images: (ProductVariantReorder_productVariantReorder_product_images | null)[] | null; + variants: (ProductVariantReorder_productVariantReorder_product_variants | null)[] | null; + weight: ProductVariantReorder_productVariantReorder_product_weight | null; + availableForPurchase: any | null; + visibleInListings: boolean; +} + +export interface ProductVariantReorder_productVariantReorder { + __typename: "ProductVariantReorder"; + errors: ProductVariantReorder_productVariantReorder_errors[]; + product: ProductVariantReorder_productVariantReorder_product | null; +} + +export interface ProductVariantReorder { + productVariantReorder: ProductVariantReorder_productVariantReorder | null; +} + +export interface ProductVariantReorderVariables { + move: ReorderInput; + productId: string; +} diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index 33b12df25..8aeef201c 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -20,6 +20,7 @@ import { useProductSetAvailabilityForPurchase, useProductUpdateMutation, useProductVariantBulkDeleteMutation, + useProductVariantReorderMutation, useSimpleProductUpdateMutation } from "@saleor/products/mutations"; import useCategorySearch from "@saleor/searches/useCategorySearch"; @@ -52,7 +53,8 @@ import { import { createImageReorderHandler, createImageUploadHandler, - createUpdateHandler + createUpdateHandler, + createVariantReorderHandler } from "./handlers"; interface ProductUpdateProps { @@ -232,12 +234,22 @@ export const ProductUpdate: React.FC = ({ id, params }) => { reorderProductImages({ variables }) ); + const [ + reorderProductVariants, + reorderProductVariantsOpts + ] = useProductVariantReorderMutation({}); + + const handleVariantReorder = createVariantReorderHandler(product, variables => + reorderProductVariants({ variables }) + ); + const disableFormSave = createProductImageOpts.loading || deleteProductOpts.loading || reorderProductImagesOpts.loading || updateProductOpts.loading || productAvailabilityOpts.loading || + reorderProductVariantsOpts.loading || loading; const formTransitionState = getMutationState( @@ -289,7 +301,7 @@ export const ProductUpdate: React.FC = ({ id, params }) => { onVariantsAdd={() => navigate(productVariantCreatorUrl(id))} onVariantShow={variantId => () => navigate(productVariantEditUrl(product.id, variantId))} - onVariantReorder={() => undefined} // TODO: ... + onVariantReorder={handleVariantReorder} onImageUpload={handleImageUpload} onImageEdit={handleImageEdit} onImageDelete={handleImageDelete} diff --git a/src/products/views/ProductUpdate/handlers.ts b/src/products/views/ProductUpdate/handlers.ts index 1d1cdd56f..6e3de1180 100644 --- a/src/products/views/ProductUpdate/handlers.ts +++ b/src/products/views/ProductUpdate/handlers.ts @@ -14,6 +14,9 @@ import { ProductUpdate, ProductUpdateVariables } from "@saleor/products/types/ProductUpdate"; +import { ProductVariantCreateData_product } from "@saleor/products/types/ProductVariantCreateData"; +import { ProductVariantDetails_productVariant_product } from "@saleor/products/types/ProductVariantDetails"; +import { ProductVariantReorderVariables } from "@saleor/products/types/ProductVariantReorder"; import { SimpleProductUpdate, SimpleProductUpdateVariables @@ -134,3 +137,21 @@ export function createImageReorderHandler( }); }; } + +export function createVariantReorderHandler( + product: + | ProductDetails_product + | ProductVariantDetails_productVariant_product + | ProductVariantCreateData_product, + reorderProductVariants: (variables: ProductVariantReorderVariables) => void +) { + return ({ newIndex, oldIndex }: ReorderEvent) => { + reorderProductVariants({ + move: { + id: product.variants[oldIndex].id, + sortOrder: newIndex - oldIndex + }, + productId: product.id + }); + }; +} diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index 36fac92c9..d090a2f70 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -21,6 +21,7 @@ import ProductVariantPage, { ProductVariantPageSubmitData } from "../components/ProductVariantPage"; import { + useProductVariantReorderMutation, useVariantDeleteMutation, useVariantImageAssignMutation, useVariantImageUnassignMutation, @@ -36,6 +37,7 @@ import { ProductVariantEditUrlQueryParams } from "../urls"; import { mapFormsetStockToStockInput } from "../utils/data"; +import { createVariantReorderHandler } from "./ProductUpdate/handlers"; interface ProductUpdateProps { variantId: string; @@ -120,12 +122,23 @@ export const ProductVariant: React.FC = ({ return ; } + const [ + reorderProductVariants, + reorderProductVariantsOpts + ] = useProductVariantReorderMutation({}); + + const handleVariantReorder = createVariantReorderHandler( + variant?.product, + variables => reorderProductVariants({ variables }) + ); + const disableFormSave = loading || deleteVariantOpts.loading || updateVariantOpts.loading || assignImageOpts.loading || - unassignImageOpts.loading; + unassignImageOpts.loading || + reorderProductVariantsOpts.loading; const handleImageSelect = (id: string) => () => { if (variant) { @@ -202,7 +215,7 @@ export const ProductVariant: React.FC = ({ onVariantClick={variantId => { navigate(productVariantEditUrl(productId, variantId)); }} - onVariantReorder={() => undefined} // TODO: ... + onVariantReorder={handleVariantReorder} /> = ({ return navigate(productListUrl())} />; } + const [ + reorderProductVariants, + reorderProductVariantsOpts + ] = useProductVariantReorderMutation({}); + + const handleVariantReorder = createVariantReorderHandler(product, variables => + reorderProductVariants({ variables }) + ); + const handleBack = () => navigate(productUrl(productId)); const handleCreate = async (formData: ProductVariantCreatePageSubmitData) => { const result = await variantCreate({ @@ -104,7 +117,10 @@ export const ProductVariant: React.FC = ({ const handleVariantClick = (id: string) => navigate(productVariantEditUrl(productId, id)); - const disableForm = productLoading || variantCreateResult.loading; + const disableForm = + productLoading || + variantCreateResult.loading || + reorderProductVariantsOpts.loading; return ( <> @@ -126,7 +142,7 @@ export const ProductVariant: React.FC = ({ onBack={handleBack} onSubmit={handleSubmit} onVariantClick={handleVariantClick} - onVariantReorder={() => undefined} // TODO: ... + onVariantReorder={handleVariantReorder} saveButtonBarState={variantCreateResult.status} warehouses={ warehouses.data?.warehouses.edges.map(edge => edge.node) || [] diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 7430a65da..5d626b4df 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -548,6 +548,7 @@ export enum OrderErrorCode { REQUIRED = "REQUIRED", SHIPPING_METHOD_NOT_APPLICABLE = "SHIPPING_METHOD_NOT_APPLICABLE", SHIPPING_METHOD_REQUIRED = "SHIPPING_METHOD_REQUIRED", + TAX_ERROR = "TAX_ERROR", UNIQUE = "UNIQUE", VOID_INACTIVE_PAYMENT = "VOID_INACTIVE_PAYMENT", ZERO_QUANTITY = "ZERO_QUANTITY", @@ -905,6 +906,7 @@ export enum WebhookEventTypeEnum { ORDER_FULLY_PAID = "ORDER_FULLY_PAID", ORDER_UPDATED = "ORDER_UPDATED", PRODUCT_CREATED = "PRODUCT_CREATED", + PRODUCT_UPDATED = "PRODUCT_UPDATED", } export enum WebhookSortField { diff --git a/src/webhooks/components/WebhookEvents/WebhookEvents.tsx b/src/webhooks/components/WebhookEvents/WebhookEvents.tsx index 7bae16ebb..8578fa752 100644 --- a/src/webhooks/components/WebhookEvents/WebhookEvents.tsx +++ b/src/webhooks/components/WebhookEvents/WebhookEvents.tsx @@ -68,6 +68,10 @@ const WebhookEvents: React.FC = ({ defaultMessage: "Product created", description: "event" }), + [WebhookEventTypeEnum.PRODUCT_UPDATED]: intl.formatMessage({ + defaultMessage: "Product updated", + description: "event" + }), [WebhookEventTypeEnum.CHECKOUT_QUANTITY_CHANGED]: intl.formatMessage({ defaultMessage: "Changed quantity in checkout", description: "event" From b39183da9327563b78632aff4cc343ec890dc8d1 Mon Sep 17 00:00:00 2001 From: Dawid Tarasiuk Date: Thu, 17 Sep 2020 16:44:07 +0200 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b14159dc..5416f8bed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ All notable, unreleased changes to this project will be documented in this file. - Restyle side menu - #697 by @dominik-zeglen - Add error info when fetching taxes - #701 by @dominik-zeglen - Fix return to previous page on screen size change - #710 by @orzechdev +- Add variants reordering possibility - #716 by @orzechdev ## 2.10.1 From 8d9162cd1d2b03b91d5fa976069881860d28d227 Mon Sep 17 00:00:00 2001 From: Dawid Tarasiuk Date: Thu, 17 Sep 2020 17:04:47 +0200 Subject: [PATCH 4/4] Update test snapshots --- .../__snapshots__/Stories.test.ts.snap | 1460 +++++++++++++---- 1 file changed, 1149 insertions(+), 311 deletions(-) diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index af14341ef..2dee6d7ea 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -15259,6 +15259,48 @@ exports[`Storyshots Views / Apps / Webhooks / Create webhook default 1`] = `
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
New Variant @@ -140878,6 +141177,43 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` + + + @@ -140906,6 +141242,43 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` + + + @@ -140981,6 +141354,7 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` New Variant @@ -141698,6 +142072,43 @@ exports[`Storyshots Views / Products / Create product variant when loading data + + + @@ -141784,6 +142195,7 @@ exports[`Storyshots Views / Products / Create product variant when loading data New Variant @@ -142446,6 +142858,43 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = + + + @@ -142474,6 +142923,43 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = + + + @@ -142549,6 +143035,7 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = New Variant @@ -143899,56 +144386,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -144390,7 +144828,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -144506,6 +144985,43 @@ Ctrl + K" + + + @@ -146171,56 +146687,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -146403,7 +146870,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -146519,6 +147027,43 @@ Ctrl + K" + + + @@ -148173,56 +148718,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -150593,56 +151089,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -153147,56 +153594,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -153638,7 +154036,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -153754,6 +154193,43 @@ Ctrl + K" + + + @@ -157112,7 +157588,11 @@ Ctrl + K" class="MuiTableRow-root-id MuiTableRow-head-id" > + + + + @@ -157228,6 +157745,43 @@ Ctrl + K" + + + @@ -158882,56 +159436,7 @@ Ctrl + K"
-
- - -
- Drop here to upload -
-
-
+ />
@@ -167375,10 +167880,47 @@ exports[`Storyshots Views / Products / Product variant details attribute errors class="MuiTableBody-root-id" > + + +
+ + + @@ -167433,6 +168012,43 @@ exports[`Storyshots Views / Products / Product variant details attribute errors + + + @@ -167461,6 +168077,43 @@ exports[`Storyshots Views / Products / Product variant details attribute errors + + + @@ -167491,7 +168144,7 @@ exports[`Storyshots Views / Products / Product variant details attribute errors >