diff --git a/schema.graphql b/schema.graphql index 710daf06a..7ef07e0b0 100644 --- a/schema.graphql +++ b/schema.graphql @@ -4643,7 +4643,7 @@ input ProductVariantBulkCreateInput { costPrice: Decimal priceOverride: Decimal sku: String! - quantity: Int + stocks: [StockInput!]! trackInventory: Boolean weight: WeightScalar } diff --git a/src/hooks/useWizard.ts b/src/hooks/useWizard.ts index 963f4de96..8e33d0d0b 100644 --- a/src/hooks/useWizard.ts +++ b/src/hooks/useWizard.ts @@ -1,19 +1,33 @@ import { useState } from "react"; -export interface UseWizardOpts { +export interface UseWizardActions { next: () => void; prev: () => void; set: (step: T) => void; } -export type UseWizard = [T, UseWizardOpts]; -function useWizard(initial: T, steps: T[]): UseWizard { +export interface UseWizardOpts { + onTransition: (prevStep: T, nextStep: T) => void; +} +export type UseWizard = [T, UseWizardActions]; +function useWizard( + initial: T, + steps: T[], + opts?: UseWizardOpts +): UseWizard { const [stepIndex, setStepIndex] = useState(steps.indexOf(initial)); + function goToStep(nextStepIndex) { + if (typeof opts?.onTransition === "function") { + opts.onTransition(steps[stepIndex], steps[nextStepIndex]); + } + setStepIndex(nextStepIndex); + } + function next() { if (stepIndex === steps.length - 1) { console.error("This is the last step"); } else { - setStepIndex(stepIndex + 1); + goToStep(stepIndex + 1); } } @@ -21,7 +35,7 @@ function useWizard(initial: T, steps: T[]): UseWizard { if (stepIndex === 0) { console.error("This is the first step"); } else { - setStepIndex(stepIndex - 1); + goToStep(stepIndex - 1); } } @@ -30,7 +44,7 @@ function useWizard(initial: T, steps: T[]): UseWizard { if (newStepIndex === -1) { console.error("Step does not exist"); } else { - setStepIndex(newStepIndex); + goToStep(newStepIndex); } } diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreator.stories.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreator.stories.tsx index 3305d853f..73fa3367a 100644 --- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreator.stories.tsx +++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreator.stories.tsx @@ -5,9 +5,10 @@ import { attributes } from "@saleor/attributes/fixtures"; import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate"; import { ProductErrorCode } from "@saleor/types/globalTypes"; import Container from "@saleor/components/Container"; +import { warehouseList } from "@saleor/warehouses/fixtures"; import Decorator from "../../../storybook/Decorator"; import { createVariants } from "./createVariants"; -import { AllOrAttribute } from "./form"; +import { AllOrAttribute, ProductVariantCreateFormData } from "./form"; import ProductVariantCreatorContent, { ProductVariantCreatorContentProps } from "./ProductVariantCreatorContent"; @@ -15,24 +16,33 @@ import ProductVariantCreatorPage from "./ProductVariantCreatorPage"; import { ProductVariantCreatorStep } from "./types"; const selectedAttributes = [1, 4, 5].map(index => attributes[index]); +const selectedWarehouses = [0, 1, 3].map(index => warehouseList[index]); -const price: AllOrAttribute = { +const price: AllOrAttribute = { all: false, - attribute: selectedAttributes[1].id, + attribute: selectedAttributes[0].id, value: "2.79", - values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ + values: selectedAttributes[0].values.map((attribute, attributeIndex) => ({ slug: attribute.slug, value: (attributeIndex + 4).toFixed(2) })) }; -const stock: AllOrAttribute = { +const stock: AllOrAttribute = { all: false, - attribute: selectedAttributes[1].id, - value: "8", - values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ + attribute: selectedAttributes[0].id, + value: selectedWarehouses.map((_, warehouseIndex) => + ((warehouseIndex + 2) * 3).toString() + ), + values: selectedAttributes[0].values.map((attribute, attributeIndex) => ({ slug: attribute.slug, - value: (selectedAttributes.length * 10 - attributeIndex).toString() + value: selectedWarehouses.map((_, warehouseIndex) => + ( + selectedAttributes.length * 10 - + attributeIndex - + warehouseIndex * 3 + ).toString() + ) })) }; @@ -52,23 +62,30 @@ const errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[] = [ } ]; +const data: ProductVariantCreateFormData = { + attributes: dataAttributes, + price, + stock, + variants: createVariants({ + attributes: dataAttributes, + price, + stock, + variants: [], + warehouses: selectedWarehouses.map(warehouse => warehouse.id) + }), + warehouses: selectedWarehouses.map(warehouse => warehouse.id) +}; const props: ProductVariantCreatorContentProps = { attributes, currencySymbol: "USD", data: { - attributes: dataAttributes, - price, - stock, - variants: createVariants({ - attributes: dataAttributes, - price, - stock, - variants: [] - }) + ...data, + variants: createVariants(data) }, dispatchFormDataAction: () => undefined, errors: [], - step: ProductVariantCreatorStep.values + step: ProductVariantCreatorStep.values, + warehouses: warehouseList }; storiesOf("Views / Products / Create multiple variants", module) diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorContent.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorContent.tsx index 4316c2df5..959d11c3f 100644 --- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorContent.tsx +++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorContent.tsx @@ -3,6 +3,7 @@ import React from "react"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate"; import { isSelected } from "@saleor/utils/lists"; +import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; import { ProductVariantCreateFormData } from "./form"; import ProductVariantCreatePrices from "./ProductVariantCreatorPrices"; import ProductVariantCreateSummary from "./ProductVariantCreatorSummary"; @@ -17,6 +18,7 @@ export interface ProductVariantCreatorContentProps { dispatchFormDataAction: React.Dispatch; errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[]; step: ProductVariantCreatorStep; + warehouses: WarehouseFragment[]; } const ProductVariantCreatorContent: React.FC = props => { @@ -26,7 +28,8 @@ const ProductVariantCreatorContent: React.FC data, dispatchFormDataAction, errors, - step + step, + warehouses } = props; const selectedAttributes = attributes.filter(attribute => isSelected( @@ -106,12 +109,23 @@ const ProductVariantCreatorContent: React.FC variantIndex }) } + onVariantStockDataChange={(variantIndex, warehouse, value) => + dispatchFormDataAction({ + stock: { + quantity: parseInt(value, 10), + warehouse + }, + type: "changeVariantStockData", + variantIndex + }) + } onVariantDelete={variantIndex => dispatchFormDataAction({ type: "deleteVariant", variantIndex }) } + warehouses={warehouses} /> )} diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx index dca44165f..4c3c3ad70 100644 --- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx +++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorPage.tsx @@ -50,17 +50,8 @@ function canHitNext( } } - if (data.stock.all) { - if (data.stock.value === "") { - return false; - } - } else { - if ( - data.stock.attribute === "" || - data.stock.values.some(attributeValue => attributeValue.value === "") - ) { - return false; - } + if (!data.stock.all || data.stock.attribute) { + return false; } return true; @@ -102,25 +93,42 @@ function getTitle(step: ProductVariantCreatorStep, intl: IntlShape): string { } const ProductVariantCreatePage: React.FC = props => { - const { attributes, defaultPrice, errors, onSubmit, ...contentProps } = props; + const { + attributes, + defaultPrice, + errors, + onSubmit, + warehouses, + ...contentProps + } = props; const classes = useStyles(props); const intl = useIntl(); - const [step, { next: nextStep, prev: prevStep, set: setStep }] = useWizard< - ProductVariantCreatorStep - >(ProductVariantCreatorStep.values, [ - ProductVariantCreatorStep.values, - ProductVariantCreatorStep.prices, - ProductVariantCreatorStep.summary - ]); - const [wizardData, dispatchFormDataAction] = React.useReducer( reduceProductVariantCreateFormData, - createInitialForm(attributes, defaultPrice) + createInitialForm(attributes, defaultPrice, warehouses) + ); + const [step, { next: nextStep, prev: prevStep, set: setStep }] = useWizard< + ProductVariantCreatorStep + >( + ProductVariantCreatorStep.values, + [ + ProductVariantCreatorStep.values, + ProductVariantCreatorStep.prices, + ProductVariantCreatorStep.summary + ], + { + onTransition: (_, nextStep) => { + if (nextStep === ProductVariantCreatorStep.summary) { + dispatchFormDataAction({ + type: "reload" + }); + } + } + } ); - const reloadForm = () => dispatchFormDataAction({ - data: createInitialForm(attributes, defaultPrice), + data: createInitialForm(attributes, defaultPrice, warehouses), type: "reload" }); @@ -170,6 +178,7 @@ const ProductVariantCreatePage: React.FC = props dispatchFormDataAction={dispatchFormDataAction} errors={errors} step={step} + warehouses={warehouses} /> ); diff --git a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx index aab3db002..46a5dd80f 100644 --- a/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx +++ b/src/products/components/ProductVariantCreatorPage/ProductVariantCreatorSummary.tsx @@ -5,7 +5,7 @@ import purple from "@material-ui/core/colors/purple"; import yellow from "@material-ui/core/colors/yellow"; import Card from "@material-ui/core/Card"; import IconButton from "@material-ui/core/IconButton"; -import { makeStyles } from "@material-ui/core/styles"; +import { makeStyles, Theme } from "@material-ui/core/styles"; import TextField from "@material-ui/core/TextField"; import DeleteIcon from "@material-ui/icons/Delete"; import classNames from "classnames"; @@ -17,7 +17,8 @@ import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { getFormErrors } from "@saleor/utils/errors"; import { getBulkProductErrorMessage } from "@saleor/utils/errors/product"; import CardTitle from "@saleor/components/CardTitle"; -import { commonMessages } from "@saleor/intl"; +import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; +import Hr from "@saleor/components/Hr"; import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails"; import { ProductVariantCreateFormData } from "./form"; import { VariantField } from "./reducer"; @@ -27,39 +28,62 @@ export interface ProductVariantCreatorSummaryProps { currencySymbol: string; data: ProductVariantCreateFormData; errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[]; + warehouses: WarehouseFragment[]; onVariantDataChange: ( variantIndex: number, field: VariantField, value: string ) => void; + onVariantStockDataChange: ( + variantIndex: number, + warehouseId: string, + value: string + ) => void; onVariantDelete: (variantIndex: number) => void; } +type ClassKey = + | "attributeValue" + | "card" + | "col" + | "colHeader" + | "colName" + | "colPrice" + | "colSku" + | "colStock" + | "delete" + | "hr" + | "input" + | "summary"; const colors = [blue, cyan, green, purple, yellow].map(color => color[800]); -const useStyles = makeStyles( +const useStyles = makeStyles< + Theme, + ProductVariantCreatorSummaryProps, + ClassKey +>( theme => ({ attributeValue: { display: "inline-block", marginRight: theme.spacing(1) }, - col: { - ...theme.typography.body1, - fontSize: 14, - paddingLeft: theme.spacing(), - paddingRight: theme.spacing(1) + card: { + paddingBottom: theme.spacing() }, - colHeader: { + col: { ...theme.typography.body1, fontSize: 14 }, + colHeader: { + ...theme.typography.body1, + fontSize: 14, + paddingTop: theme.spacing(3) + }, colName: { - "&&": { - paddingLeft: "0 !important" - }, "&:not($colHeader)": { paddingTop: theme.spacing(2) - } + }, + paddingLeft: theme.spacing(3) }, colPrice: {}, colSku: {}, @@ -67,22 +91,21 @@ const useStyles = makeStyles( delete: { marginTop: theme.spacing(0.5) }, - errorRow: {}, hr: { - marginBottom: theme.spacing(), - marginTop: theme.spacing(0.5) + gridColumn: props => `span ${4 + props.data.stock.value.length}` }, input: { "& input": { padding: "16px 12px 17px" - }, - marginTop: theme.spacing(0.5) + } }, - row: { - borderBottom: `1px solid ${theme.palette.divider}`, + summary: { + columnGap: theme.spacing(3), display: "grid", - gridTemplateColumns: "1fr 180px 120px 180px 64px", - padding: theme.spacing(1, 1, 1, 3) + gridTemplateColumns: props => + `minmax(240px, auto) 170px repeat(${props.data.stock.value.length}, 140px) 140px 64px`, + overflowX: "scroll", + rowGap: theme.spacing() + "px" } }), { @@ -115,63 +138,66 @@ const ProductVariantCreatorSummary: React.FC currencySymbol, data, errors, + warehouses, onVariantDataChange, - onVariantDelete + onVariantDelete, + onVariantStockDataChange } = props; const classes = useStyles(props); const intl = useIntl(); return ( - - -
-
-
- -
-
- -
+ + +
+
+ +
+
+ +
+ {data.warehouses.map(warehouseId => (
- -
-
- + {warehouses.find(warehouse => warehouse.id === warehouseId).name}
+ ))} +
+
+
+
{data.variants.map((variant, variantIndex) => { const variantErrors = errors.filter( error => error.index === variantIndex @@ -182,10 +208,7 @@ const ProductVariantCreatorSummary: React.FC ); return ( -
0 - })} + attribute.values[0]) .join(":")} @@ -198,7 +221,7 @@ const ProductVariantCreatorSummary: React.FC style={{ color: colors[valueIndex % colors.length] }} - key={value} + key={`${value}:${valueIndex}`} > {value} @@ -231,29 +254,34 @@ const ProductVariantCreatorSummary: React.FC } />
-
- - onVariantDataChange( - variantIndex, - "stock", - event.target.value - ) - } - /> -
+ {variant.stocks.map(stock => ( +
+ + onVariantStockDataChange( + variantIndex, + stock.warehouse, + event.target.value + ) + } + /> +
+ ))}
-
+ {variantIndex !== data.variants.length - 1 && ( +
+ )} + ); })}
diff --git a/src/products/components/ProductVariantCreatorPage/createVariants.ts b/src/products/components/ProductVariantCreatorPage/createVariants.ts index 186972c60..ae41eeda2 100644 --- a/src/products/components/ProductVariantCreatorPage/createVariants.ts +++ b/src/products/components/ProductVariantCreatorPage/createVariants.ts @@ -11,10 +11,10 @@ interface CreateVariantAttributeValueInput { } type CreateVariantInput = CreateVariantAttributeValueInput[]; -function getAttributeValuePriceOrStock( +function getAttributeValuePriceOrStock( attributes: CreateVariantInput, - priceOrStock: AllOrAttribute -): string { + priceOrStock: AllOrAttribute +): T { const attribute = attributes.find( attribute => attribute.attributeId === priceOrStock.attribute ); @@ -33,12 +33,9 @@ function createVariant( const priceOverride = data.price.all ? data.price.value : getAttributeValuePriceOrStock(attributes, data.price); - const quantity = parseInt( - data.stock.all - ? data.stock.value - : getAttributeValuePriceOrStock(attributes, data.stock), - 10 - ); + const stocks = data.stock.all + ? data.stock.value + : getAttributeValuePriceOrStock(attributes, data.stock); return { attributes: attributes.map(attribute => ({ @@ -46,8 +43,11 @@ function createVariant( values: [attribute.attributeValueSlug] })), priceOverride, - quantity, - sku: "" + sku: "", + stocks: stocks.map((quantity, stockIndex) => ({ + quantity: parseInt(quantity, 10), + warehouse: data.warehouses[stockIndex] + })) }; } diff --git a/src/products/components/ProductVariantCreatorPage/form.ts b/src/products/components/ProductVariantCreatorPage/form.ts index d51f13b5d..689f1b784 100644 --- a/src/products/components/ProductVariantCreatorPage/form.ts +++ b/src/products/components/ProductVariantCreatorPage/form.ts @@ -1,15 +1,16 @@ import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; -export interface AttributeValue { +export interface AttributeValue { slug: string; - value: string; + value: T; } -export interface AllOrAttribute { +export interface AllOrAttribute { all: boolean; attribute: string; - value: string; - values: AttributeValue[]; + value: T; + values: Array>; } export interface Attribute { id: string; @@ -17,14 +18,16 @@ export interface Attribute { } export interface ProductVariantCreateFormData { attributes: Attribute[]; - price: AllOrAttribute; - stock: AllOrAttribute; + price: AllOrAttribute; + stock: AllOrAttribute; variants: ProductVariantBulkCreateInput[]; + warehouses: string[]; } export const createInitialForm = ( attributes: ProductDetails_product_productType_variantAttributes[], - price: string + price: string, + warehouses: WarehouseFragment[] ): ProductVariantCreateFormData => ({ attributes: attributes.map(attribute => ({ id: attribute.id, @@ -39,8 +42,9 @@ export const createInitialForm = ( stock: { all: true, attribute: undefined, - value: "", + value: warehouses.length === 1 ? [0] : [], values: [] }, - variants: [] + variants: [], + warehouses: warehouses.length === 1 ? [warehouses[0].id] : [] }); diff --git a/src/products/components/ProductVariantCreatorPage/reducer.ts b/src/products/components/ProductVariantCreatorPage/reducer.ts index 6280a3e24..1ae8287a4 100644 --- a/src/products/components/ProductVariantCreatorPage/reducer.ts +++ b/src/products/components/ProductVariantCreatorPage/reducer.ts @@ -3,8 +3,10 @@ import { remove, removeAtIndex, toggle, - updateAtIndex + updateAtIndex, + update } from "@saleor/utils/lists"; +import { StockInput } from "@saleor/types/globalTypes"; import { createVariants } from "./createVariants"; import { ProductVariantCreateFormData } from "./form"; @@ -20,20 +22,24 @@ export type ProductVariantCreateReducerActionType = | "changeAttributeValuePrice" | "changeAttributeValueStock" | "changeVariantData" + | "changeVariantStockData" | "deleteVariant" | "reload" | "selectValue"; -export type VariantField = "stock" | "price" | "sku"; +export type VariantField = "price" | "sku"; export interface ProductVariantCreateReducerAction { all?: boolean; attributeId?: string; data?: ProductVariantCreateFormData; field?: VariantField; + quantity?: number; + stock?: StockInput; type: ProductVariantCreateReducerActionType; value?: string; valueId?: string; variantIndex?: number; + warehouseIndex?: number; } function selectValue( @@ -70,7 +76,7 @@ function selectValue( ? toggle( { slug: valueSlug, - value: "" + value: [] }, prevState.stock.values, (a, b) => a.slug === b.slug @@ -95,36 +101,26 @@ function applyPriceToAll( state: ProductVariantCreateFormData, value: boolean ): ProductVariantCreateFormData { - const data = { + return { ...state, price: { ...state.price, all: value } }; - - return { - ...data, - variants: createVariants(data) - }; } function applyStockToAll( state: ProductVariantCreateFormData, value: boolean ): ProductVariantCreateFormData { - const data = { + return { ...state, stock: { ...state.stock, all: value } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeAttributeValuePrice( @@ -149,24 +145,20 @@ function changeAttributeValuePrice( index ); - const data = { + return { ...state, price: { ...state.price, values } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeAttributeValueStock( state: ProductVariantCreateFormData, attributeValueSlug: string, - stock: string + warehouseIndex: number, + quantity: number ): ProductVariantCreateFormData { const index = state.stock.values.findIndex( value => value.slug === attributeValueSlug @@ -179,24 +171,19 @@ function changeAttributeValueStock( const values = updateAtIndex( { slug: attributeValueSlug, - value: stock + value: updateAtIndex(quantity, state.stock.value, warehouseIndex) }, state.stock.values, index ); - const data = { + return { ...state, stock: { ...state.stock, values } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeApplyPriceToAttributeId( @@ -210,7 +197,8 @@ function changeApplyPriceToAttributeId( slug, value: "" })); - const data = { + + return { ...state, price: { ...state.price, @@ -218,11 +206,6 @@ function changeApplyPriceToAttributeId( values } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeApplyStockToAttributeId( @@ -234,10 +217,10 @@ function changeApplyStockToAttributeId( ); const values = attribute.values.map(slug => ({ slug, - value: "" + value: [] })); - const data = { + return { ...state, stock: { ...state.stock, @@ -245,47 +228,33 @@ function changeApplyStockToAttributeId( values } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeApplyPriceToAllValue( state: ProductVariantCreateFormData, value: string ): ProductVariantCreateFormData { - const data = { + return { ...state, price: { ...state.price, value } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeApplyStockToAllValue( state: ProductVariantCreateFormData, - value: string + warehouseIndex: number, + quantity: number ): ProductVariantCreateFormData { - const data = { + return { ...state, stock: { ...state.stock, - value + value: updateAtIndex(quantity, state.stock.value, warehouseIndex) } }; - - return { - ...data, - variants: createVariants(data) - }; } function changeVariantData( @@ -299,8 +268,6 @@ function changeVariantData( variant.priceOverride = value; } else if (field === "sku") { variant.sku = value; - } else { - variant.quantity = parseInt(value, 10); } return { @@ -309,6 +276,24 @@ function changeVariantData( }; } +function changeVariantStockData( + state: ProductVariantCreateFormData, + stock: StockInput, + variantIndex: number +): ProductVariantCreateFormData { + const variant = state.variants[variantIndex]; + variant.stocks = update( + stock, + variant.stocks, + (a, b) => a.warehouse === b.warehouse + ); + + return { + ...state, + variants: updateAtIndex(variant, state.variants, variantIndex) + }; +} + function deleteVariant( state: ProductVariantCreateFormData, variantIndex: number @@ -319,6 +304,15 @@ function deleteVariant( }; } +function createVariantMatrix( + state: ProductVariantCreateFormData +): ProductVariantCreateFormData { + return { + ...state, + variants: createVariants(state) + }; +} + function reduceProductVariantCreateFormData( prevState: ProductVariantCreateFormData, action: ProductVariantCreateReducerAction @@ -326,7 +320,6 @@ function reduceProductVariantCreateFormData( switch (action.type) { case "selectValue": return selectValue(prevState, action.attributeId, action.valueId); - case "applyPriceToAll": return applyPriceToAll(prevState, action.all); case "applyStockToAll": @@ -334,7 +327,12 @@ function reduceProductVariantCreateFormData( case "changeAttributeValuePrice": return changeAttributeValuePrice(prevState, action.valueId, action.value); case "changeAttributeValueStock": - return changeAttributeValueStock(prevState, action.valueId, action.value); + return changeAttributeValueStock( + prevState, + action.valueId, + action.quantity, + action.warehouseIndex + ); case "changeApplyPriceToAttributeId": return changeApplyPriceToAttributeId(prevState, action.attributeId); case "changeApplyStockToAttributeId": @@ -342,7 +340,11 @@ function reduceProductVariantCreateFormData( case "changeApplyPriceToAllValue": return changeApplyPriceToAllValue(prevState, action.value); case "changeApplyStockToAllValue": - return changeApplyStockToAllValue(prevState, action.value); + return changeApplyStockToAllValue( + prevState, + action.quantity, + action.warehouseIndex + ); case "changeVariantData": return changeVariantData( prevState, @@ -350,10 +352,16 @@ function reduceProductVariantCreateFormData( action.value, action.variantIndex ); + case "changeVariantStockData": + return changeVariantStockData( + prevState, + action.stock, + action.variantIndex + ); case "deleteVariant": return deleteVariant(prevState, action.variantIndex); case "reload": - return action.data; + return action.data ? action.data : createVariantMatrix(prevState); default: return prevState; } diff --git a/src/products/queries.ts b/src/products/queries.ts index 2749cf718..621f185db 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -1,6 +1,7 @@ import gql from "graphql-tag"; import makeQuery from "@saleor/hooks/makeQuery"; +import { warehouseFragment } from "@saleor/warehouses/queries"; import { pageInfoFragment, TypedQuery } from "../queries"; import { AvailableInGridAttributes, @@ -480,6 +481,7 @@ export const AvailableInGridAttributesQuery = TypedQuery< const createMultipleVariantsData = gql` ${fragmentMoney} ${productVariantAttributesFragment} + ${warehouseFragment} query CreateMultipleVariantsData($id: ID!) { product(id: $id) { ...ProductVariantAttributesFragment @@ -487,6 +489,13 @@ const createMultipleVariantsData = gql` ...Money } } + warehouses(first: 20) { + edges { + node { + ...WarehouseFragment + } + } + } } `; export const useCreateMultipleVariantsData = makeQuery< diff --git a/src/products/types/CreateMultipleVariantsData.ts b/src/products/types/CreateMultipleVariantsData.ts index d73feae44..8b52f63e7 100644 --- a/src/products/types/CreateMultipleVariantsData.ts +++ b/src/products/types/CreateMultipleVariantsData.ts @@ -71,8 +71,25 @@ export interface CreateMultipleVariantsData_product { basePrice: CreateMultipleVariantsData_product_basePrice | null; } +export interface CreateMultipleVariantsData_warehouses_edges_node { + __typename: "Warehouse"; + id: string; + name: string; +} + +export interface CreateMultipleVariantsData_warehouses_edges { + __typename: "WarehouseCountableEdge"; + node: CreateMultipleVariantsData_warehouses_edges_node; +} + +export interface CreateMultipleVariantsData_warehouses { + __typename: "WarehouseCountableConnection"; + edges: CreateMultipleVariantsData_warehouses_edges[]; +} + export interface CreateMultipleVariantsData { product: CreateMultipleVariantsData_product | null; + warehouses: CreateMultipleVariantsData_warehouses | null; } export interface CreateMultipleVariantsDataVariables { diff --git a/src/products/views/ProductVariantCreator/ProductVariantCreator.tsx b/src/products/views/ProductVariantCreator/ProductVariantCreator.tsx index 7428eb267..24b599a32 100644 --- a/src/products/views/ProductVariantCreator/ProductVariantCreator.tsx +++ b/src/products/views/ProductVariantCreator/ProductVariantCreator.tsx @@ -63,6 +63,7 @@ const ProductVariantCreator: React.FC = ({ variables: { id, inputs } }) } + warehouses={data?.warehouses.edges.map(edge => edge.node)} /> ); diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 229e4662b..b9656c878 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -1223,7 +1223,7 @@ export interface ProductVariantBulkCreateInput { costPrice?: any | null; priceOverride?: any | null; sku: string; - quantity?: number | null; + stocks: StockInput[]; trackInventory?: boolean | null; weight?: any | null; } diff --git a/src/warehouses/components/WarehouseList/WarehouseList.tsx b/src/warehouses/components/WarehouseList/WarehouseList.tsx index c8e7c4e3a..68602fb33 100644 --- a/src/warehouses/components/WarehouseList/WarehouseList.tsx +++ b/src/warehouses/components/WarehouseList/WarehouseList.tsx @@ -10,7 +10,6 @@ import IconButton from "@material-ui/core/IconButton"; import DeleteIcon from "@material-ui/icons/Delete"; import EditIcon from "@material-ui/icons/Edit"; -import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; import ResponsiveTable from "@saleor/components/ResponsiveTable"; import Skeleton from "@saleor/components/Skeleton"; import TablePagination from "@saleor/components/TablePagination"; @@ -19,6 +18,7 @@ import { ListProps, SortPage } from "@saleor/types"; import { WarehouseListUrlSortField } from "@saleor/warehouses/urls"; import TableCellHeader from "@saleor/components/TableCellHeader"; import { getArrowDirection } from "@saleor/utils/sort"; +import { WarehouseWithShippingFragment } from "@saleor/warehouses/types/WarehouseWithShippingFragment"; const useStyles = makeStyles( theme => ({ @@ -59,7 +59,7 @@ const useStyles = makeStyles( interface WarehouseListProps extends ListProps, SortPage { - warehouses: WarehouseFragment[]; + warehouses: WarehouseWithShippingFragment[]; onAdd: () => void; onRemove: (id: string) => void; } diff --git a/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx b/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx index 05c66b060..dc3d0b257 100644 --- a/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx +++ b/src/warehouses/components/WarehouseListPage/WarehouseListPage.tsx @@ -3,7 +3,6 @@ import Card from "@material-ui/core/Card"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import SearchBar from "@saleor/components/SearchBar"; @@ -16,6 +15,7 @@ import { } from "@saleor/types"; import { WarehouseListUrlSortField } from "@saleor/warehouses/urls"; import AppHeader from "@saleor/components/AppHeader"; +import { WarehouseWithShippingFragment } from "@saleor/warehouses/types/WarehouseWithShippingFragment"; import WarehouseList from "../WarehouseList"; export interface WarehouseListPageProps @@ -23,7 +23,7 @@ export interface WarehouseListPageProps SearchPageProps, SortPage, TabPageProps { - warehouses: WarehouseFragment[]; + warehouses: WarehouseWithShippingFragment[]; onBack: () => void; onRemove: (id: string) => void; } diff --git a/src/warehouses/queries.ts b/src/warehouses/queries.ts index 6eeefdeb4..8075c0ee0 100644 --- a/src/warehouses/queries.ts +++ b/src/warehouses/queries.ts @@ -13,6 +13,12 @@ export const warehouseFragment = gql` fragment WarehouseFragment on Warehouse { id name + } +`; +export const warehouseWithShippingFragment = gql` + ${warehouseFragment} + fragment WarehouseWithShippingFragment on Warehouse { + ...WarehouseFragment shippingZones(first: 100) { edges { node { @@ -26,9 +32,9 @@ export const warehouseFragment = gql` export const warehouseDetailsFragment = gql` ${fragmentAddress} - ${warehouseFragment} + ${warehouseWithShippingFragment} fragment WarehouseDetailsFragment on Warehouse { - ...WarehouseFragment + ...WarehouseWithShippingFragment address { ...AddressFragment } @@ -36,7 +42,7 @@ export const warehouseDetailsFragment = gql` `; const warehouseList = gql` - ${warehouseFragment} + ${warehouseWithShippingFragment} ${pageInfoFragment} query WarehouseList( $first: Int @@ -56,7 +62,7 @@ const warehouseList = gql` ) { edges { node { - ...WarehouseFragment + ...WarehouseWithShippingFragment } } pageInfo { diff --git a/src/warehouses/types/WarehouseFragment.ts b/src/warehouses/types/WarehouseFragment.ts index 1f6e19e37..0f91a3997 100644 --- a/src/warehouses/types/WarehouseFragment.ts +++ b/src/warehouses/types/WarehouseFragment.ts @@ -6,25 +6,8 @@ // GraphQL fragment: WarehouseFragment // ==================================================== -export interface WarehouseFragment_shippingZones_edges_node { - __typename: "ShippingZone"; - id: string; - name: string; -} - -export interface WarehouseFragment_shippingZones_edges { - __typename: "ShippingZoneCountableEdge"; - node: WarehouseFragment_shippingZones_edges_node; -} - -export interface WarehouseFragment_shippingZones { - __typename: "ShippingZoneCountableConnection"; - edges: WarehouseFragment_shippingZones_edges[]; -} - export interface WarehouseFragment { __typename: "Warehouse"; id: string; name: string; - shippingZones: WarehouseFragment_shippingZones; } diff --git a/src/warehouses/types/WarehouseWithShippingFragment.ts b/src/warehouses/types/WarehouseWithShippingFragment.ts new file mode 100644 index 000000000..85f1ab270 --- /dev/null +++ b/src/warehouses/types/WarehouseWithShippingFragment.ts @@ -0,0 +1,30 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL fragment: WarehouseWithShippingFragment +// ==================================================== + +export interface WarehouseWithShippingFragment_shippingZones_edges_node { + __typename: "ShippingZone"; + id: string; + name: string; +} + +export interface WarehouseWithShippingFragment_shippingZones_edges { + __typename: "ShippingZoneCountableEdge"; + node: WarehouseWithShippingFragment_shippingZones_edges_node; +} + +export interface WarehouseWithShippingFragment_shippingZones { + __typename: "ShippingZoneCountableConnection"; + edges: WarehouseWithShippingFragment_shippingZones_edges[]; +} + +export interface WarehouseWithShippingFragment { + __typename: "Warehouse"; + id: string; + name: string; + shippingZones: WarehouseWithShippingFragment_shippingZones; +}