Handle errors on finalizing draft order (#2191)
* Add warning alert before finilizing draft order * Add line error indicators in draft order view * Handle unfilled fields errors before draft order finalize * Handle draft order line errors * Differentiate line alert severity * Fix order line alert margin * Remove unnecessairy comment * Refactor order draft alert components * Update order draft test snapshots * Refaactor order details code * Hide add products button when no products available on order draft page * Hide no shipping methods warning if they cannot be determined * Update product assignment dialog messaages * Update order channel error messages * Fix missing order lines in error crash
This commit is contained in:
parent
fc15227b4c
commit
f01966f0d4
60 changed files with 3138 additions and 1427 deletions
|
@ -1,6 +1,6 @@
|
||||||
export const DRAFT_ORDER_SELECTORS = {
|
export const DRAFT_ORDER_SELECTORS = {
|
||||||
addProducts: "[data-test-id='add-products-button']",
|
addProducts: "[data-test-id='add-products-button']",
|
||||||
salesChannel: "[data-test-id='sales-channel']",
|
salesChannel: "[data-test-id='order-sales-channel']",
|
||||||
editCustomerButton: "[data-test-id='edit-customer']",
|
editCustomerButton: "[data-test-id='edit-customer']",
|
||||||
selectCustomer: "[data-test-id='select-customer']",
|
selectCustomer: "[data-test-id='select-customer']",
|
||||||
selectCustomerOption: "[data-test-type='option']",
|
selectCustomerOption: "[data-test-type='option']",
|
||||||
|
@ -9,5 +9,5 @@ export const DRAFT_ORDER_SELECTORS = {
|
||||||
pageHeader: "[data-test-id='page-header']",
|
pageHeader: "[data-test-id='page-header']",
|
||||||
editShippingAddress: '[data-test-id="edit-shipping-address"]',
|
editShippingAddress: '[data-test-id="edit-shipping-address"]',
|
||||||
editBillingAddress: '[data-test-id="edit-billing-address"]',
|
editBillingAddress: '[data-test-id="edit-billing-address"]',
|
||||||
customerEmail: '[data-test-id="customer-email"]'
|
customerEmail: '[data-test-id="customer-email"]',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,5 +8,5 @@ export const ORDERS_SELECTORS = {
|
||||||
"[data-test-id='cancel-fulfillment-select-field']",
|
"[data-test-id='cancel-fulfillment-select-field']",
|
||||||
orderFulfillmentFrame: "[data-test-id='order-fulfillment']",
|
orderFulfillmentFrame: "[data-test-id='order-fulfillment']",
|
||||||
refundButton: '[data-test-id="refund-button"]',
|
refundButton: '[data-test-id="refund-button"]',
|
||||||
fulfillMenuButton: '[data-test-id="fulfill-menu"]'
|
fulfillMenuButton: '[data-test-id="fulfill-menu"]',
|
||||||
};
|
};
|
||||||
|
|
|
@ -741,6 +741,10 @@
|
||||||
"context": "notification",
|
"context": "notification",
|
||||||
"string": "Removed pages"
|
"string": "Removed pages"
|
||||||
},
|
},
|
||||||
|
"43AOvZ": {
|
||||||
|
"context": "alert group message",
|
||||||
|
"string": "You will not be able to finalize this draft because:"
|
||||||
|
},
|
||||||
"43Nlay": {
|
"43Nlay": {
|
||||||
"context": "warehouse",
|
"context": "warehouse",
|
||||||
"string": "Address Information"
|
"string": "Address Information"
|
||||||
|
@ -1359,6 +1363,10 @@
|
||||||
"9UHfux": {
|
"9UHfux": {
|
||||||
"string": "Voucher Specific Information"
|
"string": "Voucher Specific Information"
|
||||||
},
|
},
|
||||||
|
"9Y6vg+": {
|
||||||
|
"context": "dialog header",
|
||||||
|
"string": "Add product"
|
||||||
|
},
|
||||||
"9YazHG": {
|
"9YazHG": {
|
||||||
"string": "Company"
|
"string": "Company"
|
||||||
},
|
},
|
||||||
|
@ -1390,6 +1398,10 @@
|
||||||
"context": "button linking to dashboard",
|
"context": "button linking to dashboard",
|
||||||
"string": "Dashboard"
|
"string": "Dashboard"
|
||||||
},
|
},
|
||||||
|
"9mrWKz": {
|
||||||
|
"context": "no products placeholder",
|
||||||
|
"string": "No products are available matching query in the channel assigned to this order."
|
||||||
|
},
|
||||||
"9piUVz": {
|
"9piUVz": {
|
||||||
"context": "order history message",
|
"context": "order history message",
|
||||||
"string": "Order refund information was sent to customer"
|
"string": "Order refund information was sent to customer"
|
||||||
|
@ -1659,6 +1671,10 @@
|
||||||
"BtErCZ": {
|
"BtErCZ": {
|
||||||
"string": "Search Plugins..."
|
"string": "Search Plugins..."
|
||||||
},
|
},
|
||||||
|
"BvRyoX": {
|
||||||
|
"context": "alert message",
|
||||||
|
"string": "There are no available shipping methods in this channel."
|
||||||
|
},
|
||||||
"BvmnJq": {
|
"BvmnJq": {
|
||||||
"context": "section header",
|
"context": "section header",
|
||||||
"string": "Third Party Apps"
|
"string": "Third Party Apps"
|
||||||
|
@ -3298,6 +3314,10 @@
|
||||||
"context": "deletion error message",
|
"context": "deletion error message",
|
||||||
"string": "Cant's delete group which is out of your permission scope"
|
"string": "Cant's delete group which is out of your permission scope"
|
||||||
},
|
},
|
||||||
|
"O4QNFx": {
|
||||||
|
"context": "alert message",
|
||||||
|
"string": "There are no available products in this channel."
|
||||||
|
},
|
||||||
"O95R3Z": {
|
"O95R3Z": {
|
||||||
"string": "Phone"
|
"string": "Phone"
|
||||||
},
|
},
|
||||||
|
@ -3344,6 +3364,10 @@
|
||||||
"context": "input label",
|
"context": "input label",
|
||||||
"string": "Stock reservation for authenticated user (in minutes)"
|
"string": "Stock reservation for authenticated user (in minutes)"
|
||||||
},
|
},
|
||||||
|
"Oad+ES": {
|
||||||
|
"context": "alert message",
|
||||||
|
"string": "This product is not published in this channel."
|
||||||
|
},
|
||||||
"ObRk1O": {
|
"ObRk1O": {
|
||||||
"string": "If this option is disabled, discount will be counted for every eligible product"
|
"string": "If this option is disabled, discount will be counted for every eligible product"
|
||||||
},
|
},
|
||||||
|
@ -3818,6 +3842,10 @@
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Set new password"
|
"string": "Set new password"
|
||||||
},
|
},
|
||||||
|
"S2xLxV": {
|
||||||
|
"context": "search placeholder",
|
||||||
|
"string": "Search by product name, attribute, product type etc..."
|
||||||
|
},
|
||||||
"S52JMl": {
|
"S52JMl": {
|
||||||
"context": "default gift card delete description",
|
"context": "default gift card delete description",
|
||||||
"string": "{selectedItemsCount,plural,one{Are you sure you want to delete this gift card?} other{Are you sure you want to delete {selectedItemsCount} giftCards?}}"
|
"string": "{selectedItemsCount,plural,one{Are you sure you want to delete this gift card?} other{Are you sure you want to delete {selectedItemsCount} giftCards?}}"
|
||||||
|
@ -3864,6 +3892,10 @@
|
||||||
"context": "header",
|
"context": "header",
|
||||||
"string": "Translation Attribute \"{attribute}\" - {languageCode}"
|
"string": "Translation Attribute \"{attribute}\" - {languageCode}"
|
||||||
},
|
},
|
||||||
|
"SPp3cx": {
|
||||||
|
"context": "alert message",
|
||||||
|
"string": "Orders cannot be placed in an inactive channel."
|
||||||
|
},
|
||||||
"SSWFo8": {
|
"SSWFo8": {
|
||||||
"context": "window title",
|
"context": "window title",
|
||||||
"string": "Create Product Type"
|
"string": "Create Product Type"
|
||||||
|
@ -4408,9 +4440,6 @@
|
||||||
"context": "list of warehouses",
|
"context": "list of warehouses",
|
||||||
"string": "Warehouses A to Z"
|
"string": "Warehouses A to Z"
|
||||||
},
|
},
|
||||||
"WQnltU": {
|
|
||||||
"string": "No products available in order channel matching given query"
|
|
||||||
},
|
|
||||||
"WR8rir": {
|
"WR8rir": {
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Create rate"
|
"string": "Create rate"
|
||||||
|
@ -6357,10 +6386,6 @@
|
||||||
"mxtAFx": {
|
"mxtAFx": {
|
||||||
"string": "Are you sure you want to delete draft #{orderNumber}?"
|
"string": "Are you sure you want to delete draft #{orderNumber}?"
|
||||||
},
|
},
|
||||||
"myyWNp": {
|
|
||||||
"context": "dialog header",
|
|
||||||
"string": "Add Product"
|
|
||||||
},
|
|
||||||
"mzASqs": {
|
"mzASqs": {
|
||||||
"context": "days after label",
|
"context": "days after label",
|
||||||
"string": "days after issue"
|
"string": "days after issue"
|
||||||
|
@ -6993,6 +7018,10 @@
|
||||||
"context": "option label",
|
"context": "option label",
|
||||||
"string": "Change address"
|
"string": "Change address"
|
||||||
},
|
},
|
||||||
|
"s6oAC+": {
|
||||||
|
"context": "search label",
|
||||||
|
"string": "Search products"
|
||||||
|
},
|
||||||
"s8FlDW": {
|
"s8FlDW": {
|
||||||
"context": "hide error log label in notification",
|
"context": "hide error log label in notification",
|
||||||
"string": "Hide log"
|
"string": "Hide log"
|
||||||
|
@ -7062,6 +7091,10 @@
|
||||||
"sfErC+": {
|
"sfErC+": {
|
||||||
"string": "Voucher Name"
|
"string": "Voucher Name"
|
||||||
},
|
},
|
||||||
|
"shmSDX": {
|
||||||
|
"context": "no products placeholder",
|
||||||
|
"string": "No products are available in the channel assigned to this order."
|
||||||
|
},
|
||||||
"sidKce": {
|
"sidKce": {
|
||||||
"context": "delete channel",
|
"context": "delete channel",
|
||||||
"string": "All order information from this channel need to be moved to a different channel. Please select channel orders need to be moved to:."
|
"string": "All order information from this channel need to be moved to a different channel. Please select channel orders need to be moved to:."
|
||||||
|
@ -7881,6 +7914,10 @@
|
||||||
"context": "gift card removed success alert message",
|
"context": "gift card removed success alert message",
|
||||||
"string": "{selectedItemsCount,plural,one{Successfully deleted gift card} other{Successfully deleted gift cards}}"
|
"string": "{selectedItemsCount,plural,one{Successfully deleted gift card} other{Successfully deleted gift cards}}"
|
||||||
},
|
},
|
||||||
|
"zO+l0L": {
|
||||||
|
"context": "alert message",
|
||||||
|
"string": "This product is not available for sale in this channel."
|
||||||
|
},
|
||||||
"zQX6xO": {
|
"zQX6xO": {
|
||||||
"context": "dialog header",
|
"context": "dialog header",
|
||||||
"string": "Delete App"
|
"string": "Delete App"
|
||||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -29949,7 +29949,7 @@
|
||||||
"throttleit": {
|
"throttleit": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
|
||||||
"integrity": "sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==",
|
"integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"through": {
|
"through": {
|
||||||
|
@ -32750,7 +32750,7 @@
|
||||||
"yauzl": {
|
"yauzl": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
"integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"buffer-crc32": "~0.2.3",
|
"buffer-crc32": "~0.2.3",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
TableCell,
|
TableCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
TextField,
|
TextField,
|
||||||
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import ConfirmButton from "@saleor/components/ConfirmButton";
|
import ConfirmButton from "@saleor/components/ConfirmButton";
|
||||||
import Money from "@saleor/components/Money";
|
import Money from "@saleor/components/Money";
|
||||||
|
@ -17,10 +18,6 @@ import { SearchProductsQuery } from "@saleor/graphql";
|
||||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
import { maybe, renderCollection } from "@saleor/misc";
|
||||||
import {
|
|
||||||
getById,
|
|
||||||
getByUnmatchingId,
|
|
||||||
} from "@saleor/orders/components/OrderReturnPage/utils";
|
|
||||||
import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
|
import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
|
||||||
import { DialogProps, FetchMoreProps, RelayToFlat } from "@saleor/types";
|
import { DialogProps, FetchMoreProps, RelayToFlat } from "@saleor/types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -31,12 +28,14 @@ import BackButton from "../BackButton";
|
||||||
import Checkbox from "../Checkbox";
|
import Checkbox from "../Checkbox";
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
import { useStyles } from "./styles";
|
import { useStyles } from "./styles";
|
||||||
|
import {
|
||||||
|
handleProductAssign,
|
||||||
|
handleVariantAssign,
|
||||||
|
hasAllVariantsSelected,
|
||||||
|
isVariantSelected,
|
||||||
|
SearchVariant,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
type SearchVariant = RelayToFlat<
|
|
||||||
SearchProductsQuery["search"]
|
|
||||||
>[0]["variants"][0];
|
|
||||||
|
|
||||||
type SetVariantsAction = (data: SearchVariant[]) => void;
|
|
||||||
export interface AssignVariantDialogFormData {
|
export interface AssignVariantDialogFormData {
|
||||||
products: RelayToFlat<SearchProductsQuery["search"]>;
|
products: RelayToFlat<SearchProductsQuery["search"]>;
|
||||||
query: string;
|
query: string;
|
||||||
|
@ -49,57 +48,6 @@ export interface AssignVariantDialogProps extends FetchMoreProps, DialogProps {
|
||||||
onSubmit: (data: SearchVariant[]) => void;
|
onSubmit: (data: SearchVariant[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVariantSelected(
|
|
||||||
variant: SearchVariant,
|
|
||||||
selectedVariantsToProductsMap: SearchVariant[],
|
|
||||||
): boolean {
|
|
||||||
return !!selectedVariantsToProductsMap.find(getById(variant.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleProductAssign = (
|
|
||||||
product: RelayToFlat<SearchProductsQuery["search"]>[0],
|
|
||||||
productIndex: number,
|
|
||||||
productsWithAllVariantsSelected: boolean[],
|
|
||||||
variants: SearchVariant[],
|
|
||||||
setVariants: SetVariantsAction,
|
|
||||||
) =>
|
|
||||||
productsWithAllVariantsSelected[productIndex]
|
|
||||||
? setVariants(
|
|
||||||
variants.filter(
|
|
||||||
selectedVariant =>
|
|
||||||
!product.variants.find(getById(selectedVariant.id)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: setVariants([
|
|
||||||
...variants,
|
|
||||||
...product.variants.filter(
|
|
||||||
productVariant => !variants.find(getById(productVariant.id)),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleVariantAssign = (
|
|
||||||
variant: SearchVariant,
|
|
||||||
variantIndex: number,
|
|
||||||
productIndex: number,
|
|
||||||
variants: SearchVariant[],
|
|
||||||
selectedVariantsToProductsMap: boolean[][],
|
|
||||||
setVariants: SetVariantsAction,
|
|
||||||
) =>
|
|
||||||
selectedVariantsToProductsMap[productIndex][variantIndex]
|
|
||||||
? setVariants(variants.filter(getByUnmatchingId(variant.id)))
|
|
||||||
: setVariants([...variants, variant]);
|
|
||||||
|
|
||||||
function hasAllVariantsSelected(
|
|
||||||
productVariants: SearchVariant[],
|
|
||||||
selectedVariantsToProductsMap: SearchVariant[],
|
|
||||||
): boolean {
|
|
||||||
return productVariants.reduce(
|
|
||||||
(acc, productVariant) =>
|
|
||||||
acc && !!selectedVariantsToProductsMap.find(getById(productVariant.id)),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollableTargetId = "assignVariantScrollableDialog";
|
const scrollableTargetId = "assignVariantScrollableDialog";
|
||||||
|
|
||||||
const AssignVariantDialog: React.FC<AssignVariantDialogProps> = props => {
|
const AssignVariantDialog: React.FC<AssignVariantDialogProps> = props => {
|
||||||
|
@ -264,14 +212,11 @@ const AssignVariantDialog: React.FC<AssignVariantDialogProps> = props => {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
() => (
|
() => (
|
||||||
<TableRow>
|
<Typography className={classes.noContentText}>
|
||||||
<TableCell colSpan={4}>
|
{!!query
|
||||||
<FormattedMessage
|
? intl.formatMessage(messages.noProductsInQuery)
|
||||||
id="WQnltU"
|
: intl.formatMessage(messages.noProductsInChannel)}
|
||||||
defaultMessage="No products available in order channel matching given query"
|
</Typography>
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
@ -24,4 +24,16 @@ export const messages = defineMessages({
|
||||||
defaultMessage: "SKU {sku}",
|
defaultMessage: "SKU {sku}",
|
||||||
description: "variant sku",
|
description: "variant sku",
|
||||||
},
|
},
|
||||||
|
noProductsInChannel: {
|
||||||
|
id: "shmSDX",
|
||||||
|
defaultMessage:
|
||||||
|
"No products are available in the channel assigned to this order.",
|
||||||
|
description: "no products placeholder",
|
||||||
|
},
|
||||||
|
noProductsInQuery: {
|
||||||
|
id: "9mrWKz",
|
||||||
|
defaultMessage:
|
||||||
|
"No products are available matching query in the channel assigned to this order.",
|
||||||
|
description: "no products placeholder",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,9 @@ export const useStyles = makeStyles(
|
||||||
colVariantCheckbox: {
|
colVariantCheckbox: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
},
|
},
|
||||||
|
noContentText: {
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
overflowY: "scroll",
|
overflowY: "scroll",
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
|
|
63
src/components/AssignVariantDialog/utils.ts
Normal file
63
src/components/AssignVariantDialog/utils.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { SearchProductsQuery } from "@saleor/graphql";
|
||||||
|
import {
|
||||||
|
getById,
|
||||||
|
getByUnmatchingId,
|
||||||
|
} from "@saleor/orders/components/OrderReturnPage/utils";
|
||||||
|
import { RelayToFlat } from "@saleor/types";
|
||||||
|
|
||||||
|
export type SearchVariant = RelayToFlat<
|
||||||
|
SearchProductsQuery["search"]
|
||||||
|
>[0]["variants"][0];
|
||||||
|
|
||||||
|
type SetVariantsAction = (data: SearchVariant[]) => void;
|
||||||
|
|
||||||
|
export function isVariantSelected(
|
||||||
|
variant: SearchVariant,
|
||||||
|
selectedVariantsToProductsMap: SearchVariant[],
|
||||||
|
): boolean {
|
||||||
|
return !!selectedVariantsToProductsMap.find(getById(variant.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleProductAssign = (
|
||||||
|
product: RelayToFlat<SearchProductsQuery["search"]>[0],
|
||||||
|
productIndex: number,
|
||||||
|
productsWithAllVariantsSelected: boolean[],
|
||||||
|
variants: SearchVariant[],
|
||||||
|
setVariants: SetVariantsAction,
|
||||||
|
) =>
|
||||||
|
productsWithAllVariantsSelected[productIndex]
|
||||||
|
? setVariants(
|
||||||
|
variants.filter(
|
||||||
|
selectedVariant =>
|
||||||
|
!product.variants.find(getById(selectedVariant.id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: setVariants([
|
||||||
|
...variants,
|
||||||
|
...product.variants.filter(
|
||||||
|
productVariant => !variants.find(getById(productVariant.id)),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const handleVariantAssign = (
|
||||||
|
variant: SearchVariant,
|
||||||
|
variantIndex: number,
|
||||||
|
productIndex: number,
|
||||||
|
variants: SearchVariant[],
|
||||||
|
selectedVariantsToProductsMap: boolean[][],
|
||||||
|
setVariants: SetVariantsAction,
|
||||||
|
) =>
|
||||||
|
selectedVariantsToProductsMap[productIndex][variantIndex]
|
||||||
|
? setVariants(variants.filter(getByUnmatchingId(variant.id)))
|
||||||
|
: setVariants([...variants, variant]);
|
||||||
|
|
||||||
|
export function hasAllVariantsSelected(
|
||||||
|
productVariants: SearchVariant[],
|
||||||
|
selectedVariantsToProductsMap: SearchVariant[],
|
||||||
|
): boolean {
|
||||||
|
return productVariants.reduce(
|
||||||
|
(acc, productVariant) =>
|
||||||
|
acc && !!selectedVariantsToProductsMap.find(getById(productVariant.id)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,21 +1,20 @@
|
||||||
import { Table } from "@material-ui/core";
|
import { Table } from "@material-ui/core";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
root: {
|
root: {
|
||||||
[theme.breakpoints.up("md")]: {
|
|
||||||
"&& table": {
|
|
||||||
tableLayout: "fixed",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"& table": {
|
|
||||||
tableLayout: "auto",
|
|
||||||
},
|
|
||||||
overflowX: "auto",
|
overflowX: "auto",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
|
table: {
|
||||||
|
[theme.breakpoints.up("md")]: {
|
||||||
|
tableLayout: "fixed",
|
||||||
|
},
|
||||||
|
tableLayout: "auto",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "ResponsiveTable",
|
name: "ResponsiveTable",
|
||||||
|
@ -35,7 +34,7 @@ const ResponsiveTable: React.FC<ResponsiveTableProps> = props => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
<Table className={className}>{children}</Table>
|
<Table className={classNames(classes.table, className)}>{children}</Table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -73,6 +73,7 @@ export const orderErrorFragment = gql`
|
||||||
field
|
field
|
||||||
addressType
|
addressType
|
||||||
message
|
message
|
||||||
|
orderLines
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,17 @@ export const fragmentOrderLine = gql`
|
||||||
stocks {
|
stocks {
|
||||||
...Stock
|
...Stock
|
||||||
}
|
}
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
channelListings {
|
||||||
|
id
|
||||||
|
isPublished
|
||||||
|
isAvailableForPurchase
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
productName
|
productName
|
||||||
productSku
|
productSku
|
||||||
|
|
|
@ -587,6 +587,7 @@ export const OrderErrorFragmentDoc = gql`
|
||||||
field
|
field
|
||||||
addressType
|
addressType
|
||||||
message
|
message
|
||||||
|
orderLines
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const OrderSettingsErrorFragmentDoc = gql`
|
export const OrderSettingsErrorFragmentDoc = gql`
|
||||||
|
@ -1239,6 +1240,17 @@ export const OrderLineFragmentDoc = gql`
|
||||||
stocks {
|
stocks {
|
||||||
...Stock
|
...Stock
|
||||||
}
|
}
|
||||||
|
product {
|
||||||
|
id
|
||||||
|
channelListings {
|
||||||
|
id
|
||||||
|
isPublished
|
||||||
|
isAvailableForPurchase
|
||||||
|
channel {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
productName
|
productName
|
||||||
productSku
|
productSku
|
||||||
|
@ -8873,7 +8885,6 @@ export const FulfillOrderDocument = gql`
|
||||||
errors {
|
errors {
|
||||||
...OrderError
|
...OrderError
|
||||||
warehouse
|
warehouse
|
||||||
orderLines
|
|
||||||
}
|
}
|
||||||
order {
|
order {
|
||||||
...OrderDetails
|
...OrderDetails
|
||||||
|
@ -9423,6 +9434,41 @@ export function useOrderRefundDataLazyQuery(baseOptions?: ApolloReactHooks.LazyQ
|
||||||
export type OrderRefundDataQueryHookResult = ReturnType<typeof useOrderRefundDataQuery>;
|
export type OrderRefundDataQueryHookResult = ReturnType<typeof useOrderRefundDataQuery>;
|
||||||
export type OrderRefundDataLazyQueryHookResult = ReturnType<typeof useOrderRefundDataLazyQuery>;
|
export type OrderRefundDataLazyQueryHookResult = ReturnType<typeof useOrderRefundDataLazyQuery>;
|
||||||
export type OrderRefundDataQueryResult = Apollo.QueryResult<Types.OrderRefundDataQuery, Types.OrderRefundDataQueryVariables>;
|
export type OrderRefundDataQueryResult = Apollo.QueryResult<Types.OrderRefundDataQuery, Types.OrderRefundDataQueryVariables>;
|
||||||
|
export const ChannelUsabilityDataDocument = gql`
|
||||||
|
query ChannelUsabilityData($channel: String!) {
|
||||||
|
products(channel: $channel) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useChannelUsabilityDataQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useChannelUsabilityDataQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useChannelUsabilityDataQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useChannelUsabilityDataQuery({
|
||||||
|
* variables: {
|
||||||
|
* channel: // value for 'channel'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useChannelUsabilityDataQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return ApolloReactHooks.useQuery<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>(ChannelUsabilityDataDocument, options);
|
||||||
|
}
|
||||||
|
export function useChannelUsabilityDataLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return ApolloReactHooks.useLazyQuery<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>(ChannelUsabilityDataDocument, options);
|
||||||
|
}
|
||||||
|
export type ChannelUsabilityDataQueryHookResult = ReturnType<typeof useChannelUsabilityDataQuery>;
|
||||||
|
export type ChannelUsabilityDataLazyQueryHookResult = ReturnType<typeof useChannelUsabilityDataLazyQuery>;
|
||||||
|
export type ChannelUsabilityDataQueryResult = Apollo.QueryResult<Types.ChannelUsabilityDataQuery, Types.ChannelUsabilityDataQueryVariables>;
|
||||||
export const PageTypeUpdateDocument = gql`
|
export const PageTypeUpdateDocument = gql`
|
||||||
mutation PageTypeUpdate($id: ID!, $input: PageTypeUpdateInput!) {
|
mutation PageTypeUpdate($id: ID!, $input: PageTypeUpdateInput!) {
|
||||||
pageTypeUpdate(id: $id, input: $input) {
|
pageTypeUpdate(id: $id, input: $input) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,20 +0,0 @@
|
||||||
import CardDecorator from "@saleor/storybook/CardDecorator";
|
|
||||||
import Decorator from "@saleor/storybook/Decorator";
|
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import DraftOrderChannelSectionCard, {
|
|
||||||
DraftOrderChannelSectionCardProps,
|
|
||||||
} from ".";
|
|
||||||
|
|
||||||
const props: DraftOrderChannelSectionCardProps = {
|
|
||||||
channelName: "Default Channel",
|
|
||||||
};
|
|
||||||
|
|
||||||
storiesOf("Orders / Draft order channel section", module)
|
|
||||||
.addDecorator(CardDecorator)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("default", () => <DraftOrderChannelSectionCard {...props} />)
|
|
||||||
.add("loading", () => (
|
|
||||||
<DraftOrderChannelSectionCard {...props} channelName={undefined} />
|
|
||||||
));
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
|
||||||
import React from "react";
|
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
|
|
||||||
export interface DraftOrderChannelSectionCardProps {
|
|
||||||
channelName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DraftOrderChannelSectionCard: React.FC<DraftOrderChannelSectionCardProps> = ({
|
|
||||||
channelName,
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardTitle
|
|
||||||
title={intl.formatMessage({
|
|
||||||
id: "aY0HAT",
|
|
||||||
defaultMessage: "Sales channel",
|
|
||||||
description: "section header",
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<CardContent data-test-id="sales-channel">
|
|
||||||
{!channelName ? <Skeleton /> : <Typography>{channelName}</Typography>}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
DraftOrderChannelSectionCard.displayName = "DraftOrderChannelSectionCard";
|
|
||||||
export default DraftOrderChannelSectionCard;
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default } from "./DraftOrderChannelSectionCard";
|
|
||||||
export * from "./DraftOrderChannelSectionCard";
|
|
45
src/orders/components/OrderAlerts/OrderAlerts.tsx
Normal file
45
src/orders/components/OrderAlerts/OrderAlerts.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from "react";
|
||||||
|
import { MessageDescriptor, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
interface OrderAlertsProps {
|
||||||
|
alertsHeader?: string;
|
||||||
|
alerts: Array<string | MessageDescriptor>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderAlerts: React.FC<OrderAlertsProps> = ({
|
||||||
|
alertsHeader,
|
||||||
|
alerts,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const formattedAlerts = alerts.map((alert, index) => {
|
||||||
|
if (typeof alert === "string") {
|
||||||
|
return { id: `${index}_${alert}`, text: alert };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: alert.id,
|
||||||
|
text: intl.formatMessage(alert),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!formattedAlerts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formattedAlerts.length === 1) {
|
||||||
|
return <>{formattedAlerts[0].text}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!!alertsHeader && alertsHeader}
|
||||||
|
<ul>
|
||||||
|
{formattedAlerts.map(alert => (
|
||||||
|
<li key={alert.id}>{alert.text}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
OrderAlerts.displayName = "OrderAlerts";
|
||||||
|
export default OrderAlerts;
|
2
src/orders/components/OrderAlerts/index.ts
Normal file
2
src/orders/components/OrderAlerts/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./OrderAlerts";
|
||||||
|
export * from "./OrderAlerts";
|
|
@ -6,7 +6,10 @@ import React from "react";
|
||||||
import OrderChannelSectionCard, { OrderChannelSectionCardProps } from ".";
|
import OrderChannelSectionCard, { OrderChannelSectionCardProps } from ".";
|
||||||
|
|
||||||
const props: OrderChannelSectionCardProps = {
|
const props: OrderChannelSectionCardProps = {
|
||||||
selectedChannelName: "International store",
|
channel: {
|
||||||
|
id: "dh87hf34hk8i",
|
||||||
|
name: "International store",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Orders / Order details channel section", module)
|
storiesOf("Orders / Order details channel section", module)
|
||||||
|
@ -14,5 +17,5 @@ storiesOf("Orders / Order details channel section", module)
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <OrderChannelSectionCard {...props} />)
|
.add("default", () => <OrderChannelSectionCard {...props} />)
|
||||||
.add("loading", () => (
|
.add("loading", () => (
|
||||||
<OrderChannelSectionCard {...props} selectedChannelName={undefined} />
|
<OrderChannelSectionCard {...props} channel={undefined} />
|
||||||
));
|
));
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
import { Card, CardContent, Typography } from "@material-ui/core";
|
||||||
|
import { channelUrl } from "@saleor/channels/urls";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
|
import Link from "@saleor/components/Link";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
|
import { ChannelFragment } from "@saleor/graphql";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
export interface OrderChannelSectionCardProps {
|
export interface OrderChannelSectionCardProps {
|
||||||
selectedChannelName: string;
|
channel?: Pick<ChannelFragment, "id" | "name">;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrderChannelSectionCard: React.FC<OrderChannelSectionCardProps> = ({
|
export const OrderChannelSectionCard: React.FC<OrderChannelSectionCardProps> = ({
|
||||||
selectedChannelName,
|
channel,
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
@ -23,10 +26,14 @@ export const OrderChannelSectionCard: React.FC<OrderChannelSectionCardProps> = (
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{selectedChannelName === undefined ? (
|
{!channel ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : (
|
) : (
|
||||||
<Typography>{selectedChannelName}</Typography>
|
<Typography>
|
||||||
|
<Link href={channelUrl(channel.id) ?? ""} disabled={!channel.id}>
|
||||||
|
{channel.name ?? "..."}
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
28
src/orders/components/OrderCustomer/AddrssTextError.tsx
Normal file
28
src/orders/components/OrderCustomer/AddrssTextError.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
|
import { OrderErrorFragment } from "@saleor/graphql";
|
||||||
|
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { useAddressTextErrorStyles } from "./styles";
|
||||||
|
|
||||||
|
interface AddressTextErrorProps {
|
||||||
|
orderError: OrderErrorFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddressTextError: React.FC<AddressTextErrorProps> = ({
|
||||||
|
orderError,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const classes = useAddressTextErrorStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" className={classes.textError}>
|
||||||
|
{getOrderErrorMessage(orderError, intl)}
|
||||||
|
</Typography>
|
||||||
|
<FormSpacer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,9 +1,9 @@
|
||||||
import { Card, CardContent, Typography } from "@material-ui/core";
|
import { Card, CardContent, Typography } from "@material-ui/core";
|
||||||
|
import AddressFormatter from "@saleor/components/AddressFormatter";
|
||||||
import { Button } from "@saleor/components/Button";
|
import { Button } from "@saleor/components/Button";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import ExternalLink from "@saleor/components/ExternalLink";
|
import ExternalLink from "@saleor/components/ExternalLink";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
import Link from "@saleor/components/Link";
|
import Link from "@saleor/components/Link";
|
||||||
import RequirePermissions from "@saleor/components/RequirePermissions";
|
import RequirePermissions from "@saleor/components/RequirePermissions";
|
||||||
|
@ -11,13 +11,13 @@ import SingleAutocompleteSelectField from "@saleor/components/SingleAutocomplete
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import {
|
import {
|
||||||
OrderDetailsFragment,
|
OrderDetailsFragment,
|
||||||
|
OrderErrorCode,
|
||||||
|
OrderErrorFragment,
|
||||||
PermissionEnum,
|
PermissionEnum,
|
||||||
SearchCustomersQuery,
|
SearchCustomersQuery,
|
||||||
WarehouseClickAndCollectOptionEnum,
|
|
||||||
} from "@saleor/graphql";
|
} from "@saleor/graphql";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
|
||||||
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
|
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -25,31 +25,9 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { customerUrl } from "../../../customers/urls";
|
import { customerUrl } from "../../../customers/urls";
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import messages from "./messages";
|
import { AddressTextError } from "./AddrssTextError";
|
||||||
|
import { PickupAnnotation } from "./PickupAnnotation";
|
||||||
const useStyles = makeStyles(
|
import { useStyles } from "./styles";
|
||||||
theme => ({
|
|
||||||
sectionHeader: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
},
|
|
||||||
sectionHeaderTitle: {
|
|
||||||
flex: 1,
|
|
||||||
fontWeight: 600 as 600,
|
|
||||||
lineHeight: 1,
|
|
||||||
textTransform: "uppercase",
|
|
||||||
},
|
|
||||||
sectionHeaderToolbar: {
|
|
||||||
marginRight: theme.spacing(-2),
|
|
||||||
},
|
|
||||||
userEmail: {
|
|
||||||
fontWeight: 600 as 600,
|
|
||||||
marginBottom: theme.spacing(1),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "OrderCustomer" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface CustomerEditData {
|
export interface CustomerEditData {
|
||||||
user?: string;
|
user?: string;
|
||||||
|
@ -62,6 +40,7 @@ export interface OrderCustomerProps extends Partial<FetchMoreProps> {
|
||||||
order: OrderDetailsFragment;
|
order: OrderDetailsFragment;
|
||||||
users?: RelayToFlat<SearchCustomersQuery["search"]>;
|
users?: RelayToFlat<SearchCustomersQuery["search"]>;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
errors: OrderErrorFragment[];
|
||||||
canEditAddresses: boolean;
|
canEditAddresses: boolean;
|
||||||
canEditCustomer: boolean;
|
canEditCustomer: boolean;
|
||||||
fetchUsers?: (query: string) => void;
|
fetchUsers?: (query: string) => void;
|
||||||
|
@ -78,6 +57,7 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
hasMore: hasMoreUsers,
|
hasMore: hasMoreUsers,
|
||||||
loading,
|
loading,
|
||||||
|
errors = [],
|
||||||
order,
|
order,
|
||||||
users,
|
users,
|
||||||
onCustomerEdit,
|
onCustomerEdit,
|
||||||
|
@ -102,24 +82,12 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
const billingAddress = maybe(() => order.billingAddress);
|
const billingAddress = maybe(() => order.billingAddress);
|
||||||
const shippingAddress = maybe(() => order.shippingAddress);
|
const shippingAddress = maybe(() => order.shippingAddress);
|
||||||
|
|
||||||
const pickupAnnotation = order => {
|
const noBillingAddressError = errors.find(
|
||||||
if (order?.deliveryMethod?.__typename === "Warehouse") {
|
error => error.code === OrderErrorCode.BILLING_ADDRESS_NOT_SET,
|
||||||
return (
|
);
|
||||||
<>
|
const noShippingAddressError = errors.find(
|
||||||
<FormSpacer />
|
error => error.code === OrderErrorCode.ORDER_NO_SHIPPING_ADDRESS,
|
||||||
<Typography variant="caption" color="textSecondary">
|
|
||||||
{order?.deliveryMethod?.clickAndCollectOption ===
|
|
||||||
WarehouseClickAndCollectOptionEnum.LOCAL ? (
|
|
||||||
<FormattedMessage {...messages.orderCustomerFulfillmentLocal} />
|
|
||||||
) : (
|
|
||||||
<FormattedMessage {...messages.orderCustomerFulfillmentAll} />
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -295,7 +263,12 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
{shippingAddress === undefined ? (
|
{shippingAddress === undefined ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : shippingAddress === null ? (
|
) : (
|
||||||
|
<>
|
||||||
|
{noShippingAddressError && (
|
||||||
|
<AddressTextError orderError={noShippingAddressError} />
|
||||||
|
)}
|
||||||
|
{shippingAddress === null ? (
|
||||||
<Typography>
|
<Typography>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="e7yOai"
|
id="e7yOai"
|
||||||
|
@ -305,30 +278,10 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{shippingAddress.companyName && (
|
<AddressFormatter address={shippingAddress} />
|
||||||
<Typography>{shippingAddress.companyName}</Typography>
|
<PickupAnnotation order={order} />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<Typography>
|
|
||||||
{shippingAddress.firstName} {shippingAddress.lastName}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{shippingAddress.streetAddress1}
|
|
||||||
<br />
|
|
||||||
{shippingAddress.streetAddress2}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{shippingAddress.postalCode} {shippingAddress.city}
|
|
||||||
{shippingAddress.cityArea ? ", " + shippingAddress.cityArea : ""}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{shippingAddress.countryArea
|
|
||||||
? shippingAddress.countryArea +
|
|
||||||
", " +
|
|
||||||
shippingAddress.country.country
|
|
||||||
: shippingAddress.country.country}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{shippingAddress.phone}</Typography>
|
|
||||||
{pickupAnnotation(order)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -353,7 +306,12 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
{billingAddress === undefined ? (
|
{billingAddress === undefined ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : billingAddress === null ? (
|
) : (
|
||||||
|
<>
|
||||||
|
{noBillingAddressError && (
|
||||||
|
<AddressTextError orderError={noBillingAddressError} />
|
||||||
|
)}
|
||||||
|
{billingAddress === null ? (
|
||||||
<Typography>
|
<Typography>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="YI6Fhj"
|
id="YI6Fhj"
|
||||||
|
@ -370,30 +328,8 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<AddressFormatter address={billingAddress} />
|
||||||
{billingAddress.companyName && (
|
|
||||||
<Typography>{billingAddress.companyName}</Typography>
|
|
||||||
)}
|
)}
|
||||||
<Typography>
|
|
||||||
{billingAddress.firstName} {billingAddress.lastName}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{billingAddress.streetAddress1}
|
|
||||||
<br />
|
|
||||||
{billingAddress.streetAddress2}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{billingAddress.postalCode} {billingAddress.city}
|
|
||||||
{billingAddress.cityArea ? ", " + billingAddress.cityArea : ""}
|
|
||||||
</Typography>
|
|
||||||
<Typography>
|
|
||||||
{billingAddress.countryArea
|
|
||||||
? billingAddress.countryArea +
|
|
||||||
", " +
|
|
||||||
billingAddress.country.country
|
|
||||||
: billingAddress.country.country}
|
|
||||||
</Typography>
|
|
||||||
<Typography>{billingAddress.phone}</Typography>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
35
src/orders/components/OrderCustomer/PickupAnnotation.tsx
Normal file
35
src/orders/components/OrderCustomer/PickupAnnotation.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
|
import {
|
||||||
|
OrderDetailsFragment,
|
||||||
|
WarehouseClickAndCollectOptionEnum,
|
||||||
|
} from "@saleor/graphql";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import messages from "./messages";
|
||||||
|
|
||||||
|
interface PickupAnnotationProps {
|
||||||
|
order?: OrderDetailsFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PickupAnnotation: React.FC<PickupAnnotationProps> = ({
|
||||||
|
order,
|
||||||
|
}) => {
|
||||||
|
if (order?.deliveryMethod?.__typename === "Warehouse") {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormSpacer />
|
||||||
|
<Typography variant="caption" color="textSecondary">
|
||||||
|
{order?.deliveryMethod?.clickAndCollectOption ===
|
||||||
|
WarehouseClickAndCollectOptionEnum.LOCAL ? (
|
||||||
|
<FormattedMessage {...messages.orderCustomerFulfillmentLocal} />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage {...messages.orderCustomerFulfillmentAll} />
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
34
src/orders/components/OrderCustomer/styles.ts
Normal file
34
src/orders/components/OrderCustomer/styles.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
sectionHeader: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
sectionHeaderTitle: {
|
||||||
|
flex: 1,
|
||||||
|
fontWeight: 600 as 600,
|
||||||
|
lineHeight: 1,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
},
|
||||||
|
sectionHeaderToolbar: {
|
||||||
|
marginRight: theme.spacing(-2),
|
||||||
|
},
|
||||||
|
userEmail: {
|
||||||
|
fontWeight: 600 as 600,
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "OrderCustomer" },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useAddressTextErrorStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
textError: {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "AddrssTextError" },
|
||||||
|
);
|
|
@ -18,6 +18,7 @@ import Skeleton from "@saleor/components/Skeleton";
|
||||||
import {
|
import {
|
||||||
OrderDetailsFragment,
|
OrderDetailsFragment,
|
||||||
OrderDetailsQuery,
|
OrderDetailsQuery,
|
||||||
|
OrderErrorFragment,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
} from "@saleor/graphql";
|
} from "@saleor/graphql";
|
||||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
@ -69,6 +70,7 @@ export interface OrderDetailsPageProps {
|
||||||
}>;
|
}>;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
|
errors: OrderErrorFragment[];
|
||||||
onOrderLineAdd?: () => void;
|
onOrderLineAdd?: () => void;
|
||||||
onOrderLineChange?: (
|
onOrderLineChange?: (
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -121,6 +123,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||||
order,
|
order,
|
||||||
shop,
|
shop,
|
||||||
saveButtonBarState,
|
saveButtonBarState,
|
||||||
|
errors,
|
||||||
onBillingAddressEdit,
|
onBillingAddressEdit,
|
||||||
onFulfillmentApprove,
|
onFulfillmentApprove,
|
||||||
onFulfillmentCancel,
|
onFulfillmentCancel,
|
||||||
|
@ -268,6 +271,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||||
<>
|
<>
|
||||||
<OrderDraftDetails
|
<OrderDraftDetails
|
||||||
order={order}
|
order={order}
|
||||||
|
errors={errors}
|
||||||
onOrderLineAdd={onOrderLineAdd}
|
onOrderLineAdd={onOrderLineAdd}
|
||||||
onOrderLineChange={onOrderLineChange}
|
onOrderLineChange={onOrderLineChange}
|
||||||
onOrderLineRemove={onOrderLineRemove}
|
onOrderLineRemove={onOrderLineRemove}
|
||||||
|
@ -319,14 +323,13 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||||
canEditAddresses={canEditAddresses}
|
canEditAddresses={canEditAddresses}
|
||||||
canEditCustomer={false}
|
canEditCustomer={false}
|
||||||
order={order}
|
order={order}
|
||||||
|
errors={errors}
|
||||||
onBillingAddressEdit={onBillingAddressEdit}
|
onBillingAddressEdit={onBillingAddressEdit}
|
||||||
onShippingAddressEdit={onShippingAddressEdit}
|
onShippingAddressEdit={onShippingAddressEdit}
|
||||||
onProfileView={onProfileView}
|
onProfileView={onProfileView}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<OrderChannelSectionCard
|
<OrderChannelSectionCard channel={order?.channel} />
|
||||||
selectedChannelName={order?.channel?.name}
|
|
||||||
/>
|
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
{!isOrderUnconfirmed && (
|
{!isOrderUnconfirmed && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { Card, CardContent } from "@material-ui/core";
|
import { Card, CardContent } from "@material-ui/core";
|
||||||
import { Button } from "@saleor/components/Button";
|
import { Button } from "@saleor/components/Button";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import { OrderDetailsFragment, OrderLineInput } from "@saleor/graphql";
|
import {
|
||||||
|
ChannelUsabilityDataQuery,
|
||||||
|
OrderDetailsFragment,
|
||||||
|
OrderErrorFragment,
|
||||||
|
OrderLineInput,
|
||||||
|
} from "@saleor/graphql";
|
||||||
import {
|
import {
|
||||||
OrderDiscountContext,
|
OrderDiscountContext,
|
||||||
OrderDiscountContextConsumerProps,
|
OrderDiscountContextConsumerProps,
|
||||||
|
@ -15,6 +20,8 @@ import OrderDraftDetailsSummary from "../OrderDraftDetailsSummary";
|
||||||
|
|
||||||
interface OrderDraftDetailsProps {
|
interface OrderDraftDetailsProps {
|
||||||
order: OrderDetailsFragment;
|
order: OrderDetailsFragment;
|
||||||
|
channelUsabilityData?: ChannelUsabilityDataQuery;
|
||||||
|
errors: OrderErrorFragment[];
|
||||||
onOrderLineAdd: () => void;
|
onOrderLineAdd: () => void;
|
||||||
onOrderLineChange: (id: string, data: OrderLineInput) => void;
|
onOrderLineChange: (id: string, data: OrderLineInput) => void;
|
||||||
onOrderLineRemove: (id: string) => void;
|
onOrderLineRemove: (id: string) => void;
|
||||||
|
@ -23,6 +30,8 @@ interface OrderDraftDetailsProps {
|
||||||
|
|
||||||
const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
order,
|
order,
|
||||||
|
channelUsabilityData,
|
||||||
|
errors,
|
||||||
onOrderLineAdd,
|
onOrderLineAdd,
|
||||||
onOrderLineChange,
|
onOrderLineChange,
|
||||||
onOrderLineRemove,
|
onOrderLineRemove,
|
||||||
|
@ -30,6 +39,9 @@ const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const isChannelActive = order?.channel.isActive;
|
||||||
|
const areProductsInChannel = !!channelUsabilityData?.products.totalCount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
|
@ -39,7 +51,8 @@ const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
description: "section header",
|
description: "section header",
|
||||||
})}
|
})}
|
||||||
toolbar={
|
toolbar={
|
||||||
order?.channel.isActive && (
|
isChannelActive &&
|
||||||
|
areProductsInChannel && (
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
onClick={onOrderLineAdd}
|
onClick={onOrderLineAdd}
|
||||||
|
@ -55,7 +68,8 @@ const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<OrderDraftDetailsProducts
|
<OrderDraftDetailsProducts
|
||||||
lines={maybe(() => order.lines)}
|
order={order}
|
||||||
|
errors={errors}
|
||||||
onOrderLineChange={onOrderLineChange}
|
onOrderLineChange={onOrderLineChange}
|
||||||
onOrderLineRemove={onOrderLineRemove}
|
onOrderLineRemove={onOrderLineRemove}
|
||||||
/>
|
/>
|
||||||
|
@ -65,6 +79,7 @@ const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
{(orderDiscountProps: OrderDiscountContextConsumerProps) => (
|
{(orderDiscountProps: OrderDiscountContextConsumerProps) => (
|
||||||
<OrderDraftDetailsSummary
|
<OrderDraftDetailsSummary
|
||||||
order={order}
|
order={order}
|
||||||
|
errors={errors}
|
||||||
onShippingMethodEdit={onShippingMethodEdit}
|
onShippingMethodEdit={onShippingMethodEdit}
|
||||||
{...orderDiscountProps}
|
{...orderDiscountProps}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
import { TableBody, TableCell, TableHead, TableRow } from "@material-ui/core";
|
import {
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
} from "@material-ui/core";
|
||||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||||
import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import { OrderLineFragment } from "@saleor/graphql";
|
import { OrderDetailsFragment, OrderErrorFragment } from "@saleor/graphql";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import {
|
import {
|
||||||
OrderLineDiscountConsumer,
|
OrderLineDiscountConsumer,
|
||||||
OrderLineDiscountContextConsumerProps,
|
OrderLineDiscountContextConsumerProps,
|
||||||
} from "@saleor/products/components/OrderDiscountProviders/OrderLineDiscountProvider";
|
} from "@saleor/products/components/OrderDiscountProviders/OrderLineDiscountProvider";
|
||||||
|
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { maybe, renderCollection } from "../../../misc";
|
import { renderCollection } from "../../../misc";
|
||||||
import TableLine from "./TableLine";
|
import TableLine from "./TableLine";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
|
@ -20,60 +27,65 @@ export interface FormData {
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
colAction: {
|
colAction: {
|
||||||
"&:last-child": {
|
|
||||||
paddingRight: 0,
|
|
||||||
},
|
|
||||||
width: theme.spacing(10),
|
width: theme.spacing(10),
|
||||||
},
|
},
|
||||||
colName: {
|
colName: {
|
||||||
width: "auto",
|
width: "auto",
|
||||||
},
|
},
|
||||||
colNameLabel: {
|
colNameLabel: {},
|
||||||
marginLeft: AVATAR_MARGIN,
|
colPrice: {},
|
||||||
},
|
colQuantity: {},
|
||||||
colPrice: {
|
colTotal: {},
|
||||||
textAlign: "right",
|
skeleton: {
|
||||||
},
|
margin: theme.spacing(0, 4),
|
||||||
colQuantity: {
|
|
||||||
textAlign: "right",
|
|
||||||
},
|
|
||||||
colTotal: {
|
|
||||||
textAlign: "right",
|
|
||||||
},
|
},
|
||||||
errorInfo: {
|
errorInfo: {
|
||||||
color: theme.palette.error.main,
|
color: theme.palette.error.main,
|
||||||
|
marginLeft: theme.spacing(1.5),
|
||||||
|
display: "inline",
|
||||||
},
|
},
|
||||||
quantityField: {
|
quantityField: {
|
||||||
"& input": {
|
"& input": {
|
||||||
padding: "12px 12px 10px",
|
padding: "12px 12px 10px",
|
||||||
textAlign: "right",
|
|
||||||
},
|
},
|
||||||
width: 60,
|
width: 60,
|
||||||
},
|
},
|
||||||
table: {
|
table: {
|
||||||
tableLayout: "fixed",
|
[theme.breakpoints.up("md")]: {
|
||||||
|
tableLayout: "auto",
|
||||||
|
},
|
||||||
|
tableLayout: "auto",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ name: "OrderDraftDetailsProducts" },
|
{ name: "OrderDraftDetailsProducts" },
|
||||||
);
|
);
|
||||||
|
|
||||||
interface OrderDraftDetailsProductsProps {
|
interface OrderDraftDetailsProductsProps {
|
||||||
lines: OrderLineFragment[];
|
order?: OrderDetailsFragment;
|
||||||
|
errors: OrderErrorFragment[];
|
||||||
onOrderLineChange: (id: string, data: FormData) => void;
|
onOrderLineChange: (id: string, data: FormData) => void;
|
||||||
onOrderLineRemove: (id: string) => void;
|
onOrderLineRemove: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderDraftDetailsProducts: React.FC<OrderDraftDetailsProductsProps> = props => {
|
const OrderDraftDetailsProducts: React.FC<OrderDraftDetailsProductsProps> = props => {
|
||||||
const { lines, onOrderLineChange, onOrderLineRemove } = props;
|
const { order, errors, onOrderLineChange, onOrderLineRemove } = props;
|
||||||
|
const lines = order?.lines ?? [];
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
const formErrors = errors.filter(error => error.field === "lines");
|
||||||
|
|
||||||
|
if (order === undefined) {
|
||||||
|
return <Skeleton className={classes.skeleton} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResponsiveTable className={classes.table}>
|
<ResponsiveTable className={classes.table}>
|
||||||
{maybe(() => !!lines.length) && (
|
{!!lines.length && (
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell className={classes.colName} colSpan={2}>
|
||||||
<span className={classes.colNameLabel}>
|
<span className={classes.colNameLabel}>
|
||||||
<FormattedMessage id="x/ZVlU" defaultMessage="Product" />
|
<FormattedMessage id="x/ZVlU" defaultMessage="Product" />
|
||||||
</span>
|
</span>
|
||||||
|
@ -104,7 +116,7 @@ const OrderDraftDetailsProducts: React.FC<OrderDraftDetailsProductsProps> = prop
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{!!lines?.length ? (
|
{!!lines.length ? (
|
||||||
renderCollection(lines, line => (
|
renderCollection(lines, line => (
|
||||||
<OrderLineDiscountConsumer key={line.id} orderLineId={line.id}>
|
<OrderLineDiscountConsumer key={line.id} orderLineId={line.id}>
|
||||||
{(
|
{(
|
||||||
|
@ -113,6 +125,10 @@ const OrderDraftDetailsProducts: React.FC<OrderDraftDetailsProductsProps> = prop
|
||||||
<TableLine
|
<TableLine
|
||||||
{...orderLineDiscountProps}
|
{...orderLineDiscountProps}
|
||||||
line={line}
|
line={line}
|
||||||
|
channelId={order.channel.id}
|
||||||
|
error={formErrors.find(error =>
|
||||||
|
error.orderLines?.some(id => id === line.id),
|
||||||
|
)}
|
||||||
onOrderLineChange={onOrderLineChange}
|
onOrderLineChange={onOrderLineChange}
|
||||||
onOrderLineRemove={onOrderLineRemove}
|
onOrderLineRemove={onOrderLineRemove}
|
||||||
/>
|
/>
|
||||||
|
@ -120,14 +136,21 @@ const OrderDraftDetailsProducts: React.FC<OrderDraftDetailsProductsProps> = prop
|
||||||
</OrderLineDiscountConsumer>
|
</OrderLineDiscountConsumer>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5}>
|
<TableCell colSpan={5}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="UD7/q8"
|
id="UD7/q8"
|
||||||
defaultMessage="No Products added to Order"
|
defaultMessage="No Products added to Order"
|
||||||
/>
|
/>
|
||||||
|
{!!formErrors.length && (
|
||||||
|
<Typography variant="body2" className={classes.errorInfo}>
|
||||||
|
{getOrderErrorMessage(formErrors[0], intl)}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</ResponsiveTable>
|
</ResponsiveTable>
|
||||||
|
|
|
@ -3,22 +3,31 @@ import Link from "@saleor/components/Link";
|
||||||
import Money from "@saleor/components/Money";
|
import Money from "@saleor/components/Money";
|
||||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||||
import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar";
|
import { AVATAR_MARGIN } from "@saleor/components/TableCellAvatar/Avatar";
|
||||||
import { OrderLineFragment, OrderLineInput } from "@saleor/graphql";
|
import {
|
||||||
|
OrderErrorFragment,
|
||||||
|
OrderLineFragment,
|
||||||
|
OrderLineInput,
|
||||||
|
} from "@saleor/graphql";
|
||||||
import { DeleteIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
import { DeleteIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
||||||
import { OrderLineDiscountContextConsumerProps } from "@saleor/products/components/OrderDiscountProviders/OrderLineDiscountProvider";
|
import { OrderLineDiscountContextConsumerProps } from "@saleor/products/components/OrderDiscountProviders/OrderLineDiscountProvider";
|
||||||
|
import classNames from "classnames";
|
||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
|
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import OrderDiscountCommonModal from "../OrderDiscountCommonModal";
|
import OrderDiscountCommonModal from "../OrderDiscountCommonModal";
|
||||||
import { ORDER_LINE_DISCOUNT } from "../OrderDiscountCommonModal/types";
|
import { ORDER_LINE_DISCOUNT } from "../OrderDiscountCommonModal/types";
|
||||||
|
import TableLineAlert from "./TableLineAlert";
|
||||||
import TableLineForm from "./TableLineForm";
|
import TableLineForm from "./TableLineForm";
|
||||||
|
import useLineAlerts from "./useLineAlerts";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
colAction: {
|
colStatusEmpty: {
|
||||||
"&:last-child": {
|
"&:first-child:not(.MuiTableCell-paddingCheckbox)": {
|
||||||
paddingRight: 0,
|
paddingRight: 0,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
colAction: {
|
||||||
width: `calc(76px + ${theme.spacing(0.5)})`,
|
width: `calc(76px + ${theme.spacing(0.5)})`,
|
||||||
},
|
},
|
||||||
colName: {
|
colName: {
|
||||||
|
@ -40,24 +49,22 @@ const useStyles = makeStyles(
|
||||||
textDecoration: "line-through",
|
textDecoration: "line-through",
|
||||||
color: theme.palette.grey[400],
|
color: theme.palette.grey[400],
|
||||||
},
|
},
|
||||||
errorInfo: {
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
},
|
|
||||||
table: {
|
|
||||||
tableLayout: "fixed",
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
{ name: "OrderDraftDetailsProducts" },
|
{ name: "OrderDraftDetailsProducts" },
|
||||||
);
|
);
|
||||||
|
|
||||||
interface TableLineProps extends OrderLineDiscountContextConsumerProps {
|
interface TableLineProps extends OrderLineDiscountContextConsumerProps {
|
||||||
line: OrderLineFragment;
|
line: OrderLineFragment;
|
||||||
|
channelId: string;
|
||||||
|
error?: OrderErrorFragment;
|
||||||
onOrderLineChange: (id: string, data: OrderLineInput) => void;
|
onOrderLineChange: (id: string, data: OrderLineInput) => void;
|
||||||
onOrderLineRemove: (id: string) => void;
|
onOrderLineRemove: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableLine: React.FC<TableLineProps> = ({
|
const TableLine: React.FC<TableLineProps> = ({
|
||||||
line,
|
line,
|
||||||
|
channelId,
|
||||||
|
error,
|
||||||
onOrderLineChange,
|
onOrderLineChange,
|
||||||
onOrderLineRemove,
|
onOrderLineRemove,
|
||||||
orderLineDiscount,
|
orderLineDiscount,
|
||||||
|
@ -71,10 +78,16 @@ const TableLine: React.FC<TableLineProps> = ({
|
||||||
discountedPrice,
|
discountedPrice,
|
||||||
orderLineDiscountUpdateStatus,
|
orderLineDiscountUpdateStatus,
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles({});
|
const classes = useStyles();
|
||||||
const popperAnchorRef = useRef<HTMLTableRowElement | null>(null);
|
const popperAnchorRef = useRef<HTMLTableRowElement | null>(null);
|
||||||
const { id, thumbnail, productName, productSku, quantity } = line;
|
const { id, thumbnail, productName, productSku, quantity } = line;
|
||||||
|
|
||||||
|
const alerts = useLineAlerts({
|
||||||
|
line,
|
||||||
|
channelId,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
const getUnitPriceLabel = () => {
|
const getUnitPriceLabel = () => {
|
||||||
const money = <Money money={undiscountedPrice} />;
|
const money = <Money money={undiscountedPrice} />;
|
||||||
|
|
||||||
|
@ -94,6 +107,18 @@ const TableLine: React.FC<TableLineProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={id}>
|
<TableRow key={id}>
|
||||||
|
<TableCell
|
||||||
|
className={classNames({
|
||||||
|
[classes.colStatusEmpty]: !alerts.length,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{!!alerts.length && (
|
||||||
|
<TableLineAlert
|
||||||
|
alerts={alerts}
|
||||||
|
variant={!!error ? "error" : "warning"}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
<TableCellAvatar
|
<TableCellAvatar
|
||||||
className={classes.colName}
|
className={classes.colName}
|
||||||
thumbnail={maybe(() => thumbnail.url)}
|
thumbnail={maybe(() => thumbnail.url)}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {
|
||||||
|
IndicatorOutlined,
|
||||||
|
Tooltip,
|
||||||
|
TooltipMountWrapper,
|
||||||
|
} from "@saleor/macaw-ui";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import OrderAlerts from "../OrderAlerts";
|
||||||
|
|
||||||
|
interface TableLineAlertProps {
|
||||||
|
alerts?: string[];
|
||||||
|
variant: "warning" | "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const TableLineAlert: React.FC<TableLineAlertProps> = ({ alerts, variant }) => {
|
||||||
|
if (!alerts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = <OrderAlerts alerts={alerts} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={title} variant={variant}>
|
||||||
|
<TooltipMountWrapper>
|
||||||
|
<IndicatorOutlined icon={variant} />
|
||||||
|
</TooltipMountWrapper>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default TableLineAlert;
|
14
src/orders/components/OrderDraftDetailsProducts/messages.ts
Normal file
14
src/orders/components/OrderDraftDetailsProducts/messages.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const lineAlertMessages = defineMessages({
|
||||||
|
notPublished: {
|
||||||
|
id: "Oad+ES",
|
||||||
|
defaultMessage: "This product is not published in this channel.",
|
||||||
|
description: "alert message",
|
||||||
|
},
|
||||||
|
notAvailable: {
|
||||||
|
id: "zO+l0L",
|
||||||
|
defaultMessage: "This product is not available for sale in this channel.",
|
||||||
|
description: "alert message",
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { OrderErrorFragment, OrderLineFragment } from "@saleor/graphql";
|
||||||
|
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { lineAlertMessages } from "./messages";
|
||||||
|
|
||||||
|
interface UseLineAlertsOpts {
|
||||||
|
line: OrderLineFragment;
|
||||||
|
channelId: string;
|
||||||
|
error?: OrderErrorFragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useLineAlerts = ({ line, channelId, error }: UseLineAlertsOpts) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const alerts = useMemo(() => {
|
||||||
|
const {
|
||||||
|
variant: {
|
||||||
|
product: { channelListings },
|
||||||
|
},
|
||||||
|
} = line;
|
||||||
|
const channelListing = channelListings.find(
|
||||||
|
channelListing => channelListing.channel.id === channelId,
|
||||||
|
);
|
||||||
|
const isPublished = channelListing?.isPublished;
|
||||||
|
const isAvailable = channelListing?.isAvailableForPurchase;
|
||||||
|
|
||||||
|
const alerts: string[] = [];
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
alerts.push(getOrderErrorMessage(error, intl));
|
||||||
|
}
|
||||||
|
if (!isPublished) {
|
||||||
|
alerts.push(intl.formatMessage(lineAlertMessages.notPublished));
|
||||||
|
}
|
||||||
|
if (!isAvailable) {
|
||||||
|
alerts.push(intl.formatMessage(lineAlertMessages.notAvailable));
|
||||||
|
}
|
||||||
|
|
||||||
|
return alerts;
|
||||||
|
}, [line, channelId, error]);
|
||||||
|
|
||||||
|
return alerts;
|
||||||
|
};
|
||||||
|
export default useLineAlerts;
|
|
@ -2,10 +2,16 @@ import { Typography } from "@material-ui/core";
|
||||||
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
|
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
|
||||||
import Link from "@saleor/components/Link";
|
import Link from "@saleor/components/Link";
|
||||||
import Money from "@saleor/components/Money";
|
import Money from "@saleor/components/Money";
|
||||||
import { DiscountValueTypeEnum, OrderDetailsFragment } from "@saleor/graphql";
|
import {
|
||||||
|
DiscountValueTypeEnum,
|
||||||
|
OrderDetailsFragment,
|
||||||
|
OrderErrorFragment,
|
||||||
|
} from "@saleor/graphql";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import { OrderDiscountContextConsumerProps } from "@saleor/products/components/OrderDiscountProviders/OrderDiscountProvider";
|
import { OrderDiscountContextConsumerProps } from "@saleor/products/components/OrderDiscountProviders/OrderDiscountProvider";
|
||||||
import { OrderDiscountData } from "@saleor/products/components/OrderDiscountProviders/types";
|
import { OrderDiscountData } from "@saleor/products/components/OrderDiscountProviders/types";
|
||||||
|
import { getFormErrors } from "@saleor/utils/errors";
|
||||||
|
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||||
import React, { useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -23,6 +29,11 @@ const useStyles = makeStyles(
|
||||||
textRight: {
|
textRight: {
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
},
|
},
|
||||||
|
textError: {
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
marginLeft: theme.spacing(1.5),
|
||||||
|
display: "inline",
|
||||||
|
},
|
||||||
subtitle: {
|
subtitle: {
|
||||||
color: theme.palette.grey[500],
|
color: theme.palette.grey[500],
|
||||||
paddingRight: theme.spacing(1),
|
paddingRight: theme.spacing(1),
|
||||||
|
@ -51,12 +62,14 @@ interface OrderDraftDetailsSummaryProps
|
||||||
extends OrderDiscountContextConsumerProps {
|
extends OrderDiscountContextConsumerProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
order: OrderDetailsFragment;
|
order: OrderDetailsFragment;
|
||||||
|
errors: OrderErrorFragment[];
|
||||||
onShippingMethodEdit: () => void;
|
onShippingMethodEdit: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderDraftDetailsSummary: React.FC<OrderDraftDetailsSummaryProps> = props => {
|
const OrderDraftDetailsSummary: React.FC<OrderDraftDetailsSummaryProps> = props => {
|
||||||
const {
|
const {
|
||||||
order,
|
order,
|
||||||
|
errors,
|
||||||
onShippingMethodEdit,
|
onShippingMethodEdit,
|
||||||
orderDiscount,
|
orderDiscount,
|
||||||
addOrderDiscount,
|
addOrderDiscount,
|
||||||
|
@ -89,6 +102,8 @@ const OrderDraftDetailsSummary: React.FC<OrderDraftDetailsSummaryProps> = props
|
||||||
isShippingRequired,
|
isShippingRequired,
|
||||||
} = order;
|
} = order;
|
||||||
|
|
||||||
|
const formErrors = getFormErrors(["shipping"], errors);
|
||||||
|
|
||||||
const hasChosenShippingMethod =
|
const hasChosenShippingMethod =
|
||||||
shippingMethod !== null && shippingMethodName !== null;
|
shippingMethod !== null && shippingMethodName !== null;
|
||||||
|
|
||||||
|
@ -192,11 +207,18 @@ const OrderDraftDetailsSummary: React.FC<OrderDraftDetailsSummaryProps> = props
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{hasShippingMethods && <td>{getShippingMethodComponent()}</td>}
|
<td>
|
||||||
|
{hasShippingMethods && getShippingMethodComponent()}
|
||||||
|
|
||||||
{!hasShippingMethods && (
|
{!hasShippingMethods &&
|
||||||
<td>{intl.formatMessage(messages.noShippingCarriers)}</td>
|
intl.formatMessage(messages.noShippingCarriers)}
|
||||||
|
|
||||||
|
{formErrors.shipping && (
|
||||||
|
<Typography variant="body2" className={classes.textError}>
|
||||||
|
{getOrderErrorMessage(formErrors.shipping, intl)}
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
<td className={classes.textRight}>
|
<td className={classes.textRight}>
|
||||||
{hasChosenShippingMethod ? (
|
{hasChosenShippingMethod ? (
|
||||||
|
|
67
src/orders/components/OrderDraftPage/OrderDraftAlert.tsx
Normal file
67
src/orders/components/OrderDraftPage/OrderDraftAlert.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
ChannelUsabilityDataQuery,
|
||||||
|
OrderDetailsFragment,
|
||||||
|
} from "@saleor/graphql";
|
||||||
|
import { Alert, AlertProps } from "@saleor/macaw-ui";
|
||||||
|
import React from "react";
|
||||||
|
import { MessageDescriptor, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import OrderAlerts from "../OrderAlerts";
|
||||||
|
import { alertMessages } from "./messages";
|
||||||
|
import { useAlertStyles } from "./styles";
|
||||||
|
|
||||||
|
const getAlerts = (
|
||||||
|
order?: OrderDetailsFragment,
|
||||||
|
channelUsabilityData?: ChannelUsabilityDataQuery,
|
||||||
|
) => {
|
||||||
|
const canDetermineShippingMethods =
|
||||||
|
order?.shippingAddress?.country.code && !!order?.lines?.length;
|
||||||
|
|
||||||
|
const isChannelInactive = order && !order.channel.isActive;
|
||||||
|
const noProductsInChannel = channelUsabilityData?.products.totalCount === 0;
|
||||||
|
const noShippingMethodsInChannel =
|
||||||
|
canDetermineShippingMethods && order?.shippingMethods.length === 0;
|
||||||
|
|
||||||
|
let alerts: MessageDescriptor[] = [];
|
||||||
|
|
||||||
|
if (isChannelInactive) {
|
||||||
|
alerts = [...alerts, alertMessages.inactiveChannel];
|
||||||
|
}
|
||||||
|
if (noProductsInChannel) {
|
||||||
|
alerts = [...alerts, alertMessages.noProductsInChannel];
|
||||||
|
}
|
||||||
|
if (noShippingMethodsInChannel) {
|
||||||
|
alerts = [...alerts, alertMessages.noShippingMethodsInChannel];
|
||||||
|
}
|
||||||
|
|
||||||
|
return alerts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderDraftAlertProps = Omit<AlertProps, "variant" | "close"> & {
|
||||||
|
order?: OrderDetailsFragment;
|
||||||
|
channelUsabilityData?: ChannelUsabilityDataQuery;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OrderDraftAlert: React.FC<OrderDraftAlertProps> = props => {
|
||||||
|
const { order, channelUsabilityData, ...alertProps } = props;
|
||||||
|
const classes = useAlertStyles();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const alerts = getAlerts(order, channelUsabilityData);
|
||||||
|
|
||||||
|
if (!alerts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert variant="warning" close className={classes.root} {...alertProps}>
|
||||||
|
<OrderAlerts
|
||||||
|
alerts={alerts}
|
||||||
|
alertsHeader={intl.formatMessage(alertMessages.manyAlerts)}
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
OrderDraftAlert.displayName = "OrderDraftAlert";
|
||||||
|
export default OrderDraftAlert;
|
|
@ -9,15 +9,17 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import {
|
import {
|
||||||
|
ChannelUsabilityDataQuery,
|
||||||
OrderDetailsFragment,
|
OrderDetailsFragment,
|
||||||
|
OrderErrorFragment,
|
||||||
OrderLineInput,
|
OrderLineInput,
|
||||||
SearchCustomersQuery,
|
SearchCustomersQuery,
|
||||||
} from "@saleor/graphql";
|
} from "@saleor/graphql";
|
||||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState, makeStyles } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import DraftOrderChannelSectionCard from "@saleor/orders/components/DraftOrderChannelSectionCard";
|
import OrderChannelSectionCard from "@saleor/orders/components/OrderChannelSectionCard";
|
||||||
import { orderDraftListUrl } from "@saleor/orders/urls";
|
import { orderDraftListUrl } from "@saleor/orders/urls";
|
||||||
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
|
import { FetchMoreProps, RelayToFlat } from "@saleor/types";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -26,25 +28,16 @@ import { useIntl } from "react-intl";
|
||||||
import OrderCustomer, { CustomerEditData } from "../OrderCustomer";
|
import OrderCustomer, { CustomerEditData } from "../OrderCustomer";
|
||||||
import OrderDraftDetails from "../OrderDraftDetails/OrderDraftDetails";
|
import OrderDraftDetails from "../OrderDraftDetails/OrderDraftDetails";
|
||||||
import OrderHistory, { FormData as HistoryFormData } from "../OrderHistory";
|
import OrderHistory, { FormData as HistoryFormData } from "../OrderHistory";
|
||||||
|
import OrderDraftAlert from "./OrderDraftAlert";
|
||||||
const useStyles = makeStyles(
|
import { usePageStyles } from "./styles";
|
||||||
theme => ({
|
|
||||||
date: {
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
display: "flex",
|
|
||||||
marginBottom: 0,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "OrderDraftPage" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface OrderDraftPageProps extends FetchMoreProps {
|
export interface OrderDraftPageProps extends FetchMoreProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
order: OrderDetailsFragment;
|
order?: OrderDetailsFragment;
|
||||||
|
channelUsabilityData?: ChannelUsabilityDataQuery;
|
||||||
users: RelayToFlat<SearchCustomersQuery["search"]>;
|
users: RelayToFlat<SearchCustomersQuery["search"]>;
|
||||||
usersLoading: boolean;
|
usersLoading: boolean;
|
||||||
|
errors: OrderErrorFragment[];
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
fetchUsers: (query: string) => void;
|
fetchUsers: (query: string) => void;
|
||||||
onBillingAddressEdit: () => void;
|
onBillingAddressEdit: () => void;
|
||||||
|
@ -80,10 +73,12 @@ const OrderDraftPage: React.FC<OrderDraftPageProps> = props => {
|
||||||
onShippingMethodEdit,
|
onShippingMethodEdit,
|
||||||
onProfileView,
|
onProfileView,
|
||||||
order,
|
order,
|
||||||
|
channelUsabilityData,
|
||||||
users,
|
users,
|
||||||
usersLoading,
|
usersLoading,
|
||||||
|
errors,
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = usePageStyles(props);
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -122,8 +117,14 @@ const OrderDraftPage: React.FC<OrderDraftPageProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
<Grid>
|
<Grid>
|
||||||
<div>
|
<div>
|
||||||
|
<OrderDraftAlert
|
||||||
|
order={order}
|
||||||
|
channelUsabilityData={channelUsabilityData}
|
||||||
|
/>
|
||||||
<OrderDraftDetails
|
<OrderDraftDetails
|
||||||
order={order}
|
order={order}
|
||||||
|
channelUsabilityData={channelUsabilityData}
|
||||||
|
errors={errors}
|
||||||
onOrderLineAdd={onOrderLineAdd}
|
onOrderLineAdd={onOrderLineAdd}
|
||||||
onOrderLineChange={onOrderLineChange}
|
onOrderLineChange={onOrderLineChange}
|
||||||
onOrderLineRemove={onOrderLineRemove}
|
onOrderLineRemove={onOrderLineRemove}
|
||||||
|
@ -136,12 +137,15 @@ const OrderDraftPage: React.FC<OrderDraftPageProps> = props => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<OrderChannelSectionCard channel={order?.channel} />
|
||||||
|
<CardSpacer />
|
||||||
<OrderCustomer
|
<OrderCustomer
|
||||||
canEditAddresses={!!order?.user}
|
canEditAddresses={!!order?.user}
|
||||||
canEditCustomer={true}
|
canEditCustomer={true}
|
||||||
fetchUsers={fetchUsers}
|
fetchUsers={fetchUsers}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
loading={usersLoading}
|
loading={usersLoading}
|
||||||
|
errors={errors}
|
||||||
order={order}
|
order={order}
|
||||||
users={users}
|
users={users}
|
||||||
onBillingAddressEdit={onBillingAddressEdit}
|
onBillingAddressEdit={onBillingAddressEdit}
|
||||||
|
@ -150,13 +154,11 @@ const OrderDraftPage: React.FC<OrderDraftPageProps> = props => {
|
||||||
onProfileView={onProfileView}
|
onProfileView={onProfileView}
|
||||||
onShippingAddressEdit={onShippingAddressEdit}
|
onShippingAddressEdit={onShippingAddressEdit}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
|
||||||
<DraftOrderChannelSectionCard channelName={order?.channel?.name} />
|
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Savebar
|
<Savebar
|
||||||
state={saveButtonBarState}
|
state={saveButtonBarState}
|
||||||
disabled={disabled || !order?.canFinalize}
|
disabled={disabled}
|
||||||
onCancel={() => navigate(orderDraftListUrl())}
|
onCancel={() => navigate(orderDraftListUrl())}
|
||||||
onSubmit={onDraftFinalize}
|
onSubmit={onDraftFinalize}
|
||||||
labels={{
|
labels={{
|
||||||
|
|
24
src/orders/components/OrderDraftPage/messages.ts
Normal file
24
src/orders/components/OrderDraftPage/messages.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const alertMessages = defineMessages({
|
||||||
|
manyAlerts: {
|
||||||
|
id: "43AOvZ",
|
||||||
|
defaultMessage: "You will not be able to finalize this draft because:",
|
||||||
|
description: "alert group message",
|
||||||
|
},
|
||||||
|
inactiveChannel: {
|
||||||
|
id: "SPp3cx",
|
||||||
|
defaultMessage: "Orders cannot be placed in an inactive channel.",
|
||||||
|
description: "alert message",
|
||||||
|
},
|
||||||
|
noProductsInChannel: {
|
||||||
|
id: "O4QNFx",
|
||||||
|
defaultMessage: "There are no available products in this channel.",
|
||||||
|
description: "alert message",
|
||||||
|
},
|
||||||
|
noShippingMethodsInChannel: {
|
||||||
|
id: "BvRyoX",
|
||||||
|
defaultMessage: "There are no available shipping methods in this channel.",
|
||||||
|
description: "alert message",
|
||||||
|
},
|
||||||
|
});
|
23
src/orders/components/OrderDraftPage/styles.ts
Normal file
23
src/orders/components/OrderDraftPage/styles.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
|
||||||
|
export const usePageStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
date: {
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
display: "flex",
|
||||||
|
marginBottom: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "OrderDraftPage" },
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useAlertStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
root: {
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "OrderDraftAlert" },
|
||||||
|
);
|
|
@ -9,6 +9,7 @@ import {
|
||||||
TableCell,
|
TableCell,
|
||||||
TableRow,
|
TableRow,
|
||||||
TextField,
|
TextField,
|
||||||
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import BackButton from "@saleor/components/BackButton";
|
import BackButton from "@saleor/components/BackButton";
|
||||||
import Checkbox from "@saleor/components/Checkbox";
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
|
@ -21,7 +22,7 @@ import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
|
||||||
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
||||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState, makeStyles } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
import { maybe, renderCollection } from "@saleor/misc";
|
||||||
import { ChannelProps, FetchMoreProps, RelayToFlat } from "@saleor/types";
|
import { ChannelProps, FetchMoreProps, RelayToFlat } from "@saleor/types";
|
||||||
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||||
|
@ -30,65 +31,14 @@ import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import OrderPriceLabel from "../OrderPriceLabel/OrderPriceLabel";
|
import OrderPriceLabel from "../OrderPriceLabel/OrderPriceLabel";
|
||||||
|
import { messages } from "./messages";
|
||||||
const useStyles = makeStyles(
|
import { useStyles } from "./styles";
|
||||||
theme => ({
|
import {
|
||||||
avatar: {
|
hasAllVariantsSelected,
|
||||||
paddingLeft: 0,
|
isVariantSelected,
|
||||||
width: 64,
|
onProductAdd,
|
||||||
},
|
onVariantAdd,
|
||||||
colName: {
|
} from "./utils";
|
||||||
paddingLeft: 0,
|
|
||||||
},
|
|
||||||
colVariantCheckbox: {
|
|
||||||
padding: 0,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
overflowY: "scroll",
|
|
||||||
paddingTop: 0,
|
|
||||||
marginBottom: theme.spacing(3),
|
|
||||||
},
|
|
||||||
grayText: {
|
|
||||||
color: theme.palette.text.disabled,
|
|
||||||
},
|
|
||||||
loadMoreLoaderContainer: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
height: theme.spacing(3),
|
|
||||||
justifyContent: "center",
|
|
||||||
marginTop: theme.spacing(3),
|
|
||||||
},
|
|
||||||
overflow: {
|
|
||||||
overflowY: "hidden",
|
|
||||||
},
|
|
||||||
topArea: {
|
|
||||||
overflowY: "hidden",
|
|
||||||
paddingBottom: theme.spacing(6),
|
|
||||||
margin: theme.spacing(0, 3, 3, 3),
|
|
||||||
},
|
|
||||||
productCheckboxCell: {
|
|
||||||
"&:first-child": {
|
|
||||||
paddingLeft: 0,
|
|
||||||
paddingRight: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
textRight: {
|
|
||||||
textAlign: "right",
|
|
||||||
},
|
|
||||||
variantCheckbox: {
|
|
||||||
left: theme.spacing(),
|
|
||||||
position: "relative",
|
|
||||||
},
|
|
||||||
wideCell: {
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{ name: "OrderProductAddDialog" },
|
|
||||||
);
|
|
||||||
|
|
||||||
type SetVariantsAction = (
|
|
||||||
data: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
export interface OrderProductAddDialogProps
|
export interface OrderProductAddDialogProps
|
||||||
extends FetchMoreProps,
|
extends FetchMoreProps,
|
||||||
|
@ -104,69 +54,6 @@ export interface OrderProductAddDialogProps
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasAllVariantsSelected(
|
|
||||||
productVariants: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
|
||||||
selectedVariantsToProductsMap: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
|
||||||
): boolean {
|
|
||||||
return productVariants.reduce(
|
|
||||||
(acc, productVariant) =>
|
|
||||||
acc &&
|
|
||||||
!!selectedVariantsToProductsMap.find(
|
|
||||||
selectedVariant => selectedVariant.id === productVariant.id,
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isVariantSelected(
|
|
||||||
variant: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"][0],
|
|
||||||
selectedVariantsToProductsMap: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
|
||||||
): boolean {
|
|
||||||
return !!selectedVariantsToProductsMap.find(
|
|
||||||
selectedVariant => selectedVariant.id === variant.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onProductAdd = (
|
|
||||||
product: SearchOrderVariantQuery["search"]["edges"][0]["node"],
|
|
||||||
productIndex: number,
|
|
||||||
productsWithAllVariantsSelected: boolean[],
|
|
||||||
variants: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
|
||||||
setVariants: SetVariantsAction,
|
|
||||||
) =>
|
|
||||||
productsWithAllVariantsSelected[productIndex]
|
|
||||||
? setVariants(
|
|
||||||
variants.filter(
|
|
||||||
selectedVariant =>
|
|
||||||
!product.variants.find(
|
|
||||||
productVariant => productVariant.id === selectedVariant.id,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: setVariants([
|
|
||||||
...variants,
|
|
||||||
...product.variants.filter(
|
|
||||||
productVariant =>
|
|
||||||
!variants.find(
|
|
||||||
selectedVariant => selectedVariant.id === productVariant.id,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const onVariantAdd = (
|
|
||||||
variant: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"][0],
|
|
||||||
variantIndex: number,
|
|
||||||
productIndex: number,
|
|
||||||
variants: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
|
||||||
selectedVariantsToProductsMap: boolean[][],
|
|
||||||
setVariants: SetVariantsAction,
|
|
||||||
) =>
|
|
||||||
selectedVariantsToProductsMap[productIndex][variantIndex]
|
|
||||||
? setVariants(
|
|
||||||
variants.filter(selectedVariant => selectedVariant.id !== variant.id),
|
|
||||||
)
|
|
||||||
: setVariants([...variants, variant]);
|
|
||||||
|
|
||||||
const scrollableTargetId = "orderProductAddScrollableDialog";
|
const scrollableTargetId = "orderProductAddScrollableDialog";
|
||||||
|
|
||||||
const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
|
const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
|
||||||
|
@ -249,26 +136,15 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
|
||||||
maxWidth="sm"
|
maxWidth="sm"
|
||||||
>
|
>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<FormattedMessage
|
<FormattedMessage {...messages.title} />
|
||||||
id="myyWNp"
|
|
||||||
defaultMessage="Add Product"
|
|
||||||
description="dialog header"
|
|
||||||
/>
|
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent className={classes.topArea} data-test-id="search-query">
|
<DialogContent className={classes.topArea} data-test-id="search-query">
|
||||||
<TextField
|
<TextField
|
||||||
name="query"
|
name="query"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={onQueryChange}
|
onChange={onQueryChange}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage(messages.search)}
|
||||||
id: "/TF6BZ",
|
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||||
defaultMessage: "Search Products",
|
|
||||||
})}
|
|
||||||
placeholder={intl.formatMessage({
|
|
||||||
id: "SHm7ee",
|
|
||||||
defaultMessage:
|
|
||||||
"Search by product name, attribute, product type etc...",
|
|
||||||
})}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
InputProps={{
|
InputProps={{
|
||||||
autoComplete: "off",
|
autoComplete: "off",
|
||||||
|
@ -355,9 +231,7 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
|
||||||
{variant.sku && (
|
{variant.sku && (
|
||||||
<div className={classes.grayText}>
|
<div className={classes.grayText}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="+HuipK"
|
{...messages.sku}
|
||||||
defaultMessage="SKU {sku}"
|
|
||||||
description="variant sku"
|
|
||||||
values={{
|
values={{
|
||||||
sku: variant.sku,
|
sku: variant.sku,
|
||||||
}}
|
}}
|
||||||
|
@ -373,14 +247,11 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
() => (
|
() => (
|
||||||
<TableRow>
|
<Typography className={classes.noContentText}>
|
||||||
<TableCell colSpan={4}>
|
{!!query
|
||||||
<FormattedMessage
|
? intl.formatMessage(messages.noProductsInQuery)
|
||||||
id="WQnltU"
|
: intl.formatMessage(messages.noProductsInChannel)}
|
||||||
defaultMessage="No products available in order channel matching given query"
|
</Typography>
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
36
src/orders/components/OrderProductAddDialog/messages.ts
Normal file
36
src/orders/components/OrderProductAddDialog/messages.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
defaultMessage: "Add product",
|
||||||
|
description: "dialog header",
|
||||||
|
id: "9Y6vg+",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
defaultMessage: "Search products",
|
||||||
|
description: "search label",
|
||||||
|
id: "s6oAC+",
|
||||||
|
},
|
||||||
|
searchPlaceholder: {
|
||||||
|
defaultMessage: "Search by product name, attribute, product type etc...",
|
||||||
|
description: "search placeholder",
|
||||||
|
id: "S2xLxV",
|
||||||
|
},
|
||||||
|
sku: {
|
||||||
|
defaultMessage: "SKU {sku}",
|
||||||
|
description: "variant sku",
|
||||||
|
id: "+HuipK",
|
||||||
|
},
|
||||||
|
noProductsInChannel: {
|
||||||
|
id: "shmSDX",
|
||||||
|
defaultMessage:
|
||||||
|
"No products are available in the channel assigned to this order.",
|
||||||
|
description: "no products placeholder",
|
||||||
|
},
|
||||||
|
noProductsInQuery: {
|
||||||
|
id: "9mrWKz",
|
||||||
|
defaultMessage:
|
||||||
|
"No products are available matching query in the channel assigned to this order.",
|
||||||
|
description: "no products placeholder",
|
||||||
|
},
|
||||||
|
});
|
59
src/orders/components/OrderProductAddDialog/styles.ts
Normal file
59
src/orders/components/OrderProductAddDialog/styles.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
|
|
||||||
|
export const useStyles = makeStyles(
|
||||||
|
theme => ({
|
||||||
|
avatar: {
|
||||||
|
paddingLeft: 0,
|
||||||
|
width: 64,
|
||||||
|
},
|
||||||
|
colName: {
|
||||||
|
paddingLeft: 0,
|
||||||
|
},
|
||||||
|
colVariantCheckbox: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
noContentText: {
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
overflowY: "scroll",
|
||||||
|
paddingTop: 0,
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
},
|
||||||
|
grayText: {
|
||||||
|
color: theme.palette.text.disabled,
|
||||||
|
},
|
||||||
|
loadMoreLoaderContainer: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
height: theme.spacing(3),
|
||||||
|
justifyContent: "center",
|
||||||
|
marginTop: theme.spacing(3),
|
||||||
|
},
|
||||||
|
overflow: {
|
||||||
|
overflowY: "hidden",
|
||||||
|
},
|
||||||
|
topArea: {
|
||||||
|
overflowY: "hidden",
|
||||||
|
paddingBottom: theme.spacing(6),
|
||||||
|
margin: theme.spacing(0, 3, 3, 3),
|
||||||
|
},
|
||||||
|
productCheckboxCell: {
|
||||||
|
"&:first-child": {
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
textRight: {
|
||||||
|
textAlign: "right",
|
||||||
|
},
|
||||||
|
variantCheckbox: {
|
||||||
|
left: theme.spacing(),
|
||||||
|
position: "relative",
|
||||||
|
},
|
||||||
|
wideCell: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ name: "OrderProductAddDialog" },
|
||||||
|
);
|
68
src/orders/components/OrderProductAddDialog/utils.ts
Normal file
68
src/orders/components/OrderProductAddDialog/utils.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { SearchOrderVariantQuery } from "@saleor/graphql";
|
||||||
|
|
||||||
|
type SetVariantsAction = (
|
||||||
|
data: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export function hasAllVariantsSelected(
|
||||||
|
productVariants: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
||||||
|
selectedVariantsToProductsMap: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
||||||
|
): boolean {
|
||||||
|
return productVariants.reduce(
|
||||||
|
(acc, productVariant) =>
|
||||||
|
acc &&
|
||||||
|
!!selectedVariantsToProductsMap.find(
|
||||||
|
selectedVariant => selectedVariant.id === productVariant.id,
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVariantSelected(
|
||||||
|
variant: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"][0],
|
||||||
|
selectedVariantsToProductsMap: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
||||||
|
): boolean {
|
||||||
|
return !!selectedVariantsToProductsMap.find(
|
||||||
|
selectedVariant => selectedVariant.id === variant.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const onProductAdd = (
|
||||||
|
product: SearchOrderVariantQuery["search"]["edges"][0]["node"],
|
||||||
|
productIndex: number,
|
||||||
|
productsWithAllVariantsSelected: boolean[],
|
||||||
|
variants: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
||||||
|
setVariants: SetVariantsAction,
|
||||||
|
) =>
|
||||||
|
productsWithAllVariantsSelected[productIndex]
|
||||||
|
? setVariants(
|
||||||
|
variants.filter(
|
||||||
|
selectedVariant =>
|
||||||
|
!product.variants.find(
|
||||||
|
productVariant => productVariant.id === selectedVariant.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: setVariants([
|
||||||
|
...variants,
|
||||||
|
...product.variants.filter(
|
||||||
|
productVariant =>
|
||||||
|
!variants.find(
|
||||||
|
selectedVariant => selectedVariant.id === productVariant.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const onVariantAdd = (
|
||||||
|
variant: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"][0],
|
||||||
|
variantIndex: number,
|
||||||
|
productIndex: number,
|
||||||
|
variants: SearchOrderVariantQuery["search"]["edges"][0]["node"]["variants"],
|
||||||
|
selectedVariantsToProductsMap: boolean[][],
|
||||||
|
setVariants: SetVariantsAction,
|
||||||
|
) =>
|
||||||
|
selectedVariantsToProductsMap[productIndex][variantIndex]
|
||||||
|
? setVariants(
|
||||||
|
variants.filter(selectedVariant => selectedVariant.id !== variant.id),
|
||||||
|
)
|
||||||
|
: setVariants([...variants, variant]);
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ChannelUsabilityDataQuery,
|
||||||
CountryWithCodeFragment,
|
CountryWithCodeFragment,
|
||||||
FulfillmentStatus,
|
FulfillmentStatus,
|
||||||
InvoiceFragment,
|
InvoiceFragment,
|
||||||
|
@ -1132,6 +1133,11 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
||||||
id: "dsfsfuhb",
|
id: "dsfsfuhb",
|
||||||
quantityAvailable: 10,
|
quantityAvailable: 10,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -1241,6 +1247,11 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
||||||
id: "dsfsfuhb",
|
id: "dsfsfuhb",
|
||||||
quantityAvailable: 10,
|
quantityAvailable: 10,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -1358,6 +1369,11 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
||||||
id: "dsfsfuhb",
|
id: "dsfsfuhb",
|
||||||
quantityAvailable: 10,
|
quantityAvailable: 10,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -1453,6 +1469,11 @@ export const order = (placeholder: string): OrderDetailsFragment => ({
|
||||||
id: "dsfsfuhb",
|
id: "dsfsfuhb",
|
||||||
quantityAvailable: 10,
|
quantityAvailable: 10,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -1585,7 +1606,7 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
||||||
__typename: "Order" as "Order",
|
__typename: "Order" as "Order",
|
||||||
giftCards: [],
|
giftCards: [],
|
||||||
actions: [OrderAction.CAPTURE],
|
actions: [OrderAction.CAPTURE],
|
||||||
shippingMethods: null,
|
shippingMethods: [],
|
||||||
billingAddress: null,
|
billingAddress: null,
|
||||||
canFinalize: true,
|
canFinalize: true,
|
||||||
channel: {
|
channel: {
|
||||||
|
@ -1686,6 +1707,11 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
||||||
id: "dsfsfuhb",
|
id: "dsfsfuhb",
|
||||||
quantityAvailable: 10,
|
quantityAvailable: 10,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -1780,6 +1806,11 @@ export const draftOrder = (placeholder: string): OrderDetailsFragment => ({
|
||||||
id: "dsfsfuhb",
|
id: "dsfsfuhb",
|
||||||
quantityAvailable: 10,
|
quantityAvailable: 10,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -2492,3 +2523,11 @@ export const warehouseSearch: SearchWarehousesQuery["search"] = {
|
||||||
},
|
},
|
||||||
__typename: "WarehouseCountableConnection",
|
__typename: "WarehouseCountableConnection",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const channelUsabilityData: ChannelUsabilityDataQuery = {
|
||||||
|
__typename: "Query",
|
||||||
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
|
totalCount: 50,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -422,7 +422,6 @@ export const fulfillOrder = gql`
|
||||||
errors {
|
errors {
|
||||||
...OrderError
|
...OrderError
|
||||||
warehouse
|
warehouse
|
||||||
orderLines
|
|
||||||
}
|
}
|
||||||
order {
|
order {
|
||||||
...OrderDetails
|
...OrderDetails
|
||||||
|
|
|
@ -192,3 +192,11 @@ export const orderRefundData = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const channelUsabilityData = gql`
|
||||||
|
query ChannelUsabilityData($channel: String!) {
|
||||||
|
products(channel: $channel) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -539,6 +539,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
quantityAvailable: 50,
|
quantityAvailable: 50,
|
||||||
preorder: null,
|
preorder: null,
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
stocks: [
|
stocks: [
|
||||||
{
|
{
|
||||||
id: "stock_test_id1",
|
id: "stock_test_id1",
|
||||||
|
@ -660,6 +665,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -755,6 +765,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "T-shirt",
|
productName: "T-shirt",
|
||||||
productSku: "29810068",
|
productSku: "29810068",
|
||||||
|
@ -856,6 +871,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -956,6 +976,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1056,6 +1081,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "T-shirt",
|
productName: "T-shirt",
|
||||||
productSku: "29810068",
|
productSku: "29810068",
|
||||||
|
@ -1156,6 +1186,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1256,6 +1291,11 @@ describe("Get the total value of all replaced products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1490,6 +1530,11 @@ describe("Get the total value of all selected products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1585,6 +1630,11 @@ describe("Get the total value of all selected products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1680,6 +1730,11 @@ describe("Get the total value of all selected products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "T-shirt",
|
productName: "T-shirt",
|
||||||
productSku: "29810068",
|
productSku: "29810068",
|
||||||
|
@ -1781,6 +1836,11 @@ describe("Get the total value of all selected products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1881,6 +1941,11 @@ describe("Get the total value of all selected products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -1981,6 +2046,11 @@ describe("Get the total value of all selected products", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "T-shirt",
|
productName: "T-shirt",
|
||||||
productSku: "29810068",
|
productSku: "29810068",
|
||||||
|
@ -2209,6 +2279,11 @@ describe("Merge repeated order lines of fulfillment lines", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -2309,6 +2384,11 @@ describe("Merge repeated order lines of fulfillment lines", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "Lake Tunes",
|
productName: "Lake Tunes",
|
||||||
productSku: "lake-tunes-mp3",
|
productSku: "lake-tunes-mp3",
|
||||||
|
@ -2409,6 +2489,11 @@ describe("Merge repeated order lines of fulfillment lines", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
|
product: {
|
||||||
|
__typename: "Product",
|
||||||
|
id: "UHJvZHVjdDo1",
|
||||||
|
channelListings: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
productName: "T-shirt",
|
productName: "T-shirt",
|
||||||
productSku: "29810068",
|
productSku: "29810068",
|
||||||
|
|
|
@ -4,11 +4,14 @@ import {
|
||||||
OrderDetailsQuery,
|
OrderDetailsQuery,
|
||||||
OrderDraftCancelMutation,
|
OrderDraftCancelMutation,
|
||||||
OrderDraftCancelMutationVariables,
|
OrderDraftCancelMutationVariables,
|
||||||
|
OrderDraftFinalizeMutation,
|
||||||
|
OrderDraftFinalizeMutationVariables,
|
||||||
OrderDraftUpdateMutation,
|
OrderDraftUpdateMutation,
|
||||||
OrderDraftUpdateMutationVariables,
|
OrderDraftUpdateMutationVariables,
|
||||||
OrderLineUpdateMutation,
|
OrderLineUpdateMutation,
|
||||||
OrderLineUpdateMutationVariables,
|
OrderLineUpdateMutationVariables,
|
||||||
StockAvailability,
|
StockAvailability,
|
||||||
|
useChannelUsabilityDataQuery,
|
||||||
useCustomerAddressesQuery,
|
useCustomerAddressesQuery,
|
||||||
} from "@saleor/graphql";
|
} from "@saleor/graphql";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
|
@ -67,7 +70,10 @@ interface OrderDraftDetailsProps {
|
||||||
OrderDraftCancelMutation,
|
OrderDraftCancelMutation,
|
||||||
OrderDraftCancelMutationVariables
|
OrderDraftCancelMutationVariables
|
||||||
>;
|
>;
|
||||||
orderDraftFinalize: any;
|
orderDraftFinalize: PartialMutationProviderOutput<
|
||||||
|
OrderDraftFinalizeMutation,
|
||||||
|
OrderDraftFinalizeMutationVariables
|
||||||
|
>;
|
||||||
openModal: (action: OrderUrlDialog, newParams?: OrderUrlQueryParams) => void;
|
openModal: (action: OrderUrlDialog, newParams?: OrderUrlQueryParams) => void;
|
||||||
closeModal: any;
|
closeModal: any;
|
||||||
}
|
}
|
||||||
|
@ -98,6 +104,12 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
const order = data.order;
|
const order = data.order;
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
|
|
||||||
|
const { data: channelUsabilityData } = useChannelUsabilityDataQuery({
|
||||||
|
variables: {
|
||||||
|
channel: order.channel.slug,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loadMore,
|
loadMore,
|
||||||
search: variantSearch,
|
search: variantSearch,
|
||||||
|
@ -184,6 +196,8 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const errors = orderDraftFinalize.opts.data?.draftOrderComplete.errors || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle
|
<WindowTitle
|
||||||
|
@ -203,6 +217,7 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
<OrderLineDiscountProvider order={order}>
|
<OrderLineDiscountProvider order={order}>
|
||||||
<OrderDraftPage
|
<OrderDraftPage
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
errors={errors}
|
||||||
onNoteAdd={variables =>
|
onNoteAdd={variables =>
|
||||||
extractMutationErrors(
|
extractMutationErrors(
|
||||||
orderAddNote.mutate({
|
orderAddNote.mutate({
|
||||||
|
@ -222,6 +237,7 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
onDraftRemove={() => openModal("cancel")}
|
onDraftRemove={() => openModal("cancel")}
|
||||||
onOrderLineAdd={() => openModal("add-order-line")}
|
onOrderLineAdd={() => openModal("add-order-line")}
|
||||||
order={order}
|
order={order}
|
||||||
|
channelUsabilityData={channelUsabilityData}
|
||||||
onProductClick={id => () =>
|
onProductClick={id => () =>
|
||||||
navigate(productUrl(encodeURIComponent(id)))}
|
navigate(productUrl(encodeURIComponent(id)))}
|
||||||
onBillingAddressEdit={() => openModal("edit-billing-address")}
|
onBillingAddressEdit={() => openModal("edit-billing-address")}
|
||||||
|
|
|
@ -149,6 +149,8 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
||||||
}
|
}
|
||||||
}, [approvalErrors]);
|
}, [approvalErrors]);
|
||||||
|
|
||||||
|
const errors = orderUpdate.opts.data?.orderUpdate.errors || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle
|
<WindowTitle
|
||||||
|
@ -168,6 +170,7 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
||||||
disabled={
|
disabled={
|
||||||
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
||||||
}
|
}
|
||||||
|
errors={errors}
|
||||||
onNoteAdd={variables =>
|
onNoteAdd={variables =>
|
||||||
extractMutationErrors(
|
extractMutationErrors(
|
||||||
orderAddNote.mutate({
|
orderAddNote.mutate({
|
||||||
|
|
|
@ -147,6 +147,8 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [transactionReference, setTransactionReference] = React.useState("");
|
const [transactionReference, setTransactionReference] = React.useState("");
|
||||||
|
|
||||||
|
const errors = orderUpdate.opts.data?.orderUpdate.errors || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle
|
<WindowTitle
|
||||||
|
@ -168,6 +170,7 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
|
||||||
disabled={
|
disabled={
|
||||||
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
||||||
}
|
}
|
||||||
|
errors={errors}
|
||||||
onNoteAdd={variables =>
|
onNoteAdd={variables =>
|
||||||
extractMutationErrors(
|
extractMutationErrors(
|
||||||
orderAddNote.mutate({
|
orderAddNote.mutate({
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -38,6 +38,7 @@ storiesOf("Orders / OrderCancelDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Cannot cancel order",
|
message: "Cannot cancel order",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -20,6 +20,7 @@ const props: Omit<OrderCustomerProps, "classes"> = {
|
||||||
onShippingAddressEdit: undefined,
|
onShippingAddressEdit: undefined,
|
||||||
order,
|
order,
|
||||||
users: clients,
|
users: clients,
|
||||||
|
errors: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const OrderCustomer = props => {
|
const OrderCustomer = props => {
|
||||||
|
|
|
@ -40,6 +40,7 @@ const props: Omit<OrderDetailsPageProps, "classes"> = {
|
||||||
onShippingAddressEdit: undefined,
|
onShippingAddressEdit: undefined,
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => undefined,
|
||||||
order,
|
order,
|
||||||
|
errors: [],
|
||||||
shop: shopFixture,
|
shop: shopFixture,
|
||||||
saveButtonBarState: "default",
|
saveButtonBarState: "default",
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,6 +29,7 @@ storiesOf("Orders / OrderDraftCancelDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Graphql Error",
|
message: "Graphql Error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,16 +1,48 @@
|
||||||
import placeholderImage from "@assets/images/placeholder60x60.png";
|
import placeholderImage from "@assets/images/placeholder60x60.png";
|
||||||
import { fetchMoreProps } from "@saleor/fixtures";
|
import { fetchMoreProps } from "@saleor/fixtures";
|
||||||
|
import { OrderErrorCode, OrderErrorFragment } from "@saleor/graphql";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import OrderDraftPageComponent, {
|
import OrderDraftPageComponent, {
|
||||||
OrderDraftPageProps,
|
OrderDraftPageProps,
|
||||||
} from "../../../../orders/components/OrderDraftPage";
|
} from "../../../../orders/components/OrderDraftPage";
|
||||||
import { clients, draftOrder } from "../../../../orders/fixtures";
|
import {
|
||||||
|
channelUsabilityData,
|
||||||
|
clients,
|
||||||
|
draftOrder,
|
||||||
|
} from "../../../../orders/fixtures";
|
||||||
import Decorator from "../../../Decorator";
|
import Decorator from "../../../Decorator";
|
||||||
import { MockedUserProvider } from "../../customers/MockedUserProvider";
|
import { MockedUserProvider } from "../../customers/MockedUserProvider";
|
||||||
import { getDiscountsProvidersWrapper } from "./utils";
|
import { getDiscountsProvidersWrapper } from "./utils";
|
||||||
|
|
||||||
|
const finalizeErrors: OrderErrorFragment[] = [
|
||||||
|
{
|
||||||
|
code: OrderErrorCode.BILLING_ADDRESS_NOT_SET,
|
||||||
|
field: "order",
|
||||||
|
addressType: null,
|
||||||
|
message: "Can't finalize draft with no billing address.",
|
||||||
|
orderLines: null,
|
||||||
|
__typename: "OrderError",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: OrderErrorCode.ORDER_NO_SHIPPING_ADDRESS,
|
||||||
|
field: "order",
|
||||||
|
addressType: null,
|
||||||
|
message: "Can't finalize draft with no shipping address.",
|
||||||
|
orderLines: null,
|
||||||
|
__typename: "OrderError",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: OrderErrorCode.SHIPPING_METHOD_REQUIRED,
|
||||||
|
field: "shipping",
|
||||||
|
addressType: null,
|
||||||
|
message: "Shipping method is required.",
|
||||||
|
orderLines: null,
|
||||||
|
__typename: "OrderError",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const order = draftOrder(placeholderImage);
|
const order = draftOrder(placeholderImage);
|
||||||
|
|
||||||
const props: Omit<OrderDraftPageProps, "classes"> = {
|
const props: Omit<OrderDraftPageProps, "classes"> = {
|
||||||
|
@ -30,7 +62,9 @@ const props: Omit<OrderDraftPageProps, "classes"> = {
|
||||||
onShippingAddressEdit: undefined,
|
onShippingAddressEdit: undefined,
|
||||||
onShippingMethodEdit: undefined,
|
onShippingMethodEdit: undefined,
|
||||||
order,
|
order,
|
||||||
|
channelUsabilityData,
|
||||||
saveButtonBarState: "default",
|
saveButtonBarState: "default",
|
||||||
|
errors: [],
|
||||||
users: clients,
|
users: clients,
|
||||||
usersLoading: false,
|
usersLoading: false,
|
||||||
};
|
};
|
||||||
|
@ -52,11 +86,19 @@ storiesOf("Views / Orders / Order draft", module)
|
||||||
.addDecorator(DiscountsDecorator)
|
.addDecorator(DiscountsDecorator)
|
||||||
.add("default", () => <OrderDraftPage {...props} />)
|
.add("default", () => <OrderDraftPage {...props} />)
|
||||||
.add("loading", () => (
|
.add("loading", () => (
|
||||||
<OrderDraftPage {...props} disabled={true} order={undefined} />
|
<OrderDraftPage
|
||||||
|
{...props}
|
||||||
|
disabled={true}
|
||||||
|
order={undefined}
|
||||||
|
channelUsabilityData={undefined}
|
||||||
|
/>
|
||||||
))
|
))
|
||||||
.add("without lines", () => (
|
.add("without lines", () => (
|
||||||
<OrderDraftPage {...props} order={{ ...order, lines: [] }} />
|
<OrderDraftPage {...props} order={{ ...order, lines: [] }} />
|
||||||
))
|
))
|
||||||
.add("no user permissions", () => (
|
.add("no user permissions", () => (
|
||||||
<OrderDraftPage {...props} customPermissions={[]} />
|
<OrderDraftPage {...props} customPermissions={[]} />
|
||||||
|
))
|
||||||
|
.add("with errors", () => (
|
||||||
|
<OrderDraftPage {...props} errors={finalizeErrors} />
|
||||||
));
|
));
|
||||||
|
|
|
@ -30,6 +30,7 @@ storiesOf("Orders / OrderFulfillmentCancelDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Graphql Error",
|
message: "Graphql Error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -29,6 +29,7 @@ storiesOf("Orders / OrderFulfillmentTrackingDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Graphql Error",
|
message: "Graphql Error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "OrderError",
|
__typename: "OrderError",
|
||||||
|
@ -36,6 +37,7 @@ storiesOf("Orders / OrderFulfillmentTrackingDialog", module)
|
||||||
field: "trackingNumber",
|
field: "trackingNumber",
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Tracking number field invalid",
|
message: "Tracking number field invalid",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -30,6 +30,7 @@ storiesOf("Orders / OrderMarkAsPaidDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Graphql error",
|
message: "Graphql error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -29,6 +29,7 @@ storiesOf("Orders / OrderPaymentDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Capture inactive payment",
|
message: "Capture inactive payment",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "OrderError",
|
__typename: "OrderError",
|
||||||
|
@ -36,6 +37,7 @@ storiesOf("Orders / OrderPaymentDialog", module)
|
||||||
field: "payment",
|
field: "payment",
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Payment field invalid",
|
message: "Payment field invalid",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,6 +28,7 @@ storiesOf("Orders / OrderPaymentVoidDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Void inactive payment Error",
|
message: "Void inactive payment Error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -37,6 +37,7 @@ storiesOf("Orders / OrderProductAddDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Graphql Error",
|
message: "Graphql Error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -32,6 +32,7 @@ storiesOf("Orders / OrderShippingMethodEditDialog", module)
|
||||||
field: "shippingMethod",
|
field: "shippingMethod",
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Shipping method not applicable",
|
message: "Shipping method not applicable",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: "OrderError",
|
__typename: "OrderError",
|
||||||
|
@ -39,6 +40,7 @@ storiesOf("Orders / OrderShippingMethodEditDialog", module)
|
||||||
field: null,
|
field: null,
|
||||||
addressType: null,
|
addressType: null,
|
||||||
message: "Graphql error",
|
message: "Graphql error",
|
||||||
|
orderLines: null,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in a new issue