Add drag-and-drop to allow variants reordering
This commit is contained in:
parent
45d33c3920
commit
88b8950408
13 changed files with 98 additions and 55 deletions
|
@ -15,6 +15,7 @@ interface ImageUploadProps {
|
|||
isActiveClassName?: string;
|
||||
iconContainerClassName?: string;
|
||||
iconContainerActiveClassName?: string;
|
||||
hideUploadIcon?: boolean;
|
||||
onImageUpload: (file: FileList) => void;
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,7 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
|
|||
iconContainerActiveClassName,
|
||||
iconContainerClassName,
|
||||
isActiveClassName,
|
||||
hideUploadIcon,
|
||||
onImageUpload
|
||||
} = props;
|
||||
|
||||
|
@ -82,24 +84,26 @@ export const ImageUpload: React.FC<ImageUploadProps> = props => {
|
|||
[isActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(iconContainerClassName, {
|
||||
[iconContainerActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<input
|
||||
{...getInputProps()}
|
||||
className={classes.fileField}
|
||||
accept="image/*"
|
||||
/>
|
||||
<ImageIcon className={classes.photosIcon} />
|
||||
<Typography className={classes.uploadText}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Drop here to upload"
|
||||
description="image upload"
|
||||
{!hideUploadIcon && (
|
||||
<div
|
||||
className={classNames(iconContainerClassName, {
|
||||
[iconContainerActiveClassName]: isDragActive
|
||||
})}
|
||||
>
|
||||
<input
|
||||
{...getInputProps()}
|
||||
className={classes.fileField}
|
||||
accept="image/*"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
<ImageIcon className={classes.photosIcon} />
|
||||
<Typography className={classes.uploadText}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Drop here to upload"
|
||||
description="image upload"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{children && children({ isDragActive })}
|
||||
</>
|
||||
|
|
|
@ -83,9 +83,6 @@ const useStyles = makeStyles(
|
|||
imageUploadActive: {
|
||||
zIndex: 1
|
||||
},
|
||||
imageUploadIcon: {
|
||||
display: "none"
|
||||
},
|
||||
imageUploadIconActive: {
|
||||
display: "block"
|
||||
},
|
||||
|
@ -253,7 +250,7 @@ const ProductImages: React.FC<ProductImagesProps> = props => {
|
|||
className={classes.imageUpload}
|
||||
isActiveClassName={classes.imageUploadActive}
|
||||
disableClick={true}
|
||||
iconContainerClassName={classes.imageUploadIcon}
|
||||
hideUploadIcon={true}
|
||||
iconContainerActiveClassName={classes.imageUploadIconActive}
|
||||
onImageUpload={handleImageUpload}
|
||||
>
|
||||
|
|
|
@ -18,7 +18,7 @@ import { sectionNames } from "@saleor/intl";
|
|||
import { maybe } from "@saleor/misc";
|
||||
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
|
||||
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
|
||||
import { FetchMoreProps, ListActions } from "@saleor/types";
|
||||
import { FetchMoreProps, ListActions, ReorderAction } from "@saleor/types";
|
||||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
|
@ -73,6 +73,7 @@ export interface ProductUpdatePageProps extends ListActions {
|
|||
fetchCollections: (query: string) => void;
|
||||
onVariantsAdd: () => void;
|
||||
onVariantShow: (id: string) => () => void;
|
||||
onVariantReorder: ReorderAction;
|
||||
onImageDelete: (id: string) => () => void;
|
||||
onBack?();
|
||||
onDelete();
|
||||
|
@ -120,6 +121,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
onVariantAdd,
|
||||
onVariantsAdd,
|
||||
onVariantShow,
|
||||
onVariantReorder,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
|
@ -302,6 +304,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
onRowClick={onVariantShow}
|
||||
onVariantAdd={onVariantAdd}
|
||||
onVariantsAdd={onVariantsAdd}
|
||||
onVariantReorder={onVariantReorder}
|
||||
toolbar={toolbar}
|
||||
isChecked={isChecked}
|
||||
selected={selected}
|
||||
|
|
|
@ -14,6 +14,7 @@ import useFormset, {
|
|||
} from "@saleor/hooks/useFormset";
|
||||
import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data";
|
||||
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -56,6 +57,7 @@ interface ProductVariantCreatePageProps {
|
|||
onBack: () => void;
|
||||
onSubmit: (data: ProductVariantCreatePageSubmitData) => void;
|
||||
onVariantClick: (variantId: string) => void;
|
||||
onVariantReorder: ReorderAction;
|
||||
}
|
||||
|
||||
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
||||
|
@ -69,7 +71,8 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
weightUnit,
|
||||
onBack,
|
||||
onSubmit,
|
||||
onVariantClick
|
||||
onVariantClick,
|
||||
onVariantReorder
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const attributeInput = React.useMemo(
|
||||
|
@ -131,6 +134,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
|||
return onVariantClick(variantId);
|
||||
}
|
||||
}}
|
||||
onReorder={onVariantReorder}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import {
|
||||
SortableTableBody,
|
||||
SortableTableRow
|
||||
} from "@saleor/components/SortableTable";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
@ -26,16 +30,18 @@ const useStyles = makeStyles(
|
|||
cursor: "pointer"
|
||||
},
|
||||
tabActive: {
|
||||
"&:before": {
|
||||
background: theme.palette.primary.main,
|
||||
content: '""',
|
||||
height: "100%",
|
||||
left: 0,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
width: 2
|
||||
},
|
||||
position: "relative"
|
||||
"& > td:first-child": {
|
||||
"&:before": {
|
||||
background: theme.palette.primary.main,
|
||||
content: '""',
|
||||
height: "100%",
|
||||
left: 0,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
width: 2
|
||||
},
|
||||
position: "relative"
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ name: "ProductVariantNavigation" }
|
||||
|
@ -49,10 +55,18 @@ interface ProductVariantNavigationProps {
|
|||
| ProductVariantCreateData_product_variants[];
|
||||
onAdd?: () => void;
|
||||
onRowClick: (variantId: string) => void;
|
||||
onReorder: ReorderAction;
|
||||
}
|
||||
|
||||
const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props => {
|
||||
const { current, fallbackThumbnail, variants, onAdd, onRowClick } = props;
|
||||
const {
|
||||
current,
|
||||
fallbackThumbnail,
|
||||
variants,
|
||||
onAdd,
|
||||
onRowClick,
|
||||
onReorder
|
||||
} = props;
|
||||
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
|
@ -66,18 +80,18 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
|
|||
})}
|
||||
/>
|
||||
<ResponsiveTable>
|
||||
<TableBody>
|
||||
{renderCollection(variants, variant => (
|
||||
<TableRow
|
||||
<SortableTableBody onSortEnd={onReorder}>
|
||||
{renderCollection(variants, (variant, variantIndex) => (
|
||||
<SortableTableRow
|
||||
hover={!!variant}
|
||||
key={variant ? variant.id : "skeleton"}
|
||||
className={classes.link}
|
||||
index={variantIndex}
|
||||
className={classNames(classes.link, {
|
||||
[classes.tabActive]: variant && variant.id === current
|
||||
})}
|
||||
onClick={variant ? () => onRowClick(variant.id) : undefined}
|
||||
>
|
||||
<TableCellAvatar
|
||||
className={classNames({
|
||||
[classes.tabActive]: variant && variant.id === current
|
||||
})}
|
||||
thumbnail={maybe(
|
||||
() => variant.images[0].url,
|
||||
fallbackThumbnail
|
||||
|
@ -86,11 +100,11 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
|
|||
<TableCell className={classes.colName}>
|
||||
{variant ? variant.name || variant.sku : <Skeleton />}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</SortableTableRow>
|
||||
))}
|
||||
{onAdd ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
<TableCell colSpan={3}>
|
||||
<Button color="primary" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add variant"
|
||||
|
@ -110,7 +124,7 @@ const ProductVariantNavigation: React.FC<ProductVariantNavigationProps> = props
|
|||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</SortableTableBody>
|
||||
</ResponsiveTable>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
getAttributeInputFromVariant,
|
||||
getStockInputFromVariant
|
||||
} from "@saleor/products/utils/data";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { diff } from "fast-array-diff";
|
||||
|
@ -60,6 +61,7 @@ interface ProductVariantPageProps {
|
|||
placeholderImage?: string;
|
||||
header: string;
|
||||
warehouses: WarehouseFragment[];
|
||||
onVariantReorder: ReorderAction;
|
||||
onAdd();
|
||||
onBack();
|
||||
onDelete();
|
||||
|
@ -82,7 +84,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
onDelete,
|
||||
onImageSelect,
|
||||
onSubmit,
|
||||
onVariantClick
|
||||
onVariantClick,
|
||||
onVariantReorder
|
||||
}) => {
|
||||
const attributeInput = React.useMemo(
|
||||
() => getAttributeInputFromVariant(variant),
|
||||
|
@ -188,6 +191,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
return onVariantClick(variantId);
|
||||
}
|
||||
}}
|
||||
onReorder={onVariantReorder}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -3,9 +3,7 @@ import Card from "@material-ui/core/Card";
|
|||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Hidden from "@material-ui/core/Hidden";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
|
@ -14,13 +12,17 @@ import Money from "@saleor/components/Money";
|
|||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import {
|
||||
SortableTableBody,
|
||||
SortableTableRow
|
||||
} from "@saleor/components/SortableTable";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import { ProductVariant_costPrice } from "@saleor/fragments/types/ProductVariant";
|
||||
import React from "react";
|
||||
import { FormattedMessage, IntlShape, useIntl } from "react-intl";
|
||||
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions } from "../../../types";
|
||||
import { ListActions, ReorderAction } from "../../../types";
|
||||
import {
|
||||
ProductDetails_product_variants,
|
||||
ProductDetails_product_variants_stocks_warehouse
|
||||
|
@ -171,12 +173,13 @@ interface ProductVariantsProps extends ListActions {
|
|||
disabled: boolean;
|
||||
variants: ProductDetails_product_variants[];
|
||||
fallbackPrice?: ProductVariant_costPrice;
|
||||
onVariantReorder: ReorderAction;
|
||||
onRowClick: (id: string) => () => void;
|
||||
onVariantAdd?();
|
||||
onVariantsAdd?();
|
||||
}
|
||||
|
||||
const numberOfColumns = 5;
|
||||
const numberOfColumns = 6;
|
||||
|
||||
export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
||||
const {
|
||||
|
@ -186,6 +189,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
onRowClick,
|
||||
onVariantAdd,
|
||||
onVariantsAdd,
|
||||
onVariantReorder,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
|
@ -266,6 +270,7 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
items={variants}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
dragRows
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<FormattedMessage
|
||||
|
@ -291,8 +296,8 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
/>
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderCollection(variants, variant => {
|
||||
<SortableTableBody onSortEnd={onVariantReorder}>
|
||||
{renderCollection(variants, (variant, variantIndex) => {
|
||||
const isSelected = variant ? isChecked(variant.id) : false;
|
||||
const numAvailable =
|
||||
variant && variant.stocks
|
||||
|
@ -303,11 +308,12 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
: null;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
<SortableTableRow
|
||||
selected={isSelected}
|
||||
hover={!!variant}
|
||||
onClick={onRowClick(variant.id)}
|
||||
key={variant ? variant.id : "skeleton"}
|
||||
index={variantIndex || 0}
|
||||
className={classes.link}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
|
@ -354,10 +360,10 @@ export const ProductVariants: React.FC<ProductVariantsProps> = props => {
|
|||
)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</SortableTableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</SortableTableBody>
|
||||
</ResponsiveTable>
|
||||
)}
|
||||
</Card>
|
||||
|
|
|
@ -289,6 +289,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
|
||||
onVariantShow={variantId => () =>
|
||||
navigate(productVariantEditUrl(product.id, variantId))}
|
||||
onVariantReorder={() => undefined} // TODO: ...
|
||||
onImageUpload={handleImageUpload}
|
||||
onImageEdit={handleImageEdit}
|
||||
onImageDelete={handleImageDelete}
|
||||
|
|
|
@ -202,6 +202,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
onVariantClick={variantId => {
|
||||
navigate(productVariantEditUrl(productId, variantId));
|
||||
}}
|
||||
onVariantReorder={() => undefined} // TODO: ...
|
||||
/>
|
||||
<ProductVariantDeleteDialog
|
||||
confirmButtonState={deleteVariantOpts.status}
|
||||
|
|
|
@ -126,6 +126,7 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
|||
onBack={handleBack}
|
||||
onSubmit={handleSubmit}
|
||||
onVariantClick={handleVariantClick}
|
||||
onVariantReorder={() => undefined} // TODO: ...
|
||||
saveButtonBarState={variantCreateResult.status}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||
|
|
|
@ -34,6 +34,7 @@ const props: ProductUpdatePageProps = {
|
|||
onImageUpload: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onVariantAdd: () => undefined,
|
||||
onVariantReorder: () => undefined,
|
||||
onVariantShow: () => undefined,
|
||||
onVariantsAdd: () => undefined,
|
||||
placeholderImage,
|
||||
|
|
|
@ -23,6 +23,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -54,6 +55,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -69,6 +71,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -87,6 +90,7 @@ storiesOf("Views / Products / Create product variant", module)
|
|||
onBack={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
|
|
@ -24,6 +24,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onImageSelect={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -41,6 +42,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onImageSelect={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
warehouses={warehouseList}
|
||||
/>
|
||||
|
@ -56,6 +58,7 @@ storiesOf("Views / Products / Product variant details", module)
|
|||
onImageSelect={() => undefined}
|
||||
onSubmit={() => undefined}
|
||||
onVariantClick={() => undefined}
|
||||
onVariantReorder={() => undefined}
|
||||
saveButtonBarState="default"
|
||||
errors={[
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue