Excluded Products in shipping view (#866)
* Clean up stories * Add missing props * Add zip codes section (#861) * Add zip code listing * Add list wrapping * Update snapshots * Set up API data * Fix lgtm warning * Update snapshots * Run Actions on all PR * Checks on PR * Test envs on PR * Cleanup action on PR * Update messages Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> * Allow zip codes to be assigned to shipping method * Add zip code deletion (#871) * Add zip code range dialog * Fix path management * Use query params to handle modal actions * Allow zip codes to be assigned to shipping method * Make params optional * Fix types * Clean up urls * Add zip code range delete action * Update snapshots and messages * Update schema * Refresh zip code list after assigning them * Update types and snapshots * Update snapshots * Fix error message, checkbox default value (#880) * Fix error message, checkbox default value * Update snapshots * Update schema and types * Update stories * add excluded products section in shipping methods views * create UnassignDialog component * use priceRangeFragment in shipping queries * remove unneeded price from ShippingMethodAddProductsDialog * update messages in ShippingMethodProducts * updates after rebase * update snapshots, fix lint errors * fix ShippingMethodProductsAddDialog * update snapshots * small fix in ShippingMethodProducts * update snapshots after rebase * add handleClose func in ShippingMethodProductsAddDialog * Fix metadata not showing in category update * update snapshots again * update ShippingMethodProductsAddDialog * updates after rebase * update Price and Weight rates views Co-authored-by: dominik-zeglen <flesz3@o2.pl> Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> Co-authored-by: Tomasz Szymański <lime129@gmail.com> Co-authored-by: Magdalena Markusik <magdalena.markusik@mirumee.com>
This commit is contained in:
parent
f97ddd0128
commit
b774cc9002
32 changed files with 6931 additions and 848 deletions
|
@ -5349,6 +5349,33 @@
|
|||
"context": "column title",
|
||||
"string": "Channel name"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProductsAddDialog_dot_2850255786": {
|
||||
"string": "Search Products"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProductsAddDialog_dot_353369701": {
|
||||
"string": "No products matching given query"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProductsAddDialog_dot_3756118423": {
|
||||
"context": "dialog header",
|
||||
"string": "Assign Products"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProducts_dot_1737533260": {
|
||||
"string": "No Products"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProducts_dot_1781179817": {
|
||||
"context": "section header",
|
||||
"string": "Excluded Products"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProducts_dot_2100305525": {
|
||||
"context": "button",
|
||||
"string": "Assign products"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProducts_dot_2697405188": {
|
||||
"string": "Product Name"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingMethodProducts_dot_4190792473": {
|
||||
"string": "Actions"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingRateZipCodeRangeRemoveDialog_dot_1083561409": {
|
||||
"string": "Are you sure you want to remove this ZIP-code rule?"
|
||||
},
|
||||
|
@ -5427,17 +5454,20 @@
|
|||
"src_dot_shipping_dot_components_dot_ShippingZoneInfo_dot_579967655": {
|
||||
"string": "Shipping rate name"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesPage_dot_1161979494": {
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesCreatePage_dot_1161979494": {
|
||||
"context": "page title",
|
||||
"string": "Price Rate Create"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesPage_dot_1325966144": {
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesCreatePage_dot_1325966144": {
|
||||
"string": "Shipping"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesPage_dot_3538551526": {
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesCreatePage_dot_3538551526": {
|
||||
"context": "page title",
|
||||
"string": "Weight Rate Create"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRatesPage_dot_1325966144": {
|
||||
"string": "Shipping"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_ShippingZoneRates_dot_1134347598": {
|
||||
"context": "shipping method price",
|
||||
"string": "Price"
|
||||
|
@ -5554,6 +5584,14 @@
|
|||
"src_dot_shipping_dot_components_dot_ShippingZonesList_dot_655374584": {
|
||||
"string": "No shipping zones found"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_UnassignDialog_dot_1203193503": {
|
||||
"context": "dialog header",
|
||||
"string": "Unassign Products From Shipping"
|
||||
},
|
||||
"src_dot_shipping_dot_components_dot_UnassignDialog_dot_3215481647": {
|
||||
"context": "dialog content",
|
||||
"string": "{counter,plural,one{Are you sure you want to unassign this product?} other{Are you sure you want to unassign {displayQuantity} products?}}"
|
||||
},
|
||||
"src_dot_shipping_dot_invalid": {
|
||||
"context": "error message",
|
||||
"string": "Value is invalid"
|
||||
|
@ -5587,6 +5625,10 @@
|
|||
"src_dot_shipping_dot_views_dot_PriceRatesUpdate_dot_3823295269": {
|
||||
"string": "Manage Channel Availability"
|
||||
},
|
||||
"src_dot_shipping_dot_views_dot_PriceRatesUpdate_dot_870815507": {
|
||||
"context": "unassign products from shipping method, button",
|
||||
"string": "Unassign"
|
||||
},
|
||||
"src_dot_shipping_dot_views_dot_ShippingZoneDetails_dot_1010705153": {
|
||||
"context": "dialog header",
|
||||
"string": "Delete Shipping Zone"
|
||||
|
@ -5609,6 +5651,10 @@
|
|||
"src_dot_shipping_dot_views_dot_WeightRatesUpdate_dot_3014453080": {
|
||||
"string": "Manage Channels Availability"
|
||||
},
|
||||
"src_dot_shipping_dot_views_dot_WeightRatesUpdate_dot_870815507": {
|
||||
"context": "unassign products from shipping method, button",
|
||||
"string": "Unassign"
|
||||
},
|
||||
"src_dot_shipping_dot_weight": {
|
||||
"context": "error message",
|
||||
"string": "Maximum weight cannot be lower than minimum"
|
||||
|
|
|
@ -75,9 +75,9 @@ function useCategoryUpdateForm(
|
|||
// Need to make it function to always have description.current up to date
|
||||
const getData = (): CategoryUpdateData => ({
|
||||
...form.data,
|
||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
|
||||
description: description.current
|
||||
});
|
||||
|
||||
const getSubmitData = (): CategoryUpdateData => ({
|
||||
...getData(),
|
||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
|
||||
|
|
|
@ -56,6 +56,36 @@ export const shippingMethodFragment = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const shippingMethodWithExcludedProductsFragment = gql`
|
||||
${fragmentMoney}
|
||||
${shippingMethodFragment}
|
||||
fragment ShippingMethodWithExcludedProductsFragment on ShippingMethod {
|
||||
...ShippingMethodFragment
|
||||
excludedProducts(
|
||||
before: $before
|
||||
after: $after
|
||||
first: $first
|
||||
last: $last
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
endCursor
|
||||
startCursor
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
thumbnail {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const shippingZoneDetailsFragment = gql`
|
||||
${shippingZoneFragment}
|
||||
${shippingMethodFragment}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { WeightUnitsEnum, ShippingMethodTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: ShippingMethodWithExcludedProductsFragment
|
||||
// ====================================================
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_zipCodeRules {
|
||||
__typename: "ShippingMethodZipCodeRule";
|
||||
id: string;
|
||||
start: string | null;
|
||||
end: string | null;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_minimumOrderWeight {
|
||||
__typename: "Weight";
|
||||
unit: WeightUnitsEnum;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_maximumOrderWeight {
|
||||
__typename: "Weight";
|
||||
unit: WeightUnitsEnum;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_channelListings_channel {
|
||||
__typename: "Channel";
|
||||
id: string;
|
||||
name: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_channelListings_price {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_channelListings_minimumOrderPrice {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_channelListings_maximumOrderPrice {
|
||||
__typename: "Money";
|
||||
amount: number;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_channelListings {
|
||||
__typename: "ShippingMethodChannelListing";
|
||||
id: string;
|
||||
channel: ShippingMethodWithExcludedProductsFragment_channelListings_channel;
|
||||
price: ShippingMethodWithExcludedProductsFragment_channelListings_price | null;
|
||||
minimumOrderPrice: ShippingMethodWithExcludedProductsFragment_channelListings_minimumOrderPrice | null;
|
||||
maximumOrderPrice: ShippingMethodWithExcludedProductsFragment_channelListings_maximumOrderPrice | null;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_excludedProducts_pageInfo {
|
||||
__typename: "PageInfo";
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
endCursor: string | null;
|
||||
startCursor: string | null;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_excludedProducts_edges_node_thumbnail {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_excludedProducts_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
name: string;
|
||||
thumbnail: ShippingMethodWithExcludedProductsFragment_excludedProducts_edges_node_thumbnail | null;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_excludedProducts_edges {
|
||||
__typename: "ProductCountableEdge";
|
||||
node: ShippingMethodWithExcludedProductsFragment_excludedProducts_edges_node;
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment_excludedProducts {
|
||||
__typename: "ProductCountableConnection";
|
||||
pageInfo: ShippingMethodWithExcludedProductsFragment_excludedProducts_pageInfo;
|
||||
edges: ShippingMethodWithExcludedProductsFragment_excludedProducts_edges[];
|
||||
}
|
||||
|
||||
export interface ShippingMethodWithExcludedProductsFragment {
|
||||
__typename: "ShippingMethod";
|
||||
id: string;
|
||||
zipCodeRules: (ShippingMethodWithExcludedProductsFragment_zipCodeRules | null)[] | null;
|
||||
minimumOrderWeight: ShippingMethodWithExcludedProductsFragment_minimumOrderWeight | null;
|
||||
maximumOrderWeight: ShippingMethodWithExcludedProductsFragment_maximumOrderWeight | null;
|
||||
name: string;
|
||||
type: ShippingMethodTypeEnum | null;
|
||||
channelListings: ShippingMethodWithExcludedProductsFragment_channelListings[] | null;
|
||||
excludedProducts: ShippingMethodWithExcludedProductsFragment_excludedProducts | null;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { shippingZone } from "@saleor/shipping/fixtures";
|
||||
import Decorator from "@saleor/storybook//Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ShippingMethodProducts, {
|
||||
ShippingMethodProductsProps
|
||||
} from "./ShippingMethodProducts";
|
||||
|
||||
const products = shippingZone.shippingMethods[0].excludedProducts.edges.map(
|
||||
edge => edge.node
|
||||
);
|
||||
|
||||
const props: ShippingMethodProductsProps = {
|
||||
disabled: false,
|
||||
isChecked: () => undefined,
|
||||
onNextPage: () => undefined,
|
||||
onPreviousPage: () => undefined,
|
||||
onProductAssign: () => undefined,
|
||||
onProductUnassign: () => undefined,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false
|
||||
},
|
||||
products,
|
||||
selected: products.length,
|
||||
toggle: () => undefined,
|
||||
toggleAll: () => undefined,
|
||||
toolbar: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Shipping / ShippingMethodProducts", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ShippingMethodProducts {...props} />);
|
|
@ -0,0 +1,171 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { ShippingZone_shippingZone_shippingMethods_excludedProducts_edges_node } from "@saleor/shipping/types/ShippingZone";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
colAction: {
|
||||
"&:last-child": {
|
||||
paddingRight: theme.spacing(3)
|
||||
},
|
||||
textAlign: "right",
|
||||
width: 100
|
||||
},
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colProductName: {
|
||||
paddingLeft: 0
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
}
|
||||
}),
|
||||
{ name: "ShippingMethodProducts" }
|
||||
);
|
||||
|
||||
export interface ShippingMethodProductsProps
|
||||
extends Pick<ListProps, Exclude<keyof ListProps, "onRowClick">>,
|
||||
ListActions {
|
||||
products: ShippingZone_shippingZone_shippingMethods_excludedProducts_edges_node[];
|
||||
onProductAssign: () => void;
|
||||
onProductUnassign: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
const numberOfColumns = 3;
|
||||
|
||||
const ShippingMethodProducts: React.FC<ShippingMethodProductsProps> = props => {
|
||||
const {
|
||||
disabled,
|
||||
pageInfo,
|
||||
products,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onProductAssign,
|
||||
onProductUnassign,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Excluded Products",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onProductAssign}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign products"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ResponsiveTable className={classes.table}>
|
||||
{!!products?.length && (
|
||||
<>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={products}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colProductName}>
|
||||
<FormattedMessage defaultMessage="Product Name" />
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
<FormattedMessage defaultMessage="Actions" />
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={
|
||||
pageInfo && !disabled ? pageInfo.hasNextPage : false
|
||||
}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</>
|
||||
)}
|
||||
<TableBody>
|
||||
{products?.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<FormattedMessage defaultMessage="No Products" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
renderCollection(products, product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
return (
|
||||
<TableRow key={product ? product.id : "skeleton"}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={product?.thumbnail?.url}
|
||||
>
|
||||
{product?.name ? (
|
||||
<Typography variant="body2">{product.name}</Typography>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colAction}>
|
||||
<IconButton onClick={() => onProductUnassign([product.id])}>
|
||||
<DeleteIcon color="primary" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
ShippingMethodProducts.displayName = "ShippingMethodProducts";
|
||||
export default ShippingMethodProducts;
|
2
src/shipping/components/ShippingMethodProducts/index.ts
Normal file
2
src/shipping/components/ShippingMethodProducts/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./ShippingMethodProducts";
|
||||
export { default } from "./ShippingMethodProducts";
|
|
@ -0,0 +1,24 @@
|
|||
import { products } from "@saleor/shipping/fixtures";
|
||||
import Decorator from "@saleor/storybook//Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ShippingMethodProductsAddDialog, {
|
||||
ShippingMethodProductsAddDialogProps
|
||||
} from "./ShippingMethodProductsAddDialog";
|
||||
|
||||
const props: ShippingMethodProductsAddDialogProps = {
|
||||
confirmButtonState: "default",
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
onClose: () => undefined,
|
||||
onFetch: () => undefined,
|
||||
onFetchMore: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
open: true,
|
||||
products
|
||||
};
|
||||
|
||||
storiesOf("Shipping / ShippingMethodProductsAddDialog", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ShippingMethodProductsAddDialog {...props} />);
|
|
@ -0,0 +1,242 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
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 TextField from "@material-ui/core/TextField";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
|
||||
import { ShippingPriceExcludeProduct } from "@saleor/shipping/types/ShippingPriceExcludeProduct";
|
||||
import { FetchMoreProps } from "@saleor/types";
|
||||
import React from "react";
|
||||
import { MutationFetchResult } from "react-apollo";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
avatar: {
|
||||
paddingLeft: 0,
|
||||
width: 64
|
||||
},
|
||||
colName: {
|
||||
paddingLeft: 0
|
||||
},
|
||||
content: {
|
||||
overflowY: "scroll"
|
||||
},
|
||||
loadMoreLoaderContainer: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
height: theme.spacing(3),
|
||||
justifyContent: "center",
|
||||
marginTop: theme.spacing(3)
|
||||
},
|
||||
overflow: {
|
||||
overflowY: "visible"
|
||||
},
|
||||
productCheckboxCell: {
|
||||
"&:first-child": {
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ name: "ShippingMethodProductsAddDialog" }
|
||||
);
|
||||
|
||||
export interface ShippingMethodProductsAddDialogProps extends FetchMoreProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
products: SearchProducts_search_edges_node[];
|
||||
onClose: () => void;
|
||||
onFetch: (query: string) => void;
|
||||
onSubmit: (
|
||||
ids: string[]
|
||||
) => Promise<MutationFetchResult<ShippingPriceExcludeProduct>>;
|
||||
}
|
||||
|
||||
const handleProductAssign = (
|
||||
product: SearchProducts_search_edges_node,
|
||||
isSelected: boolean,
|
||||
selectedProducts: SearchProducts_search_edges_node[],
|
||||
setSelectedProducts: (data: SearchProducts_search_edges_node[]) => void
|
||||
) => {
|
||||
if (isSelected) {
|
||||
setSelectedProducts(
|
||||
selectedProducts.filter(
|
||||
selectedProduct => selectedProduct.id !== product.id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setSelectedProducts([...selectedProducts, product]);
|
||||
}
|
||||
};
|
||||
|
||||
const ShippingMethodProductsAddDialog: React.FC<ShippingMethodProductsAddDialogProps> = props => {
|
||||
const {
|
||||
confirmButtonState,
|
||||
open,
|
||||
loading,
|
||||
hasMore,
|
||||
products,
|
||||
onFetch,
|
||||
onFetchMore,
|
||||
onClose,
|
||||
onSubmit
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
const [query, onQueryChange, resetQuery] = useSearchQuery(onFetch);
|
||||
const [selectedProducts, setSelectedProducts] = React.useState<
|
||||
SearchProducts_search_edges_node[]
|
||||
>([]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSubmit(selectedProducts.map(product => product.id)).then(() => {
|
||||
setSelectedProducts([]);
|
||||
resetQuery();
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setSelectedProducts([]);
|
||||
resetQuery();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={handleClose}
|
||||
open={open}
|
||||
classes={{ paper: classes.overflow }}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign Products"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<TextField
|
||||
name="query"
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Search Products"
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
defaultMessage: "Search Products"
|
||||
})}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
autoComplete: "off",
|
||||
endAdornment: loading && <CircularProgress size={16} />
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogContent className={classes.content}>
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={onFetchMore}
|
||||
hasMore={hasMore}
|
||||
useWindow={false}
|
||||
loader={
|
||||
<div key="loader" className={classes.loadMoreLoaderContainer}>
|
||||
<CircularProgress size={16} />
|
||||
</div>
|
||||
}
|
||||
threshold={10}
|
||||
>
|
||||
<ResponsiveTable key="table">
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
products,
|
||||
(product, productIndex) => {
|
||||
const isSelected = selectedProducts.some(
|
||||
selectedProduct => selectedProduct.id === product.id
|
||||
);
|
||||
return (
|
||||
<React.Fragment
|
||||
key={product ? product.id : `skeleton-${productIndex}`}
|
||||
>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
className={classes.productCheckboxCell}
|
||||
>
|
||||
{product && (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={loading}
|
||||
onChange={() =>
|
||||
handleProductAssign(
|
||||
product,
|
||||
isSelected,
|
||||
selectedProducts,
|
||||
setSelectedProducts
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.avatar}
|
||||
thumbnail={product?.thumbnail?.url}
|
||||
/>
|
||||
<TableCell className={classes.colName} colSpan={2}>
|
||||
{product?.name || <Skeleton />}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>
|
||||
<FormattedMessage defaultMessage="No products matching given query" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</InfiniteScroll>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
disabled={loading || !selectedProducts?.length}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<FormattedMessage {...buttonMessages.confirm} />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
ShippingMethodProductsAddDialog.displayName = "ShippingMethodProductsAddDialog";
|
||||
export default ShippingMethodProductsAddDialog;
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./ShippingMethodProductsAddDialog";
|
||||
export { default } from "./ShippingMethodProductsAddDialog";
|
|
@ -0,0 +1,87 @@
|
|||
import Decorator from "@saleor/storybook//Decorator";
|
||||
import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ShippingZoneRatesCreatePage, {
|
||||
ShippingZoneRatesCreatePageProps
|
||||
} from "./ShippingZoneRatesCreatePage";
|
||||
|
||||
const channels = [
|
||||
{
|
||||
currency: "USD",
|
||||
id: "1",
|
||||
maxValue: "10",
|
||||
minValue: "0",
|
||||
name: "channel",
|
||||
price: "5"
|
||||
},
|
||||
{
|
||||
currency: "USD",
|
||||
id: "2",
|
||||
maxValue: "20",
|
||||
minValue: "1",
|
||||
name: "test",
|
||||
price: "6"
|
||||
}
|
||||
];
|
||||
|
||||
const defaultChannels = [
|
||||
{
|
||||
currency: "USD",
|
||||
id: "1",
|
||||
maxValue: "",
|
||||
minValue: "",
|
||||
name: "channel",
|
||||
price: ""
|
||||
}
|
||||
];
|
||||
|
||||
const props: ShippingZoneRatesCreatePageProps = {
|
||||
allChannelsCount: 3,
|
||||
channelErrors: [],
|
||||
disabled: false,
|
||||
errors: [],
|
||||
onBack: () => undefined,
|
||||
onChannelsChange: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onZipCodeAssign: () => undefined,
|
||||
onZipCodeUnassign: () => undefined,
|
||||
openChannelsModal: () => undefined,
|
||||
saveButtonBarState: "default",
|
||||
shippingChannels: defaultChannels,
|
||||
variant: ShippingMethodTypeEnum.PRICE,
|
||||
zipCodes: [
|
||||
{
|
||||
__typename: "ShippingMethodZipCodeRule",
|
||||
end: "51-200",
|
||||
id: "1",
|
||||
start: "51-220"
|
||||
},
|
||||
{
|
||||
__typename: "ShippingMethodZipCodeRule",
|
||||
end: "31-101",
|
||||
id: "1",
|
||||
start: "44-205"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
storiesOf("Shipping / ShippingZoneRatesCreatePage page", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("create price", () => <ShippingZoneRatesCreatePage {...props} />)
|
||||
.add("loading", () => (
|
||||
<ShippingZoneRatesCreatePage
|
||||
{...props}
|
||||
disabled={true}
|
||||
saveButtonBarState={"loading"}
|
||||
/>
|
||||
))
|
||||
.add("create weight", () => (
|
||||
<ShippingZoneRatesCreatePage
|
||||
{...props}
|
||||
shippingChannels={channels}
|
||||
variant={ShippingMethodTypeEnum.WEIGHT}
|
||||
/>
|
||||
));
|
|
@ -0,0 +1,190 @@
|
|||
import { ChannelShippingData } from "@saleor/channels/utils";
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import ChannelsAvailability from "@saleor/components/ChannelsAvailability";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { ShippingChannelsErrorFragment } from "@saleor/fragments/types/ShippingChannelsErrorFragment";
|
||||
import { ShippingErrorFragment } from "@saleor/fragments/types/ShippingErrorFragment";
|
||||
import { ShippingMethodFragment_zipCodeRules } from "@saleor/fragments/types/ShippingMethodFragment";
|
||||
import { validatePrice } from "@saleor/products/utils/validation";
|
||||
import OrderValue from "@saleor/shipping/components/OrderValue";
|
||||
import OrderWeight from "@saleor/shipping/components/OrderWeight";
|
||||
import PricingCard from "@saleor/shipping/components/PricingCard";
|
||||
import ShippingZoneInfo from "@saleor/shipping/components/ShippingZoneInfo";
|
||||
import { createChannelsChangeHandler } from "@saleor/shipping/handlers";
|
||||
import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ShippingZoneZipCodes, {
|
||||
ZipCodeInclusion
|
||||
} from "../ShippingZoneZipCodes";
|
||||
|
||||
export interface FormData {
|
||||
channelListings: ChannelShippingData[];
|
||||
includeZipCodes: ZipCodeInclusion;
|
||||
name: string;
|
||||
noLimits: boolean;
|
||||
minValue: string;
|
||||
maxValue: string;
|
||||
type: ShippingMethodTypeEnum;
|
||||
}
|
||||
|
||||
export interface ShippingZoneRatesCreatePageProps {
|
||||
allChannelsCount?: number;
|
||||
shippingChannels: ChannelShippingData[];
|
||||
disabled: boolean;
|
||||
hasChannelChanged?: boolean;
|
||||
zipCodes?: ShippingMethodFragment_zipCodeRules[];
|
||||
channelErrors: ShippingChannelsErrorFragment[];
|
||||
errors: ShippingErrorFragment[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
onBack: () => void;
|
||||
onDelete?: () => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
onZipCodeAssign: () => void;
|
||||
onZipCodeUnassign: (id: string) => void;
|
||||
onChannelsChange: (data: ChannelShippingData[]) => void;
|
||||
openChannelsModal: () => void;
|
||||
variant: ShippingMethodTypeEnum;
|
||||
}
|
||||
|
||||
export const ShippingZoneRatesCreatePage: React.FC<ShippingZoneRatesCreatePageProps> = ({
|
||||
allChannelsCount,
|
||||
shippingChannels,
|
||||
channelErrors,
|
||||
disabled,
|
||||
errors,
|
||||
hasChannelChanged,
|
||||
onBack,
|
||||
onDelete,
|
||||
onSubmit,
|
||||
onChannelsChange,
|
||||
onZipCodeAssign,
|
||||
onZipCodeUnassign,
|
||||
openChannelsModal,
|
||||
saveButtonBarState,
|
||||
variant,
|
||||
zipCodes
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const isPriceVariant = variant === ShippingMethodTypeEnum.PRICE;
|
||||
const initialForm: FormData = {
|
||||
channelListings: shippingChannels,
|
||||
includeZipCodes: ZipCodeInclusion.Include,
|
||||
maxValue: "",
|
||||
minValue: "",
|
||||
name: "",
|
||||
noLimits: false,
|
||||
type: null
|
||||
};
|
||||
|
||||
return (
|
||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ change, data, hasChanged, submit, triggerChange }) => {
|
||||
const handleChannelsChange = createChannelsChangeHandler(
|
||||
shippingChannels,
|
||||
onChannelsChange,
|
||||
triggerChange
|
||||
);
|
||||
const formDisabled = data.channelListings?.some(channel =>
|
||||
validatePrice(channel.price)
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
<FormattedMessage defaultMessage="Shipping" />
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={
|
||||
isPriceVariant
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Price Rate Create",
|
||||
description: "page title"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Weight Rate Create",
|
||||
description: "page title"
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Grid>
|
||||
<div>
|
||||
<ShippingZoneInfo
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
{isPriceVariant ? (
|
||||
<OrderValue
|
||||
channels={data.channelListings}
|
||||
errors={channelErrors}
|
||||
noLimits={data.noLimits}
|
||||
disabled={disabled}
|
||||
onChange={change}
|
||||
onChannelsChange={handleChannelsChange}
|
||||
/>
|
||||
) : (
|
||||
<OrderWeight
|
||||
noLimits={data.noLimits}
|
||||
disabled={disabled}
|
||||
minValue={data.minValue}
|
||||
maxValue={data.maxValue}
|
||||
onChange={change}
|
||||
errors={errors}
|
||||
/>
|
||||
)}
|
||||
<CardSpacer />
|
||||
<PricingCard
|
||||
channels={data.channelListings}
|
||||
onChange={handleChannelsChange}
|
||||
disabled={disabled}
|
||||
errors={channelErrors}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<ShippingZoneZipCodes
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onZipCodeDelete={onZipCodeUnassign}
|
||||
onZipCodeInclusionChange={() => undefined}
|
||||
onZipCodeRangeAdd={onZipCodeAssign}
|
||||
zipCodes={zipCodes}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ChannelsAvailability
|
||||
allChannelsCount={allChannelsCount}
|
||||
selectedChannelsCount={shippingChannels?.length}
|
||||
channelsList={data.channelListings.map(channel => ({
|
||||
id: channel.id,
|
||||
name: channel.name
|
||||
}))}
|
||||
openModal={openChannelsModal}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
disabled={
|
||||
disabled || formDisabled || (!hasChanged && !hasChannelChanged)
|
||||
}
|
||||
onCancel={onBack}
|
||||
onDelete={onDelete}
|
||||
onSave={submit}
|
||||
state={saveButtonBarState}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingZoneRatesCreatePage;
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./ShippingZoneRatesCreatePage";
|
||||
export { default } from "./ShippingZoneRatesCreatePage";
|
|
@ -43,31 +43,26 @@ const props: ShippingZoneRatesPageProps = {
|
|||
channelErrors: [],
|
||||
disabled: false,
|
||||
errors: [],
|
||||
isChecked: () => undefined,
|
||||
onBack: () => undefined,
|
||||
onChannelsChange: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
onNextPage: () => undefined,
|
||||
onPreviousPage: () => undefined,
|
||||
onProductAssign: () => undefined,
|
||||
onProductUnassign: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onZipCodeAssign: () => undefined,
|
||||
onZipCodeUnassign: () => undefined,
|
||||
openChannelsModal: () => undefined,
|
||||
rate: null,
|
||||
rate: shippingZone.shippingMethods[0],
|
||||
saveButtonBarState: "default",
|
||||
selected: 0,
|
||||
shippingChannels: defaultChannels,
|
||||
variant: ShippingMethodTypeEnum.PRICE,
|
||||
zipCodes: [
|
||||
{
|
||||
__typename: "ShippingMethodZipCodeRule",
|
||||
end: "51-200",
|
||||
id: "1",
|
||||
start: "51-220"
|
||||
},
|
||||
{
|
||||
__typename: "ShippingMethodZipCodeRule",
|
||||
end: "31-101",
|
||||
id: "1",
|
||||
start: "44-205"
|
||||
}
|
||||
]
|
||||
toggle: () => undefined,
|
||||
toggleAll: () => undefined,
|
||||
toolbar: () => undefined,
|
||||
variant: ShippingMethodTypeEnum.PRICE
|
||||
};
|
||||
|
||||
storiesOf("Views / Shipping / Shipping rate", module)
|
||||
|
@ -95,10 +90,6 @@ storiesOf("Views / Shipping / Shipping rate", module)
|
|||
<ShippingZoneRatesPage
|
||||
{...props}
|
||||
shippingChannels={channels}
|
||||
rate={shippingZone.shippingMethods[0]}
|
||||
variant={ShippingMethodTypeEnum.WEIGHT}
|
||||
/>
|
||||
))
|
||||
.add("no zip codes", () => (
|
||||
<ShippingZoneRatesPage {...props} zipCodes={[]} />
|
||||
));
|
||||
|
|
|
@ -10,19 +10,18 @@ import PageHeader from "@saleor/components/PageHeader";
|
|||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { ShippingChannelsErrorFragment } from "@saleor/fragments/types/ShippingChannelsErrorFragment";
|
||||
import { ShippingErrorFragment } from "@saleor/fragments/types/ShippingErrorFragment";
|
||||
import {
|
||||
ShippingMethodFragment,
|
||||
ShippingMethodFragment_zipCodeRules
|
||||
} from "@saleor/fragments/types/ShippingMethodFragment";
|
||||
import { validatePrice } from "@saleor/products/utils/validation";
|
||||
import OrderValue from "@saleor/shipping/components/OrderValue";
|
||||
import OrderWeight from "@saleor/shipping/components/OrderWeight";
|
||||
import PricingCard from "@saleor/shipping/components/PricingCard";
|
||||
import ShippingMethodProducts from "@saleor/shipping/components/ShippingMethodProducts";
|
||||
import ShippingZoneInfo from "@saleor/shipping/components/ShippingZoneInfo";
|
||||
import { createChannelsChangeHandler } from "@saleor/shipping/handlers";
|
||||
import { ShippingZone_shippingZone_shippingMethods } from "@saleor/shipping/types/ShippingZone";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import ShippingZoneZipCodes, {
|
||||
ZipCodeInclusion
|
||||
|
@ -38,13 +37,14 @@ export interface FormData {
|
|||
type: ShippingMethodTypeEnum;
|
||||
}
|
||||
|
||||
export interface ShippingZoneRatesPageProps {
|
||||
export interface ShippingZoneRatesPageProps
|
||||
extends Pick<ListProps, Exclude<keyof ListProps, "onRowClick">>,
|
||||
ListActions {
|
||||
allChannelsCount?: number;
|
||||
shippingChannels: ChannelShippingData[];
|
||||
disabled: boolean;
|
||||
hasChannelChanged?: boolean;
|
||||
rate: ShippingMethodFragment | null;
|
||||
zipCodes?: ShippingMethodFragment_zipCodeRules[];
|
||||
rate: ShippingZone_shippingZone_shippingMethods;
|
||||
channelErrors: ShippingChannelsErrorFragment[];
|
||||
errors: ShippingErrorFragment[];
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
|
@ -55,6 +55,8 @@ export interface ShippingZoneRatesPageProps {
|
|||
onZipCodeUnassign: (id: string) => void;
|
||||
onChannelsChange: (data: ChannelShippingData[]) => void;
|
||||
openChannelsModal: () => void;
|
||||
onProductAssign: () => void;
|
||||
onProductUnassign: (ids: string[]) => void;
|
||||
variant: ShippingMethodTypeEnum;
|
||||
}
|
||||
|
||||
|
@ -71,13 +73,14 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
|
|||
onChannelsChange,
|
||||
onZipCodeAssign,
|
||||
onZipCodeUnassign,
|
||||
onProductAssign,
|
||||
onProductUnassign,
|
||||
openChannelsModal,
|
||||
rate,
|
||||
saveButtonBarState,
|
||||
variant,
|
||||
zipCodes
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const isPriceVariant = variant === ShippingMethodTypeEnum.PRICE;
|
||||
const initialForm: FormData = {
|
||||
channelListings: shippingChannels,
|
||||
|
@ -89,8 +92,6 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
|
|||
type: rate?.type || null
|
||||
};
|
||||
|
||||
const rateExists = rate !== null;
|
||||
|
||||
return (
|
||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ change, data, hasChanged, submit, triggerChange }) => {
|
||||
|
@ -108,20 +109,7 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
|
|||
<AppHeader onBack={onBack}>
|
||||
<FormattedMessage defaultMessage="Shipping" />
|
||||
</AppHeader>
|
||||
<PageHeader
|
||||
title={
|
||||
rate?.name ||
|
||||
(isPriceVariant
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Price Rate Create",
|
||||
description: "page title"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Weight Rate Create",
|
||||
description: "page title"
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<PageHeader title={rate?.name} />
|
||||
<Grid>
|
||||
<div>
|
||||
<ShippingZoneInfo
|
||||
|
@ -164,7 +152,17 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
|
|||
onZipCodeDelete={onZipCodeUnassign}
|
||||
onZipCodeInclusionChange={() => undefined}
|
||||
onZipCodeRangeAdd={onZipCodeAssign}
|
||||
zipCodes={rateExists ? rate?.zipCodeRules : zipCodes}
|
||||
zipCodes={rate?.zipCodeRules}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<ShippingMethodProducts
|
||||
products={rate?.excludedProducts?.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
onProductAssign={onProductAssign}
|
||||
onProductUnassign={onProductUnassign}
|
||||
disabled={disabled}
|
||||
{...listProps}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import Decorator from "@saleor/storybook//Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import UnassignDialog, { UnassignDialogProps } from "./UnassignDialog";
|
||||
|
||||
const props: UnassignDialogProps = {
|
||||
closeModal: () => undefined,
|
||||
confirmButtonState: "default",
|
||||
idsLength: 2,
|
||||
onConfirm: () => undefined,
|
||||
open: true
|
||||
};
|
||||
|
||||
storiesOf("Shipping / UnassignDialog", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <UnassignDialog {...props} />);
|
48
src/shipping/components/UnassignDialog/UnassignDialog.tsx
Normal file
48
src/shipping/components/UnassignDialog/UnassignDialog.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export interface UnassignDialogProps {
|
||||
open: boolean;
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
idsLength: number;
|
||||
closeModal: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const UnassignDialog: React.FC<UnassignDialogProps> = ({
|
||||
closeModal,
|
||||
confirmButtonState,
|
||||
idsLength,
|
||||
onConfirm,
|
||||
open
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<ActionDialog
|
||||
open={open}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unassign Products From Shipping",
|
||||
description: "dialog header"
|
||||
})}
|
||||
confirmButtonState={confirmButtonState}
|
||||
onClose={closeModal}
|
||||
onConfirm={onConfirm}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="{counter,plural,one{Are you sure you want to unassign this product?} other{Are you sure you want to unassign {displayQuantity} products?}}"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: idsLength,
|
||||
displayQuantity: <strong>{idsLength}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnassignDialog;
|
2
src/shipping/components/UnassignDialog/index.ts
Normal file
2
src/shipping/components/UnassignDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./UnassignDialog";
|
||||
export { default } from "./UnassignDialog";
|
|
@ -1,5 +1,6 @@
|
|||
import { ShippingZoneDetailsFragment } from "@saleor/fragments/types/ShippingZoneDetailsFragment";
|
||||
import { ShippingZoneFragment } from "@saleor/fragments/types/ShippingZoneFragment";
|
||||
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
|
||||
import { ShippingZone_shippingZone } from "@saleor/shipping/types/ShippingZone";
|
||||
|
||||
import { ShippingMethodTypeEnum, WeightUnitsEnum } from "../types/globalTypes";
|
||||
|
||||
|
@ -1285,7 +1286,7 @@ export const shippingZones: ShippingZoneFragment[] = [
|
|||
}
|
||||
];
|
||||
|
||||
export const shippingZone: ShippingZoneDetailsFragment = {
|
||||
export const shippingZone: ShippingZone_shippingZone = {
|
||||
__typename: "ShippingZone",
|
||||
countries: [
|
||||
{
|
||||
|
@ -1577,6 +1578,30 @@ export const shippingZone: ShippingZoneDetailsFragment = {
|
|||
}
|
||||
}
|
||||
],
|
||||
excludedProducts: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "1",
|
||||
name: "Apple Juice",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
pageInfo: {
|
||||
__typename: "PageInfo",
|
||||
endCursor: "",
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: ""
|
||||
}
|
||||
},
|
||||
id: "U2hpcHBpbmdNZXRob2Q6NA==",
|
||||
maximumOrderWeight: {
|
||||
__typename: "Weight",
|
||||
|
@ -1614,6 +1639,30 @@ export const shippingZone: ShippingZoneDetailsFragment = {
|
|||
{
|
||||
__typename: "ShippingMethod",
|
||||
channelListings: [],
|
||||
excludedProducts: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "1",
|
||||
name: "Apple Juice",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
pageInfo: {
|
||||
__typename: "PageInfo",
|
||||
endCursor: "",
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: ""
|
||||
}
|
||||
},
|
||||
id: "U2hpcHBpbmdNZXRob2Q6Mw==",
|
||||
maximumOrderWeight: null,
|
||||
minimumOrderWeight: {
|
||||
|
@ -1647,6 +1696,30 @@ export const shippingZone: ShippingZoneDetailsFragment = {
|
|||
{
|
||||
__typename: "ShippingMethod",
|
||||
channelListings: [],
|
||||
excludedProducts: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [
|
||||
{
|
||||
__typename: "ProductCountableEdge",
|
||||
node: {
|
||||
__typename: "Product",
|
||||
id: "1",
|
||||
name: "Apple Juice",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
pageInfo: {
|
||||
__typename: "PageInfo",
|
||||
endCursor: "",
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: ""
|
||||
}
|
||||
},
|
||||
id: "U2hpcHBpbmdNZXRob2Q6Mg==",
|
||||
maximumOrderWeight: null,
|
||||
minimumOrderWeight: {
|
||||
|
@ -1680,6 +1753,17 @@ export const shippingZone: ShippingZoneDetailsFragment = {
|
|||
{
|
||||
__typename: "ShippingMethod",
|
||||
channelListings: [],
|
||||
excludedProducts: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
__typename: "PageInfo",
|
||||
endCursor: "",
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: ""
|
||||
}
|
||||
},
|
||||
id: "U2hpcHBpbmdNZXRob2Q6MQ==",
|
||||
maximumOrderWeight: null,
|
||||
minimumOrderWeight: {
|
||||
|
@ -1724,3 +1808,24 @@ export const shippingZone: ShippingZoneDetailsFragment = {
|
|||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const products: SearchProducts_search_edges_node[] = [
|
||||
{
|
||||
__typename: "Product",
|
||||
id: "1",
|
||||
name: "Apple Juice",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
__typename: "Product",
|
||||
id: "2",
|
||||
name: "Banana Juice",
|
||||
thumbnail: {
|
||||
__typename: "Image",
|
||||
url: ""
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -47,6 +47,14 @@ import {
|
|||
ShippingMethodZipCodeRangeUnassign,
|
||||
ShippingMethodZipCodeRangeUnassignVariables
|
||||
} from "./types/ShippingMethodZipCodeRangeUnassign";
|
||||
import {
|
||||
ShippingPriceExcludeProduct,
|
||||
ShippingPriceExcludeProductVariables
|
||||
} from "./types/ShippingPriceExcludeProduct";
|
||||
import {
|
||||
ShippingPriceRemoveProductFromExclude,
|
||||
ShippingPriceRemoveProductFromExcludeVariables
|
||||
} from "./types/ShippingPriceRemoveProductFromExclude";
|
||||
import {
|
||||
UpdateDefaultWeightUnit,
|
||||
UpdateDefaultWeightUnitVariables
|
||||
|
@ -297,3 +305,38 @@ export const useShippingMethodZipCodeRangeUnassign = makeMutation<
|
|||
ShippingMethodZipCodeRangeUnassign,
|
||||
ShippingMethodZipCodeRangeUnassignVariables
|
||||
>(shippingMethodZipCodeRulesDelete);
|
||||
|
||||
export const shippingPriceExcludeProducts = gql`
|
||||
${shippingErrorFragment}
|
||||
mutation ShippingPriceExcludeProduct(
|
||||
$id: ID!
|
||||
$input: ShippingPriceExcludeProductsInput!
|
||||
) {
|
||||
shippingPriceExcludeProducts(id: $id, input: $input) {
|
||||
errors: shippingErrors {
|
||||
...ShippingErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useShippingPriceExcludeProduct = makeMutation<
|
||||
ShippingPriceExcludeProduct,
|
||||
ShippingPriceExcludeProductVariables
|
||||
>(shippingPriceExcludeProducts);
|
||||
|
||||
export const shippingPriceRemoveProductsFromExclude = gql`
|
||||
${shippingErrorFragment}
|
||||
mutation ShippingPriceRemoveProductFromExclude($id: ID!, $products: [ID]!) {
|
||||
shippingPriceRemoveProductFromExclude(id: $id, products: $products) {
|
||||
errors: shippingErrors {
|
||||
...ShippingErrorFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useShippingPriceRemoveProductsFromExclude = makeMutation<
|
||||
ShippingPriceRemoveProductFromExclude,
|
||||
ShippingPriceRemoveProductFromExcludeVariables
|
||||
>(shippingPriceRemoveProductsFromExclude);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { pageInfoFragment } from "@saleor/fragments/pageInfo";
|
||||
import {
|
||||
shippingZoneDetailsFragment,
|
||||
shippingMethodWithExcludedProductsFragment,
|
||||
shippingZoneFragment
|
||||
} from "@saleor/fragments/shipping";
|
||||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
|
@ -36,10 +36,25 @@ export const useShippingZoneList = makeQuery<
|
|||
>(shippingZones);
|
||||
|
||||
const shippingZone = gql`
|
||||
${shippingZoneDetailsFragment}
|
||||
query ShippingZone($id: ID!) {
|
||||
${shippingZoneFragment}
|
||||
${shippingMethodWithExcludedProductsFragment}
|
||||
query ShippingZone(
|
||||
$id: ID!
|
||||
$before: String
|
||||
$after: String
|
||||
$first: Int
|
||||
$last: Int
|
||||
) {
|
||||
shippingZone(id: $id) {
|
||||
...ShippingZoneDetailsFragment
|
||||
...ShippingZoneFragment
|
||||
default
|
||||
shippingMethods {
|
||||
...ShippingMethodWithExcludedProductsFragment
|
||||
}
|
||||
warehouses {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
29
src/shipping/types/ShippingPriceExcludeProduct.ts
Normal file
29
src/shipping/types/ShippingPriceExcludeProduct.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ShippingPriceExcludeProductsInput, ShippingErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ShippingPriceExcludeProduct
|
||||
// ====================================================
|
||||
|
||||
export interface ShippingPriceExcludeProduct_shippingPriceExcludeProducts_errors {
|
||||
__typename: "ShippingError";
|
||||
code: ShippingErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface ShippingPriceExcludeProduct_shippingPriceExcludeProducts {
|
||||
__typename: "ShippingPriceExcludeProducts";
|
||||
errors: ShippingPriceExcludeProduct_shippingPriceExcludeProducts_errors[];
|
||||
}
|
||||
|
||||
export interface ShippingPriceExcludeProduct {
|
||||
shippingPriceExcludeProducts: ShippingPriceExcludeProduct_shippingPriceExcludeProducts | null;
|
||||
}
|
||||
|
||||
export interface ShippingPriceExcludeProductVariables {
|
||||
id: string;
|
||||
input: ShippingPriceExcludeProductsInput;
|
||||
}
|
29
src/shipping/types/ShippingPriceRemoveProductFromExclude.ts
Normal file
29
src/shipping/types/ShippingPriceRemoveProductFromExclude.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ShippingErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: ShippingPriceRemoveProductFromExclude
|
||||
// ====================================================
|
||||
|
||||
export interface ShippingPriceRemoveProductFromExclude_shippingPriceRemoveProductFromExclude_errors {
|
||||
__typename: "ShippingError";
|
||||
code: ShippingErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface ShippingPriceRemoveProductFromExclude_shippingPriceRemoveProductFromExclude {
|
||||
__typename: "ShippingPriceRemoveProductFromExclude";
|
||||
errors: ShippingPriceRemoveProductFromExclude_shippingPriceRemoveProductFromExclude_errors[];
|
||||
}
|
||||
|
||||
export interface ShippingPriceRemoveProductFromExclude {
|
||||
shippingPriceRemoveProductFromExclude: ShippingPriceRemoveProductFromExclude_shippingPriceRemoveProductFromExclude | null;
|
||||
}
|
||||
|
||||
export interface ShippingPriceRemoveProductFromExcludeVariables {
|
||||
id: string;
|
||||
products: (string | null)[];
|
||||
}
|
|
@ -67,6 +67,37 @@ export interface ShippingZone_shippingZone_shippingMethods_channelListings {
|
|||
maximumOrderPrice: ShippingZone_shippingZone_shippingMethods_channelListings_maximumOrderPrice | null;
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_shippingMethods_excludedProducts_pageInfo {
|
||||
__typename: "PageInfo";
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
endCursor: string | null;
|
||||
startCursor: string | null;
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_shippingMethods_excludedProducts_edges_node_thumbnail {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_shippingMethods_excludedProducts_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
name: string;
|
||||
thumbnail: ShippingZone_shippingZone_shippingMethods_excludedProducts_edges_node_thumbnail | null;
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_shippingMethods_excludedProducts_edges {
|
||||
__typename: "ProductCountableEdge";
|
||||
node: ShippingZone_shippingZone_shippingMethods_excludedProducts_edges_node;
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_shippingMethods_excludedProducts {
|
||||
__typename: "ProductCountableConnection";
|
||||
pageInfo: ShippingZone_shippingZone_shippingMethods_excludedProducts_pageInfo;
|
||||
edges: ShippingZone_shippingZone_shippingMethods_excludedProducts_edges[];
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_shippingMethods {
|
||||
__typename: "ShippingMethod";
|
||||
id: string;
|
||||
|
@ -76,6 +107,7 @@ export interface ShippingZone_shippingZone_shippingMethods {
|
|||
name: string;
|
||||
type: ShippingMethodTypeEnum | null;
|
||||
channelListings: ShippingZone_shippingZone_shippingMethods_channelListings[] | null;
|
||||
excludedProducts: ShippingZone_shippingZone_shippingMethods_excludedProducts | null;
|
||||
}
|
||||
|
||||
export interface ShippingZone_shippingZone_warehouses {
|
||||
|
@ -100,4 +132,8 @@ export interface ShippingZone {
|
|||
|
||||
export interface ShippingZoneVariables {
|
||||
id: string;
|
||||
before?: string | null;
|
||||
after?: string | null;
|
||||
first?: number | null;
|
||||
last?: number | null;
|
||||
}
|
||||
|
|
|
@ -26,20 +26,30 @@ export type ShippingZoneUrlDialog =
|
|||
| "remove"
|
||||
| "remove-rate"
|
||||
| "unassign-country";
|
||||
|
||||
export type ShippingMethodActions = "assign-product" | "unassign-product";
|
||||
|
||||
export type ShippingZoneUrlQueryParams = Dialog<ShippingZoneUrlDialog> &
|
||||
SingleAction &
|
||||
Partial<{
|
||||
type: ShippingMethodTypeEnum;
|
||||
}>;
|
||||
}> &
|
||||
Pagination;
|
||||
export const shippingZoneUrl = (
|
||||
id: string,
|
||||
params?: ShippingZoneUrlQueryParams
|
||||
) => shippingZonePath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
type ZipCodeRangeActions = "add-range" | "remove-range";
|
||||
export type ShippingRateUrlDialog = ZipCodeRangeActions | "remove";
|
||||
export type ShippingRateUrlDialog =
|
||||
| ZipCodeRangeActions
|
||||
| "remove"
|
||||
| ShippingMethodActions;
|
||||
|
||||
export type ShippingRateUrlQueryParams = Dialog<ShippingRateUrlDialog> &
|
||||
SingleAction;
|
||||
SingleAction &
|
||||
BulkAction &
|
||||
Pagination;
|
||||
export type ShippingRateCreateUrlDialog = ZipCodeRangeActions;
|
||||
export type ShippingRateCreateUrlQueryParams = Dialog<
|
||||
ShippingRateCreateUrlDialog
|
||||
|
|
|
@ -7,7 +7,7 @@ import useChannels from "@saleor/hooks/useChannels";
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog";
|
||||
import ShippingZoneRatesPage from "@saleor/shipping/components/ShippingZoneRatesPage";
|
||||
import ShippingZoneRatesCreatePage from "@saleor/shipping/components/ShippingZoneRatesCreatePage";
|
||||
import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog";
|
||||
import { useShippingRateCreator } from "@saleor/shipping/handlers";
|
||||
import {
|
||||
|
@ -114,7 +114,7 @@ export const PriceRatesCreate: React.FC<PriceRatesCreateProps> = ({
|
|||
/>
|
||||
)}
|
||||
|
||||
<ShippingZoneRatesPage
|
||||
<ShippingZoneRatesCreatePage
|
||||
allChannelsCount={allChannels?.length}
|
||||
shippingChannels={currentChannels}
|
||||
disabled={channelsLoading || status === "loading"}
|
||||
|
@ -123,7 +123,6 @@ export const PriceRatesCreate: React.FC<PriceRatesCreateProps> = ({
|
|||
onBack={handleBack}
|
||||
errors={errors}
|
||||
channelErrors={channelErrors}
|
||||
rate={null}
|
||||
zipCodes={zipCodes}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import { useChannelsList } from "@saleor/channels/queries";
|
||||
import {
|
||||
createShippingChannelsFromRate,
|
||||
|
@ -5,17 +6,26 @@ import {
|
|||
} from "@saleor/channels/utils";
|
||||
import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import { PAGINATE_BY } from "@saleor/config";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useChannels from "@saleor/hooks/useChannels";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import useProductSearch from "@saleor/searches/useProductSearch";
|
||||
import DeleteShippingRateDialog from "@saleor/shipping/components/DeleteShippingRateDialog";
|
||||
import ShippingMethodProductsAddDialog from "@saleor/shipping/components/ShippingMethodProductsAddDialog";
|
||||
import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog";
|
||||
import ShippingZoneRatesPage, {
|
||||
FormData
|
||||
} from "@saleor/shipping/components/ShippingZoneRatesPage";
|
||||
import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog";
|
||||
import UnassignDialog from "@saleor/shipping/components/UnassignDialog";
|
||||
import {
|
||||
getShippingMethodChannelVariables,
|
||||
getUpdateShippingPriceRateVariables
|
||||
|
@ -23,9 +33,9 @@ import {
|
|||
import {
|
||||
useShippingMethodChannelListingUpdate,
|
||||
useShippingMethodZipCodeRangeAssign,
|
||||
useShippingMethodZipCodeRangeUnassign
|
||||
} from "@saleor/shipping/mutations";
|
||||
import {
|
||||
useShippingMethodZipCodeRangeUnassign,
|
||||
useShippingPriceExcludeProduct,
|
||||
useShippingPriceRemoveProductsFromExclude,
|
||||
useShippingRateDelete,
|
||||
useShippingRateUpdate
|
||||
} from "@saleor/shipping/mutations";
|
||||
|
@ -39,7 +49,7 @@ import {
|
|||
import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export interface PriceRatesUpdateProps {
|
||||
id: string;
|
||||
|
@ -55,11 +65,19 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
const paginate = usePaginator();
|
||||
|
||||
const { data, loading } = useShippingZone({
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
|
||||
const { data, loading, refetch } = useShippingZone({
|
||||
displayLoader: true,
|
||||
variables: { id }
|
||||
variables: { id, ...paginationState }
|
||||
});
|
||||
const {
|
||||
loadMore,
|
||||
search: productsSearch,
|
||||
result: productsSearchOpts
|
||||
} = useProductSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA });
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
ShippingRateUrlDialog,
|
||||
|
@ -69,6 +87,17 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
const rate = data?.shippingZone?.shippingMethods.find(
|
||||
rate => rate.id === rateId
|
||||
);
|
||||
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
[]
|
||||
);
|
||||
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
rate?.excludedProducts.pageInfo,
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const { data: channelsData } = useChannelsList({});
|
||||
|
||||
const [
|
||||
|
@ -98,6 +127,7 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [
|
||||
unassignZipCodeRange,
|
||||
unassignZipCodeRangeOpts
|
||||
|
@ -108,11 +138,32 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [
|
||||
unassignProduct,
|
||||
unassignProductOpts
|
||||
] = useShippingPriceRemoveProductsFromExclude({
|
||||
onCompleted: data => {
|
||||
if (data.shippingPriceRemoveProductFromExclude.errors.length === 0) {
|
||||
handleSuccess();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [assignProduct, assignProductOpts] = useShippingPriceExcludeProduct({
|
||||
onCompleted: data => {
|
||||
if (data.shippingPriceExcludeProducts.errors.length === 0) {
|
||||
handleSuccess();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
const shippingChannels = createShippingChannelsFromRate(
|
||||
rate?.channelListings
|
||||
);
|
||||
|
@ -168,6 +219,18 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleProductAssign = (ids: string[]) =>
|
||||
assignProduct({
|
||||
variables: { id: rateId, input: { products: ids } }
|
||||
});
|
||||
|
||||
const handleProductUnassign = (ids: string[]) => {
|
||||
unassignProduct({
|
||||
variables: { id: rateId, products: ids }
|
||||
});
|
||||
reset();
|
||||
};
|
||||
|
||||
const handleBack = () => navigate(shippingZoneUrl(id));
|
||||
|
||||
return (
|
||||
|
@ -203,13 +266,35 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
open={params.action === "remove"}
|
||||
name={rate?.name}
|
||||
/>
|
||||
<UnassignDialog
|
||||
open={params.action === "unassign-product" && !!listElements.length}
|
||||
idsLength={listElements.length}
|
||||
confirmButtonState={unassignProductOpts.status}
|
||||
closeModal={closeModal}
|
||||
onConfirm={() => handleProductUnassign(listElements)}
|
||||
/>
|
||||
<ShippingMethodProductsAddDialog
|
||||
confirmButtonState={assignProductOpts.status}
|
||||
loading={productsSearchOpts.loading}
|
||||
open={params.action === "assign-product"}
|
||||
hasMore={productsSearchOpts.data?.search?.pageInfo.hasNextPage}
|
||||
products={productsSearchOpts.data?.search?.edges
|
||||
.map(edge => edge.node)
|
||||
.filter(suggestedProduct => suggestedProduct.id)}
|
||||
onClose={closeModal}
|
||||
onFetch={productsSearch}
|
||||
onFetchMore={loadMore}
|
||||
onSubmit={handleProductAssign}
|
||||
/>
|
||||
<ShippingZoneRatesPage
|
||||
allChannelsCount={allChannels?.length}
|
||||
shippingChannels={currentChannels}
|
||||
disabled={
|
||||
loading ||
|
||||
updateShippingRateOpts?.status === "loading" ||
|
||||
updateShippingMethodChannelListingOpts?.status === "loading"
|
||||
updateShippingMethodChannelListingOpts?.status === "loading" ||
|
||||
unassignProductOpts?.status === "loading" ||
|
||||
assignProductOpts?.status === "loading"
|
||||
}
|
||||
hasChannelChanged={shippingChannels?.length !== currentChannels?.length}
|
||||
saveButtonBarState={updateShippingRateOpts.status}
|
||||
|
@ -224,7 +309,24 @@ export const PriceRatesUpdate: React.FC<PriceRatesUpdateProps> = ({
|
|||
}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
onProductUnassign={handleProductUnassign}
|
||||
onProductAssign={() => openModal("assign-product")}
|
||||
variant={ShippingMethodTypeEnum.PRICE}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
pageInfo={pageInfo}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={() => openModal("unassign-product")}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unassign"
|
||||
description="unassign products from shipping method, button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
onZipCodeAssign={() => openModal("add-range")}
|
||||
onZipCodeUnassign={id =>
|
||||
openModal("remove-range", {
|
||||
|
|
|
@ -3,8 +3,10 @@ import ActionDialog from "@saleor/components/ActionDialog";
|
|||
import useAppChannel from "@saleor/components/AppLayout/AppChannelContext";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import { PAGINATE_BY } from "@saleor/config";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { createPaginationState } from "@saleor/hooks/usePaginator";
|
||||
import useShop from "@saleor/hooks/useShop";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
|
@ -53,6 +55,8 @@ const ShippingZoneDetails: React.FC<ShippingZoneDetailsProps> = ({
|
|||
const intl = useIntl();
|
||||
const shop = useShop();
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
|
||||
const { result: searchWarehousesOpts, loadMore, search } = useWarehouseSearch(
|
||||
{
|
||||
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||
|
@ -61,7 +65,7 @@ const ShippingZoneDetails: React.FC<ShippingZoneDetailsProps> = ({
|
|||
|
||||
const { data, loading } = useShippingZone({
|
||||
displayLoader: true,
|
||||
variables: { id }
|
||||
variables: { id, ...paginationState }
|
||||
});
|
||||
const { channel } = useAppChannel();
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import useChannels from "@saleor/hooks/useChannels";
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog";
|
||||
import ShippingZoneRatesPage from "@saleor/shipping/components/ShippingZoneRatesPage";
|
||||
import ShippingZoneRatesCreatePage from "@saleor/shipping/components/ShippingZoneRatesCreatePage";
|
||||
import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog";
|
||||
import { useShippingRateCreator } from "@saleor/shipping/handlers";
|
||||
import {
|
||||
|
@ -117,7 +117,7 @@ export const WeightRatesCreate: React.FC<WeightRatesCreateProps> = ({
|
|||
toggleAll={toggleAllChannels}
|
||||
/>
|
||||
)}
|
||||
<ShippingZoneRatesPage
|
||||
<ShippingZoneRatesCreatePage
|
||||
allChannelsCount={allChannels?.length}
|
||||
shippingChannels={currentChannels}
|
||||
disabled={channelsLoading || status === "loading"}
|
||||
|
@ -126,7 +126,6 @@ export const WeightRatesCreate: React.FC<WeightRatesCreateProps> = ({
|
|||
onBack={handleBack}
|
||||
errors={errors}
|
||||
channelErrors={channelErrors}
|
||||
rate={null}
|
||||
zipCodes={zipCodes}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import { useChannelsList } from "@saleor/channels/queries";
|
||||
import {
|
||||
createShippingChannelsFromRate,
|
||||
|
@ -5,28 +6,39 @@ import {
|
|||
} from "@saleor/channels/utils";
|
||||
import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import { PAGINATE_BY } from "@saleor/config";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useChannels from "@saleor/hooks/useChannels";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import useProductSearch from "@saleor/searches/useProductSearch";
|
||||
import DeleteShippingRateDialog from "@saleor/shipping/components/DeleteShippingRateDialog";
|
||||
import ShippingMethodProductsAddDialog from "@saleor/shipping/components/ShippingMethodProductsAddDialog";
|
||||
import ShippingRateZipCodeRangeRemoveDialog from "@saleor/shipping/components/ShippingRateZipCodeRangeRemoveDialog";
|
||||
import ShippingZoneRatesPage, {
|
||||
FormData
|
||||
} from "@saleor/shipping/components/ShippingZoneRatesPage";
|
||||
import ShippingZoneZipCodeRangeDialog from "@saleor/shipping/components/ShippingZoneZipCodeRangeDialog";
|
||||
import UnassignDialog from "@saleor/shipping/components/UnassignDialog";
|
||||
import {
|
||||
getShippingMethodChannelVariables,
|
||||
getUpdateShippingWeightRateVariables
|
||||
} from "@saleor/shipping/handlers";
|
||||
import {
|
||||
useShippingMethodChannelListingUpdate,
|
||||
useShippingMethodZipCodeRangeAssign,
|
||||
useShippingMethodZipCodeRangeUnassign,
|
||||
useShippingPriceExcludeProduct,
|
||||
useShippingPriceRemoveProductsFromExclude,
|
||||
useShippingRateDelete,
|
||||
useShippingRateUpdate
|
||||
} from "@saleor/shipping/mutations";
|
||||
import { useShippingMethodChannelListingUpdate } from "@saleor/shipping/mutations";
|
||||
import { useShippingZone } from "@saleor/shipping/queries";
|
||||
import {
|
||||
ShippingRateUrlDialog,
|
||||
|
@ -37,7 +49,7 @@ import {
|
|||
import { ShippingMethodTypeEnum } from "@saleor/types/globalTypes";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export interface WeightRatesUpdateProps {
|
||||
id: string;
|
||||
|
@ -53,10 +65,13 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
|
|||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
const paginate = usePaginator();
|
||||
|
||||
const { data, loading } = useShippingZone({
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
|
||||
const { data, loading, refetch } = useShippingZone({
|
||||
displayLoader: true,
|
||||
variables: { id }
|
||||
variables: { id, ...paginationState }
|
||||
});
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
|
@ -64,15 +79,55 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
|
|||
ShippingRateUrlQueryParams
|
||||
>(navigate, params => shippingWeightRatesEditUrl(id, rateId, params), params);
|
||||
|
||||
const {
|
||||
loadMore,
|
||||
search: productsSearch,
|
||||
result: productsSearchOpts
|
||||
} = useProductSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA });
|
||||
|
||||
const rate = data?.shippingZone?.shippingMethods.find(
|
||||
rate => rate.id === rateId
|
||||
);
|
||||
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
[]
|
||||
);
|
||||
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
rate?.excludedProducts.pageInfo,
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const { data: channelsData } = useChannelsList({});
|
||||
const [
|
||||
updateShippingMethodChannelListing,
|
||||
updateShippingMethodChannelListingOpts
|
||||
] = useShippingMethodChannelListingUpdate({});
|
||||
|
||||
const [
|
||||
unassignProduct,
|
||||
unassignProductOpts
|
||||
] = useShippingPriceRemoveProductsFromExclude({
|
||||
onCompleted: data => {
|
||||
if (data.shippingPriceRemoveProductFromExclude.errors.length === 0) {
|
||||
handleSuccess();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [assignProduct, assignProductOpts] = useShippingPriceExcludeProduct({
|
||||
onCompleted: data => {
|
||||
if (data.shippingPriceExcludeProducts.errors.length === 0) {
|
||||
handleSuccess();
|
||||
refetch();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const shippingChannels = createShippingChannelsFromRate(
|
||||
rate?.channelListings
|
||||
);
|
||||
|
@ -166,6 +221,18 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleProductAssign = (ids: string[]) =>
|
||||
assignProduct({
|
||||
variables: { id: rateId, input: { products: ids } }
|
||||
});
|
||||
|
||||
const handleProductUnassign = (ids: string[]) => {
|
||||
unassignProduct({
|
||||
variables: { id: rateId, products: ids }
|
||||
});
|
||||
reset();
|
||||
};
|
||||
|
||||
const handleBack = () => navigate(shippingZoneUrl(id));
|
||||
|
||||
return (
|
||||
|
@ -201,13 +268,35 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
|
|||
open={params.action === "remove"}
|
||||
name={rate?.name}
|
||||
/>
|
||||
<UnassignDialog
|
||||
open={params.action === "unassign-product" && !!listElements.length}
|
||||
idsLength={listElements.length}
|
||||
confirmButtonState={unassignProductOpts.status}
|
||||
closeModal={closeModal}
|
||||
onConfirm={() => handleProductUnassign(listElements)}
|
||||
/>
|
||||
<ShippingMethodProductsAddDialog
|
||||
confirmButtonState={assignProductOpts.status}
|
||||
loading={productsSearchOpts.loading}
|
||||
open={params.action === "assign-product"}
|
||||
hasMore={productsSearchOpts.data?.search?.pageInfo.hasNextPage}
|
||||
products={productsSearchOpts.data?.search?.edges
|
||||
.map(edge => edge.node)
|
||||
.filter(suggestedProduct => suggestedProduct.id)}
|
||||
onClose={closeModal}
|
||||
onFetch={productsSearch}
|
||||
onFetchMore={loadMore}
|
||||
onSubmit={handleProductAssign}
|
||||
/>
|
||||
<ShippingZoneRatesPage
|
||||
allChannelsCount={allChannels?.length}
|
||||
shippingChannels={currentChannels}
|
||||
disabled={
|
||||
loading ||
|
||||
updateShippingRateOpts?.status === "loading" ||
|
||||
updateShippingMethodChannelListingOpts?.status === "loading"
|
||||
updateShippingMethodChannelListingOpts?.status === "loading" ||
|
||||
unassignProductOpts?.status === "loading" ||
|
||||
assignProductOpts?.status === "loading"
|
||||
}
|
||||
hasChannelChanged={shippingChannels?.length !== currentChannels?.length}
|
||||
saveButtonBarState={updateShippingRateOpts.status}
|
||||
|
@ -222,7 +311,24 @@ export const WeightRatesUpdate: React.FC<WeightRatesUpdateProps> = ({
|
|||
}
|
||||
openChannelsModal={handleChannelsModalOpen}
|
||||
onChannelsChange={setCurrentChannels}
|
||||
onProductUnassign={handleProductUnassign}
|
||||
onProductAssign={() => openModal("assign-product")}
|
||||
variant={ShippingMethodTypeEnum.WEIGHT}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
pageInfo={pageInfo}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={() => openModal("unassign-product")}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unassign"
|
||||
description="unassign products from shipping method, button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
onZipCodeAssign={() => openModal("add-range")}
|
||||
onZipCodeUnassign={id =>
|
||||
openModal("remove-range", {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1689,6 +1689,10 @@ export interface ShippingMethodChannelListingInput {
|
|||
removeChannels?: string[] | null;
|
||||
}
|
||||
|
||||
export interface ShippingPriceExcludeProductsInput {
|
||||
products: (string | null)[];
|
||||
}
|
||||
|
||||
export interface ShippingPriceInput {
|
||||
name?: string | null;
|
||||
minimumOrderWeight?: any | null;
|
||||
|
|
Loading…
Reference in a new issue