Fix variant creator (#2085)

* Set channels for products without any variants

* Fix duplicated_input_item error

* Persist skus between steps

* Cleanup code

* Fix types
This commit is contained in:
Dominik Żegleń 2022-06-08 13:27:33 +02:00 committed by GitHub
parent 056b3501eb
commit 014ae0f362
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 355 additions and 288 deletions

View file

@ -1,4 +1,3 @@
import CannotDefineChannelsAvailabilityCard from "@saleor/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard";
import { ChannelData } from "@saleor/channels/utils"; import { ChannelData } from "@saleor/channels/utils";
import ChannelAvailabilityItemContent from "@saleor/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent"; import ChannelAvailabilityItemContent from "@saleor/components/ChannelsAvailabilityCard/Channel/ChannelAvailabilityItemContent";
import ChannelsAvailabilityCardWrapper, { import ChannelsAvailabilityCardWrapper, {
@ -66,10 +65,6 @@ const ChannelsWithVariantsAvailabilityCard: React.FC<ChannelsWithVariantsAvailab
channelsWithVariantsData channelsWithVariantsData
); );
if (!variants?.length) {
return <CannotDefineChannelsAvailabilityCard />;
}
return ( return (
<ChannelsAvailabilityCardWrapper <ChannelsAvailabilityCardWrapper
managePermissions={[PermissionEnum.MANAGE_PRODUCTS]} managePermissions={[PermissionEnum.MANAGE_PRODUCTS]}

View file

@ -151,7 +151,6 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
}} }}
managePermissions={[PermissionEnum.MANAGE_PRODUCTS]} managePermissions={[PermissionEnum.MANAGE_PRODUCTS]}
errors={channelsErrors} errors={channelsErrors}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={channelsCount} allChannelsCount={channelsCount}
channels={data.channelListings} channels={data.channelListings}
disabled={disabled} disabled={disabled}

View file

@ -141,7 +141,6 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
}) })
}} }}
errors={channelsErrors} errors={channelsErrors}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={channelsCount} allChannelsCount={channelsCount}
channels={data.channelListings} channels={data.channelListings}
disabled={disabled} disabled={disabled}

View file

@ -38,8 +38,7 @@ const props: ChannelsAvailabilityCardProps = {
})), })),
errors: [], errors: [],
onChange: () => undefined, onChange: () => undefined,
openModal: () => undefined, openModal: () => undefined
selectedChannelsCount: 3
}; };
storiesOf("Generics / Channels availability card", module) storiesOf("Generics / Channels availability card", module)

View file

