Merge pull request #716 from mirumee/feature/variants-reorder
Add variants reordering possibility
This commit is contained in:
commit
734c9c0dc6
22 changed files with 1594 additions and 371 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 })}
|
||||
</>
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
236
src/products/types/ProductVariantReorder.ts
Normal file
236
src/products/types/ProductVariantReorder.ts
Normal 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;
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
@ -34,6 +34,7 @@ const props: ProductUpdatePageProps = {
|
|||
onImageUpload: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onVariantAdd: () => undefined,
|
||||
onVariantReorder: () => undefined,
|
||||
onVariantShow: () => undefined,
|
||||
onVariantsAdd: () => undefined,
|
||||
placeholderImage,
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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={[
|
||||
{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue