Merge pull request #1066 from mirumee/SALEOR-2547/update-adding-draft-order-products-per-channel (#1070)

Update adding draft order products per channel
This commit is contained in:
mmarkusik 2021-04-20 16:06:39 +02:00 committed by GitHub
parent bddfa2c4af
commit 9d6cc99103
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 807 additions and 1677 deletions

View file

@ -3938,8 +3938,8 @@
"src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_2850255786": { "src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_2850255786": {
"string": "Search Products" "string": "Search Products"
}, },
"src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_353369701": { "src_dot_orders_dot_components_dot_OrderProductAddDialog_dot_3284796469": {
"string": "No products matching given query" "string": "No products available in order channel matching given query"
}, },
"src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_1134347598": { "src_dot_orders_dot_components_dot_OrderProductsCardElements_dot_1134347598": {
"context": "product price", "context": "product price",
@ -5310,6 +5310,10 @@
"context": "button", "context": "button",
"string": "Delete Variant" "string": "Delete Variant"
}, },
"src_dot_products_dot_components_dot_ProductVariantCreatePage_dot_pricingCardSubtitle": {
"context": "variant pricing section subtitle",
"string": "There is no channel to define prices for. You need to first add variant to channels to define prices."
},
"src_dot_products_dot_components_dot_ProductVariantCreatePage_dot_saveVariant": { "src_dot_products_dot_components_dot_ProductVariantCreatePage_dot_saveVariant": {
"context": "button", "context": "button",
"string": "Save variant" "string": "Save variant"
@ -5462,6 +5466,18 @@
"context": "variant name", "context": "variant name",
"string": "New Variant" "string": "New Variant"
}, },
"src_dot_products_dot_components_dot_ProductVariantPage_dot_itemSubtitleHidden": {
"context": "VariantDetailsChannelsAvailabilityCard item subtitle hidden",
"string": "Hidden"
},
"src_dot_products_dot_components_dot_ProductVariantPage_dot_itemSubtitlePublished": {
"context": "VariantDetailsChannelsAvailabilityCard item subtitle published",
"string": "Published since {publicationDate}"
},
"src_dot_products_dot_components_dot_ProductVariantPage_dot_noItemsAvailable": {
"context": "VariantDetailsChannelsAvailabilityCard no items available",
"string": "This variant is not available at any of the channels"
},
"src_dot_products_dot_components_dot_ProductVariantPage_dot_nonSelectionAttributes": { "src_dot_products_dot_components_dot_ProductVariantPage_dot_nonSelectionAttributes": {
"context": "attributes, section header", "context": "attributes, section header",
"string": "Variant Attributes" "string": "Variant Attributes"
@ -5470,6 +5486,14 @@
"context": "attributes, section header", "context": "attributes, section header",
"string": "Variant Selection Attributes" "string": "Variant Selection Attributes"
}, },
"src_dot_products_dot_components_dot_ProductVariantPage_dot_subtitle": {
"context": "VariantDetailsChannelsAvailabilityCard subtitle",
"string": "Available in {publishedInChannelsCount} out of {availableChannelsCount}"
},
"src_dot_products_dot_components_dot_ProductVariantPage_dot_title": {
"context": "VariantDetailsChannelsAvailabilityCard title",
"string": "Availability"
},
"src_dot_products_dot_components_dot_ProductVariantPrice_dot_1099355007": { "src_dot_products_dot_components_dot_ProductVariantPrice_dot_1099355007": {
"context": "product pricing, section header", "context": "product pricing, section header",
"string": "Pricing" "string": "Pricing"

View file

@ -272,6 +272,7 @@ export const fragmentOrderDetails = gql`
id id
name name
currencyCode currencyCode
slug
} }
isPaid isPaid
} }

View file

@ -298,6 +298,8 @@ export const fragmentVariant = gql`
url url
} }
channelListings { channelListings {
publicationDate
isPublished
channel { channel {
id id
name name

View file

@ -471,6 +471,7 @@ export interface OrderDetailsFragment_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDetailsFragment { export interface OrderDetailsFragment {

View file

@ -189,6 +189,8 @@ export interface ProductVariant_product_channelListings_pricing {
export interface ProductVariant_product_channelListings { export interface ProductVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: ProductVariant_product_channelListings_channel; channel: ProductVariant_product_channelListings_channel;
pricing: ProductVariant_product_channelListings_pricing | null; pricing: ProductVariant_product_channelListings_pricing | null;
} }

View file

@ -363,7 +363,7 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={4}> <TableCell colSpan={4}>
<FormattedMessage defaultMessage="No products matching given query" /> <FormattedMessage defaultMessage="No products available in order channel matching given query" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )

View file

@ -814,6 +814,7 @@ export const order = (placeholder: string): OrderDetails_order => ({
canFinalize: true, canFinalize: true,
channel: { channel: {
__typename: "Channel", __typename: "Channel",
slug: "channel-default",
currencyCode: "USD", currencyCode: "USD",
id: "123454", id: "123454",
isActive: true, isActive: true,
@ -1371,6 +1372,7 @@ export const draftOrder = (placeholder: string): OrderDetails_order => ({
canFinalize: true, canFinalize: true,
channel: { channel: {
__typename: "Channel", __typename: "Channel",
slug: "channel-default",
currencyCode: "USD", currencyCode: "USD",
id: "123454", id: "123454",
isActive: true, isActive: true,

View file

@ -164,8 +164,18 @@ export const useOrderQuery = makeQuery<OrderDetails, OrderDetailsVariables>(
); );
export const searchOrderVariant = gql` export const searchOrderVariant = gql`
query SearchOrderVariant($first: Int!, $query: String!, $after: String) { query SearchOrderVariant(
search: products(first: $first, after: $after, filter: { search: $query }) { $channel: String!
$first: Int!
$query: String!
$after: String
) {
search: products(
first: $first
after: $after
filter: { search: $query }
channel: $channel
) {
edges { edges {
node { node {
id id

View file

@ -479,6 +479,7 @@ export interface FulfillOrder_orderFulfill_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface FulfillOrder_orderFulfill_order { export interface FulfillOrder_orderFulfill_order {

View file

@ -477,6 +477,7 @@ export interface OrderCancel_orderCancel_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderCancel_orderCancel_order { export interface OrderCancel_orderCancel_order {

View file

@ -477,6 +477,7 @@ export interface OrderCapture_orderCapture_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderCapture_orderCapture_order { export interface OrderCapture_orderCapture_order {

View file

@ -477,6 +477,7 @@ export interface OrderConfirm_orderConfirm_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderConfirm_orderConfirm_order { export interface OrderConfirm_orderConfirm_order {

View file

@ -471,6 +471,7 @@ export interface OrderDetails_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDetails_order { export interface OrderDetails_order {

View file

@ -477,6 +477,7 @@ export interface OrderDiscountAdd_orderDiscountAdd_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDiscountAdd_orderDiscountAdd_order { export interface OrderDiscountAdd_orderDiscountAdd_order {

View file

@ -477,6 +477,7 @@ export interface OrderDiscountDelete_orderDiscountDelete_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDiscountDelete_orderDiscountDelete_order { export interface OrderDiscountDelete_orderDiscountDelete_order {

View file

@ -477,6 +477,7 @@ export interface OrderDiscountUpdate_orderDiscountUpdate_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDiscountUpdate_orderDiscountUpdate_order { export interface OrderDiscountUpdate_orderDiscountUpdate_order {

View file

@ -477,6 +477,7 @@ export interface OrderDraftCancel_draftOrderDelete_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDraftCancel_draftOrderDelete_order { export interface OrderDraftCancel_draftOrderDelete_order {

View file

@ -477,6 +477,7 @@ export interface OrderDraftFinalize_draftOrderComplete_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDraftFinalize_draftOrderComplete_order { export interface OrderDraftFinalize_draftOrderComplete_order {

View file

@ -477,6 +477,7 @@ export interface OrderDraftUpdate_draftOrderUpdate_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderDraftUpdate_draftOrderUpdate_order { export interface OrderDraftUpdate_draftOrderUpdate_order {

View file

@ -477,6 +477,7 @@ export interface OrderFulfillmentCancel_orderFulfillmentCancel_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderFulfillmentCancel_orderFulfillmentCancel_order { export interface OrderFulfillmentCancel_orderFulfillmentCancel_order {

View file

@ -572,6 +572,7 @@ export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_o
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_order { export interface OrderFulfillmentRefundProducts_orderFulfillmentRefundProducts_order {

View file

@ -477,6 +477,7 @@ export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_o
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_order { export interface OrderFulfillmentUpdateTracking_orderFulfillmentUpdateTracking_order {

View file

@ -477,6 +477,7 @@ export interface OrderLineDelete_orderLineDelete_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderLineDelete_orderLineDelete_order { export interface OrderLineDelete_orderLineDelete_order {

View file

@ -477,6 +477,7 @@ export interface OrderLineDiscountRemove_orderLineDiscountRemove_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderLineDiscountRemove_orderLineDiscountRemove_order { export interface OrderLineDiscountRemove_orderLineDiscountRemove_order {

View file

@ -477,6 +477,7 @@ export interface OrderLineDiscountUpdate_orderLineDiscountUpdate_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderLineDiscountUpdate_orderLineDiscountUpdate_order { export interface OrderLineDiscountUpdate_orderLineDiscountUpdate_order {

View file

@ -477,6 +477,7 @@ export interface OrderLineUpdate_orderLineUpdate_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderLineUpdate_orderLineUpdate_order { export interface OrderLineUpdate_orderLineUpdate_order {

View file

@ -477,6 +477,7 @@ export interface OrderLinesAdd_orderLinesCreate_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderLinesAdd_orderLinesCreate_order { export interface OrderLinesAdd_orderLinesCreate_order {

View file

@ -477,6 +477,7 @@ export interface OrderMarkAsPaid_orderMarkAsPaid_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderMarkAsPaid_orderMarkAsPaid_order { export interface OrderMarkAsPaid_orderMarkAsPaid_order {

View file

@ -477,6 +477,7 @@ export interface OrderRefund_orderRefund_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderRefund_orderRefund_order { export interface OrderRefund_orderRefund_order {

View file

@ -485,6 +485,7 @@ export interface OrderShippingMethodUpdate_orderUpdateShipping_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderShippingMethodUpdate_orderUpdateShipping_order { export interface OrderShippingMethodUpdate_orderUpdateShipping_order {

View file

@ -477,6 +477,7 @@ export interface OrderUpdate_orderUpdate_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderUpdate_orderUpdate_order { export interface OrderUpdate_orderUpdate_order {

View file

@ -477,6 +477,7 @@ export interface OrderVoid_orderVoid_order_channel {
id: string; id: string;
name: string; name: string;
currencyCode: string; currencyCode: string;
slug: string;
} }
export interface OrderVoid_orderVoid_order { export interface OrderVoid_orderVoid_order {

View file

@ -72,6 +72,7 @@ export interface SearchOrderVariant {
} }
export interface SearchOrderVariantVariables { export interface SearchOrderVariantVariables {
channel: string;
first: number; first: number;
query: string; query: string;
after?: string | null; after?: string | null;

View file

@ -61,7 +61,7 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
search: variantSearch, search: variantSearch,
result: variantSearchOpts result: variantSearchOpts
} = useOrderVariantSearch({ } = useOrderVariantSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA variables: { ...DEFAULT_INITIAL_SEARCH_DATA, channel: order.channel.slug }
}); });
const { const {

View file

@ -89,7 +89,7 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
search: variantSearch, search: variantSearch,
result: variantSearchOpts result: variantSearchOpts
} = useOrderVariantSearch({ } = useOrderVariantSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA variables: { ...DEFAULT_INITIAL_SEARCH_DATA, channel: order.channel.slug }
}); });
const warehouses = useWarehouseList({ const warehouses = useWarehouseList({
displayLoader: true, displayLoader: true,

View file

@ -16,7 +16,6 @@ import Grid from "@saleor/components/Grid";
import Metadata from "@saleor/components/Metadata"; import Metadata from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { ProductChannelListingErrorFragment } from "@saleor/fragments/types/ProductChannelListingErrorFragment";
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts"; import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
@ -51,12 +50,16 @@ const messages = defineMessages({
saveVariant: { saveVariant: {
defaultMessage: "Save variant", defaultMessage: "Save variant",
description: "button" description: "button"
},
pricingCardSubtitle: {
defaultMessage:
"There is no channel to define prices for. You need to first add variant to channels to define prices.",
description: "variant pricing section subtitle"
} }
}); });
interface ProductVariantCreatePageProps { interface ProductVariantCreatePageProps {
channels: ChannelPriceData[]; channels: ChannelPriceData[];
channelErrors: ProductChannelListingErrorFragment[] | undefined;
disabled: boolean; disabled: boolean;
errors: ProductErrorWithAttributesFragment[]; errors: ProductErrorWithAttributesFragment[];
header: string; header: string;
@ -82,7 +85,6 @@ interface ProductVariantCreatePageProps {
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
channels, channels,
channelErrors = [],
disabled, disabled,
errors, errors,
header, header,
@ -209,15 +211,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
/> />
<CardSpacer /> <CardSpacer />
<ProductVariantPrice <ProductVariantPrice
ProductVariantChannelListings={data.channelListings.map( disabledMessage={messages.pricingCardSubtitle}
channel => ({
...channel.data,
...channel.value
})
)}
errors={channelErrors}
loading={disabled}
onChange={handlers.changeChannels}
/> />
<CardSpacer /> <CardSpacer />
<ProductStocks <ProductStocks

View file

@ -8,7 +8,7 @@ import {
createFetchMoreReferencesHandler, createFetchMoreReferencesHandler,
createFetchReferencesHandler createFetchReferencesHandler
} from "@saleor/attributes/utils/handlers"; } from "@saleor/attributes/utils/handlers";
import { ChannelPriceData, IChannelPriceArgs } from "@saleor/channels/utils"; import { ChannelPriceData } from "@saleor/channels/utils";
import { AttributeInput } from "@saleor/components/Attributes"; import { AttributeInput } from "@saleor/components/Attributes";
import { MetadataFormData } from "@saleor/components/Metadata"; import { MetadataFormData } from "@saleor/components/Metadata";
import useForm, { FormChange } from "@saleor/hooks/useForm"; import useForm, { FormChange } from "@saleor/hooks/useForm";
@ -18,11 +18,6 @@ import useFormset, {
} from "@saleor/hooks/useFormset"; } from "@saleor/hooks/useFormset";
import { ProductVariantCreateData_product } from "@saleor/products/types/ProductVariantCreateData"; import { ProductVariantCreateData_product } from "@saleor/products/types/ProductVariantCreateData";
import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data";
import { getChannelsInput } from "@saleor/products/utils/handlers";
import {
validateCostPrice,
validatePrice
} from "@saleor/products/utils/validation";
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts"; import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
@ -38,7 +33,6 @@ export interface ProductVariantCreateFormData extends MetadataFormData {
weight: string; weight: string;
} }
export interface ProductVariantCreateData extends ProductVariantCreateFormData { export interface ProductVariantCreateData extends ProductVariantCreateFormData {
channelListings: FormsetData<ChannelPriceData, IChannelPriceArgs>;
attributes: AttributeInput[]; attributes: AttributeInput[];
attributesWithNewFileValue: FormsetData<null, File>; attributesWithNewFileValue: FormsetData<null, File>;
stocks: ProductStockInput[]; stocks: ProductStockInput[];
@ -58,10 +52,7 @@ export interface UseProductVariantCreateFormOpts {
export interface ProductVariantCreateHandlers export interface ProductVariantCreateHandlers
extends Record< extends Record<
| "changeStock" "changeStock" | "selectAttribute" | "selectAttributeMultiple",
| "selectAttribute"
| "selectAttributeMultiple"
| "changeChannels",
FormsetChange FormsetChange
>, >,
Record<"selectAttributeReference", FormsetChange<string[]>>, Record<"selectAttributeReference", FormsetChange<string[]>>,
@ -107,13 +98,11 @@ function useProductVariantCreateForm(
const triggerChange = () => setChanged(true); const triggerChange = () => setChanged(true);
const attributeInput = getVariantAttributeInputFromProduct(product); const attributeInput = getVariantAttributeInputFromProduct(product);
const channelsInput = getChannelsInput(opts.currentChannels);
const form = useForm(initial); const form = useForm(initial);
const attributes = useFormset(attributeInput); const attributes = useFormset(attributeInput);
const attributesWithNewFileValue = useFormset<null, File>([]); const attributesWithNewFileValue = useFormset<null, File>([]);
const stocks = useFormset<ProductStockFormsetData, string>([]); const stocks = useFormset<ProductStockFormsetData, string>([]);
const channels = useFormset(channelsInput);
const { const {
makeChangeHandler: makeMetadataChangeHandler makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger(); } = useMetadataChangeTrigger();
@ -179,16 +168,6 @@ function useProductVariantCreateForm(
triggerChange(); triggerChange();
stocks.remove(id); stocks.remove(id);
}; };
const handleChannelChange: FormsetChange = (id, value) => {
channels.change(id, value);
triggerChange();
};
const disabled = channels?.data.some(
channelData =>
validatePrice(channelData.value.price) ||
validateCostPrice(channelData.value.costPrice)
);
const data: ProductVariantCreateData = { const data: ProductVariantCreateData = {
...form.data, ...form.data,
@ -199,7 +178,6 @@ function useProductVariantCreateForm(
opts.referenceProducts opts.referenceProducts
), ),
attributesWithNewFileValue: attributesWithNewFileValue.data, attributesWithNewFileValue: attributesWithNewFileValue.data,
channelListings: channels.data,
stocks: stocks.data stocks: stocks.data
}; };
@ -208,10 +186,9 @@ function useProductVariantCreateForm(
return { return {
change: handleChange, change: handleChange,
data, data,
disabled, disabled: false,
handlers: { handlers: {
addStock: handleStockAdd, addStock: handleStockAdd,
changeChannels: handleChannelChange,
changeMetadata, changeMetadata,
changeStock: handleStockChange, changeStock: handleStockChange,
deleteStock: handleStockDelete, deleteStock: handleStockDelete,

View file

@ -41,6 +41,7 @@ import ProductVariantUpdateForm, {
ProductVariantUpdateHandlers, ProductVariantUpdateHandlers,
ProductVariantUpdateSubmitData ProductVariantUpdateSubmitData
} from "./form"; } from "./form";
import VariantDetailsChannelsAvailabilityCard from "./VariantDetailsChannelsAvailabilityCard";
const messages = defineMessages({ const messages = defineMessages({
nonSelectionAttributes: { nonSelectionAttributes: {
@ -217,6 +218,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
/> />
</div> </div>
<div> <div>
<VariantDetailsChannelsAvailabilityCard variant={variant} />
<Attributes <Attributes
title={intl.formatMessage(messages.nonSelectionAttributes)} title={intl.formatMessage(messages.nonSelectionAttributes)}
attributes={data.attributes.filter( attributes={data.attributes.filter(
@ -263,6 +265,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
/> />
<CardSpacer /> <CardSpacer />
<ProductVariantPrice <ProductVariantPrice
disabled={!variant}
ProductVariantChannelListings={data.channelListings.map( ProductVariantChannelListings={data.channelListings.map(
channel => ({ channel => ({
...channel.data, ...channel.data,

View file

@ -0,0 +1,25 @@
import { Card } from "@material-ui/core";
import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import React from "react";
import { FormattedMessage } from "react-intl";
import { variantDetailsChannelsAvailabilityCardMessages as messages } from "../messages";
interface VariantDetailsChannelsAvailabilityCardContainerProps {
children: React.ReactNode;
}
const VariantDetailsChannelsAvailabilityCardContainer: React.FC<VariantDetailsChannelsAvailabilityCardContainerProps> = ({
children
}) => (
<>
<Card>
<CardTitle title={<FormattedMessage {...messages.title} />} />
{children}
</Card>
<CardSpacer />
</>
);
export default VariantDetailsChannelsAvailabilityCardContainer;

View file

@ -0,0 +1,180 @@
import {
CardContent,
Divider,
ExpansionPanel,
ExpansionPanelSummary,
makeStyles,
Typography
} from "@material-ui/core";
import Skeleton from "@saleor/components/Skeleton";
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
import useDateLocalize from "@saleor/hooks/useDateLocalize";
import IconChevronDown from "@saleor/icons/ChevronDown";
import React from "react";
import { useIntl } from "react-intl";
import { variantDetailsChannelsAvailabilityCardMessages as messages } from "../messages";
import CardContainer from "./VariantDetailsChannelsAvailabilityCardContainer";
const useExpanderStyles = makeStyles(
() => ({
expanded: {},
root: {
boxShadow: "none",
margin: 0,
padding: 0,
"&:before": {
content: "none"
},
"&$expanded": {
margin: 0,
border: "none"
}
}
}),
{ name: "VariantDetailsChannelsAvailabilityCardExpander" }
);
const useSummaryStyles = makeStyles(
theme => ({
expanded: {},
root: {
width: "100%",
border: "none",
margin: 0,
padding: 0,
minHeight: 0,
paddingTop: theme.spacing(2),
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(5.5),
paddingBottom: theme.spacing(2),
"&$expanded": {
minHeight: 0
}
},
content: {
margin: 0,
"&$expanded": {
margin: 0
}
}
}),
{ name: "VariantDetailsChannelsAvailabilityCardExpanderSummary" }
);
interface VariantDetailsChannelsAvailabilityCardProps {
variant: ProductVariant;
}
const VariantDetailsChannelsAvailabilityCard: React.FC<VariantDetailsChannelsAvailabilityCardProps> = ({
variant
}) => {
const expanderClasses = useExpanderStyles({});
const summaryClasses = useSummaryStyles({});
const localizeDate = useDateLocalize();
const intl = useIntl();
const getProductChannelListingByChannelId = (channelId: string) =>
variant?.product.channelListings.find(
({ channel }) => channel.id === channelId
);
const getItemSubtitle = (channelId: string) => {
const {
isPublished,
publicationDate
} = getProductChannelListingByChannelId(channelId);
if (!isPublished) {
return intl.formatMessage(messages.itemSubtitleHidden);
}
return intl.formatMessage(messages.itemSubtitlePublished, {
publicationDate: localizeDate(publicationDate)
});
};
if (!variant) {
return (
<CardContainer>
<CardContent>
<Skeleton />
</CardContent>
</CardContainer>
);
}
const { channelListings } = variant;
const isAvailableInAnyChannels = !!channelListings.length;
const variantChannelListingsChannelsIds = channelListings.map(
({ channel: { id } }) => id
);
const allAvailableChannelsListings = variant.product.channelListings.filter(
({ channel }) => variantChannelListingsChannelsIds.includes(channel.id)
);
const publishedInChannelsListings = allAvailableChannelsListings.filter(
({ isPublished }) => isPublished
);
if (!isAvailableInAnyChannels) {
return (
<CardContainer>
<CardContent>
<Typography variant="caption">
{intl.formatMessage(messages.noItemsAvailable)}
</Typography>
</CardContent>
</CardContainer>
);
}
return (
<CardContainer>
<ExpansionPanel classes={expanderClasses}>
<ExpansionPanelSummary
expandIcon={<IconChevronDown />}
classes={summaryClasses}
data-test-id="channels-variant-availability-summary"
>
<>
<Typography variant="caption">
{intl.formatMessage(messages.subtitle, {
publishedInChannelsCount: publishedInChannelsListings.length,
availableChannelsCount: allAvailableChannelsListings.length
})}
</Typography>
</>
</ExpansionPanelSummary>
{channelListings.map(({ channel }) => (
<>
<Divider />
<CardContent>
<Typography
data-test-id={`channels-variant-availability-item-title-${channel.id}`}
>
{channel.name}
</Typography>
<Typography
variant="caption"
data-test-id={`channels-variant-availability-item-subtitle-${channel.id}`}
>
{getItemSubtitle(channel.id)}
</Typography>
</CardContent>
</>
))}
</ExpansionPanel>
</CardContainer>
);
};
export default VariantDetailsChannelsAvailabilityCard;

View file

@ -0,0 +1,26 @@
import { defineMessages } from "react-intl";
export const variantDetailsChannelsAvailabilityCardMessages = defineMessages({
title: {
defaultMessage: "Availability",
description: "VariantDetailsChannelsAvailabilityCard title"
},
subtitle: {
defaultMessage:
"Available in {publishedInChannelsCount} out of {availableChannelsCount}",
description: "VariantDetailsChannelsAvailabilityCard subtitle"
},
itemSubtitlePublished: {
defaultMessage: "Published since {publicationDate}",
description:
"VariantDetailsChannelsAvailabilityCard item subtitle published"
},
itemSubtitleHidden: {
defaultMessage: "Hidden",
description: "VariantDetailsChannelsAvailabilityCard item subtitle hidden"
},
noItemsAvailable: {
defaultMessage: "This variant is not available at any of the channels",
description: "VariantDetailsChannelsAvailabilityCard no items available"
}
});

View file

@ -21,7 +21,7 @@ import {
} from "@saleor/utils/errors"; } from "@saleor/utils/errors";
import getProductErrorMessage from "@saleor/utils/errors/product"; import getProductErrorMessage from "@saleor/utils/errors/product";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, MessageDescriptor, useIntl } from "react-intl";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -62,25 +62,53 @@ const useStyles = makeStyles(
); );
interface ProductVariantPriceProps { interface ProductVariantPriceProps {
ProductVariantChannelListings: ChannelData[]; ProductVariantChannelListings?: ChannelData[];
errors: ProductChannelListingErrorFragment[]; errors?: ProductChannelListingErrorFragment[];
loading?: boolean; loading?: boolean;
onChange: (id: string, data: ChannelPriceArgs) => void; disabled?: boolean;
onChange?: (id: string, data: ChannelPriceArgs) => void;
disabledMessage?: MessageDescriptor;
} }
const numberOfColumns = 2; const numberOfColumns = 2;
const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => { const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
const { const {
disabled = false,
errors = [], errors = [],
ProductVariantChannelListings, ProductVariantChannelListings = [],
loading, loading,
onChange onChange,
disabledMessage
} = props; } = props;
const classes = useStyles(props); const classes = useStyles(props);
const intl = useIntl(); const intl = useIntl();
const formErrors = getFormChannelErrors(["price", "costPrice"], errors); const formErrors = getFormChannelErrors(["price", "costPrice"], errors);
if (disabled || !ProductVariantChannelListings.length) {
return (
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Pricing",
description: "product pricing, section header"
})}
/>
<CardContent>
<Typography variant="caption">
{intl.formatMessage(
disabledMessage || {
defaultMessage: "There is no channel to define prices for",
description: "variant pricing section subtitle",
id: "product variant pricing card disabled subtitle"
}
)}
</Typography>
</CardContent>
</Card>
);
}
return ( return (
<Card> <Card>
<CardTitle <CardTitle

View file

@ -2817,6 +2817,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
channelListings: [ channelListings: [
{ {
__typename: "ProductChannelListing", __typename: "ProductChannelListing",
isPublished: false,
publicationDate: null,
channel: { channel: {
__typename: "Channel", __typename: "Channel",
currencyCode: "USD", currencyCode: "USD",
@ -2848,6 +2850,8 @@ export const variant = (placeholderImage: string): ProductVariant => ({
}, },
{ {
__typename: "ProductChannelListing", __typename: "ProductChannelListing",
isPublished: true,
publicationDate: "2022-01-21",
channel: { channel: {
__typename: "Channel", __typename: "Channel",
currencyCode: "USD", currencyCode: "USD",

View file

@ -189,6 +189,8 @@ export interface ProductVariantChannelListingUpdate_productVariantChannelListing
export interface ProductVariantChannelListingUpdate_productVariantChannelListingUpdate_variant_product_channelListings { export interface ProductVariantChannelListingUpdate_productVariantChannelListingUpdate_variant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: ProductVariantChannelListingUpdate_productVariantChannelListingUpdate_variant_product_channelListings_channel; channel: ProductVariantChannelListingUpdate_productVariantChannelListingUpdate_variant_product_channelListings_channel;
pricing: ProductVariantChannelListingUpdate_productVariantChannelListingUpdate_variant_product_channelListings_pricing | null; pricing: ProductVariantChannelListingUpdate_productVariantChannelListingUpdate_variant_product_channelListings_pricing | null;
} }

View file

@ -189,6 +189,8 @@ export interface ProductVariantDetails_productVariant_product_channelListings_pr
export interface ProductVariantDetails_productVariant_product_channelListings { export interface ProductVariantDetails_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: ProductVariantDetails_productVariant_product_channelListings_channel; channel: ProductVariantDetails_productVariant_product_channelListings_channel;
pricing: ProductVariantDetails_productVariant_product_channelListings_pricing | null; pricing: ProductVariantDetails_productVariant_product_channelListings_pricing | null;
} }

View file

@ -480,6 +480,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_product
export interface SimpleProductUpdate_productVariantUpdate_productVariant_product_channelListings { export interface SimpleProductUpdate_productVariantUpdate_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: SimpleProductUpdate_productVariantUpdate_productVariant_product_channelListings_channel; channel: SimpleProductUpdate_productVariantUpdate_productVariant_product_channelListings_channel;
pricing: SimpleProductUpdate_productVariantUpdate_productVariant_product_channelListings_pricing | null; pricing: SimpleProductUpdate_productVariantUpdate_productVariant_product_channelListings_pricing | null;
} }
@ -767,6 +769,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_p
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_product_channelListings { export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: SimpleProductUpdate_productVariantStocksCreate_productVariant_product_channelListings_channel; channel: SimpleProductUpdate_productVariantStocksCreate_productVariant_product_channelListings_channel;
pricing: SimpleProductUpdate_productVariantStocksCreate_productVariant_product_channelListings_pricing | null; pricing: SimpleProductUpdate_productVariantStocksCreate_productVariant_product_channelListings_pricing | null;
} }
@ -1053,6 +1057,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_p
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_product_channelListings { export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: SimpleProductUpdate_productVariantStocksDelete_productVariant_product_channelListings_channel; channel: SimpleProductUpdate_productVariantStocksDelete_productVariant_product_channelListings_channel;
pricing: SimpleProductUpdate_productVariantStocksDelete_productVariant_product_channelListings_pricing | null; pricing: SimpleProductUpdate_productVariantStocksDelete_productVariant_product_channelListings_pricing | null;
} }
@ -1340,6 +1346,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_p
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_product_channelListings { export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: SimpleProductUpdate_productVariantStocksUpdate_productVariant_product_channelListings_channel; channel: SimpleProductUpdate_productVariantStocksUpdate_productVariant_product_channelListings_channel;
pricing: SimpleProductUpdate_productVariantStocksUpdate_productVariant_product_channelListings_pricing | null; pricing: SimpleProductUpdate_productVariantStocksUpdate_productVariant_product_channelListings_pricing | null;
} }

View file

@ -196,6 +196,8 @@ export interface VariantCreate_productVariantCreate_productVariant_product_chann
export interface VariantCreate_productVariantCreate_productVariant_product_channelListings { export interface VariantCreate_productVariantCreate_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: VariantCreate_productVariantCreate_productVariant_product_channelListings_channel; channel: VariantCreate_productVariantCreate_productVariant_product_channelListings_channel;
pricing: VariantCreate_productVariantCreate_productVariant_product_channelListings_pricing | null; pricing: VariantCreate_productVariantCreate_productVariant_product_channelListings_pricing | null;
} }

View file

@ -195,6 +195,8 @@ export interface VariantMediaAssign_variantMediaAssign_productVariant_product_ch
export interface VariantMediaAssign_variantMediaAssign_productVariant_product_channelListings { export interface VariantMediaAssign_variantMediaAssign_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: VariantMediaAssign_variantMediaAssign_productVariant_product_channelListings_channel; channel: VariantMediaAssign_variantMediaAssign_productVariant_product_channelListings_channel;
pricing: VariantMediaAssign_variantMediaAssign_productVariant_product_channelListings_pricing | null; pricing: VariantMediaAssign_variantMediaAssign_productVariant_product_channelListings_pricing | null;
} }

View file

@ -195,6 +195,8 @@ export interface VariantMediaUnassign_variantMediaUnassign_productVariant_produc
export interface VariantMediaUnassign_variantMediaUnassign_productVariant_product_channelListings { export interface VariantMediaUnassign_variantMediaUnassign_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: VariantMediaUnassign_variantMediaUnassign_productVariant_product_channelListings_channel; channel: VariantMediaUnassign_variantMediaUnassign_productVariant_product_channelListings_channel;
pricing: VariantMediaUnassign_variantMediaUnassign_productVariant_product_channelListings_pricing | null; pricing: VariantMediaUnassign_variantMediaUnassign_productVariant_product_channelListings_pricing | null;
} }

View file

@ -196,6 +196,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_product_chann
export interface VariantUpdate_productVariantUpdate_productVariant_product_channelListings { export interface VariantUpdate_productVariantUpdate_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: VariantUpdate_productVariantUpdate_productVariant_product_channelListings_channel; channel: VariantUpdate_productVariantUpdate_productVariant_product_channelListings_channel;
pricing: VariantUpdate_productVariantUpdate_productVariant_product_channelListings_pricing | null; pricing: VariantUpdate_productVariantUpdate_productVariant_product_channelListings_pricing | null;
} }
@ -483,6 +485,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_product
export interface VariantUpdate_productVariantStocksUpdate_productVariant_product_channelListings { export interface VariantUpdate_productVariantStocksUpdate_productVariant_product_channelListings {
__typename: "ProductChannelListing"; __typename: "ProductChannelListing";
publicationDate: any | null;
isPublished: boolean;
channel: VariantUpdate_productVariantStocksUpdate_productVariant_product_channelListings_channel; channel: VariantUpdate_productVariantStocksUpdate_productVariant_product_channelListings_channel;
pricing: VariantUpdate_productVariantStocksUpdate_productVariant_product_channelListings_pricing | null; pricing: VariantUpdate_productVariantStocksUpdate_productVariant_product_channelListings_pricing | null;
} }

View file

@ -10,11 +10,7 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { useFileUploadMutation } from "@saleor/files/mutations"; import { useFileUploadMutation } from "@saleor/files/mutations";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl";
import { useProductVariantChannelListingUpdate } from "@saleor/products/mutations";
import { ProductVariantChannelListingUpdate } from "@saleor/products/types/ProductVariantChannelListingUpdate";
import usePageSearch from "@saleor/searches/usePageSearch"; import usePageSearch from "@saleor/searches/usePageSearch";
import useProductSearch from "@saleor/searches/useProductSearch"; import useProductSearch from "@saleor/searches/useProductSearch";
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler"; import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
@ -54,7 +50,6 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
params params
}) => { }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier();
const shop = useShop(); const shop = useShop();
const intl = useIntl(); const intl = useIntl();
const warehouses = useWarehouseList({ const warehouses = useWarehouseList({
@ -63,20 +58,6 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
first: 50 first: 50
} }
}); });
const handleCreateSuccess = (data: ProductVariantChannelListingUpdate) => {
if (data.productVariantChannelListingUpdate.errors.length === 0) {
notify({
status: "success",
text: intl.formatMessage(commonMessages.savedChanges)
});
navigate(
productVariantEditUrl(
productId,
data.productVariantChannelListingUpdate.variant.id
)
);
}
};
const { data, loading: productLoading } = useProductVariantCreateQuery({ const { data, loading: productLoading } = useProductVariantCreateQuery({
displayLoader: true, displayLoader: true,
@ -85,13 +66,6 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
const [uploadFile, uploadFileOpts] = useFileUploadMutation({}); const [uploadFile, uploadFileOpts] = useFileUploadMutation({});
const [
updateChannels,
updateChannelsOpts
] = useProductVariantChannelListingUpdate({
onCompleted: handleCreateSuccess
});
const product = data?.product; const product = data?.product;
const channels: ChannelPriceData[] = product?.channelListings.map( const channels: ChannelPriceData[] = product?.channelListings.map(
@ -104,21 +78,6 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
}) })
); );
const handleSubmitChannels = (
data: ProductVariantCreateData,
variantId: string
) =>
updateChannels({
variables: {
id: variantId,
input: data.channelListings.map(listing => ({
channelId: listing.id,
costPrice: listing.value.costPrice || null,
price: listing.value.price
}))
}
});
const [variantCreate, variantCreateResult] = useVariantCreateMutation({}); const [variantCreate, variantCreateResult] = useVariantCreateMutation({});
const [updateMetadata] = useMetadataUpdate({}); const [updateMetadata] = useMetadataUpdate({});
@ -170,9 +129,6 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
} }
}); });
const id = result.data?.productVariantCreate?.productVariant?.id; const id = result.data?.productVariantCreate?.productVariant?.id;
if (id) {
await handleSubmitChannels(formData, id);
}
return id || null; return id || null;
}; };
@ -234,9 +190,6 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
/> />
<ProductVariantCreatePage <ProductVariantCreatePage
channels={channels} channels={channels}
channelErrors={
updateChannelsOpts?.data?.productVariantChannelListingUpdate?.errors
}
disabled={disableForm} disabled={disableForm}
errors={variantCreateResult.data?.productVariantCreate.errors || []} errors={variantCreateResult.data?.productVariantCreate.errors || []}
header={intl.formatMessage({ header={intl.formatMessage({

File diff suppressed because it is too large Load diff

View file

@ -22,7 +22,6 @@ storiesOf("Views / Products / Create product variant", module)
.add("default", () => ( .add("default", () => (
<ProductVariantCreatePage <ProductVariantCreatePage
channels={channels} channels={channels}
channelErrors={[]}
weightUnit="kg" weightUnit="kg"
disabled={false} disabled={false}
errors={[]} errors={[]}
@ -44,7 +43,6 @@ storiesOf("Views / Products / Create product variant", module)
.add("with errors", () => ( .add("with errors", () => (
<ProductVariantCreatePage <ProductVariantCreatePage
channels={channels} channels={channels}
channelErrors={[]}
weightUnit="kg" weightUnit="kg"
disabled={false} disabled={false}
errors={[ errors={[
@ -85,7 +83,6 @@ storiesOf("Views / Products / Create product variant", module)
.add("when loading data", () => ( .add("when loading data", () => (
<ProductVariantCreatePage <ProductVariantCreatePage
channels={channels} channels={channels}
channelErrors={[]}
weightUnit="kg" weightUnit="kg"
disabled={true} disabled={true}
errors={[]} errors={[]}
@ -107,7 +104,6 @@ storiesOf("Views / Products / Create product variant", module)
.add("add first variant", () => ( .add("add first variant", () => (
<ProductVariantCreatePage <ProductVariantCreatePage
channels={channels} channels={channels}
channelErrors={[]}
weightUnit="kg" weightUnit="kg"
disabled={false} disabled={false}
errors={[]} errors={[]}
@ -132,7 +128,6 @@ storiesOf("Views / Products / Create product variant", module)
.add("no warehouses", () => ( .add("no warehouses", () => (
<ProductVariantCreatePage <ProductVariantCreatePage
channels={channels} channels={channels}
channelErrors={[]}
weightUnit="kg" weightUnit="kg"
disabled={false} disabled={false}
errors={[]} errors={[]}