@ -17,7 +17,10 @@ import { ChannelOpts, ChannelsAvailabilityError, Messages } from "./types";
import { getChannelsAvailabilityMessages } from "./utils"; import { getChannelsAvailabilityMessages } from "./utils";
export interface ChannelsAvailability export interface ChannelsAvailability
extends Omit<ChannelsAvailabilityWrapperProps, "children"> { extends Omit<
ChannelsAvailabilityWrapperProps,
"children" | "selectedChannelsCount"
> {
channels: ChannelData[]; channels: ChannelData[];
channelsList: ChannelList[]; channelsList: ChannelList[];
errors?: ChannelsAvailabilityError[]; errors?: ChannelsAvailabilityError[];
@ -36,7 +39,6 @@ export const ChannelsAvailability: React.FC<ChannelsAvailabilityCardProps> = pro
const { const {
channelsList, channelsList,
errors = [], errors = [],
selectedChannelsCount = 0,
allChannelsCount = 0, allChannelsCount = 0,
channels, channels,
messages, messages,
@ -57,7 +59,7 @@ export const ChannelsAvailability: React.FC<ChannelsAvailabilityCardProps> = pro
return ( return (
<ChannelsAvailabilityCardWrapper <ChannelsAvailabilityCardWrapper
selectedChannelsCount={selectedChannelsCount} selectedChannelsCount={(channels ?? channelsList).length ?? 0}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
managePermissions={managePermissions} managePermissions={managePermissions}
openModal={openModal} openModal={openModal}

View file

@ -143,7 +143,6 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
<div> <div>
<ChannelsAvailabilityCard <ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]} managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
channelsList={data.channelListings.map(channel => ({ channelsList={data.channelListings.map(channel => ({
id: channel.id, id: channel.id,

View file

@ -334,7 +334,6 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
<CardSpacer /> <CardSpacer />
<ChannelsAvailabilityCard <ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]} managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
channelsList={data.channelListings.map(channel => ({ channelsList={data.channelListings.map(channel => ({
id: channel.id, id: channel.id,

View file

@ -191,7 +191,6 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
<div> <div>
<ChannelsAvailabilityCard <ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]} managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
channelsList={data.channelListings.map(channel => ({ channelsList={data.channelListings.map(channel => ({
id: channel.id, id: channel.id,

View file

@ -402,7 +402,6 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
<CardSpacer /> <CardSpacer />
<ChannelsAvailabilityCard <ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]} managePermissions={[PermissionEnum.MANAGE_DISCOUNTS]}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
channelsList={data.channelListings.map(channel => ({ channelsList={data.channelListings.map(channel => ({
id: channel.id, id: channel.id,

View file

@ -348,7 +348,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
}) })
}} }}
errors={channelsErrors} errors={channelsErrors}
selectedChannelsCount={data.channelListings?.length || 0}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
channels={data.channelListings || []} channels={data.channelListings || []}
disabled={loading} disabled={loading}

View file

@ -290,260 +290,273 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
submit, submit,
isSaveDisabled, isSaveDisabled,
attributeRichTextGetters attributeRichTextGetters
}) => ( }) => {
<> const availabilityCommonProps = {
<Container> managePermissions: [PermissionEnum.MANAGE_PRODUCTS],
<Backlink href={productListUrl()}> messages: {
{intl.formatMessage(sectionNames.products)} hiddenLabel: intl.formatMessage({
</Backlink> id: "saKXY3",
<PageHeader title={header}> defaultMessage: "Not published",
{extensionMenuItems.length > 0 && ( description: "product label"
<CardMenu menuItems={extensionMenuItems} data-test-id="menu" /> }),
)}
</PageHeader> visibleLabel: intl.formatMessage({
<Grid richText> id: "qJedl0",
<div> defaultMessage: "Published",
<ProductDetailsForm description: "product label"
data={data} })
disabled={disabled} },
errors={errors} errors: channelsErrors,
onChange={change} allChannelsCount,
/> disabled,
<CardSpacer /> onChange: handlers.changeChannels,
<ProductMedia openModal: openChannelsModal
media={media} };
placeholderImage={placeholderImage}
onImageDelete={onImageDelete} return (
onImageReorder={onImageReorder} <>
onImageUpload={onImageUpload} <Container>
openMediaUrlModal={() => setMediaUrlModalStatus(true)} <Backlink href={productListUrl()}>
getImageEditUrl={imageId => {intl.formatMessage(sectionNames.products)}
productImageUrl(productId, imageId) </Backlink>
<PageHeader title={header}>
{extensionMenuItems.length > 0 && (
<CardMenu
menuItems={extensionMenuItems}
data-test-id="menu"
/>
)}
</PageHeader>
<Grid richText>
<div>
<ProductDetailsForm
data={data}
disabled={disabled}
errors={errors}
onChange={change}
/>
<CardSpacer />
<ProductMedia
media={media}
placeholderImage={placeholderImage}
onImageDelete={onImageDelete}
onImageReorder={onImageReorder}
onImageUpload={onImageUpload}
openMediaUrlModal={() => setMediaUrlModalStatus(true)}
getImageEditUrl={imageId =>
productImageUrl(productId, imageId)
}
/>
<CardSpacer />
{data.attributes.length > 0 && (
<Attributes
attributes={data.attributes}
attributeValues={attributeValues}
errors={errors}
loading={disabled}
disabled={disabled}
onChange={handlers.selectAttribute}
onMultiChange={handlers.selectAttributeMultiple}
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
fetchAttributeValues={fetchAttributeValues}
fetchMoreAttributeValues={fetchMoreAttributeValues}
onAttributeSelectBlur={onAttributeSelectBlur}
richTextGetters={attributeRichTextGetters}
/>
)}
<CardSpacer />
{isSimpleProduct && (
<>
<ProductVariantPrice
ProductVariantChannelListings={data.channelListings}
errors={channelsErrors}
loading={disabled}
onChange={handlers.changeChannelPrice}
/>
<CardSpacer />
</>
)}
{hasVariants ? (
<ProductVariants
productId={productId}
disabled={disabled}
limits={limits}
variants={variants}
product={product}
onVariantsAdd={onVariantsAdd}
onVariantReorder={onVariantReorder}
onSetDefaultVariant={onSetDefaultVariant}
toolbar={toolbar}
isChecked={isChecked}
selected={selected}
selectedChannelId={selectedChannelId}
toggle={toggle}
toggleAll={toggleAll}
/>
) : (
<>
<ProductShipping
data={data}
disabled={disabled}
errors={errors}
weightUnit={product?.weight?.unit || defaultWeightUnit}
onChange={change}
/>
<CardSpacer />
<ProductStocks
onVariantChannelListingChange={
handlers.changeChannelPreorder
}
productVariantChannelListings={data.channelListings}
onEndPreorderTrigger={
!!variants?.[0]?.preorder
? () => onVariantEndPreorderDialogOpen()
: null
}
data={data}
disabled={disabled}
hasVariants={false}
errors={errors}
formErrors={formErrors}
stocks={data.stocks}
warehouses={warehouses}
onChange={handlers.changeStock}
onFormDataChange={change}
onChangePreorderEndDate={handlers.changePreorderEndDate}
onWarehouseStockAdd={handlers.addStock}
onWarehouseStockDelete={handlers.deleteStock}
onWarehouseConfigure={onWarehouseConfigure}
/>
</>
)}
<CardSpacer />
<SeoForm
errors={errors}
title={data.seoTitle}
titlePlaceholder={data.name}
description={data.seoDescription}
descriptionPlaceholder={""} // TODO: cast description to string
slug={data.slug}
slugPlaceholder={data.name}
loading={disabled}
onClick={onSeoClick}
onChange={change}
helperText={intl.formatMessage({
id: "LKoIB1",
defaultMessage:
"Add search engine title and description to make this product easier to find"
})}
/>
<CardSpacer />
<Metadata data={data} onChange={handlers.changeMetadata} />
</div>
<div>
<ProductOrganization
canChangeType={false}
categories={categories}
categoryInputDisplayValue={selectedCategory}
collections={collections}
collectionsInputDisplayValue={selectedCollections}
data={data}
disabled={disabled}
errors={errors}
fetchCategories={fetchCategories}
fetchCollections={fetchCollections}
fetchMoreCategories={fetchMoreCategories}
fetchMoreCollections={fetchMoreCollections}
productType={product?.productType}
onCategoryChange={handlers.selectCategory}
onCollectionChange={handlers.selectCollection}
/>
<CardSpacer />
{isSimpleProduct ? (
<ChannelsAvailabilityCard
{...availabilityCommonProps}
channels={data.channelListings}
/>
) : product?.variants.length === 0 ? (
<ChannelsAvailabilityCard
{...availabilityCommonProps}
channelsList={data.channelListings}
/>
) : (
<ChannelsWithVariantsAvailabilityCard
messages={{
hiddenLabel: intl.formatMessage({
id: "saKXY3",
defaultMessage: "Not published",
description: "product label"
}),
visibleLabel: intl.formatMessage({
id: "qJedl0",
defaultMessage: "Published",
description: "product label"
})
}}
errors={channelsErrors}
channels={data.channelsData}
channelsWithVariantsData={channelsWithVariantsData}
variants={variants}
onChange={handlers.changeChannels}
openModal={openChannelsModal}
/>
)}
<CardSpacer />
<ProductTaxes
data={data}
disabled={disabled}
selectedTaxTypeDisplayName={selectedTaxType}
taxTypes={taxTypes}
onChange={change}
onTaxTypeChange={handlers.selectTaxRate}
/>
</div>
</Grid>
<Savebar
onCancel={() => navigate(productListUrl())}
onDelete={onDelete}
onSubmit={submit}
state={saveButtonBarState}
disabled={isSaveDisabled}
/>
{canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences(
assignReferencesAttributeId,
data.attributes,
referencePages,
referenceProducts
)}
hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences}
onFetchMore={handlers.fetchMoreReferences?.onFetchMore}
loading={handlers.fetchMoreReferences?.loading}
onClose={onCloseDialog}
onSubmit={attributeValues =>
handleAssignReferenceAttribute(
attributeValues,
data,
handlers
)
} }
/> />
<CardSpacer /> )}
{data.attributes.length > 0 && (
<Attributes
attributes={data.attributes}
attributeValues={attributeValues}
errors={errors}
loading={disabled}
disabled={disabled}
onChange={handlers.selectAttribute}
onMultiChange={handlers.selectAttributeMultiple}
onFileChange={handlers.selectAttributeFile}
onReferencesRemove={handlers.selectAttributeReference}
onReferencesAddClick={onAssignReferencesClick}
onReferencesReorder={handlers.reorderAttributeValue}
fetchAttributeValues={fetchAttributeValues}
fetchMoreAttributeValues={fetchMoreAttributeValues}
onAttributeSelectBlur={onAttributeSelectBlur}
richTextGetters={attributeRichTextGetters}
/>
)}
<CardSpacer />
{isSimpleProduct && (
<>
<ProductVariantPrice
ProductVariantChannelListings={data.channelListings}
errors={channelsErrors}
loading={disabled}
onChange={handlers.changeChannelPrice}
/>
<CardSpacer />
</>
)}
{hasVariants ? (
<ProductVariants
productId={productId}
disabled={disabled}
limits={limits}
variants={variants}
product={product}
onVariantsAdd={onVariantsAdd}
onVariantReorder={onVariantReorder}
onSetDefaultVariant={onSetDefaultVariant}
toolbar={toolbar}
isChecked={isChecked}
selected={selected}
selectedChannelId={selectedChannelId}
toggle={toggle}
toggleAll={toggleAll}
/>
) : (
<>
<ProductShipping
data={data}
disabled={disabled}
errors={errors}
weightUnit={product?.weight?.unit || defaultWeightUnit}
onChange={change}
/>
<CardSpacer />
<ProductStocks
onVariantChannelListingChange={
handlers.changeChannelPreorder
}
productVariantChannelListings={data.channelListings}
onEndPreorderTrigger={
!!variants?.[0]?.preorder
? () => onVariantEndPreorderDialogOpen()
: null
}
data={data}
disabled={disabled}
hasVariants={false}
errors={errors}
formErrors={formErrors}
stocks={data.stocks}
warehouses={warehouses}
onChange={handlers.changeStock}
onFormDataChange={change}
onChangePreorderEndDate={handlers.changePreorderEndDate}
onWarehouseStockAdd={handlers.addStock}
onWarehouseStockDelete={handlers.deleteStock}
onWarehouseConfigure={onWarehouseConfigure}
/>
</>
)}
<CardSpacer />
<SeoForm
errors={errors}
title={data.seoTitle}
titlePlaceholder={data.name}
description={data.seoDescription}
descriptionPlaceholder={""} // TODO: cast description to string
slug={data.slug}
slugPlaceholder={data.name}
loading={disabled}
onClick={onSeoClick}
onChange={change}
helperText={intl.formatMessage({
id: "LKoIB1",
defaultMessage:
"Add search engine title and description to make this product easier to find"
})}
/>
<CardSpacer />
<Metadata data={data} onChange={handlers.changeMetadata} />
</div>
<div>
<ProductOrganization
canChangeType={false}
categories={categories}
categoryInputDisplayValue={selectedCategory}
collections={collections}
collectionsInputDisplayValue={selectedCollections}
data={data}
disabled={disabled}
errors={errors}
fetchCategories={fetchCategories}
fetchCollections={fetchCollections}
fetchMoreCategories={fetchMoreCategories}
fetchMoreCollections={fetchMoreCollections}
productType={product?.productType}
onCategoryChange={handlers.selectCategory}
onCollectionChange={handlers.selectCollection}
/>
<CardSpacer />
{isSimpleProduct ? (
<ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_PRODUCTS]}
messages={{
hiddenLabel: intl.formatMessage({
id: "saKXY3",
defaultMessage: "Not published",
description: "product label"
}),
visibleLabel: intl.formatMessage({ <ProductExternalMediaDialog
id: "qJedl0", product={product}
defaultMessage: "Published", onClose={() => setMediaUrlModalStatus(false)}
description: "product label" open={mediaUrlModalStatus}
}) onSubmit={onMediaUrlUpload}
}}
errors={channelsErrors}
selectedChannelsCount={data.channelListings.length}
allChannelsCount={allChannelsCount}
channels={data.channelListings}
disabled={disabled}
onChange={handlers.changeChannels}
openModal={openChannelsModal}
/>
) : (
<ChannelsWithVariantsAvailabilityCard
messages={{
hiddenLabel: intl.formatMessage({
id: "saKXY3",
defaultMessage: "Not published",
description: "product label"
}),
visibleLabel: intl.formatMessage({
id: "qJedl0",
defaultMessage: "Published",
description: "product label"
})
}}
errors={channelsErrors}
channels={data.channelsData}
channelsWithVariantsData={channelsWithVariantsData}
variants={variants}
onChange={handlers.changeChannels}
openModal={openChannelsModal}
/>
)}
<CardSpacer />
<ProductTaxes
data={data}
disabled={disabled}
selectedTaxTypeDisplayName={selectedTaxType}
taxTypes={taxTypes}
onChange={change}
onTaxTypeChange={handlers.selectTaxRate}
/>
</div>
</Grid>
<Savebar
onCancel={() => navigate(productListUrl())}
onDelete={onDelete}
onSubmit={submit}
state={saveButtonBarState}
disabled={isSaveDisabled}
/>
{canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences(
assignReferencesAttributeId,
data.attributes,
referencePages,
referenceProducts
)}
hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences}
onFetchMore={handlers.fetchMoreReferences?.onFetchMore}
loading={handlers.fetchMoreReferences?.loading}
onClose={onCloseDialog}
onSubmit={attributeValues =>
handleAssignReferenceAttribute(
attributeValues,
data,
handlers
)
}
/> />
)} </Container>
</>
<ProductExternalMediaDialog );
product={product} }}
onClose={() => setMediaUrlModalStatus(false)}
open={mediaUrlModalStatus}
onSubmit={onMediaUrlUpload}
/>
</Container>
</>
)}
</ProductUpdateForm> </ProductUpdateForm>
); );
}; };

View file

@ -24,6 +24,7 @@ import reduceProductVariantCreateFormData, {
ProductVariantCreateReducerActionType ProductVariantCreateReducerActionType
} from "./reducer"; } from "./reducer";
import { ProductVariantCreatorStep } from "./types"; import { ProductVariantCreatorStep } from "./types";
import { dedupeListings } from "./utils";
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
@ -176,7 +177,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = props
onTransition: (_, nextStep) => { onTransition: (_, nextStep) => {
if (nextStep === ProductVariantCreatorStep.summary) { if (nextStep === ProductVariantCreatorStep.summary) {
dispatchFormDataAction({ dispatchFormDataAction({
type: ProductVariantCreateReducerActionType.reload type: ProductVariantCreateReducerActionType.rebuild
}); });
} }
} }
@ -237,7 +238,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = props
className={classes.button} className={classes.button}
disabled={!canHitNext(step, wizardData, variantsLeft)} disabled={!canHitNext(step, wizardData, variantsLeft)}
variant="primary" variant="primary"
onClick={() => onSubmit(wizardData.variants)} onClick={() => onSubmit(dedupeListings(wizardData).variants)}
> >
<FormattedMessage <FormattedMessage
id="Q3j++G" id="Q3j++G"

View file

@ -102,7 +102,7 @@ const useStyles = makeStyles<ProductVariantCreatorSummaryProps, ClassKey>(
`minmax(180px, auto) repeat(${props.data.variants[0].channelListings `minmax(180px, auto) repeat(${props.data.variants[0].channelListings
.length + .length +
props.data.variants[0].stocks props.data.variants[0].stocks
.length}, minmax(180px, auto)) 140px 64px`, .length}, minmax(180px, auto)) 220px 64px`,
overflowX: "scroll", overflowX: "scroll",
rowGap: theme.spacing(), rowGap: theme.spacing(),
paddingBottom: 3 paddingBottom: 3

View file

@ -152,14 +152,6 @@ function addAttributeToVariant(
} }
]); ]);
} }
function addVariantAttributeInput(
data: CreateVariantInput[],
attribute: Attribute
): CreateVariantInput[] {
return data
.map(variant => addAttributeToVariant(attribute, variant))
.reduce((acc, variantInput) => [...acc, ...variantInput]);
}
export function createVariantFlatMatrixDimension( export function createVariantFlatMatrixDimension(
variants: CreateVariantInput[], variants: CreateVariantInput[],
@ -167,7 +159,9 @@ export function createVariantFlatMatrixDimension(
): CreateVariantInput[] { ): CreateVariantInput[] {
if (attributes.length > 0) { if (attributes.length > 0) {
return createVariantFlatMatrixDimension( return createVariantFlatMatrixDimension(
addVariantAttributeInput(variants, attributes[0]), variants.flatMap(variant =>
addAttributeToVariant(attributes[0], variant)
),
attributes.slice(1) attributes.slice(1)
); );
} else { } else {

View file

@ -1,4 +1,8 @@
import { AttributeValueFragment, StockInput } from "@saleor/graphql"; import {
AttributeValueFragment,
ProductVariantBulkCreateInput,
StockInput
} from "@saleor/graphql";
import { import {
add, add,
remove, remove,
@ -32,6 +36,7 @@ export enum ProductVariantCreateReducerActionType {
changeWarehouses, changeWarehouses,
deleteVariant, deleteVariant,
reload, reload,
rebuild,
selectValue selectValue
} }
export interface ProductVariantCreateReducerAction { export interface ProductVariantCreateReducerAction {
@ -80,6 +85,44 @@ export interface ProductVariantCreateReducerAction {
type: ProductVariantCreateReducerActionType; type: ProductVariantCreateReducerActionType;
} }
function getVariantId(variant: ProductVariantBulkCreateInput): string {
return variant.attributes
.map(attribute => attribute.values.join(":"))
.join("-");
}
function merge(
prev: ProductVariantBulkCreateInput[],
update: ProductVariantBulkCreateInput[]
): ProductVariantBulkCreateInput[] {
const prevIds = prev.map(getVariantId);
const updateIds = update.map(getVariantId);
return [
...prev
.filter(variant => updateIds.includes(getVariantId(variant)))
.map(variant => {
const updatedVariant = update.find(
v => getVariantId(v) === getVariantId(variant)
);
return {
...updatedVariant,
sku: variant.sku
};
}),
...update.filter(variant => !prevIds.includes(getVariantId(variant)))
];
}
function rebuild(
state: ProductVariantCreateFormData
): ProductVariantCreateFormData {
return {
...state,
variants: merge(state.variants, createVariants(state))
};
}
function selectValue( function selectValue(
prevState: ProductVariantCreateFormData, prevState: ProductVariantCreateFormData,
attributeId: string, attributeId: string,
@ -519,6 +562,8 @@ function reduceProductVariantCreateFormData(
return deleteVariant(prevState, action.deleteVariant.variantIndex); return deleteVariant(prevState, action.deleteVariant.variantIndex);
case ProductVariantCreateReducerActionType.reload: case ProductVariantCreateReducerActionType.reload:
return action.reload?.data || createVariantMatrix(prevState); return action.reload?.data || createVariantMatrix(prevState);
case ProductVariantCreateReducerActionType.rebuild:
return rebuild(prevState);
default: default:
return prevState; return prevState;
} }

View file

@ -5,6 +5,7 @@ import {
} from "@saleor/graphql"; } from "@saleor/graphql";
import { getById } from "@saleor/orders/components/OrderReturnPage/utils"; import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
import { RelayToFlat } from "@saleor/types"; import { RelayToFlat } from "@saleor/types";
import uniqBy from "lodash/uniqBy";
import { AttributeValue, ProductVariantCreateFormData } from "./form"; import { AttributeValue, ProductVariantCreateFormData } from "./form";
@ -79,3 +80,15 @@ export const getBasicAttributeValue = (
attributeValues.find(getBySlug(attributeValue)) attributeValues.find(getBySlug(attributeValue))
}; };
}; };
export function dedupeListings(
data: ProductVariantCreateFormData
): ProductVariantCreateFormData {
return {
...data,
variants: data.variants.map(variant => ({
...variant,
channelListings: uniqBy(variant.channelListings, "channelId")
}))
};
}

View file

@ -530,7 +530,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
<> <>
<WindowTitle title={data?.product?.name} /> <WindowTitle title={data?.product?.name} />
{!!allChannels?.length && {!!allChannels?.length &&
(isSimpleProduct ? ( (isSimpleProduct || product?.variants.length === 0 ? (
<ChannelsAvailabilityDialog <ChannelsAvailabilityDialog
isSelected={isChannelSelected} isSelected={isChannelSelected}
channels={allChannels} channels={allChannels}

View file

@ -142,7 +142,11 @@ export function createUpdateHandler(
const result = await updateProduct(productVariables); const result = await updateProduct(productVariables);
errors = [...errors, ...result.data.productUpdate.errors]; errors = [...errors, ...result.data.productUpdate.errors];
await updateChannels(getChannelsVariables(product, allChannels, data)); if (product.variants.length === 0) {
await updateChannels(getSimpleChannelsVariables(data, product));
} else {
await updateChannels(getChannelsVariables(product, allChannels, data));
}
} else { } else {
if (!product.variants.length) { if (!product.variants.length) {
const productVariantResult = await productVariantCreate({ const productVariantResult = await productVariantCreate({

View file

@ -204,7 +204,6 @@ export const ShippingZoneRatesCreatePage: React.FC<ShippingZoneRatesCreatePagePr
<ChannelsAvailabilityCard <ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_SHIPPING]} managePermissions={[PermissionEnum.MANAGE_SHIPPING]}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
selectedChannelsCount={shippingChannels?.length}
channelsList={data.channelListings} channelsList={data.channelListings}
openModal={openChannelsModal} openModal={openChannelsModal}
/> />

View file

@ -235,7 +235,6 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
<ChannelsAvailabilityCard <ChannelsAvailabilityCard
managePermissions={[PermissionEnum.MANAGE_SHIPPING]} managePermissions={[PermissionEnum.MANAGE_SHIPPING]}
allChannelsCount={allChannelsCount} allChannelsCount={allChannelsCount}
selectedChannelsCount={shippingChannels?.length}
channelsList={data.channelListings.map(channel => ({ channelsList={data.channelListings.map(channel => ({
id: channel.id, id: channel.id,
name: channel.name name: channel.name

View file

@ -4583,7 +4583,7 @@ exports[`Storyshots Generics / Channels availability card default 1`] = `
<div <div
class="MuiTypography-root-id ChannelsAvailabilityCard-channelInfo-id MuiTypography-body1-id" class="MuiTypography-root-id ChannelsAvailabilityCard-channelInfo-id MuiTypography-body1-id"
> >
Available at 3 out of 4 channels Available at 2 out of 4 channels
</div> </div>
<hr <hr
class="Hr-root-id ChannelsAvailabilityCard-hr-id" class="Hr-root-id ChannelsAvailabilityCard-hr-id"
@ -4667,7 +4667,7 @@ exports[`Storyshots Generics / Channels availability card with onChange 1`] = `
<div <div
class="MuiTypography-root-id ChannelsAvailabilityCard-channelInfo-id MuiTypography-body1-id" class="MuiTypography-root-id ChannelsAvailabilityCard-channelInfo-id MuiTypography-body1-id"
> >
Available at 3 out of 4 channels Available at 2 out of 4 channels
</div> </div>
<hr <hr
class="Hr-root-id ChannelsAvailabilityCard-hr-id" class="Hr-root-id ChannelsAvailabilityCard-hr-id"
@ -206300,11 +206300,21 @@ exports[`Storyshots Views / Products / Product edit when loading data 1`] = `
Availability Availability
</span> </span>
</div> </div>
<div
class="MuiCardHeader-action-id"
/>
</div> </div>
<div <div
class="MuiCardContent-root-id" class="MuiCardContent-root-id ChannelsAvailabilityCard-card-id"
> >
You will be able to define availability of product after creating variants. <div
class="MuiTypography-root-id ChannelsAvailabilityCard-channelInfo-id MuiTypography-body1-id"
>
Available at 1 out of 7 channels
</div>
<hr
class="Hr-root-id ChannelsAvailabilityCard-hr-id"
/>
</div> </div>
</div> </div>
<div <div