Merge pull request #716 from mirumee/feature/variants-reorder

Add variants reordering possibility
This commit is contained in:
Dawid Tarasiuk 2020-09-21 10:58:28 +02:00 committed by GitHub
commit 734c9c0dc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1594 additions and 371 deletions

View file

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

View file

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

View file

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

View file

@ -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<ImageUploadProps> = props => {
iconContainerActiveClassName,
iconContainerClassName,
isActiveClassName,
hideUploadIcon,
onImageUpload
} = props;
@ -82,6 +84,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
[isActiveClassName]: isDragActive
})}
>
{!hideUploadIcon && (
<div
className={classNames(iconContainerClassName, {
[iconContainerActiveClassName]: isDragActive
@ -100,6 +103,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
/>
</Typography>
</div>
)}
</div>
{children && children({ isDragActive })}
</>

View file

@ -83,9 +83,6 @@ const useStyles = makeStyles(
imageUploadActive: {
zIndex: 1
},
imageUploadIcon: {
display: "none"
},
imageUploadIconActive: {
display: "block"
},
@ -253,7 +250,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
className={classes.imageUpload}
isActiveClassName={classes.imageUploadActive}
disableClick={true}
iconContainerClassName={classes.imageUploadIcon}
hideUploadIcon={true}
iconContainerActiveClassName={classes.imageUploadIconActive}
onImageUpload={handleImageUpload}
>

View file

@ -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<ProductUpdatePageProps> = ({
onVariantAdd,
onVariantsAdd,
onVariantShow,
onVariantReorder,
isChecked,
selected,
toggle,
@ -302,6 +304,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onRowClick={onVariantShow}
onVariantAdd={onVariantAdd}
onVariantsAdd={onVariantsAdd}
onVariantReorder={onVariantReorder}
toolbar={toolbar}
isChecked={isChecked}
selected={selected}

View file

@ -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<ProductVariantCreatePageProps> = ({
@ -69,7 +71,8 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
weightUnit,
onBack,
onSubmit,
onVariantClick
onVariantClick,
onVariantReorder
}) => {
const intl = useIntl();
const attributeInput = React.useMemo(
@ -131,6 +134,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
return onVariantClick(variantId);
}
}}
onReorder={onVariantReorder}
/>
</div>
<div>

View file

@ -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,6 +30,7 @@ const useStyles = makeStyles(
cursor: "pointer"
},
tabActive: {
"& > td:first-child": {
"&:before": {
background: theme.palette.primary.main,
content: '""',
@ -37,6 +42,7 @@ const useStyles = makeStyles(
},
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<ProductVariantNavigationProps> = 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<ProductVariantNavigationProps> = props
})}
/>
<ResponsiveTable>
<TableBody>
{renderCollection(variants, variant => (
<TableRow
<SortableTableBody onSortEnd={onReorder}>
{renderCollection(variants, (variant, variantIndex) => (
<SortableTableRow
hover={!!variant}
key={variant ? variant.id : "skeleton"}
className={classes.link}
index={variantIndex}
className={classNames(classes.link, {
[classes.tabActive]: variant && variant.id === current
})}
onClick={variant ? () => onRowClick(variant.id) : undefined}
>
<TableCellAvatar
className={classNames({
[classes.tabActive]: variant && variant.id === current
})}
thumbnail={maybe(
() => variant.images[0].url,
fallbackThumbnail
@ -86,11 +100,11 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
<TableCell className={classes.colName}>
{variant ? variant.name || variant.sku : <Skeleton />}
</TableCell>
</TableRow>
</SortableTableRow>
))}
{onAdd ? (
<TableRow>
<TableCell colSpan={2}>
<TableCell colSpan={3}>
<Button color="primary" onClick={onAdd}>
<FormattedMessage
defaultMessage="Add variant"
@ -102,7 +116,7 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
) : (
<TableRow>
<TableCellAvatar className={classes.tabActive} thumbnail={null} />
<TableCell className={classes.colName}>
<TableCell className={classes.colName} colSpan={2}>
<FormattedMessage
defaultMessage="New Variant"
description="variant name"
@ -110,7 +124,7 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
</TableCell>
</TableRow>
)}
</TableBody>
</SortableTableBody>
</ResponsiveTable>
</Card>
);

View file

@ -19,6 +19,7 @@ import {
getAttributeInputFromVariant,
getStockInputFromVariant
} from "@saleor/products/utils/data";
import { ReorderAction } from "@saleor/types";
import { mapMetadataItemToInput } from "@saleor/utils/maps";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { diff } from "fast-array-diff";
@ -60,6 +61,7 @@ interface ProductVariantPageProps {
placeholderImage?: string;
header: string;
warehouses: WarehouseFragment[];
onVariantReorder: ReorderAction;
onAdd();
onBack();
onDelete();
@ -82,7 +84,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
onDelete,
onImageSelect,
onSubmit,
onVariantClick
onVariantClick,
onVariantReorder
}) => {
const attributeInput = React.useMemo(
() => getAttributeInputFromVariant(variant),
@ -188,6 +191,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
return onVariantClick(variantId);
}
}}
onReorder={onVariantReorder}
/>
</div>
<div>

View file

@ -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<ProductVariantsProps> = props => {
const {
@ -186,6 +189,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
onRowClick,
onVariantAdd,
onVariantsAdd,
onVariantReorder,
isChecked,
selected,
toggle,
@ -266,6 +270,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
items={variants}
toggleAll={toggleAll}
toolbar={toolbar}
dragRows
>
<TableCell className={classes.colName}>
<FormattedMessage
@ -291,8 +296,8 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
/>
</TableCell>
</TableHead>
<TableBody>
{renderCollection(variants, variant => {
<SortableTableBody onSortEnd={onVariantReorder}>
{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<ProductVariantsProps> = props => {
: null;
return (
<TableRow
<SortableTableRow
selected={isSelected}
hover={!!variant}
onClick={onRowClick(variant.id)}
key={variant ? variant.id : "skeleton"}
index={variantIndex || 0}
className={classes.link}
>
<TableCell padding="checkbox">
@ -354,10 +360,10 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
)
)}
</TableCell>
</TableRow>
</SortableTableRow>
);
})}
</TableBody>
</SortableTableBody>
</ResponsiveTable>
)}
</Card>

View file

@ -53,6 +53,10 @@ import {
ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables
} from "./types/ProductVariantBulkDelete";
import {
ProductVariantReorder,
ProductVariantReorderVariables
} from "./types/ProductVariantReorder";
import {
SimpleProductUpdate,
SimpleProductUpdateVariables
@ -628,3 +632,22 @@ export const useProductSetAvailabilityForPurchase = makeMutation<
ProductSetAvailabilityForPurchase,
ProductSetAvailabilityForPurchaseVariables
>(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);

View file

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

View file

@ -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<ProductUpdateProps> = ({ 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,6 +301,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
onVariantShow={variantId => () =>
navigate(productVariantEditUrl(product.id, variantId))}
onVariantReorder={handleVariantReorder}
onImageUpload={handleImageUpload}
onImageEdit={handleImageEdit}
onImageDelete={handleImageDelete}

View file

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

View file

@ -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<ProductUpdateProps> = ({
return <NotFoundPage onBack={handleBack} />;
}
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,6 +215,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
onVariantClick={variantId => {
navigate(productVariantEditUrl(productId, variantId));
}}
onVariantReorder={handleVariantReorder}
/>
<ProductVariantDeleteDialog
confirmButtonState={deleteVariantOpts.status}

View file

@ -17,9 +17,13 @@ import { decimal, weight } from "../../misc";
import ProductVariantCreatePage, {
ProductVariantCreatePageSubmitData
} from "../components/ProductVariantCreatePage";
import { useVariantCreateMutation } from "../mutations";
import {
useProductVariantReorderMutation,
useVariantCreateMutation
} from "../mutations";
import { useProductVariantCreateQuery } from "../queries";
import { productListUrl, productUrl, productVariantEditUrl } from "../urls";
import { createVariantReorderHandler } from "./ProductUpdate/handlers";
interface ProductVariantCreateProps {
productId: string;
@ -69,6 +73,15 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
return <NotFoundPage onBack={() => 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<ProductVariantCreateProps> = ({
const handleVariantClick = (id: string) =>
navigate(productVariantEditUrl(productId, id));
const disableForm = productLoading || variantCreateResult.loading;
const disableForm =
productLoading ||
variantCreateResult.loading ||
reorderProductVariantsOpts.loading;
return (
<>
@ -126,6 +142,7 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
onBack={handleBack}
onSubmit={handleSubmit}
onVariantClick={handleVariantClick}
onVariantReorder={handleVariantReorder}
saveButtonBarState={variantCreateResult.status}
warehouses={
warehouses.data?.warehouses.edges.map(edge => edge.node) || []

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,7 @@ const props: ProductUpdatePageProps = {
onImageUpload: () => undefined,
onSubmit: () => undefined,
onVariantAdd: () => undefined,
onVariantReorder: () => undefined,
onVariantShow: () => undefined,
onVariantsAdd: () => undefined,
placeholderImage,

View file

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

View file

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

View file

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

View file

@ -68,6 +68,10 @@ const WebhookEvents: React.FC<WebhookEventsProps> = ({
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"