Merge pull request #515 from mirumee/ref/add-warehouse-stocck
Update product stock management to newest design
This commit is contained in:
commit
02b3717121
31 changed files with 1093 additions and 920 deletions
|
@ -4,6 +4,8 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- Update product stock management to newest design - #515 by @dominik-zeglen
|
||||
|
||||
## 2.10.0
|
||||
|
||||
- Fix minor bugs - #244 by @dominik-zeglen
|
||||
|
|
|
@ -1427,6 +1427,9 @@
|
|||
"src_dot_configuration_dot_1233229030": {
|
||||
"string": "Miscellaneous"
|
||||
},
|
||||
"src_dot_configuration_dot_1440737903": {
|
||||
"string": "Shipping Settings"
|
||||
},
|
||||
"src_dot_configuration_dot_1639245766": {
|
||||
"string": "View and update your webhook and their settings"
|
||||
},
|
||||
|
@ -3074,6 +3077,10 @@
|
|||
"context": "dialog title",
|
||||
"string": "Delete permission group"
|
||||
},
|
||||
"src_dot_permissionGroups_dot_components_dot_PermissionGroupDeleteDialog_dot_956177443": {
|
||||
"context": "deletion error message",
|
||||
"string": "Cant's delete group which is out of your permission scope"
|
||||
},
|
||||
"src_dot_permissionGroups_dot_components_dot_PermissionGroupDetailsPage_dot_3765873075": {
|
||||
"context": "checkbox label",
|
||||
"string": "Group has full access to the store"
|
||||
|
|
|
@ -172,7 +172,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
disabled={loading}
|
||||
errors={updateResult.data?.categoryUpdate.errors || []}
|
||||
onAddCategory={() => navigate(categoryAddUrl(id))}
|
||||
onAddProduct={() => navigate(productAddUrl())}
|
||||
onAddProduct={() => navigate(productAddUrl)}
|
||||
onBack={() =>
|
||||
navigate(
|
||||
maybe(
|
||||
|
|
|
@ -46,7 +46,7 @@ export function searchInCommands(
|
|||
{
|
||||
label: intl.formatMessage(messages.createProduct),
|
||||
onClick: () => {
|
||||
navigate(productAddUrl());
|
||||
navigate(productAddUrl);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { removeAtIndex } from "@saleor/utils/lists";
|
||||
import useStateFromProps from "./useStateFromProps";
|
||||
|
||||
export type FormsetChange<TValue = any> = (id: string, value: TValue) => void;
|
||||
|
@ -11,11 +12,13 @@ export type FormsetData<TData = object, TValue = any> = Array<
|
|||
FormsetAtomicData<TData, TValue>
|
||||
>;
|
||||
export interface UseFormsetOutput<TData = object, TValue = any> {
|
||||
add: (data: FormsetAtomicData<TData, TValue>) => void;
|
||||
change: FormsetChange<TValue>;
|
||||
data: FormsetData<TData, TValue>;
|
||||
get: (id: string) => FormsetAtomicData<TData, TValue>;
|
||||
// Used for some rare situations like dataset change
|
||||
set: (data: FormsetData<TData, TValue>) => void;
|
||||
remove: (id: string) => void;
|
||||
}
|
||||
function useFormset<TData = object, TValue = any>(
|
||||
initial: FormsetData<TData, TValue>
|
||||
|
@ -24,10 +27,23 @@ function useFormset<TData = object, TValue = any>(
|
|||
initial || []
|
||||
);
|
||||
|
||||
function addItem(itemData: FormsetAtomicData<TData, TValue>) {
|
||||
setData(prevData => [...prevData, itemData]);
|
||||
}
|
||||
|
||||
function getItem(id: string): FormsetAtomicData<TData, TValue> {
|
||||
return data.find(item => item.id === id);
|
||||
}
|
||||
|
||||
function removeItem(id: string) {
|
||||
setData(prevData =>
|
||||
removeAtIndex(
|
||||
prevData,
|
||||
prevData.findIndex(item => item.id === id)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function setItemValue(id: string, value: TValue) {
|
||||
const itemIndex = data.findIndex(item => item.id === id);
|
||||
setData([
|
||||
|
@ -41,9 +57,11 @@ function useFormset<TData = object, TValue = any>(
|
|||
}
|
||||
|
||||
return {
|
||||
add: addItem,
|
||||
change: setItemValue,
|
||||
data,
|
||||
get: getItem,
|
||||
remove: removeItem,
|
||||
set: setData
|
||||
};
|
||||
}
|
||||
|
|
|
@ -338,7 +338,10 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
|||
warehouseStock.quantityAllocated;
|
||||
|
||||
return (
|
||||
<TableCell className={classes.colQuantity}>
|
||||
<TableCell
|
||||
className={classes.colQuantity}
|
||||
key={warehouseStock.id}
|
||||
>
|
||||
<div className={classes.colQuantityContent}>
|
||||
<TextField
|
||||
type="number"
|
||||
|
|
|
@ -86,7 +86,6 @@ interface ProductCreatePageProps {
|
|||
fetchCategories: (data: string) => void;
|
||||
fetchCollections: (data: string) => void;
|
||||
fetchProductTypes: (data: string) => void;
|
||||
onWarehouseEdit: () => void;
|
||||
onBack?();
|
||||
onSubmit?(data: ProductCreatePageSubmitData);
|
||||
}
|
||||
|
@ -108,8 +107,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
|||
warehouses,
|
||||
onBack,
|
||||
fetchProductTypes,
|
||||
onSubmit,
|
||||
onWarehouseEdit
|
||||
onSubmit
|
||||
}: ProductCreatePageProps) => {
|
||||
const intl = useIntl();
|
||||
const localizeDate = useDateLocalize();
|
||||
|
@ -119,18 +117,12 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
|||
data: attributes,
|
||||
set: setAttributeData
|
||||
} = useFormset<ProductAttributeInputData>([]);
|
||||
const { change: changeStockData, data: stocks, set: setStocks } = useFormset<
|
||||
null
|
||||
>([]);
|
||||
React.useEffect(() => {
|
||||
const newStocks = warehouses.map(warehouse => ({
|
||||
data: null,
|
||||
id: warehouse.id,
|
||||
label: warehouse.name,
|
||||
value: stocks.find(stock => stock.id === warehouse.id)?.value || 0
|
||||
}));
|
||||
setStocks(newStocks);
|
||||
}, [JSON.stringify(warehouses)]);
|
||||
const {
|
||||
add: addStock,
|
||||
change: changeStockData,
|
||||
data: stocks,
|
||||
remove: removeStock
|
||||
} = useFormset<null, string>([]);
|
||||
|
||||
// Ensures that it will not change after component rerenders, because it
|
||||
// generates different block keys and it causes editor to lose its content.
|
||||
|
@ -253,11 +245,29 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
|||
<ProductStocks
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onChange={changeStockData}
|
||||
onFormDataChange={change}
|
||||
errors={errors}
|
||||
stocks={stocks}
|
||||
onWarehousesEdit={onWarehouseEdit}
|
||||
warehouses={warehouses}
|
||||
onChange={(id, value) => {
|
||||
triggerChange();
|
||||
changeStockData(id, value);
|
||||
}}
|
||||
onWarehouseStockAdd={id => {
|
||||
triggerChange();
|
||||
addStock({
|
||||
data: null,
|
||||
id,
|
||||
label: warehouses.find(
|
||||
warehouse => warehouse.id === id
|
||||
).name,
|
||||
value: "0"
|
||||
});
|
||||
}}
|
||||
onWarehouseStockDelete={id => {
|
||||
triggerChange();
|
||||
removeStock(id);
|
||||
}}
|
||||
/>
|
||||
<CardSpacer />
|
||||
</>
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
||||
import Grow from "@material-ui/core/Grow";
|
||||
import Popper from "@material-ui/core/Popper";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Table from "@material-ui/core/Table";
|
||||
|
@ -11,6 +15,10 @@ import Typography from "@material-ui/core/Typography";
|
|||
import React from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import AddIcon from "@material-ui/icons/Add";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
|
||||
import { FormChange } from "@saleor/hooks/useForm";
|
||||
import { FormsetChange, FormsetAtomicData } from "@saleor/hooks/useFormset";
|
||||
|
@ -21,7 +29,8 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
|||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import Link from "@saleor/components/Link";
|
||||
import { ICONBUTTON_SIZE } from "@saleor/theme";
|
||||
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
||||
|
||||
export type ProductStockInput = FormsetAtomicData<null, string>;
|
||||
export interface ProductStockFormData {
|
||||
|
@ -34,13 +43,19 @@ export interface ProductStocksProps {
|
|||
disabled: boolean;
|
||||
errors: UserError[];
|
||||
stocks: ProductStockInput[];
|
||||
warehouses: WarehouseFragment[];
|
||||
onChange: FormsetChange;
|
||||
onFormDataChange: FormChange;
|
||||
onWarehousesEdit: () => void;
|
||||
onWarehouseStockAdd: (warehouseId: string) => void;
|
||||
onWarehouseStockDelete: (warehouseId: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
colAction: {
|
||||
padding: 0,
|
||||
width: ICONBUTTON_SIZE + theme.spacing()
|
||||
},
|
||||
colName: {},
|
||||
colQuantity: {
|
||||
textAlign: "right",
|
||||
|
@ -56,6 +71,19 @@ const useStyles = makeStyles(
|
|||
inputComponent: {
|
||||
width: 100
|
||||
},
|
||||
menuItem: {
|
||||
"&:not(:last-of-type)": {
|
||||
marginBottom: theme.spacing(2)
|
||||
}
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2)
|
||||
},
|
||||
popper: {
|
||||
boxShadow: `0px 5px 10px 0 ${fade(theme.palette.common.black, 0.05)}`,
|
||||
marginTop: theme.spacing(1),
|
||||
zIndex: 2
|
||||
},
|
||||
quantityContainer: {
|
||||
paddingTop: theme.spacing()
|
||||
},
|
||||
|
@ -80,12 +108,20 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
disabled,
|
||||
errors,
|
||||
stocks,
|
||||
warehouses,
|
||||
onChange,
|
||||
onFormDataChange,
|
||||
onWarehousesEdit
|
||||
onWarehouseStockAdd,
|
||||
onWarehouseStockDelete
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const [isExpanded, setExpansionState] = React.useState(false);
|
||||
|
||||
const warehousesToAssign = warehouses.filter(
|
||||
warehouse => !stocks.some(stock => stock.id === warehouse.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
@ -140,17 +176,6 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
description="header"
|
||||
/>
|
||||
</span>
|
||||
<Button
|
||||
className={classes.editWarehouses}
|
||||
color="primary"
|
||||
data-cy="edit-warehouses"
|
||||
onClick={onWarehousesEdit}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit Warehouses"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
|
@ -169,12 +194,11 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
description="tabel column header"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
stocks,
|
||||
stock => (
|
||||
{renderCollection(stocks, stock => (
|
||||
<TableRow key={stock.id}>
|
||||
<TableCell className={classes.colName}>{stock.label}</TableCell>
|
||||
<TableCell className={classes.colQuantity}>
|
||||
|
@ -191,22 +215,68 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
|||
value={stock.value}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
<FormattedMessage
|
||||
defaultMessage={
|
||||
"This product doesn't have any stock. You can add it <l>here</l>."
|
||||
}
|
||||
values={{
|
||||
l: str => <Link onClick={onWarehousesEdit}>{str}</Link>
|
||||
}}
|
||||
/>
|
||||
<TableCell className={classes.colAction}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => onWarehouseStockDelete(stock.id)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{warehousesToAssign.length > 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
<Typography variant="body2">
|
||||
<FormattedMessage
|
||||
defaultMessage="Assign Warehouse"
|
||||
description="button"
|
||||
/>
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colAction}>
|
||||
<ClickAwayListener onClickAway={() => setExpansionState(false)}>
|
||||
<div ref={anchor}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => setExpansionState(!isExpanded)}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
<Popper
|
||||
className={classes.popper}
|
||||
open={isExpanded}
|
||||
anchorEl={anchor.current}
|
||||
transition
|
||||
placement="top-end"
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin: "right top"
|
||||
}}
|
||||
>
|
||||
<Paper className={classes.paper}>
|
||||
{warehousesToAssign.map(warehouse => (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
onClick={() =>
|
||||
onWarehouseStockAdd(warehouse.id)
|
||||
}
|
||||
>
|
||||
{warehouse.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</div>
|
||||
</ClickAwayListener>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { convertFromRaw, RawDraftContentState } from "draft-js";
|
||||
import { diff } from "fast-array-diff";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
|
@ -23,6 +24,7 @@ import { FetchMoreProps, ListActions } from "@saleor/types";
|
|||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
|
||||
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
||||
import {
|
||||
ProductDetails_product,
|
||||
ProductDetails_product_images,
|
||||
|
@ -62,9 +64,9 @@ export interface ProductUpdatePageProps extends ListActions {
|
|||
product: ProductDetails_product;
|
||||
header: string;
|
||||
saveButtonBarState: ConfirmButtonTransitionState;
|
||||
warehouses: WarehouseFragment[];
|
||||
fetchCategories: (query: string) => void;
|
||||
fetchCollections: (query: string) => void;
|
||||
onWarehousesEdit: () => void;
|
||||
onVariantsAdd: () => void;
|
||||
onVariantShow: (id: string) => () => void;
|
||||
onImageDelete: (id: string) => () => void;
|
||||
|
@ -81,7 +83,9 @@ export interface ProductUpdatePageProps extends ListActions {
|
|||
export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData {
|
||||
attributes: ProductAttributeInput[];
|
||||
collections: string[];
|
||||
stocks: ProductStockInput[];
|
||||
addStocks: ProductStockInput[];
|
||||
updateStocks: ProductStockInput[];
|
||||
removeStocks: string[];
|
||||
}
|
||||
|
||||
export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||
|
@ -99,6 +103,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
product,
|
||||
saveButtonBarState,
|
||||
variants,
|
||||
warehouses,
|
||||
onBack,
|
||||
onDelete,
|
||||
onImageDelete,
|
||||
|
@ -110,7 +115,6 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
onVariantAdd,
|
||||
onVariantsAdd,
|
||||
onVariantShow,
|
||||
onWarehousesEdit,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
|
@ -129,7 +133,12 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
const { change: changeAttributeData, data: attributes } = useFormset(
|
||||
attributeInput
|
||||
);
|
||||
const { change: changeStockData, data: stocks } = useFormset(stockInput);
|
||||
const {
|
||||
add: addStock,
|
||||
change: changeStockData,
|
||||
data: stocks,
|
||||
remove: removeStock
|
||||
} = useFormset(stockInput);
|
||||
|
||||
const [selectedAttributes, setSelectedAttributes] = useStateFromProps<
|
||||
ProductAttributeValueChoices[]
|
||||
|
@ -153,12 +162,25 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
const currency = maybe(() => product.basePrice.currency);
|
||||
const hasVariants = maybe(() => product.productType.hasVariants, false);
|
||||
|
||||
const handleSubmit = (data: ProductUpdatePageFormData) =>
|
||||
const handleSubmit = (data: ProductUpdatePageFormData) => {
|
||||
const dataStocks = stocks.map(stock => stock.id);
|
||||
const variantStocks = product.variants[0].stocks.map(
|
||||
stock => stock.warehouse.id
|
||||
);
|
||||
const stockDiff = diff(variantStocks, dataStocks);
|
||||
|
||||
onSubmit({
|
||||
...data,
|
||||
addStocks: stocks.filter(stock =>
|
||||
stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
),
|
||||
attributes,
|
||||
stocks,
|
||||
...data
|
||||
removeStocks: stockDiff.removed,
|
||||
updateStocks: stocks.filter(
|
||||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
|
||||
|
@ -252,12 +274,27 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
disabled={disabled}
|
||||
errors={errors}
|
||||
stocks={stocks}
|
||||
warehouses={warehouses}
|
||||
onChange={(id, value) => {
|
||||
triggerChange();
|
||||
changeStockData(id, value);
|
||||
}}
|
||||
onFormDataChange={change}
|
||||
onWarehousesEdit={onWarehousesEdit}
|
||||
onWarehouseStockAdd={id => {
|
||||
triggerChange();
|
||||
addStock({
|
||||
data: null,
|
||||
id,
|
||||
label: warehouses.find(
|
||||
warehouse => warehouse.id === id
|
||||
).name,
|
||||
value: "0"
|
||||
});
|
||||
}}
|
||||
onWarehouseStockDelete={id => {
|
||||
triggerChange();
|
||||
removeStock(id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<CardSpacer />
|
||||
|
|
|
@ -51,7 +51,6 @@ interface ProductVariantCreatePageProps {
|
|||
onBack: () => void;
|
||||
onSubmit: (data: ProductVariantCreatePageSubmitData) => void;
|
||||
onVariantClick: (variantId: string) => void;
|
||||
onWarehouseEdit: () => void;
|
||||
}
|
||||
|
||||
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
||||
|
@ -64,8 +63,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
warehouses,
|
||||
onBack,
|
||||
onSubmit,
|
||||
onVariantClick,
|
||||
onWarehouseEdit
|
||||
onVariantClick
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const attributeInput = React.useMemo(
|
||||
|
@ -75,18 +73,12 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
const { change: changeAttributeData, data: attributes } = useFormset(
|
||||
attributeInput
|
||||
);
|
||||
const { change: changeStockData, data: stocks, set: setStocks } = useFormset<
|
||||
null
|
||||
>([]);
|
||||
React.useEffect(() => {
|
||||
const newStocks = warehouses.map(warehouse => ({
|
||||
data: null,
|
||||
id: warehouse.id,
|
||||
label: warehouse.name,
|
||||
value: stocks.find(stock => stock.id === warehouse.id)?.value || 0
|
||||
}));
|
||||
setStocks(newStocks);
|
||||
}, [JSON.stringify(warehouses)]);
|
||||
const {
|
||||
add: addStock,
|
||||
change: changeStockData,
|
||||
data: stocks,
|
||||
remove: removeStock
|
||||
} = useFormset<null, string>([]);
|
||||
|
||||
const initialForm: ProductVariantCreatePageFormData = {
|
||||
costPrice: "",
|
||||
|
@ -148,11 +140,28 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
<ProductStocks
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
onChange={changeStockData}
|
||||
onFormDataChange={change}
|
||||
errors={errors}
|
||||
stocks={stocks}
|
||||
onWarehousesEdit={onWarehouseEdit}
|
||||
warehouses={warehouses}
|
||||
onChange={(id, value) => {
|
||||
triggerChange();
|
||||
changeStockData(id, value);
|
||||
}}
|
||||
onWarehouseStockAdd={id => {
|
||||
triggerChange();
|
||||
addStock({
|
||||
data: null,
|
||||
id,
|
||||
label: warehouses.find(warehouse => warehouse.id === id)
|
||||
.name,
|
||||
value: "0"
|
||||
});
|
||||
}}
|
||||
onWarehouseStockDelete={id => {
|
||||
triggerChange();
|
||||
removeStock(id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import { diff } from "fast-array-diff";
|
||||
|
||||
import AppHeader from "@saleor/components/AppHeader";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
|
@ -17,6 +18,7 @@ import {
|
|||
getAttributeInputFromVariant,
|
||||
getStockInputFromVariant
|
||||
} from "@saleor/products/utils/data";
|
||||
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
||||
import { maybe } from "../../../misc";
|
||||
import { ProductVariant } from "../../types/ProductVariant";
|
||||
import ProductVariantAttributes, {
|
||||
|
@ -38,7 +40,9 @@ export interface ProductVariantPageFormData {
|
|||
export interface ProductVariantPageSubmitData
|
||||
extends ProductVariantPageFormData {
|
||||
attributes: FormsetData<VariantAttributeInputData, string>;
|
||||
stocks: ProductStockInput[];
|
||||
addStocks: ProductStockInput[];
|
||||
updateStocks: ProductStockInput[];
|
||||
removeStocks: string[];
|
||||
}
|
||||
|
||||
interface ProductVariantPageProps {
|
||||
|
@ -48,7 +52,7 @@ interface ProductVariantPageProps {
|
|||
loading?: boolean;
|
||||
placeholderImage?: string;
|
||||
header: string;
|
||||
onWarehousesEdit: () => void;
|
||||
warehouses: WarehouseFragment[];
|
||||
onAdd();
|
||||
onBack();
|
||||
onDelete();
|
||||
|
@ -64,12 +68,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
placeholderImage,
|
||||
saveButtonBarState,
|
||||
variant,
|
||||
warehouses,
|
||||
onAdd,
|
||||
onBack,
|
||||
onDelete,
|
||||
onImageSelect,
|
||||
onSubmit,
|
||||
onWarehousesEdit,
|
||||
onVariantClick
|
||||
}) => {
|
||||
const attributeInput = React.useMemo(
|
||||
|
@ -82,7 +86,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
const { change: changeAttributeData, data: attributes } = useFormset(
|
||||
attributeInput
|
||||
);
|
||||
const { change: changeStockData, data: stocks } = useFormset(stockInput);
|
||||
const {
|
||||
add: addStock,
|
||||
change: changeStockData,
|
||||
data: stocks,
|
||||
remove: removeStock
|
||||
} = useFormset(stockInput);
|
||||
|
||||
const [isModalOpened, setModalStatus] = React.useState(false);
|
||||
const toggleModal = () => setModalStatus(!isModalOpened);
|
||||
|
@ -106,12 +115,23 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
trackInventory: variant?.trackInventory
|
||||
};
|
||||
|
||||
const handleSubmit = (data: ProductVariantPageFormData) =>
|
||||
const handleSubmit = (data: ProductVariantPageFormData) => {
|
||||
const dataStocks = stocks.map(stock => stock.id);
|
||||
const variantStocks = variant.stocks.map(stock => stock.warehouse.id);
|
||||
const stockDiff = diff(variantStocks, dataStocks);
|
||||
|
||||
onSubmit({
|
||||
...data,
|
||||
addStocks: stocks.filter(stock =>
|
||||
stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
),
|
||||
attributes,
|
||||
stocks
|
||||
removeStocks: stockDiff.removed,
|
||||
updateStocks: stocks.filter(
|
||||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -180,12 +200,27 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
disabled={loading}
|
||||
errors={errors}
|
||||
stocks={stocks}
|
||||
warehouses={warehouses}
|
||||
onChange={(id, value) => {
|
||||
triggerChange();
|
||||
changeStockData(id, value);
|
||||
}}
|
||||
onFormDataChange={change}
|
||||
onWarehousesEdit={onWarehousesEdit}
|
||||
onWarehouseStockAdd={id => {
|
||||
triggerChange();
|
||||
addStock({
|
||||
data: null,
|
||||
id,
|
||||
label: warehouses.find(
|
||||
warehouse => warehouse.id === id
|
||||
).name,
|
||||
value: "0"
|
||||
});
|
||||
}}
|
||||
onWarehouseStockDelete={id => {
|
||||
triggerChange();
|
||||
removeStock(id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import Decorator from "@saleor/storybook//Decorator";
|
||||
import { warehouseList } from "@saleor/warehouses/fixtures";
|
||||
import { StockErrorCode } from "@saleor/types/globalTypes";
|
||||
import ProductWarehousesDialog, {
|
||||
ProductWarehousesDialogProps
|
||||
} from "./ProductWarehousesDialog";
|
||||
|
||||
const props: ProductWarehousesDialogProps = {
|
||||
confirmButtonState: "default",
|
||||
disabled: false,
|
||||
errors: [],
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true,
|
||||
warehouses: warehouseList,
|
||||
warehousesWithStocks: [warehouseList[0].id, warehouseList[2].id]
|
||||
};
|
||||
|
||||
storiesOf("Views / Products / Edit warehouses", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ProductWarehousesDialog {...props} />)
|
||||
.add("loading warehouses", () => (
|
||||
<ProductWarehousesDialog {...props} warehouses={undefined} />
|
||||
))
|
||||
.add("loading confirmation", () => (
|
||||
<ProductWarehousesDialog
|
||||
{...props}
|
||||
confirmButtonState="loading"
|
||||
disabled={true}
|
||||
/>
|
||||
))
|
||||
.add("with error", () => (
|
||||
<ProductWarehousesDialog
|
||||
{...props}
|
||||
errors={[
|
||||
{
|
||||
__typename: "StockError",
|
||||
code: StockErrorCode.INVALID,
|
||||
field: null
|
||||
}
|
||||
]}
|
||||
/>
|
||||
));
|
|
@ -1,152 +0,0 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Dialog from "@material-ui/core/Dialog";
|
||||
import DialogActions from "@material-ui/core/DialogActions";
|
||||
import DialogContent from "@material-ui/core/DialogContent";
|
||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl, IntlShape } from "react-intl";
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||
import { diff, DiffData } from "fast-array-diff";
|
||||
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import { isSelected, toggle } from "@saleor/utils/lists";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { BulkStockErrorFragment } from "@saleor/products/types/BulkStockErrorFragment";
|
||||
import { StockErrorFragment } from "@saleor/products/types/StockErrorFragment";
|
||||
import getStockErrorMessage, {
|
||||
getBulkStockErrorMessage
|
||||
} from "@saleor/utils/errors/stock";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
dropShadow: {
|
||||
boxShadow: `0px -5px 10px 0px ${theme.palette.divider}`
|
||||
},
|
||||
errorParagraph: {
|
||||
paddingTop: 0
|
||||
},
|
||||
helperText: {
|
||||
marginBottom: theme.spacing(1)
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "ProductWarehousesDialog"
|
||||
}
|
||||
);
|
||||
|
||||
export interface ProductWarehousesDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
disabled: boolean;
|
||||
errors: Array<BulkStockErrorFragment | StockErrorFragment>;
|
||||
open: boolean;
|
||||
warehouses: SearchWarehouses_search_edges_node[];
|
||||
warehousesWithStocks: string[];
|
||||
onClose: () => void;
|
||||
onConfirm: (data: DiffData<string>) => void;
|
||||
}
|
||||
|
||||
function getErrorMessage(
|
||||
err: BulkStockErrorFragment | StockErrorFragment,
|
||||
intl: IntlShape
|
||||
): string {
|
||||
switch (err?.__typename) {
|
||||
case "BulkStockError":
|
||||
return getBulkStockErrorMessage(err, intl);
|
||||
default:
|
||||
return getStockErrorMessage(err, intl);
|
||||
}
|
||||
}
|
||||
|
||||
const ProductWarehousesDialog: React.FC<ProductWarehousesDialogProps> = ({
|
||||
confirmButtonState,
|
||||
disabled,
|
||||
errors,
|
||||
onClose,
|
||||
onConfirm,
|
||||
open,
|
||||
warehousesWithStocks,
|
||||
warehouses
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
const [selectedWarehouses, setSelectedWarehouses] = useStateFromProps(
|
||||
warehousesWithStocks || []
|
||||
);
|
||||
|
||||
const handleConfirm = () =>
|
||||
onConfirm(diff(warehousesWithStocks, selectedWarehouses));
|
||||
|
||||
return (
|
||||
<Dialog onClose={onClose} maxWidth="sm" fullWidth open={open}>
|
||||
<DialogTitle>
|
||||
<FormattedMessage
|
||||
defaultMessage="Edit Warehouses"
|
||||
description="dialog header"
|
||||
/>
|
||||
</DialogTitle>
|
||||
<form>
|
||||
<DialogContent>
|
||||
<Typography className={classes.helperText}>
|
||||
<FormattedMessage defaultMessage="Select warehouses that stock selected product" />
|
||||
</Typography>
|
||||
{warehouses === undefined ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
warehouses.map(warehouse => (
|
||||
<div key={warehouse.id}>
|
||||
<ControlledCheckbox
|
||||
checked={isSelected(
|
||||
warehouse.id,
|
||||
selectedWarehouses,
|
||||
(a, b) => a === b
|
||||
)}
|
||||
name={`warehouse:${warehouse.id}`}
|
||||
onChange={() =>
|
||||
setSelectedWarehouses(
|
||||
toggle(
|
||||
warehouse.id,
|
||||
selectedWarehouses,
|
||||
(a, b) => a === b
|
||||
)
|
||||
)
|
||||
}
|
||||
disabled={disabled}
|
||||
label={warehouse.name}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</DialogContent>
|
||||
{errors.length > 0 && (
|
||||
<DialogContent className={classes.errorParagraph}>
|
||||
<Typography color="error">
|
||||
{getErrorMessage(errors[0], intl)}
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
)}
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
<FormattedMessage {...buttonMessages.back} />
|
||||
</Button>
|
||||
<ConfirmButton
|
||||
transitionState={confirmButtonState}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<FormattedMessage {...buttonMessages.save} />
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
ProductWarehousesDialog.displayName = "ProductWarehousesDialog";
|
||||
export default ProductWarehousesDialog;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./ProductWarehousesDialog";
|
||||
export * from "./ProductWarehousesDialog";
|
|
@ -19,11 +19,9 @@ import {
|
|||
productVariantAddPath,
|
||||
productVariantEditPath,
|
||||
ProductVariantEditUrlQueryParams,
|
||||
ProductAddUrlQueryParams,
|
||||
ProductVariantAddUrlQueryParams,
|
||||
productVariantCreatorPath
|
||||
} from "./urls";
|
||||
import ProductCreateComponent from "./views/ProductCreate";
|
||||
import ProductCreate from "./views/ProductCreate";
|
||||
import ProductImageComponent from "./views/ProductImage";
|
||||
import ProductListComponent from "./views/ProductList";
|
||||
import ProductUpdateComponent from "./views/ProductUpdate";
|
||||
|
@ -90,17 +88,11 @@ const ProductImage: React.FC<RouteComponentProps<any>> = ({
|
|||
|
||||
const ProductVariantCreate: React.FC<RouteComponentProps<any>> = ({
|
||||
match
|
||||
}) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: ProductVariantAddUrlQueryParams = qs;
|
||||
|
||||
return (
|
||||
}) => (
|
||||
<ProductVariantCreateComponent
|
||||
productId={decodeURIComponent(match.params.id)}
|
||||
params={params}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ProductVariantCreator: React.FC<RouteComponentProps<{
|
||||
id: string;
|
||||
|
@ -108,13 +100,6 @@ const ProductVariantCreator: React.FC<RouteComponentProps<{
|
|||
<ProductVariantCreatorComponent id={decodeURIComponent(match.params.id)} />
|
||||
);
|
||||
|
||||
const ProductCreate: React.FC<RouteComponentProps> = ({ location }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: ProductAddUrlQueryParams = qs;
|
||||
|
||||
return <ProductCreateComponent params={params} />;
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
|
|
@ -38,11 +38,7 @@ import {
|
|||
} from "./types/VariantImageUnassign";
|
||||
import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate";
|
||||
|
||||
import {
|
||||
fragmentVariant,
|
||||
productFragmentDetails,
|
||||
stockFragment
|
||||
} from "./queries";
|
||||
import { fragmentVariant, productFragmentDetails } from "./queries";
|
||||
import {
|
||||
productBulkDelete,
|
||||
productBulkDeleteVariables
|
||||
|
@ -59,10 +55,6 @@ import {
|
|||
ProductVariantBulkDelete,
|
||||
ProductVariantBulkDeleteVariables
|
||||
} from "./types/ProductVariantBulkDelete";
|
||||
import {
|
||||
AddOrRemoveStocks,
|
||||
AddOrRemoveStocksVariables
|
||||
} from "./types/AddOrRemoveStocks";
|
||||
|
||||
export const bulkProductErrorFragment = gql`
|
||||
fragment BulkProductErrorFragment on BulkProductError {
|
||||
|
@ -359,6 +351,8 @@ export const variantUpdateMutation = gql`
|
|||
${fragmentVariant}
|
||||
${productErrorFragment}
|
||||
mutation VariantUpdate(
|
||||
$addStocks: [StockInput!]!
|
||||
$removeStocks: [ID!]!
|
||||
$id: ID!
|
||||
$attributes: [AttributeValueInput]
|
||||
$costPrice: Decimal
|
||||
|
@ -392,6 +386,29 @@ export const variantUpdateMutation = gql`
|
|||
...ProductVariant
|
||||
}
|
||||
}
|
||||
productVariantStocksCreate(stocks: $addStocks, variantId: $id) {
|
||||
errors: bulkStockErrors {
|
||||
...BulkStockErrorFragment
|
||||
}
|
||||
productVariant {
|
||||
id
|
||||
stocks {
|
||||
...StockFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
productVariantStocksDelete(warehouseIds: $removeStocks, variantId: $id) {
|
||||
errors: stockErrors {
|
||||
code
|
||||
field
|
||||
}
|
||||
productVariant {
|
||||
id
|
||||
stocks {
|
||||
...StockFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVariantUpdateMutation = TypedMutation<
|
||||
|
@ -558,41 +575,3 @@ export const TypedProductVariantBulkDeleteMutation = TypedMutation<
|
|||
ProductVariantBulkDelete,
|
||||
ProductVariantBulkDeleteVariables
|
||||
>(ProductVariantBulkDeleteMutation);
|
||||
|
||||
const addOrRemoveStocks = gql`
|
||||
${bulkStockErrorFragment}
|
||||
${stockFragment}
|
||||
mutation AddOrRemoveStocks(
|
||||
$variantId: ID!
|
||||
$add: [StockInput!]!
|
||||
$remove: [ID!]!
|
||||
) {
|
||||
productVariantStocksCreate(stocks: $add, variantId: $variantId) {
|
||||
errors: bulkStockErrors {
|
||||
...BulkStockErrorFragment
|
||||
}
|
||||
productVariant {
|
||||
id
|
||||
stocks {
|
||||
...StockFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
productVariantStocksDelete(warehouseIds: $remove, variantId: $variantId) {
|
||||
errors: stockErrors {
|
||||
code
|
||||
field
|
||||
}
|
||||
productVariant {
|
||||
id
|
||||
stocks {
|
||||
...StockFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const useAddOrRemoveStocks = makeMutation<
|
||||
AddOrRemoveStocks,
|
||||
AddOrRemoveStocksVariables
|
||||
>(addOrRemoveStocks);
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { StockInput, ProductErrorCode, StockErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: AddOrRemoveStocks
|
||||
// ====================================================
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksCreate_errors {
|
||||
__typename: "BulkStockError";
|
||||
code: ProductErrorCode;
|
||||
field: string | null;
|
||||
index: number | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks_warehouse {
|
||||
__typename: "Warehouse";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks {
|
||||
__typename: "Stock";
|
||||
id: string;
|
||||
quantity: number;
|
||||
quantityAllocated: number;
|
||||
warehouse: AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks_warehouse;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksCreate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
stocks: (AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks | null)[] | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksCreate {
|
||||
__typename: "ProductVariantStocksCreate";
|
||||
errors: AddOrRemoveStocks_productVariantStocksCreate_errors[];
|
||||
productVariant: AddOrRemoveStocks_productVariantStocksCreate_productVariant | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksDelete_errors {
|
||||
__typename: "StockError";
|
||||
code: StockErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks_warehouse {
|
||||
__typename: "Warehouse";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks {
|
||||
__typename: "Stock";
|
||||
id: string;
|
||||
quantity: number;
|
||||
quantityAllocated: number;
|
||||
warehouse: AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks_warehouse;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksDelete_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
stocks: (AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks | null)[] | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks_productVariantStocksDelete {
|
||||
__typename: "ProductVariantStocksDelete";
|
||||
errors: AddOrRemoveStocks_productVariantStocksDelete_errors[];
|
||||
productVariant: AddOrRemoveStocks_productVariantStocksDelete_productVariant | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocks {
|
||||
productVariantStocksCreate: AddOrRemoveStocks_productVariantStocksCreate | null;
|
||||
productVariantStocksDelete: AddOrRemoveStocks_productVariantStocksDelete | null;
|
||||
}
|
||||
|
||||
export interface AddOrRemoveStocksVariables {
|
||||
variantId: string;
|
||||
add: StockInput[];
|
||||
remove: string[];
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AttributeValueInput, StockInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||
import { StockInput, AttributeValueInput, ProductErrorCode, StockErrorCode } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: VariantUpdate
|
||||
|
@ -255,12 +255,81 @@ export interface VariantUpdate_productVariantStocksUpdate {
|
|||
productVariant: VariantUpdate_productVariantStocksUpdate_productVariant | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksCreate_errors {
|
||||
__typename: "BulkStockError";
|
||||
code: ProductErrorCode;
|
||||
field: string | null;
|
||||
index: number | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksCreate_productVariant_stocks_warehouse {
|
||||
__typename: "Warehouse";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksCreate_productVariant_stocks {
|
||||
__typename: "Stock";
|
||||
id: string;
|
||||
quantity: number;
|
||||
quantityAllocated: number;
|
||||
warehouse: VariantUpdate_productVariantStocksCreate_productVariant_stocks_warehouse;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksCreate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
stocks: (VariantUpdate_productVariantStocksCreate_productVariant_stocks | null)[] | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksCreate {
|
||||
__typename: "ProductVariantStocksCreate";
|
||||
errors: VariantUpdate_productVariantStocksCreate_errors[];
|
||||
productVariant: VariantUpdate_productVariantStocksCreate_productVariant | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksDelete_errors {
|
||||
__typename: "StockError";
|
||||
code: StockErrorCode;
|
||||
field: string | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksDelete_productVariant_stocks_warehouse {
|
||||
__typename: "Warehouse";
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksDelete_productVariant_stocks {
|
||||
__typename: "Stock";
|
||||
id: string;
|
||||
quantity: number;
|
||||
quantityAllocated: number;
|
||||
warehouse: VariantUpdate_productVariantStocksDelete_productVariant_stocks_warehouse;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksDelete_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
stocks: (VariantUpdate_productVariantStocksDelete_productVariant_stocks | null)[] | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate_productVariantStocksDelete {
|
||||
__typename: "ProductVariantStocksDelete";
|
||||
errors: VariantUpdate_productVariantStocksDelete_errors[];
|
||||
productVariant: VariantUpdate_productVariantStocksDelete_productVariant | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdate {
|
||||
productVariantUpdate: VariantUpdate_productVariantUpdate | null;
|
||||
productVariantStocksUpdate: VariantUpdate_productVariantStocksUpdate | null;
|
||||
productVariantStocksCreate: VariantUpdate_productVariantStocksCreate | null;
|
||||
productVariantStocksDelete: VariantUpdate_productVariantStocksDelete | null;
|
||||
}
|
||||
|
||||
export interface VariantUpdateVariables {
|
||||
addStocks: StockInput[];
|
||||
removeStocks: string[];
|
||||
id: string;
|
||||
attributes?: (AttributeValueInput | null)[] | null;
|
||||
costPrice?: any | null;
|
||||
|
|
|
@ -17,10 +17,7 @@ import {
|
|||
const productSection = "/products/";
|
||||
|
||||
export const productAddPath = urlJoin(productSection, "add");
|
||||
export type ProductAddUrlDialog = "edit-stocks";
|
||||
export type ProductAddUrlQueryParams = Dialog<ProductAddUrlDialog>;
|
||||
export const productAddUrl = (params?: ProductAddUrlQueryParams): string =>
|
||||
productAddPath + "?" + stringifyQs(params);
|
||||
export const productAddUrl = productAddPath;
|
||||
|
||||
export const productListPath = productSection;
|
||||
export type ProductListUrlDialog =
|
||||
|
@ -69,14 +66,14 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string =>
|
|||
productListPath + "?" + stringifyQs(params);
|
||||
|
||||
export const productPath = (id: string) => urlJoin(productSection + id);
|
||||
export type ProductUrlDialog = "edit-stocks" | "remove" | "remove-variants";
|
||||
export type ProductUrlDialog = "remove" | "remove-variants";
|
||||
export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>;
|
||||
export const productUrl = (id: string, params?: ProductUrlQueryParams) =>
|
||||
productPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
export const productVariantEditPath = (productId: string, variantId: string) =>
|
||||
urlJoin(productSection, productId, "variant", variantId);
|
||||
export type ProductVariantEditUrlDialog = "edit-stocks" | "remove";
|
||||
export type ProductVariantEditUrlDialog = "remove";
|
||||
export type ProductVariantEditUrlQueryParams = Dialog<
|
||||
ProductVariantEditUrlDialog
|
||||
>;
|
||||
|
@ -99,17 +96,8 @@ export const productVariantCreatorUrl = (productId: string) =>
|
|||
|
||||
export const productVariantAddPath = (productId: string) =>
|
||||
urlJoin(productSection, productId, "variant/add");
|
||||
export type ProductVariantAddUrlDialog = "edit-stocks";
|
||||
export type ProductVariantAddUrlQueryParams = Dialog<
|
||||
ProductVariantAddUrlDialog
|
||||
>;
|
||||
export const productVariantAddUrl = (
|
||||
productId: string,
|
||||
params?: ProductVariantAddUrlQueryParams
|
||||
): string =>
|
||||
productVariantAddPath(encodeURIComponent(productId)) +
|
||||
"?" +
|
||||
stringifyQs(params);
|
||||
export const productVariantAddUrl = (productId: string): string =>
|
||||
productVariantAddPath(encodeURIComponent(productId));
|
||||
|
||||
export const productImagePath = (productId: string, imageId: string) =>
|
||||
urlJoin(productSection, productId, "image", imageId);
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
ProductDetails_product_variants
|
||||
} from "@saleor/products/types/ProductDetails";
|
||||
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
|
||||
import { StockInput } from "@saleor/types/globalTypes";
|
||||
import { FormsetAtomicData } from "@saleor/hooks/useFormset";
|
||||
import { ProductAttributeInput } from "../components/ProductAttributes";
|
||||
import { VariantAttributeInput } from "../components/ProductVariantAttributes";
|
||||
import { ProductVariant } from "../types/ProductVariant";
|
||||
|
@ -211,3 +213,12 @@ export function getProductUpdatePageFormData(
|
|||
trackInventory: !!product?.variants[0]?.trackInventory
|
||||
};
|
||||
}
|
||||
|
||||
export function mapFormsetStockToStockInput(
|
||||
stock: FormsetAtomicData<null, string>
|
||||
): StockInput {
|
||||
return {
|
||||
quantity: parseInt(stock.value, 10),
|
||||
warehouse: stock.id
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,31 +9,16 @@ import useShop from "@saleor/hooks/useShop";
|
|||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
||||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||
import useProductTypeSearch from "@saleor/searches/useProductTypeSearch";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
|
||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||
import { decimal, maybe } from "../../misc";
|
||||
import ProductCreatePage, {
|
||||
ProductCreatePageSubmitData
|
||||
} from "../components/ProductCreatePage";
|
||||
import { TypedProductCreateMutation } from "../mutations";
|
||||
import { ProductCreate } from "../types/ProductCreate";
|
||||
import {
|
||||
productListUrl,
|
||||
productUrl,
|
||||
ProductAddUrlDialog,
|
||||
ProductAddUrlQueryParams,
|
||||
productAddUrl
|
||||
} from "../urls";
|
||||
import ProductWarehousesDialog from "../components/ProductWarehousesDialog";
|
||||
import { productListUrl, productUrl } from "../urls";
|
||||
|
||||
interface ProductCreateViewProps {
|
||||
params: ProductAddUrlQueryParams;
|
||||
}
|
||||
|
||||
export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
|
||||
params
|
||||
}) => {
|
||||
export const ProductCreateView: React.FC = () => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const shop = useShop();
|
||||
|
@ -59,20 +44,12 @@ export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
|
|||
} = useProductTypeSearch({
|
||||
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||
});
|
||||
const { result: searchWarehousesOpts } = useWarehouseSearch({
|
||||
const warehouses = useWarehouseList({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||
first: 20
|
||||
first: 50
|
||||
}
|
||||
});
|
||||
const [warehouses, setWarehouses] = React.useState<
|
||||
SearchWarehouses_search_edges_node[]
|
||||
>([]);
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
ProductAddUrlDialog,
|
||||
ProductAddUrlQueryParams
|
||||
>(navigate, productAddUrl, params);
|
||||
|
||||
const handleBack = () => navigate(productListUrl());
|
||||
|
||||
|
@ -153,7 +130,6 @@ export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
|
|||
productTypes={maybe(() =>
|
||||
searchProductTypesOpts.data.search.edges.map(edge => edge.node)
|
||||
)}
|
||||
warehouses={warehouses}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmit}
|
||||
saveButtonBarState={productCreateOpts.status}
|
||||
|
@ -178,32 +154,9 @@ export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
|
|||
loading: searchProductTypesOpts.loading,
|
||||
onFetchMore: loadMoreProductTypes
|
||||
}}
|
||||
onWarehouseEdit={() => openModal("edit-stocks")}
|
||||
/>
|
||||
<ProductWarehousesDialog
|
||||
confirmButtonState="default"
|
||||
disabled={false}
|
||||
errors={[]}
|
||||
onClose={closeModal}
|
||||
open={params.action === "edit-stocks"}
|
||||
warehouses={searchWarehousesOpts.data?.search.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
warehousesWithStocks={warehouses.map(warehouse => warehouse.id)}
|
||||
onConfirm={data => {
|
||||
setWarehouses(
|
||||
[
|
||||
...warehouses,
|
||||
...data.added.map(
|
||||
addedId =>
|
||||
searchWarehousesOpts.data.search.edges.find(
|
||||
edge => edge.node.id === addedId
|
||||
).node
|
||||
)
|
||||
].filter(warehouse => !data.removed.includes(warehouse.id))
|
||||
);
|
||||
closeModal();
|
||||
}}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -306,7 +306,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
.hasNextPage,
|
||||
false
|
||||
)}
|
||||
onAdd={() => navigate(productAddUrl())}
|
||||
onAdd={() => navigate(productAddUrl)}
|
||||
disabled={loading}
|
||||
products={maybe(() =>
|
||||
data.products.edges.map(edge => edge.node)
|
||||
|
|
|
@ -16,9 +16,7 @@ import useCategorySearch from "@saleor/searches/useCategorySearch";
|
|||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import ProductWarehousesDialog from "@saleor/products/components/ProductWarehousesDialog";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import { useAddOrRemoveStocks } from "@saleor/products/mutations";
|
||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||
import { getMutationState, maybe } from "../../../misc";
|
||||
import ProductUpdatePage from "../../components/ProductUpdatePage";
|
||||
import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
|
||||
|
@ -71,24 +69,10 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
} = useCollectionSearch({
|
||||
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||
});
|
||||
const { result: searchWarehousesOpts } = useWarehouseSearch({
|
||||
const warehouses = useWarehouseList({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||
first: 20
|
||||
}
|
||||
});
|
||||
|
||||
const [addOrRemoveStocks, addOrRemoveStocksOpts] = useAddOrRemoveStocks({
|
||||
onCompleted: data => {
|
||||
if (
|
||||
data.productVariantStocksCreate.errors.length === 0 &&
|
||||
data.productVariantStocksDelete.errors.length === 0
|
||||
) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
first: 50
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -238,6 +222,11 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
header={maybe(() => product.name)}
|
||||
placeholderImage={placeholderImg}
|
||||
product={product}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(
|
||||
edge => edge.node
|
||||
) || []
|
||||
}
|
||||
variants={maybe(() => product.variants)}
|
||||
onBack={handleBack}
|
||||
onDelete={() => openModal("remove")}
|
||||
|
@ -282,7 +271,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
loading: searchCollectionsOpts.loading,
|
||||
onFetchMore: loadMoreCollections
|
||||
}}
|
||||
onWarehousesEdit={() => openModal("edit-stocks")}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "remove"}
|
||||
|
@ -333,40 +321,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
{!product?.productType?.hasVariants && (
|
||||
<ProductWarehousesDialog
|
||||
confirmButtonState={addOrRemoveStocksOpts.status}
|
||||
disabled={addOrRemoveStocksOpts.loading}
|
||||
errors={[
|
||||
...(addOrRemoveStocksOpts.data
|
||||
?.productVariantStocksCreate.errors || []),
|
||||
...(addOrRemoveStocksOpts.data
|
||||
?.productVariantStocksDelete.errors || [])
|
||||
]}
|
||||
onClose={closeModal}
|
||||
open={params.action === "edit-stocks"}
|
||||
warehouses={searchWarehousesOpts.data?.search.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
warehousesWithStocks={
|
||||
product?.variants[0].stocks.map(
|
||||
stock => stock.warehouse.id
|
||||
) || []
|
||||
}
|
||||
onConfirm={data =>
|
||||
addOrRemoveStocks({
|
||||
variables: {
|
||||
add: data.added.map(id => ({
|
||||
quantity: 0,
|
||||
warehouse: id
|
||||
})),
|
||||
remove: data.removed,
|
||||
variantId: product.variants[0].id
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ProductUpdateVariables } from "@saleor/products/types/ProductUpdate";
|
|||
import { SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate";
|
||||
import { ReorderEvent } from "@saleor/types";
|
||||
import { arrayMove } from "react-sortable-hoc";
|
||||
import { mapFormsetStockToStockInput } from "@saleor/products/utils/data";
|
||||
|
||||
export function createUpdateHandler(
|
||||
product: ProductDetails_product,
|
||||
|
@ -40,17 +41,14 @@ export function createUpdateHandler(
|
|||
} else {
|
||||
updateSimpleProduct({
|
||||
...productVariables,
|
||||
addStocks: [],
|
||||
deleteStocks: [],
|
||||
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
||||
deleteStocks: data.removeStocks,
|
||||
productVariantId: product.variants[0].id,
|
||||
productVariantInput: {
|
||||
sku: data.sku,
|
||||
trackInventory: data.trackInventory
|
||||
},
|
||||
updateStocks: data.stocks.map(stock => ({
|
||||
quantity: parseInt(stock.value, 0),
|
||||
warehouse: stock.id
|
||||
}))
|
||||
updateStocks: data.updateStocks.map(mapFormsetStockToStockInput)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,8 +8,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import { commonMessages } from "@saleor/intl";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||
import { decimal } from "../../misc";
|
||||
import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog";
|
||||
import ProductVariantPage, {
|
||||
|
@ -28,8 +27,7 @@ import {
|
|||
ProductVariantEditUrlQueryParams,
|
||||
ProductVariantEditUrlDialog
|
||||
} from "../urls";
|
||||
import ProductWarehousesDialog from "../components/ProductWarehousesDialog";
|
||||
import { useAddOrRemoveStocks } from "../mutations";
|
||||
import { mapFormsetStockToStockInput } from "../utils/data";
|
||||
|
||||
interface ProductUpdateProps {
|
||||
variantId: string;
|
||||
|
@ -52,28 +50,14 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
setErrors([]);
|
||||
}, [variantId]);
|
||||
|
||||
const { result: searchWarehousesOpts } = useWarehouseSearch({
|
||||
const warehouses = useWarehouseList({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||
first: 20
|
||||
first: 50
|
||||
}
|
||||
});
|
||||
|
||||
const [addOrRemoveStocks, addOrRemoveStocksOpts] = useAddOrRemoveStocks({
|
||||
onCompleted: data => {
|
||||
if (
|
||||
data.productVariantStocksCreate.errors.length === 0 &&
|
||||
data.productVariantStocksDelete.errors.length === 0
|
||||
) {
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
const [openModal] = createDialogActionHandlers<
|
||||
ProductVariantEditUrlDialog,
|
||||
ProductVariantEditUrlQueryParams
|
||||
>(
|
||||
|
@ -151,18 +135,20 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
placeholderImage={placeholderImg}
|
||||
variant={variant}
|
||||
header={variant?.name || variant?.sku}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(
|
||||
edge => edge.node
|
||||
) || []
|
||||
}
|
||||
onAdd={() => navigate(productVariantAddUrl(productId))}
|
||||
onBack={handleBack}
|
||||
onDelete={() =>
|
||||
navigate(
|
||||
productVariantEditUrl(productId, variantId, {
|
||||
action: "remove"
|
||||
})
|
||||
)
|
||||
}
|
||||
onDelete={() => openModal("remove")}
|
||||
onImageSelect={handleImageSelect}
|
||||
onSubmit={(data: ProductVariantPageSubmitData) =>
|
||||
updateVariant.mutate({
|
||||
addStocks: data.addStocks.map(
|
||||
mapFormsetStockToStockInput
|
||||
),
|
||||
attributes: data.attributes.map(attribute => ({
|
||||
id: attribute.id,
|
||||
values: [attribute.value]
|
||||
|
@ -170,18 +156,17 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
costPrice: decimal(data.costPrice),
|
||||
id: variantId,
|
||||
priceOverride: decimal(data.priceOverride),
|
||||
removeStocks: data.removeStocks,
|
||||
sku: data.sku,
|
||||
stocks: data.stocks.map(stock => ({
|
||||
quantity: parseInt(stock.value, 10),
|
||||
warehouse: stock.id
|
||||
})),
|
||||
stocks: data.updateStocks.map(
|
||||
mapFormsetStockToStockInput
|
||||
),
|
||||
trackInventory: data.trackInventory
|
||||
})
|
||||
}
|
||||
onVariantClick={variantId => {
|
||||
navigate(productVariantEditUrl(productId, variantId));
|
||||
}}
|
||||
onWarehousesEdit={() => openModal("edit-stocks")}
|
||||
/>
|
||||
<ProductVariantDeleteDialog
|
||||
confirmButtonState={deleteVariant.opts.status}
|
||||
|
@ -196,36 +181,6 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
open={params.action === "remove"}
|
||||
name={data?.productVariant?.name}
|
||||
/>
|
||||
<ProductWarehousesDialog
|
||||
confirmButtonState={addOrRemoveStocksOpts.status}
|
||||
disabled={addOrRemoveStocksOpts.loading}
|
||||
errors={[
|
||||
...(addOrRemoveStocksOpts.data?.productVariantStocksCreate
|
||||
.errors || []),
|
||||
...(addOrRemoveStocksOpts.data?.productVariantStocksDelete
|
||||
.errors || [])
|
||||
]}
|
||||
onClose={closeModal}
|
||||
open={params.action === "edit-stocks"}
|
||||
warehouses={searchWarehousesOpts.data?.search.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
warehousesWithStocks={
|
||||
variant?.stocks.map(stock => stock.warehouse.id) || []
|
||||
}
|
||||
onConfirm={data =>
|
||||
addOrRemoveStocks({
|
||||
variables: {
|
||||
add: data.added.map(id => ({
|
||||
quantity: 0,
|
||||
warehouse: id
|
||||
})),
|
||||
remove: data.removed,
|
||||
variantId
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -7,10 +7,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
|||
import useShop from "@saleor/hooks/useShop";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
|
||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||
import { decimal } from "../../misc";
|
||||
import ProductVariantCreatePage, {
|
||||
ProductVariantCreatePageSubmitData
|
||||
|
@ -18,43 +15,25 @@ import ProductVariantCreatePage, {
|
|||
import { TypedVariantCreateMutation } from "../mutations";
|
||||
import { TypedProductVariantCreateQuery } from "../queries";
|
||||
import { VariantCreate } from "../types/VariantCreate";
|
||||
import {
|
||||
productUrl,
|
||||
productVariantEditUrl,
|
||||
productListUrl,
|
||||
productVariantAddUrl,
|
||||
ProductVariantAddUrlDialog,
|
||||
ProductVariantAddUrlQueryParams
|
||||
} from "../urls";
|
||||
import ProductWarehousesDialog from "../components/ProductWarehousesDialog";
|
||||
import { productUrl, productVariantEditUrl, productListUrl } from "../urls";
|
||||
|
||||
interface ProductVariantCreateProps {
|
||||
params: ProductVariantAddUrlQueryParams;
|
||||
productId: string;
|
||||
}
|
||||
|
||||
export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
||||
params,
|
||||
productId
|
||||
}) => {
|
||||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const shop = useShop();
|
||||
const intl = useIntl();
|
||||
const { result: searchWarehousesOpts } = useWarehouseSearch({
|
||||
const warehouses = useWarehouseList({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||
first: 20
|
||||
first: 50
|
||||
}
|
||||
});
|
||||
const [warehouses, setWarehouses] = React.useState<
|
||||
SearchWarehouses_search_edges_node[]
|
||||
>([]);
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
ProductVariantAddUrlDialog,
|
||||
ProductVariantAddUrlQueryParams
|
||||
>(navigate, params => productVariantAddUrl(productId, params), params);
|
||||
|
||||
return (
|
||||
<TypedProductVariantCreateQuery displayLoader variables={{ id: productId }}>
|
||||
|
@ -136,37 +115,11 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
|||
onSubmit={handleSubmit}
|
||||
onVariantClick={handleVariantClick}
|
||||
saveButtonBarState={variantCreateResult.status}
|
||||
warehouses={warehouses}
|
||||
onWarehouseEdit={() => openModal("edit-stocks")}
|
||||
/>
|
||||
<ProductWarehousesDialog
|
||||
confirmButtonState="default"
|
||||
disabled={false}
|
||||
errors={[]}
|
||||
onClose={closeModal}
|
||||
open={params.action === "edit-stocks"}
|
||||
warehouses={searchWarehousesOpts.data?.search.edges.map(
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
warehousesWithStocks={warehouses.map(
|
||||
warehouse => warehouse.id
|
||||
)}
|
||||
onConfirm={data => {
|
||||
setWarehouses(
|
||||
[
|
||||
...warehouses,
|
||||
...data.added.map(
|
||||
addedId =>
|
||||
searchWarehousesOpts.data.search.edges.find(
|
||||
edge => edge.node.id === addedId
|
||||
).node
|
||||
)
|
||||
].filter(
|
||||
warehouse => !data.removed.includes(warehouse.id)
|
||||
)
|
||||
);
|
||||
closeModal();
|
||||
}}
|
||||
) || []
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,7 +33,6 @@ storiesOf("Views / Products / Create product", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
onWarehouseEdit={() => undefined}
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
))
|
||||
|
@ -55,7 +54,6 @@ storiesOf("Views / Products / Create product", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
onWarehouseEdit={() => undefined}
|
||||
warehouses={undefined}
|
||||
/>
|
||||
))
|
||||
|
@ -83,7 +81,6 @@ storiesOf("Views / Products / Create product", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
onWarehouseEdit={() => undefined}
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -10,6 +10,7 @@ import ProductUpdatePage, {
|
|||
import { product as productFixture } from "@saleor/products/fixtures";
|
||||
import { ProductUpdatePageFormData } from "@saleor/products/utils/data";
|
||||
import { ProductErrorCode } from "@saleor/types/globalTypes";
|
||||
import { warehouseList } from "@saleor/warehouses/fixtures";
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
const product = productFixture(placeholderImage);
|
||||
|
@ -34,11 +35,11 @@ const props: ProductUpdatePageProps = {
|
|||
onVariantAdd: () => undefined,
|
||||
onVariantShow: () => undefined,
|
||||
onVariantsAdd: () => undefined,
|
||||
onWarehousesEdit: () => undefined,
|
||||
placeholderImage,
|
||||
product,
|
||||
saveButtonBarState: "default",
|
||||
variants: product.variants
|
||||
variants: product.variants,
|
||||
warehouses: warehouseList
|
||||
};
|
||||
|
||||
storiesOf("Views / Products / Product edit", module)
|
||||
|
|
|
@ -24,7 +24,6 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onVariantClick={undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
onWarehouseEdit={() => undefined}
|
||||
/>
|
||||
))
|
||||
.add("with errors", () => (
|
||||
|
@ -55,7 +54,6 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onVariantClick={undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
onWarehouseEdit={() => undefined}
|
||||
/>
|
||||
))
|
||||
.add("when loading data", () => (
|
||||
|
@ -70,7 +68,6 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onVariantClick={undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
onWarehouseEdit={() => undefined}
|
||||
/>
|
||||
))
|
||||
.add("add first variant", () => (
|
||||
|
@ -88,6 +85,5 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onVariantClick={undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
onWarehouseEdit={() => undefined}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from "react";
|
|||
|
||||
import placeholderImage from "@assets/images/placeholder60x60.png";
|
||||
import { ProductErrorCode } from "@saleor/types/globalTypes";
|
||||
import { warehouseList } from "@saleor/warehouses/fixtures";
|
||||
import ProductVariantPage from "../../../products/components/ProductVariantPage";
|
||||
import { variant as variantFixture } from "../../../products/fixtures";
|
||||
import Decorator from "../../Decorator";
|
||||
|
@ -23,7 +24,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
onWarehousesEdit={() => undefined}
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
))
|
||||
.add("when loading data", () => (
|
||||
|
@ -39,7 +40,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
onWarehousesEdit={() => undefined}
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
))
|
||||
.add("attribute errors", () => (
|
||||
|
@ -71,6 +72,6 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
message: "Generic form error",
|
||||
...error
|
||||
}))}
|
||||
onWarehousesEdit={() => undefined}
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
));
|
||||
|
|
Loading…
Reference in a new issue