Update from saleor/master
This commit is contained in:
parent
9551b50351
commit
bd211a6720
262 changed files with 35477 additions and 13429 deletions
|
@ -10,14 +10,14 @@ import Typography from "@material-ui/core/Typography";
|
|||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
|
||||
import * as backgroundArt from "@assets/images/login-background.svg";
|
||||
import * as saleorDarkLogo from "@assets/images/logo-dark.svg";
|
||||
import * as saleorLightLogo from "@assets/images/logo-light.svg";
|
||||
import backgroundArt from "@assets/images/login-background.svg";
|
||||
import saleorDarkLogo from "@assets/images/logo-dark.svg";
|
||||
import saleorLightLogo from "@assets/images/logo-light.svg";
|
||||
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
||||
import Form from "@saleor/components/Form";
|
||||
import { FormSpacer } from "@saleor/components/FormSpacer";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import i18n from "../../../i18n";
|
||||
import i18n from "@saleor/i18n";
|
||||
|
||||
export interface FormData {
|
||||
email: string;
|
||||
|
|
|
@ -16,6 +16,9 @@ export const fragmentUser = gql`
|
|||
code
|
||||
name
|
||||
}
|
||||
avatar {
|
||||
url
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -20,6 +20,11 @@ export interface TokenAuth_tokenCreate_user_permissions {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface TokenAuth_tokenCreate_user_avatar {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface TokenAuth_tokenCreate_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
|
@ -29,6 +34,7 @@ export interface TokenAuth_tokenCreate_user {
|
|||
isStaff: boolean;
|
||||
note: string | null;
|
||||
permissions: (TokenAuth_tokenCreate_user_permissions | null)[] | null;
|
||||
avatar: TokenAuth_tokenCreate_user_avatar | null;
|
||||
}
|
||||
|
||||
export interface TokenAuth_tokenCreate {
|
||||
|
|
|
@ -14,6 +14,11 @@ export interface User_permissions {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface User_avatar {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
|
@ -23,4 +28,5 @@ export interface User {
|
|||
isStaff: boolean;
|
||||
note: string | null;
|
||||
permissions: (User_permissions | null)[] | null;
|
||||
avatar: User_avatar | null;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,11 @@ export interface VerifyToken_tokenVerify_user_permissions {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface VerifyToken_tokenVerify_user_avatar {
|
||||
__typename: "Image";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface VerifyToken_tokenVerify_user {
|
||||
__typename: "User";
|
||||
id: string;
|
||||
|
@ -23,6 +28,7 @@ export interface VerifyToken_tokenVerify_user {
|
|||
isStaff: boolean;
|
||||
note: string | null;
|
||||
permissions: (VerifyToken_tokenVerify_user_permissions | null)[] | null;
|
||||
avatar: VerifyToken_tokenVerify_user_avatar | null;
|
||||
}
|
||||
|
||||
export interface VerifyToken_tokenVerify {
|
||||
|
|
|
@ -18,9 +18,9 @@ import Checkbox from "@saleor/components/Checkbox";
|
|||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { renderCollection } from "../../../misc";
|
||||
import { ListActions, ListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -67,11 +67,14 @@ interface CategoryListProps
|
|||
onAdd?();
|
||||
}
|
||||
|
||||
const numberOfColumns = 4;
|
||||
|
||||
const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
||||
({
|
||||
categories,
|
||||
classes,
|
||||
disabled,
|
||||
settings,
|
||||
isRoot,
|
||||
pageInfo,
|
||||
isChecked,
|
||||
|
@ -82,6 +85,7 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
|||
onAdd,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick
|
||||
}: CategoryListProps) => (
|
||||
<Card>
|
||||
|
@ -97,6 +101,7 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
|||
)}
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={categories}
|
||||
|
@ -116,9 +121,11 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={4}
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
|
@ -144,6 +151,7 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(category.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
@ -173,7 +181,7 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{isRoot
|
||||
? i18n.t("No categories found")
|
||||
: i18n.t("No subcategories found")}
|
||||
|
|
|
@ -4,8 +4,8 @@ import React from "react";
|
|||
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import CategoryList from "../CategoryList";
|
||||
|
||||
export interface CategoryTableProps extends PageListProps, ListActions {
|
||||
|
@ -24,9 +24,11 @@ export interface CategoryTableProps extends PageListProps, ListActions {
|
|||
export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
|
||||
categories,
|
||||
disabled,
|
||||
settings,
|
||||
onAdd,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
pageInfo,
|
||||
isChecked,
|
||||
|
@ -46,9 +48,11 @@ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
|
|||
onAdd={onAdd}
|
||||
onRowClick={onRowClick}
|
||||
disabled={disabled}
|
||||
settings={settings}
|
||||
isRoot={true}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
isChecked={isChecked}
|
||||
selected={selected}
|
||||
|
|
|
@ -40,6 +40,10 @@ export const CategoryProductsCard: React.StatelessComponent<
|
|||
}
|
||||
/>
|
||||
<ProductList
|
||||
settings={{
|
||||
columns: ["isPublished", "price", "productType"],
|
||||
rowNumber: undefined
|
||||
}}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
|
|
|
@ -5,13 +5,14 @@ import React from "react";
|
|||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useListSettings from "@saleor/hooks/useListSettings";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { PAGINATE_BY } from "../../config";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import { CategoryListPage } from "../components/CategoryListPage/CategoryListPage";
|
||||
import { TypedCategoryBulkDeleteMutation } from "../mutations";
|
||||
import { TypedRootCategoriesQuery } from "../queries";
|
||||
|
@ -35,8 +36,10 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
|||
const { isSelected, listElements, toggle, toggleAll, reset } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.CATEGORY_LIST
|
||||
);
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
return (
|
||||
<TypedRootCategoriesQuery displayLoader variables={paginationState}>
|
||||
{({ data, loading, refetch }) => {
|
||||
|
@ -73,11 +76,13 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
|||
() => data.categories.edges.map(edge => edge.node),
|
||||
[]
|
||||
)}
|
||||
settings={settings}
|
||||
onAdd={() => navigate(categoryAddUrl())}
|
||||
onRowClick={id => () => navigate(categoryUrl(id))}
|
||||
disabled={loading}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
|
|
|
@ -17,9 +17,9 @@ import Skeleton from "@saleor/components/Skeleton";
|
|||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions, ListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -50,13 +50,17 @@ interface CollectionListProps
|
|||
collections: CollectionList_collections_edges_node[];
|
||||
}
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
const CollectionList = withStyles(styles, { name: "CollectionList" })(
|
||||
({
|
||||
classes,
|
||||
collections,
|
||||
disabled,
|
||||
settings,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
pageInfo,
|
||||
isChecked,
|
||||
|
@ -68,6 +72,7 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
|
|||
<Card>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={collections}
|
||||
|
@ -89,9 +94,11 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={5}
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
|
@ -116,6 +123,7 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(collection.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
@ -151,7 +159,7 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No collections found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
@ -4,8 +4,8 @@ import React from "react";
|
|||
|
||||
import { Container } from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { CollectionList_collections_edges_node } from "../../types/CollectionList";
|
||||
import CollectionList from "../CollectionList/CollectionList";
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ import CardTitle from "@saleor/components/CardTitle";
|
|||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
|
@ -29,12 +31,27 @@ import { CollectionDetails_collection } from "../../types/CollectionDetails";
|
|||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
iconCell: {
|
||||
colActions: {
|
||||
"&:last-child": {
|
||||
paddingRight: 0
|
||||
},
|
||||
width: 48 + theme.spacing.unit / 2
|
||||
},
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colNameLabel: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPublished: {
|
||||
width: 200
|
||||
},
|
||||
colType: {
|
||||
width: 200
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableRow: {
|
||||
cursor: "pointer"
|
||||
}
|
||||
|
@ -48,6 +65,8 @@ export interface CollectionProductsProps
|
|||
onProductUnassign: (id: string, event: React.MouseEvent<any>) => void;
|
||||
}
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
|
||||
({
|
||||
classes,
|
||||
|
@ -89,24 +108,32 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
|
|||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table>
|
||||
<Table className={classes.table}>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={maybe(() => collection.products.edges.map(edge => edge.node))}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell>{i18n.t("Name", { context: "table header" })}</TableCell>
|
||||
<TableCell>{i18n.t("Type", { context: "table header" })}</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameLabel}>
|
||||
{i18n.t("Name", { context: "table header" })}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colType}>
|
||||
{i18n.t("Type", { context: "table header" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{i18n.t("Published", { context: "table header" })}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colActions} />
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={6}
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={maybe(() => pageInfo.hasNextPage)}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={maybe(() => pageInfo.hasPreviousPage)}
|
||||
|
@ -132,22 +159,23 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
/>
|
||||
<TableCell>
|
||||
>
|
||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colType}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => product.productType.name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{maybe(
|
||||
() => (
|
||||
<StatusLabel
|
||||
|
@ -162,7 +190,7 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.iconCell}>
|
||||
<TableCell className={classes.colActions}>
|
||||
<IconButton
|
||||
disabled={!product}
|
||||
onClick={event => onProductUnassign(product.id, event)}
|
||||
|
@ -176,7 +204,9 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
|
|||
() => (
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell colSpan={6}>{i18n.t("No products found")}</TableCell>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No products found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -22,6 +22,46 @@ export const collections: CollectionList_collections_edges_node[] = [
|
|||
__typename: "ProductCountableConnection",
|
||||
totalCount: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
id: "Q29sbGVjdGlvbjoz",
|
||||
isPublished: true,
|
||||
name: "Vintage vibes",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
totalCount: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
id: "Q29sbGVjdGlvbjoa",
|
||||
isPublished: true,
|
||||
name: "Merry Christmas",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
totalCount: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
id: "Q29sbGVjdGlvbjob",
|
||||
isPublished: true,
|
||||
name: "80s Miami",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
totalCount: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
__typename: "Collection",
|
||||
id: "Q29sbGVjdGlvbjoc",
|
||||
isPublished: true,
|
||||
name: "Yellow Submarine 2019",
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
totalCount: 4
|
||||
}
|
||||
}
|
||||
];
|
||||
export const collection: (
|
||||
|
|
|
@ -287,14 +287,12 @@ export const CollectionDetails: React.StatelessComponent<
|
|||
open={params.action === "assign"}
|
||||
onFetch={search}
|
||||
loading={result.loading}
|
||||
onClose={() => navigate(collectionUrl(id), true, true)}
|
||||
onSubmit={formData =>
|
||||
onClose={closeModal}
|
||||
onSubmit={products =>
|
||||
assignProduct.mutate({
|
||||
...paginationState,
|
||||
collectionId: id,
|
||||
productIds: formData.products.map(
|
||||
product => product.id
|
||||
)
|
||||
productIds: products.map(product => product.id)
|
||||
})
|
||||
}
|
||||
products={maybe(() =>
|
||||
|
|
|
@ -6,14 +6,15 @@ import React from "react";
|
|||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useListSettings from "@saleor/hooks/useListSettings";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { PAGINATE_BY } from "../../config";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import CollectionListPage from "../components/CollectionListPage/CollectionListPage";
|
||||
import {
|
||||
TypedCollectionBulkDelete,
|
||||
|
@ -43,6 +44,9 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.COLLECTION_LIST
|
||||
);
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
|
@ -62,7 +66,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
})
|
||||
);
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
return (
|
||||
<TypedCollectionListQuery displayLoader variables={paginationState}>
|
||||
{({ data, loading, refetch }) => {
|
||||
|
@ -129,8 +133,10 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
|
|||
collections={maybe(() =>
|
||||
data.collections.edges.map(edge => edge.node)
|
||||
)}
|
||||
settings={settings}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
onRowClick={id => () => navigate(collectionUrl(id))}
|
||||
toolbar={
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
|
|
|
@ -7,12 +7,13 @@ import {
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
|
||||
import { AddressTypeInput } from "../../customers/types";
|
||||
import i18n from "../../i18n";
|
||||
import { maybe } from "../../misc";
|
||||
import { FormErrors } from "../../types";
|
||||
import { AddressTypeInput } from "@saleor/customers/types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { FormErrors } from "@saleor/types";
|
||||
import FormSpacer from "../FormSpacer";
|
||||
import SingleAutocompleteSelectField from "../SingleAutocompleteSelectField";
|
||||
import SingleAutocompleteSelectField, {
|
||||
SingleAutocompleteChoiceType
|
||||
} from "../SingleAutocompleteSelectField";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -24,24 +25,25 @@ const styles = (theme: Theme) =>
|
|||
});
|
||||
|
||||
interface AddressEditProps extends WithStyles<typeof styles> {
|
||||
countries?: Array<{
|
||||
code: string;
|
||||
label: string;
|
||||
}>;
|
||||
countries: SingleAutocompleteChoiceType[];
|
||||
countryDisplayValue: string;
|
||||
data: AddressTypeInput;
|
||||
disabled?: boolean;
|
||||
errors: FormErrors<keyof AddressTypeInput>;
|
||||
onChange(event: React.ChangeEvent<any>);
|
||||
onCountryChange(event: React.ChangeEvent<any>);
|
||||
}
|
||||
|
||||
const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
|
||||
({
|
||||
classes,
|
||||
countries,
|
||||
countryDisplayValue,
|
||||
data,
|
||||
disabled,
|
||||
errors,
|
||||
onChange
|
||||
onChange,
|
||||
onCountryChange
|
||||
}: AddressEditProps) => (
|
||||
<>
|
||||
<div className={classes.root}>
|
||||
|
@ -152,16 +154,14 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
|
|||
<div>
|
||||
<SingleAutocompleteSelectField
|
||||
disabled={disabled}
|
||||
displayValue={countryDisplayValue}
|
||||
error={!!errors.country}
|
||||
helperText={errors.country}
|
||||
label={i18n.t("Country")}
|
||||
name="country"
|
||||
onChange={onChange}
|
||||
onChange={onCountryChange}
|
||||
value={data.country}
|
||||
choices={maybe(
|
||||
() => countries.map(c => ({ ...c, value: c.code })),
|
||||
[]
|
||||
)}
|
||||
choices={countries}
|
||||
InputProps={{
|
||||
autoComplete: "off"
|
||||
}}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
||||
import Grow from "@material-ui/core/Grow";
|
||||
import Hidden from "@material-ui/core/Hidden";
|
||||
import LinearProgress from "@material-ui/core/LinearProgress";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Menu from "@material-ui/core/MenuList";
|
||||
|
@ -12,23 +14,25 @@ import {
|
|||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { RouteComponentProps, withRouter } from "react-router";
|
||||
|
||||
import * as saleorDarkLogo from "@assets/images/logo-dark.svg";
|
||||
import * as saleorLightLogo from "@assets/images/logo-light.svg";
|
||||
import saleorDarkLogoSmall from "@assets/logo-dark-small.svg";
|
||||
import saleorDarkLogo from "@assets/logo-dark.svg";
|
||||
import menuArrowIcon from "@assets/menu-arrow-icon.svg";
|
||||
import AppProgressProvider from "@saleor/components/AppProgress";
|
||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import useUser from "@saleor/hooks/useUser";
|
||||
import i18n from "../../i18n";
|
||||
import ArrowDropdown from "../../icons/ArrowDropdown";
|
||||
import i18n from "@saleor/i18n";
|
||||
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
|
||||
import Container from "../Container";
|
||||
import AppActionContext from "./AppActionContext";
|
||||
import AppHeaderContext from "./AppHeaderContext";
|
||||
import { appLoaderHeight, drawerWidth } from "./consts";
|
||||
import { appLoaderHeight, drawerWidth, drawerWidthExpanded } from "./consts";
|
||||
import MenuList from "./MenuList";
|
||||
import menuStructure from "./menuStructure";
|
||||
import ResponsiveDrawer from "./ResponsiveDrawer";
|
||||
|
@ -51,9 +55,18 @@ const styles = (theme: Theme) =>
|
|||
transition: theme.transitions.duration.standard + "ms"
|
||||
},
|
||||
content: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
minHeight: `calc(100vh - ${appLoaderHeight}px)`
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
paddingLeft: 0
|
||||
},
|
||||
paddingLeft: drawerWidthExpanded,
|
||||
transition: "padding-left 0.5s ease",
|
||||
width: "100%"
|
||||
},
|
||||
contentToggle: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
paddingLeft: 0
|
||||
},
|
||||
paddingLeft: drawerWidth
|
||||
},
|
||||
darkThemeSwitch: {
|
||||
marginRight: theme.spacing.unit * 2
|
||||
|
@ -67,20 +80,72 @@ const styles = (theme: Theme) =>
|
|||
hide: {
|
||||
opacity: 0
|
||||
},
|
||||
isMenuSmall: {
|
||||
"& path": {
|
||||
fill: theme.palette.primary.main
|
||||
},
|
||||
"& span": {
|
||||
margin: "0 8px"
|
||||
},
|
||||
"& svg": {
|
||||
marginTop: 12,
|
||||
transform: "rotate(180deg)"
|
||||
},
|
||||
"&:hover": {
|
||||
background: "#E6F3F3"
|
||||
},
|
||||
background: theme.palette.background.paper,
|
||||
border: `solid 1px #EAEAEA`,
|
||||
borderRadius: "50%",
|
||||
cursor: "pointer",
|
||||
height: 32,
|
||||
position: "absolute",
|
||||
right: -16,
|
||||
top: 65,
|
||||
transition: `background ${theme.transitions.duration.shorter}ms`,
|
||||
width: 32,
|
||||
zIndex: 99
|
||||
},
|
||||
isMenuSmallDark: {
|
||||
"&:hover": {
|
||||
background: `linear-gradient(0deg, rgba(25, 195, 190, 0.1), rgba(25, 195, 190, 0.1)), ${
|
||||
theme.palette.background.paper
|
||||
}`
|
||||
},
|
||||
border: `solid 1px #252728`,
|
||||
transition: `background ${theme.transitions.duration.shorter}ms`
|
||||
},
|
||||
isMenuSmallHide: {
|
||||
"& svg": {
|
||||
transform: "rotate(0deg)"
|
||||
}
|
||||
},
|
||||
logo: {
|
||||
"& svg": {
|
||||
height: "100%"
|
||||
height: "100%",
|
||||
margin: "20px 50px"
|
||||
},
|
||||
background: theme.palette.secondary.main,
|
||||
display: "block",
|
||||
height: 28
|
||||
height: 80
|
||||
},
|
||||
logoDark: {
|
||||
"& path": {
|
||||
fill: theme.palette.common.white
|
||||
},
|
||||
background: theme.palette.primary.main
|
||||
},
|
||||
logoSmall: {
|
||||
"& svg": {
|
||||
margin: "0px 25px"
|
||||
}
|
||||
},
|
||||
menu: {
|
||||
marginTop: theme.spacing.unit * 4
|
||||
background: theme.palette.background.paper,
|
||||
height: "100vh",
|
||||
padding: 25
|
||||
},
|
||||
menuIcon: {
|
||||
[theme.breakpoints.up("md")]: {
|
||||
display: "none"
|
||||
},
|
||||
"& span": {
|
||||
"&:nth-child(1)": {
|
||||
top: 15
|
||||
|
@ -101,6 +166,9 @@ const styles = (theme: Theme) =>
|
|||
transition: ".25s ease-in-out",
|
||||
width: "60%"
|
||||
},
|
||||
[theme.breakpoints.up("md")]: {
|
||||
display: "none"
|
||||
},
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: "50%",
|
||||
cursor: "pointer",
|
||||
|
@ -109,7 +177,7 @@ const styles = (theme: Theme) =>
|
|||
marginRight: theme.spacing.unit * 2,
|
||||
position: "relative",
|
||||
transform: "rotate(0deg)",
|
||||
transition: ".2s ease-in-out",
|
||||
transition: `${theme.transitions.duration.shorter}ms ease-in-out`,
|
||||
width: 42
|
||||
},
|
||||
menuIconDark: {
|
||||
|
@ -135,15 +203,16 @@ const styles = (theme: Theme) =>
|
|||
position: "absolute",
|
||||
zIndex: 1999
|
||||
},
|
||||
menuSmall: {
|
||||
background: theme.palette.background.paper,
|
||||
height: "100vh",
|
||||
padding: 25
|
||||
},
|
||||
popover: {
|
||||
zIndex: 1
|
||||
},
|
||||
root: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
gridTemplateColumns: "1fr"
|
||||
},
|
||||
display: "grid",
|
||||
gridTemplateColumns: `${drawerWidth}px 1fr`
|
||||
width: `100%`
|
||||
},
|
||||
rotate: {
|
||||
transform: "rotate(180deg)"
|
||||
|
@ -153,7 +222,7 @@ const styles = (theme: Theme) =>
|
|||
padding: 0
|
||||
},
|
||||
background: theme.palette.background.paper,
|
||||
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 4}px`
|
||||
padding: `0 ${theme.spacing.unit * 4}px`
|
||||
},
|
||||
spacer: {
|
||||
flex: 1
|
||||
|
@ -163,8 +232,8 @@ const styles = (theme: Theme) =>
|
|||
display: "flex"
|
||||
},
|
||||
userChip: {
|
||||
backgroundColor: theme.palette.common.white,
|
||||
border: `1px solid ${theme.palette.grey[200]}`
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary
|
||||
},
|
||||
userMenuContainer: {
|
||||
position: "relative"
|
||||
|
@ -191,7 +260,7 @@ interface AppLayoutProps {
|
|||
const AppLayout = withStyles(styles, {
|
||||
name: "AppLayout"
|
||||
})(
|
||||
withRouter<AppLayoutProps & RouteComponentProps<any>>(
|
||||
withRouter<AppLayoutProps & RouteComponentProps<any>, any>(
|
||||
({
|
||||
classes,
|
||||
children,
|
||||
|
@ -200,6 +269,7 @@ const AppLayout = withStyles(styles, {
|
|||
WithStyles<typeof styles> &
|
||||
RouteComponentProps<any>) => {
|
||||
const { isDark, toggleTheme } = useTheme();
|
||||
const [isMenuSmall, setMenuSmall] = useLocalStorage("isMenuSmall", false);
|
||||
const [isDrawerOpened, setDrawerState] = React.useState(false);
|
||||
const [isMenuOpened, setMenuState] = React.useState(false);
|
||||
const appActionAnchor = React.useRef<HTMLDivElement>();
|
||||
|
@ -223,6 +293,10 @@ const AppLayout = withStyles(styles, {
|
|||
navigate(url);
|
||||
};
|
||||
|
||||
const handleIsMenuSmall = () => {
|
||||
setMenuSmall(!isMenuSmall);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppProgressProvider>
|
||||
{({ isProgress }) => (
|
||||
|
@ -239,14 +313,37 @@ const AppLayout = withStyles(styles, {
|
|||
<ResponsiveDrawer
|
||||
onClose={() => setDrawerState(false)}
|
||||
open={isDrawerOpened}
|
||||
small={!isMenuSmall}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.logo, {
|
||||
[classes.logoSmall]: isMenuSmall,
|
||||
[classes.logoDark]: isDark
|
||||
})}
|
||||
>
|
||||
<SVG
|
||||
className={classes.logo}
|
||||
src={isDark ? saleorDarkLogo : saleorLightLogo}
|
||||
src={
|
||||
isMenuSmall ? saleorDarkLogoSmall : saleorDarkLogo
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Hidden smDown>
|
||||
<div
|
||||
className={classNames(classes.isMenuSmall, {
|
||||
[classes.isMenuSmallHide]: isMenuSmall,
|
||||
[classes.isMenuSmallDark]: isDark
|
||||
})}
|
||||
onClick={handleIsMenuSmall}
|
||||
>
|
||||
<SVG src={menuArrowIcon} />
|
||||
</div>
|
||||
</Hidden>
|
||||
<MenuList
|
||||
className={classes.menu}
|
||||
className={
|
||||
isMenuSmall ? classes.menuSmall : classes.menu
|
||||
}
|
||||
menuItems={menuStructure}
|
||||
isMenuSmall={!isMenuSmall}
|
||||
location={location.pathname}
|
||||
user={user}
|
||||
renderConfigure={true}
|
||||
|
@ -254,7 +351,11 @@ const AppLayout = withStyles(styles, {
|
|||
/>
|
||||
</ResponsiveDrawer>
|
||||
</div>
|
||||
<div className={classes.content}>
|
||||
<div
|
||||
className={classNames(classes.content, {
|
||||
[classes.contentToggle]: isMenuSmall
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<Container>
|
||||
<div className={classes.header}>
|
||||
|
@ -283,6 +384,11 @@ const AppLayout = withStyles(styles, {
|
|||
ref={anchor}
|
||||
>
|
||||
<Chip
|
||||
avatar={
|
||||
user.avatar && (
|
||||
<Avatar alt="user" src={user.avatar.url} />
|
||||
)
|
||||
}
|
||||
className={classes.userChip}
|
||||
label={
|
||||
<>
|
||||
|
|
|
@ -7,8 +7,11 @@ import {
|
|||
import Typography from "@material-ui/core/Typography";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
import { matchPath } from "react-router";
|
||||
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import configureIcon from "@assets/images/menu-configure-icon.svg";
|
||||
import { User } from "../../auth/types/User";
|
||||
import { configurationMenu, configurationMenuUrl } from "../../configuration";
|
||||
import i18n from "../../i18n";
|
||||
|
@ -19,6 +22,44 @@ import { IMenuItem } from "./menuStructure";
|
|||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
menuIcon: {
|
||||
"& svg": {
|
||||
height: 32,
|
||||
width: 32
|
||||
},
|
||||
display: "inline-block",
|
||||
position: "relative",
|
||||
top: 8
|
||||
},
|
||||
menuIconDark: {
|
||||
"& path": {
|
||||
fill: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
menuIsActive: {
|
||||
boxShadow: "0px 0px 12px 1px rgba(0,0,0,0.2)"
|
||||
},
|
||||
menuItemHover: {
|
||||
"& path": {
|
||||
transition: "fill 0.5s ease"
|
||||
},
|
||||
"&:hover": {
|
||||
"& path": {
|
||||
fill: theme.palette.primary.main
|
||||
},
|
||||
"&:before": {
|
||||
borderLeft: `solid 2px ${theme.palette.primary.main}`,
|
||||
content: "''",
|
||||
height: 33,
|
||||
left: -25,
|
||||
position: "absolute",
|
||||
top: 8
|
||||
},
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
cursor: "pointer",
|
||||
position: "relative"
|
||||
},
|
||||
menuList: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
@ -28,24 +69,33 @@ const styles = (theme: Theme) =>
|
|||
paddingBottom: theme.spacing.unit * 3
|
||||
},
|
||||
menuListItem: {
|
||||
"&:hover": {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
alignItems: "center",
|
||||
display: "block",
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
marginBottom: theme.spacing.unit * 5,
|
||||
paddingLeft: 0,
|
||||
textDecoration: "none",
|
||||
transition: theme.transitions.duration.standard + "ms"
|
||||
},
|
||||
menuListItemActive: {
|
||||
"&:before": {
|
||||
background: theme.palette.primary.main,
|
||||
"& $menuListItemText": {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
"& path": {
|
||||
color: theme.palette.primary.main,
|
||||
fill: theme.palette.primary.main
|
||||
}
|
||||
},
|
||||
menuListItemOpen: {
|
||||
"&:after": {
|
||||
borderBottom: `10px solid transparent`,
|
||||
borderLeft: `10px solid ${theme.palette.background.paper}`,
|
||||
borderTop: `10px solid transparent`,
|
||||
content: "''",
|
||||
height: "100%",
|
||||
left: -32,
|
||||
height: 0,
|
||||
position: "absolute",
|
||||
width: 5
|
||||
right: -35,
|
||||
top: 15,
|
||||
width: 0
|
||||
},
|
||||
position: "relative"
|
||||
},
|
||||
|
@ -54,50 +104,96 @@ const styles = (theme: Theme) =>
|
|||
color: theme.palette.primary.main
|
||||
},
|
||||
cursor: "pointer",
|
||||
display: "inline-block",
|
||||
fontSize: "1rem",
|
||||
fontWeight: 500,
|
||||
opacity: 1,
|
||||
paddingLeft: 16,
|
||||
textTransform: "uppercase",
|
||||
transition: theme.transitions.duration.standard + "ms"
|
||||
transition: `opacity ${theme.transitions.duration.shorter}ms ease 0.1s`
|
||||
},
|
||||
menuListNested: {
|
||||
"& $menuListItemActive": {
|
||||
"& $menuListItemText": {
|
||||
color: theme.palette.primary.main
|
||||
menuListItemTextHide: {
|
||||
opacity: 0,
|
||||
position: "absolute",
|
||||
transition: `opacity ${theme.transitions.duration.shorter}ms ease`
|
||||
},
|
||||
"&:before": {
|
||||
borderRadius: "100%",
|
||||
height: 8,
|
||||
marginLeft: 9,
|
||||
marginTop: 7,
|
||||
width: 8
|
||||
}
|
||||
subMenu: {
|
||||
padding: "0 15px"
|
||||
},
|
||||
"& $menuListItemText": {
|
||||
textTransform: "none"
|
||||
subMenuDrawer: {
|
||||
background: "#000",
|
||||
cursor: "pointer",
|
||||
height: "100vh",
|
||||
left: 0,
|
||||
opacity: 0.2,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
width: 0,
|
||||
zIndex: -2
|
||||
},
|
||||
marginLeft: theme.spacing.unit * 3
|
||||
subMenuDrawerOpen: {
|
||||
width: `100vw`
|
||||
}
|
||||
});
|
||||
|
||||
interface MenuListProps {
|
||||
className?: string;
|
||||
menuItems: IMenuItem[];
|
||||
isMenuSmall: boolean;
|
||||
location: string;
|
||||
user: User;
|
||||
renderConfigure: boolean;
|
||||
onMenuItemClick: (url: string, event: React.MouseEvent<any>) => void;
|
||||
}
|
||||
|
||||
export interface IActiveSubMenu {
|
||||
isActive: boolean;
|
||||
label: string | null;
|
||||
}
|
||||
|
||||
const MenuList = withStyles(styles, { name: "MenuList" })(
|
||||
({
|
||||
classes,
|
||||
className,
|
||||
menuItems,
|
||||
isMenuSmall,
|
||||
location,
|
||||
user,
|
||||
renderConfigure,
|
||||
onMenuItemClick
|
||||
}: MenuListProps & WithStyles<typeof styles>) => (
|
||||
<div className={className}>
|
||||
}: MenuListProps & WithStyles<typeof styles>) => {
|
||||
const { isDark } = useTheme();
|
||||
const [activeSubMenu, setActiveSubMenu] = React.useState<IActiveSubMenu>({
|
||||
isActive: false,
|
||||
label: null
|
||||
});
|
||||
|
||||
const handleSubMenu = itemLabel => {
|
||||
setActiveSubMenu({
|
||||
isActive:
|
||||
itemLabel === activeSubMenu.label ? !activeSubMenu.isActive : true,
|
||||
label: itemLabel
|
||||
});
|
||||
};
|
||||
|
||||
const closeSubMenu = (menuItemUrl, event) => {
|
||||
setActiveSubMenu({
|
||||
isActive: false,
|
||||
label: null
|
||||
});
|
||||
if (menuItemUrl && event) {
|
||||
onMenuItemClick(menuItemUrl, event);
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(className, {
|
||||
[classes.menuIsActive]: activeSubMenu.isActive
|
||||
})}
|
||||
>
|
||||
{/* FIXME: this .split("?")[0] looks gross */}
|
||||
{menuItems.map(menuItem => {
|
||||
const isActive = (menuItem: IMenuItem) =>
|
||||
|
@ -111,7 +207,9 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
|
|||
|
||||
if (
|
||||
menuItem.permission &&
|
||||
!user.permissions.map(perm => perm.code).includes(menuItem.permission)
|
||||
!user.permissions
|
||||
.map(perm => perm.code)
|
||||
.includes(menuItem.permission)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
@ -123,15 +221,52 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
|
|||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(classes.menuListItem, {
|
||||
[classes.menuListItemActive]: isAnyChildActive
|
||||
})}
|
||||
key={menuItem.label}
|
||||
>
|
||||
<div
|
||||
className={classNames(classes.menuItemHover, {
|
||||
[classes.menuListItemOpen]:
|
||||
menuItem.ariaLabel === activeSubMenu.label &&
|
||||
activeSubMenu.isActive
|
||||
})}
|
||||
onClick={() => handleSubMenu(menuItem.ariaLabel)}
|
||||
>
|
||||
<SVG
|
||||
className={classNames(classes.menuIcon, {
|
||||
[classes.menuIconDark]: isDark
|
||||
})}
|
||||
src={menuItem.icon}
|
||||
/>
|
||||
<Typography
|
||||
aria-label={menuItem.ariaLabel}
|
||||
className={classNames(classes.menuListItemText, {
|
||||
[classes.menuListItemTextHide]: !isMenuSmall
|
||||
})}
|
||||
>
|
||||
{menuItem.label}
|
||||
</Typography>
|
||||
</div>
|
||||
<MenuNested
|
||||
classes={classes}
|
||||
isAnyChildActive={isAnyChildActive}
|
||||
location={location}
|
||||
activeItem={activeSubMenu}
|
||||
closeSubMenu={setActiveSubMenu}
|
||||
menuItem={menuItem}
|
||||
onMenuItemClick={onMenuItemClick}
|
||||
user={user}
|
||||
key={menuItem.label}
|
||||
handleSubMenu={handleSubMenu}
|
||||
title={menuItem.label}
|
||||
icon={menuItem.icon}
|
||||
ariaLabel={menuItem.ariaLabel}
|
||||
/>
|
||||
<div
|
||||
onClick={event => closeSubMenu(null, event)}
|
||||
className={classNames(classes.subMenuDrawer, {
|
||||
[classes.subMenuDrawerOpen]: activeSubMenu.isActive
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -141,36 +276,59 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
|
|||
[classes.menuListItemActive]: isActive(menuItem)
|
||||
})}
|
||||
href={createHref(menuItem.url)}
|
||||
onClick={event => onMenuItemClick(menuItem.url, event)}
|
||||
onClick={event => closeSubMenu(menuItem.url, event)}
|
||||
key={menuItem.label}
|
||||
>
|
||||
<div className={classes.menuItemHover}>
|
||||
<SVG
|
||||
className={classNames(classes.menuIcon, {
|
||||
[classes.menuIconDark]: isDark
|
||||
})}
|
||||
src={menuItem.icon}
|
||||
/>
|
||||
<Typography
|
||||
aria-label={menuItem.ariaLabel}
|
||||
className={classes.menuListItemText}
|
||||
className={classNames(classes.menuListItemText, {
|
||||
[classes.menuListItemTextHide]: !isMenuSmall
|
||||
})}
|
||||
>
|
||||
{menuItem.label}
|
||||
</Typography>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
{renderConfigure &&
|
||||
configurationMenu.filter(menuItem =>
|
||||
user.permissions.map(perm => perm.code).includes(menuItem.permission)
|
||||
user.permissions
|
||||
.map(perm => perm.code)
|
||||
.includes(menuItem.permission)
|
||||
).length > 0 && (
|
||||
<a
|
||||
className={classes.menuListItem}
|
||||
href={createHref(configurationMenuUrl)}
|
||||
onClick={event => onMenuItemClick(configurationMenuUrl, event)}
|
||||
>
|
||||
<div className={classes.menuItemHover}>
|
||||
<SVG
|
||||
className={classNames(classes.menuIcon, {
|
||||
[classes.menuIconDark]: isDark
|
||||
})}
|
||||
src={configureIcon}
|
||||
/>
|
||||
<Typography
|
||||
aria-label="configure"
|
||||
className={classes.menuListItemText}
|
||||
className={classNames(classes.menuListItemText, {
|
||||
[classes.menuListItemTextHide]: !isMenuSmall
|
||||
})}
|
||||
>
|
||||
{i18n.t("Configure")}
|
||||
</Typography>
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
export default MenuList;
|
||||
|
|
|
@ -1,61 +1,195 @@
|
|||
import Hidden from "@material-ui/core/Hidden";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
|
||||
import { User } from "../../auth/types/User";
|
||||
import MenuList from "./MenuList";
|
||||
import menuArrowIcon from "@assets/images/menu-arrow-icon.svg";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import { createHref } from "@saleor/misc";
|
||||
import { drawerWidthExpanded } from "./consts";
|
||||
import { IActiveSubMenu } from "./MenuList";
|
||||
import { IMenuItem } from "./menuStructure";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
menuListNested: {
|
||||
background: theme.palette.background.paper,
|
||||
height: "100vh",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
transition: `right ${theme.transitions.duration.shorter}ms ease`,
|
||||
width: 300,
|
||||
zIndex: -1
|
||||
},
|
||||
menuListNestedClose: {
|
||||
"& svg": {
|
||||
fill: theme.palette.primary.main,
|
||||
left: 7,
|
||||
position: "relative",
|
||||
top: -2
|
||||
},
|
||||
border: `solid 1px #EAEAEA`,
|
||||
borderRadius: "100%",
|
||||
cursor: "pointer",
|
||||
height: 32,
|
||||
position: "absolute",
|
||||
right: 32,
|
||||
top: 35,
|
||||
transform: "rotate(180deg)",
|
||||
width: 32
|
||||
},
|
||||
menuListNestedCloseDark: {
|
||||
border: `solid 1px #252728`
|
||||
},
|
||||
menuListNestedHide: {
|
||||
opacity: 0
|
||||
},
|
||||
menuListNestedIcon: {
|
||||
"& path": {
|
||||
fill: "initial"
|
||||
},
|
||||
"& svg": { height: 32, position: "relative", top: 7, width: 32 }
|
||||
},
|
||||
menuListNestedIconDark: {
|
||||
"& path": {
|
||||
fill: theme.palette.common.white
|
||||
}
|
||||
},
|
||||
menuListNestedItem: {
|
||||
"&:hover": {
|
||||
"& p": {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
},
|
||||
display: "block",
|
||||
marginBottom: theme.spacing.unit * 2,
|
||||
padding: "0px 30px",
|
||||
textDecoration: "none"
|
||||
},
|
||||
menuListNestedOpen: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
right: 0,
|
||||
width: drawerWidthExpanded,
|
||||
zIndex: 2
|
||||
},
|
||||
right: -300,
|
||||
zIndex: -1
|
||||
},
|
||||
subHeader: {
|
||||
borderBottom: "solid 1px #EAEAEA",
|
||||
margin: "30px",
|
||||
marginBottom: 39,
|
||||
paddingBottom: 22
|
||||
},
|
||||
subHeaderDark: {
|
||||
borderBottom: "solid 1px #252728"
|
||||
},
|
||||
subHeaderTitle: {
|
||||
[theme.breakpoints.up("md")]: {
|
||||
paddingLeft: 0
|
||||
},
|
||||
display: "inline",
|
||||
paddingLeft: 10
|
||||
}
|
||||
});
|
||||
|
||||
export interface MenuNestedProps {
|
||||
classes: Record<
|
||||
| "menuListItem"
|
||||
| "menuListItemActive"
|
||||
| "menuListItemText"
|
||||
| "menuListNested",
|
||||
string
|
||||
>;
|
||||
isAnyChildActive: boolean;
|
||||
activeItem: IActiveSubMenu;
|
||||
ariaLabel: string;
|
||||
closeSubMenu: ({ isActive, label }: IActiveSubMenu) => void;
|
||||
icon: string;
|
||||
menuItem: IMenuItem;
|
||||
location: string;
|
||||
user: User;
|
||||
title: string;
|
||||
handleSubMenu: (itemLabel: string) => void;
|
||||
onMenuItemClick: (url: string, event: React.MouseEvent<any>) => void;
|
||||
}
|
||||
|
||||
const MenuNested: React.FC<MenuNestedProps> = ({
|
||||
const MenuNested = withStyles(styles, { name: "MenuNested" })(
|
||||
({
|
||||
activeItem,
|
||||
ariaLabel,
|
||||
classes,
|
||||
isAnyChildActive,
|
||||
location,
|
||||
closeSubMenu,
|
||||
icon,
|
||||
menuItem,
|
||||
onMenuItemClick,
|
||||
user
|
||||
}) => {
|
||||
const [isOpened, setOpenStatus] = React.useState(false);
|
||||
|
||||
title
|
||||
}: MenuNestedProps & WithStyles<typeof styles>) => {
|
||||
const menuItems = menuItem.children;
|
||||
const { isDark } = useTheme();
|
||||
const closeMenu = (menuItemUrl, event) => {
|
||||
onMenuItemClick(menuItemUrl, event);
|
||||
closeSubMenu({
|
||||
isActive: false,
|
||||
label: null
|
||||
});
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onClick={() => setOpenStatus(!isOpened)}
|
||||
className={classNames(classes.menuListItem, {
|
||||
[classes.menuListItemActive]: isAnyChildActive
|
||||
className={classNames(classes.menuListNested, {
|
||||
[classes.menuListNestedOpen]:
|
||||
activeItem.label === ariaLabel && activeItem.isActive
|
||||
})}
|
||||
>
|
||||
<Typography
|
||||
aria-label={menuItem.ariaLabel}
|
||||
className={classes.menuListItemText}
|
||||
className={classNames(classes.subHeader, {
|
||||
[classes.subHeaderDark]: isDark
|
||||
})}
|
||||
variant="h5"
|
||||
>
|
||||
{menuItem.label}
|
||||
</Typography>
|
||||
{isOpened && (
|
||||
<div className={classes.menuListNested}>
|
||||
<MenuList
|
||||
menuItems={menuItem.children}
|
||||
location={location}
|
||||
user={user}
|
||||
renderConfigure={false}
|
||||
onMenuItemClick={onMenuItemClick}
|
||||
<Hidden mdUp>
|
||||
<SVG
|
||||
className={classNames(classes.menuListNestedIcon, {
|
||||
[classes.menuListNestedIconDark]: isDark
|
||||
})}
|
||||
src={icon}
|
||||
/>
|
||||
</Hidden>
|
||||
<div className={classes.subHeaderTitle}>{title}</div>
|
||||
<Hidden mdUp>
|
||||
<div
|
||||
className={classNames(classes.menuListNestedClose, {
|
||||
[classes.menuListNestedCloseDark]: isDark
|
||||
})}
|
||||
onClick={() =>
|
||||
closeSubMenu({
|
||||
isActive: false,
|
||||
label: null
|
||||
})
|
||||
}
|
||||
>
|
||||
<SVG src={menuArrowIcon} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Hidden>
|
||||
</Typography>
|
||||
{menuItems.map(item => {
|
||||
return (
|
||||
<a
|
||||
className={classNames(classes.menuListNestedItem)}
|
||||
href={createHref(item.url)}
|
||||
onClick={event => closeMenu(item.url, event)}
|
||||
key={item.label}
|
||||
>
|
||||
<Typography aria-label={item.ariaLabel}>
|
||||
{item.label}
|
||||
</Typography>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
export default MenuNested;
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import { drawerWidth } from "./consts";
|
||||
import { drawerWidth, drawerWidthExpanded } from "./consts";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -15,31 +15,38 @@ const styles = (theme: Theme) =>
|
|||
backgroundColor: theme.palette.background.paper,
|
||||
border: "none",
|
||||
height: "100vh",
|
||||
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 4}px`,
|
||||
overflow: "visible",
|
||||
padding: 0,
|
||||
position: "fixed" as "fixed",
|
||||
transition: "width 0.2s ease",
|
||||
width: drawerWidthExpanded
|
||||
},
|
||||
drawerDesktopSmall: {
|
||||
overflow: "visible",
|
||||
transition: "width 0.2s ease",
|
||||
width: drawerWidth
|
||||
},
|
||||
drawerMobile: {
|
||||
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 4}px`,
|
||||
width: drawerWidth,
|
||||
width: drawerWidthExpanded
|
||||
}
|
||||
});
|
||||
|
||||
interface ResponsiveDrawerProps extends WithStyles<typeof styles> {
|
||||
children?: React.ReactNode;
|
||||
open: boolean;
|
||||
small: boolean;
|
||||
onClose?();
|
||||
}
|
||||
|
||||
const ResponsiveDrawer = withStyles(styles, { name: "ResponsiveDrawer" })(
|
||||
({ children, classes, onClose, open }: ResponsiveDrawerProps) => (
|
||||
({ children, classes, onClose, open, small }: ResponsiveDrawerProps) => (
|
||||
<>
|
||||
<Hidden smDown>
|
||||
<Drawer
|
||||
variant="persistent"
|
||||
open
|
||||
classes={{
|
||||
paper: classes.drawerDesktop
|
||||
paper: small ? classes.drawerDesktop : classes.drawerDesktopSmall
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export const drawerWidth = 256;
|
||||
export const drawerWidthExpanded = 256;
|
||||
export const drawerWidth = 80;
|
||||
export const navigationBarHeight = 64;
|
||||
export const appLoaderHeight = 4;
|
||||
|
|
|
@ -8,9 +8,17 @@ import { productListUrl } from "../../products/urls";
|
|||
import { languageListUrl } from "../../translations/urls";
|
||||
import { PermissionEnum } from "../../types/globalTypes";
|
||||
|
||||
import catalogIcon from "@assets/images/menu-catalog-icon.svg";
|
||||
import customerIcon from "@assets/images/menu-customers-icon.svg";
|
||||
import discountsIcon from "@assets/images/menu-discounts-icon.svg";
|
||||
import homeIcon from "@assets/images/menu-home-icon.svg";
|
||||
import ordersIcon from "@assets/images/menu-orders-icon.svg";
|
||||
import translationIcon from "@assets/images/menu-translation-icon.svg";
|
||||
|
||||
export interface IMenuItem {
|
||||
ariaLabel: string;
|
||||
children?: IMenuItem[];
|
||||
icon?: any;
|
||||
label: string;
|
||||
permission?: PermissionEnum;
|
||||
url?: string;
|
||||
|
@ -19,6 +27,7 @@ export interface IMenuItem {
|
|||
const menuStructure: IMenuItem[] = [
|
||||
{
|
||||
ariaLabel: "home",
|
||||
icon: homeIcon,
|
||||
label: i18n.t("Home", { context: "Menu label" }),
|
||||
url: "/"
|
||||
},
|
||||
|
@ -41,6 +50,7 @@ const menuStructure: IMenuItem[] = [
|
|||
url: collectionListUrl()
|
||||
}
|
||||
],
|
||||
icon: catalogIcon,
|
||||
label: i18n.t("Catalog", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS
|
||||
},
|
||||
|
@ -60,11 +70,13 @@ const menuStructure: IMenuItem[] = [
|
|||
url: orderDraftListUrl()
|
||||
}
|
||||
],
|
||||
icon: ordersIcon,
|
||||
label: i18n.t("Orders", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_ORDERS
|
||||
},
|
||||
{
|
||||
ariaLabel: "customers",
|
||||
icon: customerIcon,
|
||||
label: i18n.t("Customers", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_USERS,
|
||||
url: customerListUrl()
|
||||
|
@ -84,11 +96,13 @@ const menuStructure: IMenuItem[] = [
|
|||
url: voucherListUrl()
|
||||
}
|
||||
],
|
||||
icon: discountsIcon,
|
||||
label: i18n.t("Discounts", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_DISCOUNTS
|
||||
},
|
||||
{
|
||||
ariaLabel: "translations",
|
||||
icon: translationIcon,
|
||||
label: i18n.t("Translations", { context: "Menu label" }),
|
||||
permission: PermissionEnum.MANAGE_TRANSLATIONS,
|
||||
url: languageListUrl
|
||||
|
|
|
@ -15,8 +15,8 @@ import React from "react";
|
|||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import Form from "@saleor/components/Form";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import { SearchCategories_categories_edges_node } from "../../containers/SearchCategories/types/SearchCategories";
|
||||
import i18n from "../../i18n";
|
||||
import Checkbox from "../Checkbox";
|
||||
|
@ -50,13 +50,28 @@ interface AssignCategoriesDialogProps extends WithStyles<typeof styles> {
|
|||
loading: boolean;
|
||||
onClose: () => void;
|
||||
onFetch: (value: string) => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
onSubmit: (data: SearchCategories_categories_edges_node[]) => void;
|
||||
}
|
||||
|
||||
function handleCategoryAssign(
|
||||
product: SearchCategories_categories_edges_node,
|
||||
isSelected: boolean,
|
||||
selectedCategories: SearchCategories_categories_edges_node[],
|
||||
setSelectedCategories: (
|
||||
data: SearchCategories_categories_edges_node[]
|
||||
) => void
|
||||
) {
|
||||
if (isSelected) {
|
||||
setSelectedCategories(
|
||||
selectedCategories.filter(
|
||||
selectedProduct => selectedProduct.id !== product.id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setSelectedCategories([...selectedCategories, product]);
|
||||
}
|
||||
}
|
||||
|
||||
const initialForm: FormData = {
|
||||
categories: [],
|
||||
query: ""
|
||||
};
|
||||
const AssignCategoriesDialog = withStyles(styles, {
|
||||
name: "AssignCategoriesDialog"
|
||||
})(
|
||||
|
@ -69,7 +84,15 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
onClose,
|
||||
onFetch,
|
||||
onSubmit
|
||||
}: AssignCategoriesDialogProps) => (
|
||||
}: AssignCategoriesDialogProps) => {
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const [selectedCategories, setSelectedCategories] = React.useState<
|
||||
SearchCategories_categories_edges_node[]
|
||||
>([]);
|
||||
|
||||
const handleSubmit = () => onSubmit(selectedCategories);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
|
@ -77,24 +100,18 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ data, change }) => (
|
||||
<>
|
||||
<DialogTitle>{i18n.t("Assign Categories")}</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<TextField
|
||||
name="query"
|
||||
value={data.query}
|
||||
onChange={event => change(event, () => onFetch(data.query))}
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={i18n.t("Search Categories", {
|
||||
context: "product search input label"
|
||||
context: "category search input label"
|
||||
})}
|
||||
placeholder={i18n.t("Search by category name, etc...", {
|
||||
context: "category search input placeholder"
|
||||
})}
|
||||
placeholder={i18n.t(
|
||||
"Search by product name, attribute, product type etc...",
|
||||
{
|
||||
context: "product search input placeholder"
|
||||
}
|
||||
)}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
autoComplete: "off",
|
||||
|
@ -106,9 +123,8 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
<TableBody>
|
||||
{categories &&
|
||||
categories.map(category => {
|
||||
const isChecked = !!data.categories.find(
|
||||
selectedCategories =>
|
||||
selectedCategories.id === category.id
|
||||
const isSelected = !!selectedCategories.find(
|
||||
selectedCategories => selectedCategories.id === category.id
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -118,26 +134,15 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
className={classes.checkboxCell}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isChecked}
|
||||
checked={isSelected}
|
||||
onChange={() =>
|
||||
isChecked
|
||||
? change({
|
||||
target: {
|
||||
name: "categories",
|
||||
value: data.categories.filter(
|
||||
selectedCategories =>
|
||||
selectedCategories.id !==
|
||||
category.id
|
||||
handleCategoryAssign(
|
||||
category,
|
||||
isSelected,
|
||||
selectedCategories,
|
||||
setSelectedCategories
|
||||
)
|
||||
}
|
||||
} as any)
|
||||
: change({
|
||||
target: {
|
||||
name: "categories",
|
||||
value: [...data.categories, category]
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wideCell}>
|
||||
|
@ -158,15 +163,14 @@ const AssignCategoriesDialog = withStyles(styles, {
|
|||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{i18n.t("Assign categories", { context: "button" })}
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
AssignCategoriesDialog.displayName = "AssignCategoriesDialog";
|
||||
export default AssignCategoriesDialog;
|
||||
|
|
|
@ -12,13 +12,13 @@ import TableRow from "@material-ui/core/TableRow";
|
|||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { SearchCollections_collections_edges_node } from "../../containers/SearchCollections/types/SearchCollections";
|
||||
import i18n from "../../i18n";
|
||||
import Checkbox from "../Checkbox";
|
||||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "../ConfirmButton/ConfirmButton";
|
||||
import Form from "../Form";
|
||||
import FormSpacer from "../FormSpacer";
|
||||
|
||||
export interface FormData {
|
||||
|
@ -50,13 +50,28 @@ interface AssignCollectionDialogProps extends WithStyles<typeof styles> {
|
|||
loading: boolean;
|
||||
onClose: () => void;
|
||||
onFetch: (value: string) => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
onSubmit: (data: SearchCollections_collections_edges_node[]) => void;
|
||||
}
|
||||
|
||||
function handleCollectionAssign(
|
||||
product: SearchCollections_collections_edges_node,
|
||||
isSelected: boolean,
|
||||
selectedCollections: SearchCollections_collections_edges_node[],
|
||||
setSelectedCollections: (
|
||||
data: SearchCollections_collections_edges_node[]
|
||||
) => void
|
||||
) {
|
||||
if (isSelected) {
|
||||
setSelectedCollections(
|
||||
selectedCollections.filter(
|
||||
selectedProduct => selectedProduct.id !== product.id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setSelectedCollections([...selectedCollections, product]);
|
||||
}
|
||||
}
|
||||
|
||||
const initialForm: FormData = {
|
||||
collections: [],
|
||||
query: ""
|
||||
};
|
||||
const AssignCollectionDialog = withStyles(styles, {
|
||||
name: "AssignCollectionDialog"
|
||||
})(
|
||||
|
@ -69,7 +84,15 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
onClose,
|
||||
onFetch,
|
||||
onSubmit
|
||||
}: AssignCollectionDialogProps) => (
|
||||
}: AssignCollectionDialogProps) => {
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const [selectedCollections, setSelectedCollections] = React.useState<
|
||||
SearchCollections_collections_edges_node[]
|
||||
>([]);
|
||||
|
||||
const handleSubmit = () => onSubmit(selectedCollections);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
|
@ -77,24 +100,18 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ data, change }) => (
|
||||
<>
|
||||
<DialogTitle>{i18n.t("Assign Collection")}</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<TextField
|
||||
name="query"
|
||||
value={data.query}
|
||||
onChange={event => change(event, () => onFetch(data.query))}
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={i18n.t("Search Collection", {
|
||||
context: "product search input label"
|
||||
})}
|
||||
placeholder={i18n.t(
|
||||
"Search by product name, attribute, product type etc...",
|
||||
{
|
||||
placeholder={i18n.t("Search by collection name, etc...", {
|
||||
context: "product search input placeholder"
|
||||
}
|
||||
)}
|
||||
})}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
autoComplete: "off",
|
||||
|
@ -105,43 +122,32 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
<Table>
|
||||
<TableBody>
|
||||
{collections &&
|
||||
collections.map(category => {
|
||||
const isChecked = !!data.collections.find(
|
||||
collections.map(collection => {
|
||||
const isSelected = !!selectedCollections.find(
|
||||
selectedCollection =>
|
||||
selectedCollection.id === category.id
|
||||
selectedCollection.id === collection.id
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow key={category.id}>
|
||||
<TableRow key={collection.id}>
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
className={classes.checkboxCell}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isChecked}
|
||||
checked={isSelected}
|
||||
onChange={() =>
|
||||
isChecked
|
||||
? change({
|
||||
target: {
|
||||
name: "collections",
|
||||
value: data.collections.filter(
|
||||
selectedCollection =>
|
||||
selectedCollection.id !==
|
||||
category.id
|
||||
handleCollectionAssign(
|
||||
collection,
|
||||
isSelected,
|
||||
selectedCollections,
|
||||
setSelectedCollections
|
||||
)
|
||||
}
|
||||
} as any)
|
||||
: change({
|
||||
target: {
|
||||
name: "collections",
|
||||
value: [...data.collections, category]
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wideCell}>
|
||||
{category.name}
|
||||
{collection.name}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
@ -158,15 +164,14 @@ const AssignCollectionDialog = withStyles(styles, {
|
|||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{i18n.t("Assign collections", { context: "button" })}
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
AssignCollectionDialog.displayName = "AssignCollectionDialog";
|
||||
export default AssignCollectionDialog;
|
||||
|
|
|
@ -15,12 +15,12 @@ import React from "react";
|
|||
import ConfirmButton, {
|
||||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import Form from "@saleor/components/Form";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import useSearchQuery from "@saleor/hooks/useSearchQuery";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { SearchProducts_products_edges_node } from "../../containers/SearchProducts/types/SearchProducts";
|
||||
import i18n from "../../i18n";
|
||||
import { maybe } from "../../misc";
|
||||
import Checkbox from "../Checkbox";
|
||||
|
||||
export interface FormData {
|
||||
|
@ -40,25 +40,41 @@ const styles = createStyles({
|
|||
overflow: {
|
||||
overflowY: "visible"
|
||||
},
|
||||
scrollArea: {
|
||||
overflowY: "scroll"
|
||||
},
|
||||
wideCell: {
|
||||
width: "100%"
|
||||
}
|
||||
});
|
||||
|
||||
interface AssignProductDialogProps extends WithStyles<typeof styles> {
|
||||
export interface AssignProductDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
products: SearchProducts_products_edges_node[];
|
||||
loading: boolean;
|
||||
onClose: () => void;
|
||||
onFetch: (value: string) => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
onSubmit: (data: SearchProducts_products_edges_node[]) => void;
|
||||
}
|
||||
|
||||
function handleProductAssign(
|
||||
product: SearchProducts_products_edges_node,
|
||||
isSelected: boolean,
|
||||
selectedProducts: SearchProducts_products_edges_node[],
|
||||
setSelectedProducts: (data: SearchProducts_products_edges_node[]) => void
|
||||
) {
|
||||
if (isSelected) {
|
||||
setSelectedProducts(
|
||||
selectedProducts.filter(
|
||||
selectedProduct => selectedProduct.id !== product.id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setSelectedProducts([...selectedProducts, product]);
|
||||
}
|
||||
}
|
||||
|
||||
const initialForm: FormData = {
|
||||
products: [],
|
||||
query: ""
|
||||
};
|
||||
const AssignProductDialog = withStyles(styles, {
|
||||
name: "AssignProductDialog"
|
||||
})(
|
||||
|
@ -71,7 +87,15 @@ const AssignProductDialog = withStyles(styles, {
|
|||
onClose,
|
||||
onFetch,
|
||||
onSubmit
|
||||
}: AssignProductDialogProps) => (
|
||||
}: AssignProductDialogProps & WithStyles<typeof styles>) => {
|
||||
const [query, onQueryChange] = useSearchQuery(onFetch);
|
||||
const [selectedProducts, setSelectedProducts] = React.useState<
|
||||
SearchProducts_products_edges_node[]
|
||||
>([]);
|
||||
|
||||
const handleSubmit = () => onSubmit(selectedProducts);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
|
@ -79,15 +103,12 @@ const AssignProductDialog = withStyles(styles, {
|
|||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||
{({ data, change }) => (
|
||||
<>
|
||||
<DialogTitle>{i18n.t("Assign Product")}</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
name="query"
|
||||
value={data.query}
|
||||
onChange={event => change(event, () => onFetch(data.query))}
|
||||
value={query}
|
||||
onChange={onQueryChange}
|
||||
label={i18n.t("Search Products", {
|
||||
context: "product search input label"
|
||||
})}
|
||||
|
@ -104,11 +125,12 @@ const AssignProductDialog = withStyles(styles, {
|
|||
}}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<div className={classes.scrollArea}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{products &&
|
||||
products.map(product => {
|
||||
const isChecked = !!data.products.find(
|
||||
const isSelected = selectedProducts.some(
|
||||
selectedProduct => selectedProduct.id === product.id
|
||||
);
|
||||
|
||||
|
@ -126,25 +148,15 @@ const AssignProductDialog = withStyles(styles, {
|
|||
className={classes.checkboxCell}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isChecked}
|
||||
checked={isSelected}
|
||||
onChange={() =>
|
||||
isChecked
|
||||
? change({
|
||||
target: {
|
||||
name: "products",
|
||||
value: data.products.filter(
|
||||
selectedProduct =>
|
||||
selectedProduct.id !== product.id
|
||||
handleProductAssign(
|
||||
product,
|
||||
isSelected,
|
||||
selectedProducts,
|
||||
setSelectedProducts
|
||||
)
|
||||
}
|
||||
} as any)
|
||||
: change({
|
||||
target: {
|
||||
name: "products",
|
||||
value: [...data.products, product]
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
@ -152,6 +164,7 @@ const AssignProductDialog = withStyles(styles, {
|
|||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>
|
||||
|
@ -162,15 +175,14 @@ const AssignProductDialog = withStyles(styles, {
|
|||
color="primary"
|
||||
variant="contained"
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{i18n.t("Assign products", { context: "button" })}
|
||||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
AssignProductDialog.displayName = "AssignProductDialog";
|
||||
export default AssignProductDialog;
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -32,7 +32,7 @@ const styles = (theme: Theme) =>
|
|||
lineHeight: 1
|
||||
},
|
||||
toolbar: {
|
||||
marginRight: -theme.spacing.unit * 2
|
||||
marginRight: -theme.spacing.unit
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { stopPropagation } from "../../misc";
|
||||
|
||||
export type CheckboxProps = Omit<
|
||||
MuiCheckboxProps,
|
||||
|
@ -19,7 +18,9 @@ export type CheckboxProps = Omit<
|
|||
| "indeterminateIcon"
|
||||
| "classes"
|
||||
| "onChange"
|
||||
| "onClick"
|
||||
> & {
|
||||
disableClickPropagation?: boolean;
|
||||
onChange?: (event: React.ChangeEvent<any>) => void;
|
||||
};
|
||||
|
||||
|
@ -45,7 +46,6 @@ const styles = (theme: Theme) =>
|
|||
},
|
||||
"&:before": {
|
||||
background: "rgba(0, 0, 0, 0)",
|
||||
borderRadius: 2,
|
||||
content: '""',
|
||||
height: 8,
|
||||
left: 2,
|
||||
|
@ -56,7 +56,6 @@ const styles = (theme: Theme) =>
|
|||
},
|
||||
WebkitAppearance: "none",
|
||||
border: `1px solid ${theme.palette.grey[500]}`,
|
||||
borderRadius: 4,
|
||||
boxSizing: "border-box",
|
||||
cursor: "pointer",
|
||||
height: 14,
|
||||
|
@ -84,21 +83,31 @@ const Checkbox = withStyles(styles, { name: "Checkbox" })(
|
|||
className,
|
||||
classes,
|
||||
disabled,
|
||||
disableClickPropagation,
|
||||
indeterminate,
|
||||
onChange,
|
||||
onClick,
|
||||
value,
|
||||
name,
|
||||
...props
|
||||
}: CheckboxProps & WithStyles<typeof styles>) => {
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const handleClick = React.useCallback(
|
||||
disableClickPropagation
|
||||
? event => {
|
||||
event.stopPropagation();
|
||||
inputRef.current.click();
|
||||
}
|
||||
: () => inputRef.current.click(),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
{...props}
|
||||
centerRipple
|
||||
className={classNames(classes.root, className)}
|
||||
disabled={disabled}
|
||||
onClick={stopPropagation(() => inputRef.current.click())}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<input
|
||||
className={classNames(classes.box, {
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
|
|
@ -20,7 +20,7 @@ export const ControlledCheckbox: React.StatelessComponent<
|
|||
<Checkbox
|
||||
checked={checked}
|
||||
name={name}
|
||||
onClick={() => onChange({ target: { name, value: !checked } })}
|
||||
disableClickPropagation
|
||||
onChange={() => onChange({ target: { name, value: !checked } })}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ const styles = (theme: Theme) =>
|
|||
createStyles({
|
||||
label: {
|
||||
marginLeft: theme.spacing.unit * 2
|
||||
},
|
||||
labelText: {
|
||||
fontSize: 14
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -15,7 +18,7 @@ interface ControlledSwitchProps extends WithStyles<typeof styles> {
|
|||
disabled?: boolean;
|
||||
label: string | React.ReactNode;
|
||||
name: string;
|
||||
secondLabel?: string;
|
||||
secondLabel?: string | React.ReactNode;
|
||||
uncheckedLabel?: string | React.ReactNode;
|
||||
onChange?(event: React.ChangeEvent<any>);
|
||||
}
|
||||
|
@ -46,7 +49,17 @@ export const ControlledSwitch = withStyles(styles, {
|
|||
}
|
||||
label={
|
||||
<div className={classes.label}>
|
||||
{uncheckedLabel ? (checked ? label : uncheckedLabel) : label}
|
||||
{uncheckedLabel ? (
|
||||
checked ? (
|
||||
label
|
||||
) : (
|
||||
uncheckedLabel
|
||||
)
|
||||
) : typeof label === "string" ? (
|
||||
<span className={classes.labelText}>{label}</span>
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
<div>{secondLabel ? secondLabel : null}</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import * as renderer from "react-test-renderer";
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import { TimezoneProvider } from "../Timezone";
|
||||
import Date from "./Date";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import * as moment from "moment-timezone";
|
||||
import moment from "moment-timezone";
|
||||
import React from "react";
|
||||
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import * as moment from "moment-timezone";
|
||||
import moment from "moment-timezone";
|
||||
import React from "react";
|
||||
import ReactMoment from "react-moment";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { RawDraftContentState } from "draft-js";
|
||||
import * as draftToHtml from "draftjs-to-html";
|
||||
import draftToHtml from "draftjs-to-html";
|
||||
import React from "react";
|
||||
|
||||
interface DraftRendererProps {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
/* tslint:disable:no-submodule-imports */
|
||||
import * as Dropzone from "react-dropzone/dist/index";
|
||||
import Dropzone from "react-dropzone/dist/index";
|
||||
export default Dropzone;
|
||||
|
|
|
@ -9,10 +9,10 @@ import {
|
|||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TextField, { TextFieldProps } from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import Form from "@saleor/components/Form";
|
||||
import useForm from "@saleor/hooks/useForm";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
@ -66,26 +66,20 @@ export const EditableTableCell = withStyles(styles, {
|
|||
value,
|
||||
onConfirm
|
||||
}: EditableTableCellProps) => {
|
||||
const [opened, setOpenStatus] = React.useState(focused);
|
||||
const enable = () => setOpenStatus(true);
|
||||
const disable = () => setOpenStatus(false);
|
||||
|
||||
const handleConfirm = (data: { value: string }) => {
|
||||
disable();
|
||||
onConfirm(data.value);
|
||||
};
|
||||
|
||||
const [opened, setOpenStatus] = React.useState(focused);
|
||||
const { change, data } = useForm({ value }, [], handleConfirm);
|
||||
const enable = () => setOpenStatus(true);
|
||||
const disable = () => setOpenStatus(false);
|
||||
|
||||
return (
|
||||
<TableCell className={classNames(classes.container, className)}>
|
||||
{opened && <div className={classes.overlay} onClick={disable} />}
|
||||
<Form initial={{ value }} onSubmit={handleConfirm} useForm={false}>
|
||||
{({ change, data }) => (
|
||||
<>
|
||||
<Typography
|
||||
variant="caption"
|
||||
onClick={enable}
|
||||
className={classes.text}
|
||||
>
|
||||
<Typography variant="caption" onClick={enable} className={classes.text}>
|
||||
{value || defaultValue}
|
||||
</Typography>
|
||||
{opened && (
|
||||
|
@ -105,9 +99,6 @@ export const EditableTableCell = withStyles(styles, {
|
|||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</TableCell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import Typography from "@material-ui/core/Typography";
|
|||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
|
||||
import * as notFoundImage from "@assets/images/what.svg";
|
||||
import notFoundImage from "@assets/images/what.svg";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
export interface ErrorPageProps extends WithStyles<typeof styles> {
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import { FilterContent } from ".";
|
||||
|
|
|
@ -47,7 +47,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
}) => {
|
||||
const [menuValue, setMenuValue] = React.useState<string>("");
|
||||
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
|
||||
const classes = useStyles();
|
||||
const classes = useStyles({});
|
||||
|
||||
const activeMenu = menuValue
|
||||
? getMenuItemByValue(filters, menuValue)
|
||||
|
|
|
@ -41,7 +41,7 @@ const FilterElement: React.FC<FilterElementProps> = ({
|
|||
onChange,
|
||||
value
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const classes = useStyles({});
|
||||
|
||||
if (filter.data.type === FieldType.date) {
|
||||
return (
|
||||
|
|
|
@ -41,6 +41,7 @@ const FilterBar: React.FC<FilterBarProps> = ({
|
|||
<FilterTab
|
||||
onClick={() => onTabChange(tabIndex + 1)}
|
||||
label={tab.name}
|
||||
key={tabIndex}
|
||||
/>
|
||||
))}
|
||||
{isCustom && (
|
||||
|
|
|
@ -1,170 +1,42 @@
|
|||
import React from "react";
|
||||
import { UserError } from "../../types";
|
||||
|
||||
export interface FormProps<T extends {}> {
|
||||
children: (props: {
|
||||
data: T;
|
||||
hasChanged: boolean;
|
||||
errors: { [key: string]: string };
|
||||
change(event: React.ChangeEvent<any>, cb?: () => void);
|
||||
reset();
|
||||
submit(event?: React.FormEvent<any>);
|
||||
}) => React.ReactElement<any>;
|
||||
import useForm, { UseFormResult } from "@saleor/hooks/useForm";
|
||||
import { UserError } from "@saleor/types";
|
||||
|
||||
export interface FormProps<T> {
|
||||
children: (props: UseFormResult<T>) => React.ReactNode;
|
||||
confirmLeave?: boolean;
|
||||
errors?: UserError[];
|
||||
initial?: T;
|
||||
confirmLeave?: boolean;
|
||||
useForm?: boolean;
|
||||
resetOnSubmit?: boolean;
|
||||
onSubmit?(data: T);
|
||||
onSubmit?: (data: T) => void;
|
||||
}
|
||||
|
||||
interface FormComponentProps<T extends {}> extends FormProps<T> {
|
||||
hasChanged: boolean;
|
||||
toggleFormChangeState: () => void;
|
||||
}
|
||||
function Form<T>(props: FormProps<T>) {
|
||||
const { children, errors, initial, resetOnSubmit, onSubmit } = props;
|
||||
const renderProps = useForm(initial, errors, onSubmit);
|
||||
|
||||
interface FormState<T extends {}> {
|
||||
initial: T;
|
||||
fields: T;
|
||||
hasChanged: boolean;
|
||||
}
|
||||
function handleSubmit(event?: React.FormEvent<any>, cb?: () => void) {
|
||||
const { reset, submit } = renderProps;
|
||||
|
||||
class FormComponent<T extends {} = {}> extends React.Component<
|
||||
FormComponentProps<T>,
|
||||
FormState<T>
|
||||
> {
|
||||
static getDerivedStateFromProps<T extends {} = {}>(
|
||||
nextProps: FormComponentProps<T>,
|
||||
prevState: FormState<T>
|
||||
): FormState<T> {
|
||||
const changedFields = Object.keys(nextProps.initial).filter(
|
||||
nextFieldName =>
|
||||
JSON.stringify(nextProps.initial[nextFieldName]) !==
|
||||
JSON.stringify(prevState.initial[nextFieldName])
|
||||
);
|
||||
if (changedFields.length > 0) {
|
||||
const swapFields = changedFields.reduce((prev, curr) => {
|
||||
prev[curr] = nextProps.initial[curr];
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
fields: {
|
||||
...(prevState.fields as any),
|
||||
...swapFields
|
||||
},
|
||||
hasChanged: false,
|
||||
initial: {
|
||||
...(prevState.initial as any),
|
||||
...swapFields
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
state: FormState<T> = {
|
||||
fields: this.props.initial,
|
||||
hasChanged: false,
|
||||
initial: this.props.initial
|
||||
};
|
||||
|
||||
componentDidUpdate() {
|
||||
const { hasChanged, confirmLeave, toggleFormChangeState } = this.props;
|
||||
if (this.state.hasChanged !== hasChanged && confirmLeave) {
|
||||
toggleFormChangeState();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { hasChanged, confirmLeave, toggleFormChangeState } = this.props;
|
||||
if (this.state.hasChanged !== hasChanged && confirmLeave) {
|
||||
toggleFormChangeState();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { hasChanged, confirmLeave, toggleFormChangeState } = this.props;
|
||||
if (hasChanged && confirmLeave) {
|
||||
toggleFormChangeState();
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = (event: React.ChangeEvent<any>, cb?: () => void) => {
|
||||
const { target } = event;
|
||||
if (!(target.name in this.state.fields)) {
|
||||
console.error(`Unknown form field: ${target.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
fields: {
|
||||
...(this.state.fields as any),
|
||||
[target.name]: target.value
|
||||
},
|
||||
hasChanged: true
|
||||
},
|
||||
typeof cb === "function" ? cb : undefined
|
||||
);
|
||||
};
|
||||
|
||||
handleKeyDown = (event: React.KeyboardEvent<any>) => {
|
||||
switch (event.keyCode) {
|
||||
// Enter
|
||||
case 13:
|
||||
this.props.onSubmit(this.state.fields);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = (event?: React.FormEvent<any>, cb?: () => void) => {
|
||||
const { resetOnSubmit, onSubmit } = this.props;
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
if (onSubmit !== undefined) {
|
||||
onSubmit(this.state.fields);
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
|
||||
if (resetOnSubmit) {
|
||||
this.setState({
|
||||
fields: this.state.initial
|
||||
});
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, errors, useForm = true } = this.props;
|
||||
|
||||
const contents = children({
|
||||
change: this.handleChange,
|
||||
data: this.state.fields,
|
||||
errors: errors
|
||||
? errors.reduce(
|
||||
(prev, curr) => ({
|
||||
...prev,
|
||||
[curr.field.split(":")[0]]: curr.message
|
||||
}),
|
||||
{}
|
||||
)
|
||||
: {},
|
||||
hasChanged: this.state.hasChanged,
|
||||
reset: () =>
|
||||
this.setState({
|
||||
fields: this.state.initial
|
||||
}),
|
||||
submit: this.handleSubmit
|
||||
});
|
||||
|
||||
return useForm ? (
|
||||
<form onSubmit={this.handleSubmit}>{contents}</form>
|
||||
) : (
|
||||
<div onKeyDown={this.handleKeyDown}>{contents}</div>
|
||||
);
|
||||
submit();
|
||||
}
|
||||
|
||||
return <form onSubmit={handleSubmit}>{children(renderProps)}</form>;
|
||||
}
|
||||
export default FormComponent;
|
||||
Form.displayName = "Form";
|
||||
|
||||
export default Form;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export * from "./Form";
|
||||
export { default } from "./Form";
|
||||
export { default as FormActions } from "./FormActions";
|
||||
export * from "./FormActions";
|
||||
export * from "./FormContext";
|
||||
export { default } from "./FormContext";
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Typography, { TypographyProps } from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import {
|
||||
|
@ -8,27 +9,77 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import Downshift, { ControllerStateAndHelpers } from "downshift";
|
||||
import React from "react";
|
||||
import { compareTwoStrings } from "string-similarity";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
||||
import Debounce, { DebounceProps } from "../Debounce";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Debounce, { DebounceProps } from "@saleor/components/Debounce";
|
||||
import i18n from "@saleor/i18n";
|
||||
import ArrowDropdownIcon from "@saleor/icons/ArrowDropdown";
|
||||
import Hr from "../Hr";
|
||||
|
||||
interface ChoiceType {
|
||||
export interface MultiAutocompleteChoiceType {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
checkbox: {
|
||||
height: 24,
|
||||
width: 20
|
||||
},
|
||||
chip: {
|
||||
margin: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 2}px`
|
||||
width: "100%"
|
||||
},
|
||||
chipClose: {
|
||||
height: 32,
|
||||
padding: 0,
|
||||
width: 32
|
||||
},
|
||||
chipContainer: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: theme.spacing.unit
|
||||
},
|
||||
chipInner: {
|
||||
"& svg": {
|
||||
color: theme.palette.primary.contrastText
|
||||
},
|
||||
alignItems: "center",
|
||||
background: fade(theme.palette.primary.main, 0.6),
|
||||
borderRadius: 24,
|
||||
color: theme.palette.primary.contrastText,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
margin: `${theme.spacing.unit}px 0`,
|
||||
paddingLeft: theme.spacing.unit * 2,
|
||||
paddingRight: theme.spacing.unit
|
||||
},
|
||||
chipLabel: {
|
||||
color: theme.palette.primary.contrastText
|
||||
},
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
position: "relative"
|
||||
},
|
||||
hr: {
|
||||
margin: `${theme.spacing.unit}px 0`
|
||||
},
|
||||
menuItem: {
|
||||
display: "grid",
|
||||
gridColumnGap: theme.spacing.unit + "px",
|
||||
gridTemplateColumns: "20px 1fr",
|
||||
height: "auto",
|
||||
whiteSpace: "normal"
|
||||
},
|
||||
menuItemLabel: {
|
||||
overflowWrap: "break-word"
|
||||
},
|
||||
paper: {
|
||||
left: 0,
|
||||
marginTop: theme.spacing.unit,
|
||||
|
@ -39,39 +90,32 @@ const styles = (theme: Theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
export interface MultiAutocompleteSelectFieldChildrenFunc {
|
||||
deleteItem: (item: ChoiceType) => void;
|
||||
items: ChoiceType[];
|
||||
}
|
||||
export type MultiAutocompleteSelectFieldChildren = (
|
||||
props: MultiAutocompleteSelectFieldChildrenFunc
|
||||
) => React.ReactNode;
|
||||
|
||||
export interface MultiAutocompleteSelectFieldProps
|
||||
extends WithStyles<typeof styles> {
|
||||
export interface MultiAutocompleteSelectFieldProps {
|
||||
allowCustomValues?: boolean;
|
||||
displayValues: MultiAutocompleteChoiceType[];
|
||||
name: string;
|
||||
children: MultiAutocompleteSelectFieldChildren;
|
||||
choices: ChoiceType[];
|
||||
value?: ChoiceType[];
|
||||
choices: MultiAutocompleteChoiceType[];
|
||||
value: string[];
|
||||
loading?: boolean;
|
||||
placeholder?: string;
|
||||
helperText?: string;
|
||||
label?: string;
|
||||
fetchChoices(value: string);
|
||||
onChange(event);
|
||||
fetchChoices?: (value: string) => void;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
const DebounceAutocomplete: React.ComponentType<
|
||||
DebounceProps<string>
|
||||
> = Debounce;
|
||||
|
||||
export const MultiAutocompleteSelectField = withStyles(styles, {
|
||||
export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
|
||||
name: "MultiAutocompleteSelectField"
|
||||
})(
|
||||
({
|
||||
children,
|
||||
allowCustomValues,
|
||||
choices,
|
||||
classes,
|
||||
displayValues,
|
||||
helperText,
|
||||
label,
|
||||
loading,
|
||||
|
@ -80,47 +124,35 @@ export const MultiAutocompleteSelectField = withStyles(styles, {
|
|||
value,
|
||||
fetchChoices,
|
||||
onChange
|
||||
}: MultiAutocompleteSelectFieldProps) => {
|
||||
}: MultiAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
||||
const handleSelect = (
|
||||
item: ChoiceType,
|
||||
{ reset }: ControllerStateAndHelpers
|
||||
item: string,
|
||||
downshiftOpts?: ControllerStateAndHelpers
|
||||
) => {
|
||||
reset({ inputValue: "" });
|
||||
onChange({ target: { name, value: [...value, item] } });
|
||||
if (downshiftOpts) {
|
||||
downshiftOpts.reset({ inputValue: "" });
|
||||
}
|
||||
onChange({
|
||||
target: { name, value: item }
|
||||
} as any);
|
||||
};
|
||||
const handleDelete = (item: ChoiceType) => {
|
||||
const newValue = value.slice();
|
||||
newValue.splice(
|
||||
value.findIndex(listItem => listItem.value === item.value),
|
||||
1
|
||||
);
|
||||
onChange({ target: { name, value: newValue } });
|
||||
};
|
||||
|
||||
const filteredChoices = choices.filter(
|
||||
suggestion => value.map(v => v.value).indexOf(suggestion.value) === -1
|
||||
);
|
||||
const suggestions = choices.filter(choice => !value.includes(choice.value));
|
||||
|
||||
return (
|
||||
<DebounceAutocomplete debounceFn={fetchChoices}>
|
||||
{debounce => (
|
||||
<>
|
||||
<Downshift
|
||||
selectedItem={value}
|
||||
itemToString={item => (item ? item.label : "")}
|
||||
onInputValueChange={fetchChoices}
|
||||
onSelect={handleSelect}
|
||||
onInputValueChange={value => debounce(value)}
|
||||
itemToString={() => ""}
|
||||
>
|
||||
{({
|
||||
getInputProps,
|
||||
getItemProps,
|
||||
isOpen,
|
||||
selectedItem,
|
||||
toggleMenu,
|
||||
closeMenu,
|
||||
openMenu,
|
||||
highlightedIndex
|
||||
}) => {
|
||||
return (
|
||||
highlightedIndex,
|
||||
inputValue
|
||||
}) => (
|
||||
<div className={classes.container}>
|
||||
<TextField
|
||||
InputProps={{
|
||||
|
@ -137,45 +169,155 @@ export const MultiAutocompleteSelectField = withStyles(styles, {
|
|||
</div>
|
||||
),
|
||||
id: undefined,
|
||||
onBlur: closeMenu,
|
||||
onFocus: openMenu
|
||||
onClick: toggleMenu
|
||||
}}
|
||||
helperText={helperText}
|
||||
label={label}
|
||||
fullWidth={true}
|
||||
/>
|
||||
{isOpen && (
|
||||
{isOpen && (!!inputValue || !!choices.length) && (
|
||||
<Paper className={classes.paper} square>
|
||||
{!loading && filteredChoices.length > 0
|
||||
? filteredChoices.map((suggestion, index) => (
|
||||
{choices.length > 0 ||
|
||||
displayValues.length > 0 ||
|
||||
allowCustomValues ? (
|
||||
<>
|
||||
{displayValues.map(value => (
|
||||
<MenuItem
|
||||
key={suggestion.value}
|
||||
selected={highlightedIndex === index}
|
||||
className={classes.menuItem}
|
||||
key={value.value}
|
||||
selected={true}
|
||||
component="div"
|
||||
{...getItemProps({ item: suggestion })}
|
||||
{...getItemProps({
|
||||
item: value.value
|
||||
})}
|
||||
>
|
||||
{suggestion.label}
|
||||
<Checkbox
|
||||
className={classes.checkbox}
|
||||
checked={true}
|
||||
disableRipple
|
||||
/>
|
||||
<span className={classes.menuItemLabel}>
|
||||
{value.label}
|
||||
</span>
|
||||
</MenuItem>
|
||||
))
|
||||
: !loading && (
|
||||
))}
|
||||
{displayValues.length > 0 && suggestions.length > 0 && (
|
||||
<Hr className={classes.hr} />
|
||||
)}
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
key={suggestion.value}
|
||||
selected={highlightedIndex === index + value.length}
|
||||
component="div"
|
||||
{...getItemProps({
|
||||
item: suggestion.value
|
||||
})}
|
||||
>
|
||||
<Checkbox
|
||||
checked={value.includes(suggestion.value)}
|
||||
className={classes.checkbox}
|
||||
disableRipple
|
||||
/>
|
||||
<span className={classes.menuItemLabel}>
|
||||
{suggestion.label}
|
||||
</span>
|
||||
</MenuItem>
|
||||
))}
|
||||
{allowCustomValues &&
|
||||
inputValue &&
|
||||
!choices.find(
|
||||
choice =>
|
||||
choice.label.toLowerCase() ===
|
||||
inputValue.toLowerCase()
|
||||
) && (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
key={"customValue"}
|
||||
component="div"
|
||||
{...getItemProps({
|
||||
item: inputValue
|
||||
})}
|
||||
>
|
||||
<span className={classes.menuItemLabel}>
|
||||
{i18n.t("Add new value: {{ value }}", {
|
||||
context: "add custom option",
|
||||
value: inputValue
|
||||
})}
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!loading && (
|
||||
<MenuItem disabled={true} component="div">
|
||||
{i18n.t("No results found")}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
</Paper>
|
||||
)}
|
||||
{children({
|
||||
deleteItem: handleDelete,
|
||||
items: selectedItem
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
)}
|
||||
</Downshift>
|
||||
<div className={classes.chipContainer}>
|
||||
{displayValues.map(value => (
|
||||
<div className={classes.chip} key={value.value}>
|
||||
<div className={classes.chipInner}>
|
||||
<Typography className={classes.chipLabel} variant="caption">
|
||||
{value.label}
|
||||
</Typography>
|
||||
<IconButton
|
||||
className={classes.chipClose}
|
||||
onClick={() => handleSelect(value.value)}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
const MultiAutocompleteSelectField: React.FC<
|
||||
MultiAutocompleteSelectFieldProps
|
||||
> = ({ choices, fetchChoices, ...props }) => {
|
||||
const [query, setQuery] = React.useState("");
|
||||
if (fetchChoices) {
|
||||
return (
|
||||
<DebounceAutocomplete debounceFn={fetchChoices}>
|
||||
{debounceFn => (
|
||||
<MultiAutocompleteSelectFieldComponent
|
||||
choices={choices}
|
||||
{...props}
|
||||
fetchChoices={debounceFn}
|
||||
/>
|
||||
)}
|
||||
</DebounceAutocomplete>
|
||||
);
|
||||
}
|
||||
|
||||
const sortedChoices = choices.sort((a, b) => {
|
||||
const ratingA = compareTwoStrings(query, a.label);
|
||||
const ratingB = compareTwoStrings(query, b.label);
|
||||
if (ratingA > ratingB) {
|
||||
return -1;
|
||||
}
|
||||
if (ratingA < ratingB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<MultiAutocompleteSelectFieldComponent
|
||||
fetchChoices={q => setQuery(q || "")}
|
||||
choices={sortedChoices}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
MultiAutocompleteSelectField.displayName = "MultiAutocompleteSelectField";
|
||||
export default MultiAutocompleteSelectField;
|
||||
|
|
|
@ -9,8 +9,8 @@ import Typography from "@material-ui/core/Typography";
|
|||
import React from "react";
|
||||
import SVG from "react-inlinesvg";
|
||||
|
||||
import * as notFoundImage from "@assets/images/not-found-404.svg";
|
||||
import i18n from "../../i18n";
|
||||
import notFoundImage from "@assets/images/not-found-404.svg";
|
||||
import i18n from "@saleor/i18n";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
|
|
@ -12,6 +12,9 @@ import Skeleton from "../Skeleton";
|
|||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
display: "flex"
|
||||
},
|
||||
title: {
|
||||
flex: 1,
|
||||
fontSize: 24,
|
||||
|
@ -35,7 +38,7 @@ const PageHeader = withStyles(styles)(
|
|||
</Typography>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
<div className={classes.root}>{children}</div>
|
||||
</ExtendedPageHeader>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -11,11 +11,15 @@ import TableFooter from "@material-ui/core/TableFooter";
|
|||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import { ProductListColumns } from "@saleor/config";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../categories/types/CategoryDetails";
|
||||
import i18n from "../../i18n";
|
||||
import { maybe, renderCollection } from "../../misc";
|
||||
import { ListActions, ListProps } from "../../types";
|
||||
import Checkbox from "../Checkbox";
|
||||
import Money from "../Money";
|
||||
import Skeleton from "../Skeleton";
|
||||
|
@ -27,7 +31,7 @@ const styles = (theme: Theme) =>
|
|||
createStyles({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colName: {
|
||||
width: 430
|
||||
width: "auto"
|
||||
},
|
||||
colPrice: {
|
||||
width: 200
|
||||
|
@ -39,12 +43,14 @@ const styles = (theme: Theme) =>
|
|||
width: 200
|
||||
}
|
||||
},
|
||||
avatarCell: {
|
||||
paddingLeft: theme.spacing.unit * 2,
|
||||
paddingRight: 0,
|
||||
width: theme.spacing.unit * 5
|
||||
colFill: {
|
||||
padding: 0,
|
||||
width: "100%"
|
||||
},
|
||||
colName: {},
|
||||
colNameHeader: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right"
|
||||
},
|
||||
|
@ -53,6 +59,12 @@ const styles = (theme: Theme) =>
|
|||
link: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableContainer: {
|
||||
overflowX: "scroll"
|
||||
},
|
||||
textLeft: {
|
||||
textAlign: "left"
|
||||
},
|
||||
|
@ -62,7 +74,7 @@ const styles = (theme: Theme) =>
|
|||
});
|
||||
|
||||
interface ProductListProps
|
||||
extends ListProps,
|
||||
extends ListProps<ProductListColumns>,
|
||||
ListActions,
|
||||
WithStyles<typeof styles> {
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
|
@ -71,6 +83,7 @@ interface ProductListProps
|
|||
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||
({
|
||||
classes,
|
||||
settings,
|
||||
disabled,
|
||||
isChecked,
|
||||
pageInfo,
|
||||
|
@ -81,36 +94,65 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
toolbar,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick
|
||||
}: ProductListProps) => (
|
||||
<Table>
|
||||
}: ProductListProps) => {
|
||||
const displayColumn = React.useCallback(
|
||||
(column: ProductListColumns) =>
|
||||
isSelected(column, settings.columns, (a, b) => a === b),
|
||||
[settings.columns]
|
||||
);
|
||||
const numberOfColumns = 2 + settings.columns.length;
|
||||
|
||||
return (
|
||||
<div className={classes.tableContainer}>
|
||||
<Table className={classes.table}>
|
||||
<col />
|
||||
<col className={classes.colName} />
|
||||
{displayColumn("productType") && <col className={classes.colType} />}
|
||||
{displayColumn("isPublished") && (
|
||||
<col className={classes.colPublished} />
|
||||
)}
|
||||
{displayColumn("price") && <col className={classes.colPrice} />}
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={products}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell />
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameHeader}>
|
||||
{i18n.t("Name", { context: "object" })}
|
||||
</span>
|
||||
</TableCell>
|
||||
{displayColumn("productType") && (
|
||||
<TableCell className={classes.colType}>
|
||||
{i18n.t("Type", { context: "object" })}
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("isPublished") && (
|
||||
<TableCell className={classes.colPublished}>
|
||||
{i18n.t("Published", { context: "object" })}
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("price") && (
|
||||
<TableCell className={classes.colPrice}>
|
||||
{i18n.t("Price", { context: "object" })}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={6}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={
|
||||
pageInfo && !disabled ? pageInfo.hasNextPage : false
|
||||
}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
|
@ -136,15 +178,17 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
/>
|
||||
<TableCell className={classes.colName}>
|
||||
>
|
||||
{product ? product.name : <Skeleton />}
|
||||
</TableCell>
|
||||
</TableCellAvatar>
|
||||
{displayColumn("productType") && (
|
||||
<TableCell className={classes.colType}>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
|
@ -152,12 +196,17 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("isPublished") && (
|
||||
<TableCell className={classes.colPublished}>
|
||||
{product && maybe(() => product.isAvailable !== undefined) ? (
|
||||
{product &&
|
||||
maybe(() => product.isAvailable !== undefined) ? (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isAvailable
|
||||
? i18n.t("Published", { context: "product status" })
|
||||
? i18n.t("Published", {
|
||||
context: "product status"
|
||||
})
|
||||
: i18n.t("Not published", {
|
||||
context: "product status"
|
||||
})
|
||||
|
@ -168,27 +217,35 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("price") && (
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => product.basePrice) &&
|
||||
maybe(() => product.basePrice.amount) !== undefined &&
|
||||
maybe(() => product.basePrice.currency) !== undefined ? (
|
||||
maybe(() => product.basePrice.currency) !==
|
||||
undefined ? (
|
||||
<Money money={product.basePrice} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>{i18n.t("No products found")}</TableCell>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No products found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
ProductList.displayName = "ProductList";
|
||||
export default ProductList;
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import {
|
||||
BLOCK_TYPE,
|
||||
|
@ -14,6 +14,7 @@ import {
|
|||
ENTITY_TYPE,
|
||||
INLINE_STYLE
|
||||
} from "draftail";
|
||||
import isEqual from "lodash-es/isEqual";
|
||||
import React from "react";
|
||||
|
||||
import BoldIcon from "../../icons/BoldIcon";
|
||||
|
@ -29,6 +30,7 @@ import UnorderedListIcon from "../../icons/UnorderedListIcon";
|
|||
|
||||
// import ImageEntity from "./ImageEntity";
|
||||
// import ImageSource from "./ImageSource";
|
||||
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||
import LinkEntity from "./LinkEntity";
|
||||
import LinkSource from "./LinkSource";
|
||||
|
||||
|
@ -45,22 +47,6 @@ export interface RichTextEditorProps {
|
|||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
"@keyframes focus": {
|
||||
from: {
|
||||
transform: "scaleX(0) scaleY(1)"
|
||||
},
|
||||
to: {
|
||||
transform: "scaleX(1) scaleY(1)"
|
||||
}
|
||||
},
|
||||
"@keyframes hover": {
|
||||
from: {
|
||||
transform: "scaleX(1) scaleY(0)"
|
||||
},
|
||||
to: {
|
||||
transform: "scaleX(1) scaleY(1)"
|
||||
}
|
||||
},
|
||||
error: {
|
||||
color: theme.palette.error.main
|
||||
},
|
||||
|
@ -68,21 +54,13 @@ const styles = (theme: Theme) =>
|
|||
marginTop: theme.spacing.unit * 0.75
|
||||
},
|
||||
input: {
|
||||
"&:hover": {
|
||||
borderBottomColor: theme.palette.primary.main
|
||||
},
|
||||
backgroundColor: theme.overrides.MuiFilledInput.root.backgroundColor,
|
||||
borderBottom: `1px rgba(0, 0, 0, 0) solid`,
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopRightRadius: 4,
|
||||
padding: "27px 12px 10px",
|
||||
position: "relative",
|
||||
transition: theme.transitions.duration.shortest + "ms"
|
||||
position: "relative"
|
||||
},
|
||||
label: {
|
||||
fontSize: theme.typography.caption.fontSize,
|
||||
marginBottom: theme.spacing.unit * 2,
|
||||
marginTop: -21
|
||||
left: 12,
|
||||
position: "absolute",
|
||||
top: 9
|
||||
},
|
||||
linkIcon: {
|
||||
marginTop: 2
|
||||
|
@ -97,8 +75,6 @@ const styles = (theme: Theme) =>
|
|||
color: theme.palette.primary.light
|
||||
},
|
||||
"&:after": {
|
||||
animationDuration: theme.transitions.duration.shortest + "ms",
|
||||
animationFillMode: "both",
|
||||
background: theme.palette.getContrastText(
|
||||
theme.palette.background.default
|
||||
),
|
||||
|
@ -120,16 +96,19 @@ const styles = (theme: Theme) =>
|
|||
"& .Draftail": {
|
||||
"&-Editor": {
|
||||
"&--focus": {
|
||||
"& .DraftEditor": {
|
||||
"&-editorContainer": {
|
||||
"&:after": {
|
||||
animationName: "focus !important",
|
||||
background: theme.palette.primary.main,
|
||||
transform: "scaleX(0) scaleY(1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
boxShadow: `inset 0px 0px 0px 2px ${theme.palette.primary.main}`
|
||||
},
|
||||
"&:hover": {
|
||||
borderColor: theme.palette.primary.main
|
||||
},
|
||||
border: `1px ${
|
||||
theme.overrides.MuiOutlinedInput.root.borderColor
|
||||
} solid`,
|
||||
borderTopLeftRadius: 4,
|
||||
borderTopRightRadius: 4,
|
||||
padding: "27px 12px 10px",
|
||||
position: "relative",
|
||||
transition: theme.transitions.duration.shortest + "ms"
|
||||
},
|
||||
"&-Toolbar": {
|
||||
"&Button": {
|
||||
|
@ -190,6 +169,7 @@ const styles = (theme: Theme) =>
|
|||
display: "inline-flex",
|
||||
flexWrap: "wrap",
|
||||
marginBottom: theme.spacing.unit,
|
||||
marginTop: 10,
|
||||
[theme.breakpoints.down(460)]: {
|
||||
width: "min-content"
|
||||
}
|
||||
|
@ -205,24 +185,7 @@ const styles = (theme: Theme) =>
|
|||
"&$error": {
|
||||
"& .Draftail": {
|
||||
"&-Editor": {
|
||||
"& .DraftEditor": {
|
||||
"&-editorContainer": {
|
||||
"&:after": {
|
||||
animationName: "none",
|
||||
background: theme.palette.error.main,
|
||||
transform: "scaleX(1) scaleY(1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"&--focus": {
|
||||
"& .DraftEditor": {
|
||||
"&-editorContainer": {
|
||||
"&:after": {
|
||||
animationName: "none !important"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
borderColor: theme.palette.error.main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,6 +203,23 @@ const styles = (theme: Theme) =>
|
|||
marginLeft: 10
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave(
|
||||
value: any,
|
||||
initial: any,
|
||||
name: string,
|
||||
onChange: (event: ChangeEvent) => void
|
||||
) {
|
||||
if (value && !isEqual(value, initial)) {
|
||||
onChange({
|
||||
target: {
|
||||
name,
|
||||
value
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const RichTextEditor = withStyles(styles, { name: "RichTextEditor" })(
|
||||
({
|
||||
classes,
|
||||
|
@ -267,14 +247,7 @@ const RichTextEditor = withStyles(styles, { name: "RichTextEditor" })(
|
|||
rawContentState={
|
||||
initial && Object.keys(initial).length > 0 ? initial : null
|
||||
}
|
||||
onSave={value =>
|
||||
onChange({
|
||||
target: {
|
||||
name,
|
||||
value
|
||||
}
|
||||
} as any)
|
||||
}
|
||||
onSave={value => handleSave(value, initial, name, onChange)}
|
||||
blockTypes={[
|
||||
{
|
||||
icon: <HeaderOne />,
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import useScroll from "@saleor/hooks/useScroll";
|
||||
import useWindowScroll from "@saleor/hooks/useWindowScroll";
|
||||
import i18n from "../../i18n";
|
||||
import { maybe } from "../../misc";
|
||||
import AppActionContext from "../AppLayout/AppActionContext";
|
||||
|
@ -82,7 +82,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
|
|||
onSave,
|
||||
...props
|
||||
}: SaveButtonBarProps) => {
|
||||
const scrollPosition = useScroll();
|
||||
const scrollPosition = useWindowScroll();
|
||||
const scrolledToBottom =
|
||||
scrollPosition.y + window.innerHeight >= document.body.scrollHeight;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import i18n from "../../i18n";
|
||||
|
@ -43,6 +43,9 @@ const styles = (theme: Theme) =>
|
|||
flex: 1
|
||||
},
|
||||
labelContainer: {
|
||||
"& span": {
|
||||
paddingRight: 30
|
||||
},
|
||||
display: "flex"
|
||||
},
|
||||
preview: {
|
||||
|
@ -128,7 +131,6 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
|
|||
)}
|
||||
value={title.slice(0, 69)}
|
||||
disabled={loading || disabled}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
placeholder={titlePlaceholder}
|
||||
onChange={onChange}
|
||||
fullWidth
|
||||
|
@ -158,7 +160,6 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
|
|||
fullWidth
|
||||
multiline
|
||||
placeholder={descriptionPlaceholder}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
rows={10}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Omit } from "@material-ui/core";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import { InputProps } from "@material-ui/core/Input";
|
||||
import MenuItem from "@material-ui/core/MenuItem";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
|
@ -9,10 +10,12 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Downshift from "downshift";
|
||||
import React from "react";
|
||||
import { compareTwoStrings } from "string-similarity";
|
||||
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import i18n from "../../i18n";
|
||||
import ArrowDropdownIcon from "../../icons/ArrowDropdown";
|
||||
import Debounce, { DebounceProps } from "../Debounce";
|
||||
|
@ -23,6 +26,10 @@ const styles = (theme: Theme) =>
|
|||
flexGrow: 1,
|
||||
position: "relative"
|
||||
},
|
||||
menuItem: {
|
||||
height: "auto",
|
||||
whiteSpace: "normal"
|
||||
},
|
||||
paper: {
|
||||
borderRadius: 4,
|
||||
left: 0,
|
||||
|
@ -34,26 +41,26 @@ const styles = (theme: Theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
export interface SingleAutocompleteChoiceType {
|
||||
label: string;
|
||||
value: any;
|
||||
}
|
||||
export interface SingleAutocompleteSelectFieldProps {
|
||||
error?: boolean;
|
||||
name: string;
|
||||
choices: Array<{
|
||||
label: string;
|
||||
value: any;
|
||||
}>;
|
||||
value?: {
|
||||
label: string;
|
||||
value: any;
|
||||
};
|
||||
displayValue: string;
|
||||
emptyOption?: boolean;
|
||||
choices: SingleAutocompleteChoiceType[];
|
||||
value?: string;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
placeholder?: string;
|
||||
custom?: boolean;
|
||||
allowCustomValues?: boolean;
|
||||
helperText?: string;
|
||||
label?: string;
|
||||
InputProps?: InputProps;
|
||||
fetchChoices?(value: string);
|
||||
onChange(event);
|
||||
fetchChoices?: (value: string) => void;
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
}
|
||||
|
||||
interface SingleAutocompleteSelectFieldState {
|
||||
|
@ -73,8 +80,10 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
({
|
||||
choices,
|
||||
classes,
|
||||
custom,
|
||||
allowCustomValues,
|
||||
disabled,
|
||||
displayValue,
|
||||
emptyOption,
|
||||
error,
|
||||
helperText,
|
||||
label,
|
||||
|
@ -86,16 +95,24 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
fetchChoices,
|
||||
onChange
|
||||
}: SingleAutocompleteSelectFieldProps & WithStyles<typeof styles>) => {
|
||||
const handleChange = item => onChange({ target: { name, value: item } });
|
||||
const [prevDisplayValue] = useStateFromProps(displayValue);
|
||||
const handleChange = item =>
|
||||
onChange({
|
||||
target: {
|
||||
name,
|
||||
value: item
|
||||
}
|
||||
} as any);
|
||||
|
||||
return (
|
||||
<DebounceAutocomplete debounceFn={fetchChoices}>
|
||||
{debounceFn => (
|
||||
<Downshift
|
||||
selectedItem={value}
|
||||
itemToString={item => (item ? item.label : "")}
|
||||
onSelect={handleChange}
|
||||
defaultInputValue={displayValue}
|
||||
itemToString={() => displayValue}
|
||||
onInputValueChange={value => debounceFn(value)}
|
||||
onSelect={handleChange}
|
||||
selectedItem={value}
|
||||
>
|
||||
{({
|
||||
getInputProps,
|
||||
|
@ -106,13 +123,18 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
toggleMenu,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
highlightedIndex
|
||||
highlightedIndex,
|
||||
reset
|
||||
}) => {
|
||||
const isCustom =
|
||||
const isCustomValueSelected =
|
||||
choices && selectedItem
|
||||
? choices.filter(c => c.value === selectedItem.value)
|
||||
.length === 0
|
||||
? choices.filter(c => c.value === selectedItem).length === 0
|
||||
: false;
|
||||
|
||||
if (prevDisplayValue !== displayValue) {
|
||||
reset({ inputValue: displayValue });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<TextField
|
||||
|
@ -122,9 +144,13 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
placeholder
|
||||
}),
|
||||
endAdornment: (
|
||||
<ArrowDropdownIcon
|
||||
onClick={disabled ? undefined : toggleMenu}
|
||||
/>
|
||||
<div>
|
||||
{loading ? (
|
||||
<CircularProgress size={20} />
|
||||
) : (
|
||||
<ArrowDropdownIcon onClick={toggleMenu} />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
error,
|
||||
id: undefined,
|
||||
|
@ -136,34 +162,64 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
|
|||
label={label}
|
||||
fullWidth={true}
|
||||
/>
|
||||
{isOpen && (
|
||||
{isOpen && (!!inputValue || !!choices.length) && (
|
||||
<Paper className={classes.paper} square>
|
||||
{loading ? (
|
||||
<MenuItem disabled={true} component="div">
|
||||
{i18n.t("Loading...")}
|
||||
</MenuItem>
|
||||
) : choices.length > 0 || custom ? (
|
||||
{choices.length > 0 || allowCustomValues ? (
|
||||
<>
|
||||
{choices.map((suggestion, index) => (
|
||||
{emptyOption && (
|
||||
<MenuItem
|
||||
key={JSON.stringify(suggestion)}
|
||||
selected={highlightedIndex === index}
|
||||
className={classes.menuItem}
|
||||
component="div"
|
||||
{...getItemProps({ item: suggestion })}
|
||||
{...getItemProps({
|
||||
item: ""
|
||||
})}
|
||||
>
|
||||
<Typography color="textSecondary">
|
||||
{i18n.t("None")}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
)}
|
||||
{choices.map((suggestion, index) => {
|
||||
const choiceIndex = index + (emptyOption ? 1 : 0);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
key={JSON.stringify(suggestion)}
|
||||
selected={
|
||||
highlightedIndex === choiceIndex ||
|
||||
selectedItem === suggestion.value
|
||||
}
|
||||
component="div"
|
||||
{...getItemProps({
|
||||
index: choiceIndex,
|
||||
item: suggestion.value
|
||||
})}
|
||||
>
|
||||
{suggestion.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
{custom && (
|
||||
);
|
||||
})}
|
||||
{allowCustomValues &&
|
||||
!!inputValue &&
|
||||
!choices.find(
|
||||
choice =>
|
||||
choice.label.toLowerCase() ===
|
||||
inputValue.toLowerCase()
|
||||
) && (
|
||||
<MenuItem
|
||||
className={classes.menuItem}
|
||||
key={"customValue"}
|
||||
selected={isCustom}
|
||||
selected={isCustomValueSelected}
|
||||
component="div"
|
||||
{...getItemProps({
|
||||
item: { label: inputValue, value: inputValue }
|
||||
item: inputValue
|
||||
})}
|
||||
>
|
||||
{i18n.t("Add custom value")}
|
||||
{i18n.t("Add new value: {{ value }}", {
|
||||
context: "add custom option",
|
||||
value: inputValue
|
||||
})}
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -17,6 +17,11 @@ const styles = (theme: Theme) =>
|
|||
opacity: 1
|
||||
}
|
||||
},
|
||||
primary: {
|
||||
"&$skeleton": {
|
||||
background: theme.palette.primary.main
|
||||
}
|
||||
},
|
||||
skeleton: {
|
||||
animation: "skeleton-animation .75s linear infinite forwards alternate",
|
||||
background: theme.palette.background.default,
|
||||
|
@ -29,12 +34,18 @@ const styles = (theme: Theme) =>
|
|||
|
||||
interface SkeletonProps extends WithStyles<typeof styles> {
|
||||
className?: string;
|
||||
primary?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const Skeleton = withStyles(styles, { name: "Skeleton" })(
|
||||
({ className, classes, style }: SkeletonProps) => (
|
||||
<span className={classNames(classes.skeleton, className)} style={style}>
|
||||
({ className, classes, primary, style }: SkeletonProps) => (
|
||||
<span
|
||||
className={classNames(classes.skeleton, className, {
|
||||
[classes.primary]: primary
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
‌
|
||||
</span>
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Typography, { TypographyProps } from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
|
|
@ -7,11 +7,13 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import Cached from "@material-ui/icons/Cached";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import Image from "../../icons/Image";
|
||||
|
||||
export const AVATAR_MARGIN = 56;
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
avatar: {
|
||||
|
@ -19,8 +21,17 @@ const styles = (theme: Theme) =>
|
|||
border: `1px solid ${theme.overrides.MuiCard.root.borderColor}`,
|
||||
borderRadius: 2,
|
||||
color: "#bdbdbd",
|
||||
display: "inline-flex",
|
||||
padding: theme.spacing.unit / 2
|
||||
},
|
||||
children: {
|
||||
alignSelf: "center",
|
||||
marginLeft: theme.spacing.unit * 2
|
||||
},
|
||||
content: {
|
||||
alignItems: "center",
|
||||
display: "flex"
|
||||
},
|
||||
root: {
|
||||
paddingRight: theme.spacing.unit * 3,
|
||||
width: "1%"
|
||||
|
@ -31,11 +42,19 @@ interface TableCellAvatarProps extends WithStyles<typeof styles> {
|
|||
className?: string;
|
||||
thumbnail?: string;
|
||||
avatarProps?: string;
|
||||
children?: React.ReactNode | React.ReactNodeArray;
|
||||
}
|
||||
|
||||
const TableCellAvatar = withStyles(styles, { name: "TableCellAvatar" })(
|
||||
({ classes, className, thumbnail, avatarProps }: TableCellAvatarProps) => (
|
||||
({
|
||||
classes,
|
||||
children,
|
||||
className,
|
||||
thumbnail,
|
||||
avatarProps
|
||||
}: TableCellAvatarProps) => (
|
||||
<TableCell className={classNames(classes.root, className)}>
|
||||
<div className={classes.content}>
|
||||
{thumbnail === undefined ? (
|
||||
<Avatar className={classNames(classes.avatar, avatarProps)}>
|
||||
<Cached color="primary" />
|
||||
|
@ -50,6 +69,8 @@ const TableCellAvatar = withStyles(styles, { name: "TableCellAvatar" })(
|
|||
src={thumbnail}
|
||||
/>
|
||||
)}
|
||||
<span className={classes.children}>{children}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -27,7 +27,7 @@ const useInputStyles = makeStyles({
|
|||
});
|
||||
|
||||
const Search: React.FC<TextFieldProps> = props => {
|
||||
const classes = useInputStyles();
|
||||
const classes = useInputStyles({});
|
||||
return (
|
||||
<TextField
|
||||
{...props}
|
||||
|
|
|
@ -11,7 +11,7 @@ import MuiTableHead, {
|
|||
} from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
import { Node } from "../../types";
|
||||
|
@ -20,7 +20,9 @@ import i18n from "../../i18n";
|
|||
import Checkbox from "../Checkbox";
|
||||
|
||||
export interface TableHeadProps extends MuiTableHeadProps {
|
||||
colSpan: number;
|
||||
disabled: boolean;
|
||||
dragRows?: boolean;
|
||||
selected: number;
|
||||
items: Node[];
|
||||
toolbar: React.ReactNode | React.ReactNodeArray;
|
||||
|
@ -34,7 +36,7 @@ const styles = (theme: Theme) =>
|
|||
},
|
||||
checkboxPartialSelect: {
|
||||
"&:after": {
|
||||
background: "#fff",
|
||||
background: theme.palette.common.white,
|
||||
content: "''",
|
||||
height: 2,
|
||||
position: "absolute",
|
||||
|
@ -50,6 +52,10 @@ const styles = (theme: Theme) =>
|
|||
height: 47,
|
||||
marginRight: -theme.spacing.unit * 2
|
||||
},
|
||||
dragRows: {
|
||||
padding: 0,
|
||||
width: 52
|
||||
},
|
||||
padding: {
|
||||
"&:last-child": {
|
||||
padding: 0
|
||||
|
@ -57,7 +63,6 @@ const styles = (theme: Theme) =>
|
|||
},
|
||||
root: {
|
||||
backgroundColor: fade(theme.palette.primary.main, 0.05),
|
||||
borderBottom: "1px solid rgba(224, 224, 224, 1)",
|
||||
paddingLeft: 0,
|
||||
paddingRight: 24
|
||||
},
|
||||
|
@ -77,7 +82,9 @@ const TableHead = withStyles(styles, {
|
|||
({
|
||||
classes,
|
||||
children,
|
||||
colSpan,
|
||||
disabled,
|
||||
dragRows,
|
||||
items,
|
||||
selected,
|
||||
toggleAll,
|
||||
|
@ -87,13 +94,21 @@ const TableHead = withStyles(styles, {
|
|||
return (
|
||||
<MuiTableHead {...muiTableHeadProps}>
|
||||
<TableRow>
|
||||
{dragRows && (items === undefined || items.length > 0) && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
className={classNames({
|
||||
[classes.checkboxSelected]: selected
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{(items === undefined || items.length > 0) && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
className={classNames({
|
||||
[classes.checkboxSelected]: selected,
|
||||
[classes.dragRows]: dragRows
|
||||
})}
|
||||
>
|
||||
{items && items.length > 0 ? (
|
||||
<Checkbox
|
||||
className={classNames({
|
||||
[classes.checkboxPartialSelect]:
|
||||
|
@ -103,11 +118,14 @@ const TableHead = withStyles(styles, {
|
|||
disabled={disabled}
|
||||
onChange={() => toggleAll(items, selected)}
|
||||
/>
|
||||
) : null}
|
||||
</TableCell>
|
||||
)}
|
||||
{selected ? (
|
||||
<>
|
||||
<TableCell className={classNames(classes.root)} colSpan={50}>
|
||||
<TableCell
|
||||
className={classNames(classes.root)}
|
||||
colSpan={colSpan - 1}
|
||||
>
|
||||
<div className={classes.container}>
|
||||
{selected && (
|
||||
<Typography>
|
||||
|
|
|
@ -11,6 +11,9 @@ import TableCell from "@material-ui/core/TableCell";
|
|||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import React from "react";
|
||||
|
||||
import RowNumberSelect from "@saleor/components/RowNumberSelect";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { ListSettings } from "../../types";
|
||||
import TablePaginationActions from "./TablePaginationActions";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -50,6 +53,7 @@ const styles = (theme: Theme) =>
|
|||
toolbar: {
|
||||
height: 56,
|
||||
minHeight: 56,
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2
|
||||
}
|
||||
});
|
||||
|
@ -59,11 +63,13 @@ interface TablePaginationProps extends WithStyles<typeof styles> {
|
|||
backIconButtonProps?: Partial<IconButtonProps>;
|
||||
colSpan: number;
|
||||
component?: string | typeof TableCell;
|
||||
settings?: ListSettings;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
nextIconButtonProps?: Partial<IconButtonProps>;
|
||||
onNextPage(event);
|
||||
onPreviousPage(event);
|
||||
onUpdateListSettings?(key: keyof ListSettings, value: any): void;
|
||||
}
|
||||
|
||||
const TablePagination = withStyles(styles, { name: "TablePagination" })(
|
||||
|
@ -73,11 +79,13 @@ const TablePagination = withStyles(styles, { name: "TablePagination" })(
|
|||
classes,
|
||||
colSpan: colSpanProp,
|
||||
component: Component,
|
||||
settings,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
nextIconButtonProps,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
...other
|
||||
}: TablePaginationProps) => {
|
||||
let colSpan;
|
||||
|
@ -89,7 +97,15 @@ const TablePagination = withStyles(styles, { name: "TablePagination" })(
|
|||
return (
|
||||
<Component className={classes.root} colSpan={colSpan} {...other}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<div className={classes.spacer} />
|
||||
<div className={classes.spacer}>
|
||||
{maybe(() => settings.rowNumber) && (
|
||||
<RowNumberSelect
|
||||
choices={[20, 30, 50, 100]}
|
||||
settings={settings}
|
||||
onChange={onUpdateListSettings}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Actions
|
||||
backIconButtonProps={backIconButtonProps}
|
||||
hasNextPage={hasNextPage}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
|
|||
import ArrowLeft from "@material-ui/icons/ArrowLeft";
|
||||
import ArrowRight from "@material-ui/icons/ArrowRight";
|
||||
import useTheme from "@saleor/hooks/useTheme";
|
||||
import * as classNames from "classnames";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
|
|
@ -78,7 +78,7 @@ const TextFieldWithChoice = withStyles(styles, {
|
|||
<Typography component="span" variant="caption">
|
||||
{ChoiceProps.label}
|
||||
</Typography>
|
||||
<DropdownIcon />
|
||||
{ChoiceProps.values ? <DropdownIcon /> : null}
|
||||
</div>
|
||||
<Popper
|
||||
open={menuOpen}
|
||||
|
|
|
@ -14,17 +14,21 @@ const dark: IThemeColors = {
|
|||
},
|
||||
error: "#C22D74",
|
||||
font: {
|
||||
button: "#202124",
|
||||
default: "#FCFCFC",
|
||||
gray: "#9E9D9D"
|
||||
gray: "#9E9D9D",
|
||||
textButton: "#FFFFFF"
|
||||
},
|
||||
gray: {
|
||||
default: "#202124",
|
||||
disabled: "rgba(32, 33, 36, 0.6)"
|
||||
},
|
||||
input: {
|
||||
border: "#9d9d9d",
|
||||
default: "#25262A",
|
||||
disabled: "#292A2D",
|
||||
focused: "#25262A"
|
||||
disabled: "none",
|
||||
text: "#FCFCFC",
|
||||
textHover: "#616161"
|
||||
},
|
||||
paperBorder: "#252728",
|
||||
primary: "#13BEBB",
|
||||
|
@ -38,20 +42,24 @@ const light: IThemeColors = {
|
|||
},
|
||||
error: "#C22D74",
|
||||
font: {
|
||||
button: "#FFFFFF",
|
||||
default: "#3D3D3D",
|
||||
gray: "#616161"
|
||||
gray: "#616161",
|
||||
textButton: "#06847B"
|
||||
},
|
||||
gray: {
|
||||
default: "#C8C8C8",
|
||||
disabled: "rgba(216, 216, 216, 0.3)"
|
||||
},
|
||||
input: {
|
||||
default: "#F1F6F6",
|
||||
border: "#616161",
|
||||
default: "#FFFFFF",
|
||||
disabled: "#EAEAEA",
|
||||
focused: "#DCEBEB"
|
||||
text: "#3D3D3D",
|
||||
textHover: "#616161"
|
||||
},
|
||||
paperBorder: "#EAEAEA",
|
||||
primary: "#13BEBB",
|
||||
primary: "#06847B",
|
||||
secondary: "#21125E"
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Avatar from "@material-ui/core/Avatar";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import * as colors from "@material-ui/core/colors";
|
||||
import colors from "@material-ui/core/colors";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import PersonIcon from "@material-ui/icons/Person";
|
||||
import * as CRC from "crc-32";
|
||||
import CRC from "crc-32";
|
||||
import React from "react";
|
||||
|
||||
import { DateTime } from "../Date";
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { SearchQueryVariables } from "./containers/BaseSearch";
|
||||
import { ListSettings, ListViews } from "./types";
|
||||
|
||||
export const APP_MOUNT_URI = process.env.APP_MOUNT_URI || "/";
|
||||
export const API_URI = process.env.API_URI || "/graphql/";
|
||||
|
@ -10,3 +11,58 @@ export const DEFAULT_INITIAL_SEARCH_DATA: SearchQueryVariables = {
|
|||
};
|
||||
|
||||
export const PAGINATE_BY = 20;
|
||||
|
||||
export type ProductListColumns = "productType" | "isPublished" | "price";
|
||||
export interface AppListViewSettings {
|
||||
[ListViews.CATEGORY_LIST]: ListSettings;
|
||||
[ListViews.COLLECTION_LIST]: ListSettings;
|
||||
[ListViews.CUSTOMER_LIST]: ListSettings;
|
||||
[ListViews.DRAFT_LIST]: ListSettings;
|
||||
[ListViews.NAVIGATION_LIST]: ListSettings;
|
||||
[ListViews.ORDER_LIST]: ListSettings;
|
||||
[ListViews.PAGES_LIST]: ListSettings;
|
||||
[ListViews.PRODUCT_LIST]: ListSettings<ProductListColumns>;
|
||||
[ListViews.SALES_LIST]: ListSettings;
|
||||
[ListViews.SHIPPING_METHODS_LIST]: ListSettings;
|
||||
[ListViews.STAFF_MEMBERS_LIST]: ListSettings;
|
||||
[ListViews.VOUCHER_LIST]: ListSettings;
|
||||
}
|
||||
export const defaultListSettings: AppListViewSettings = {
|
||||
[ListViews.CATEGORY_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.COLLECTION_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.CUSTOMER_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.DRAFT_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.NAVIGATION_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.ORDER_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.PAGES_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.PRODUCT_LIST]: {
|
||||
columns: ["isPublished", "price", "productType"],
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.SALES_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.SHIPPING_METHODS_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.STAFF_MEMBERS_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
},
|
||||
[ListViews.VOUCHER_LIST]: {
|
||||
rowNumber: PAGINATE_BY
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
import React from "react";
|
||||
|
||||
import { attributeListUrl } from "@saleor/attributes/urls";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useUser from "@saleor/hooks/useUser";
|
||||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import i18n from "../i18n";
|
||||
import Navigation from "../icons/Navigation";
|
||||
import Pages from "../icons/Pages";
|
||||
import ProductTypes from "../icons/ProductTypes";
|
||||
import ShippingMethods from "../icons/ShippingMethods";
|
||||
import SiteSettings from "../icons/SiteSettings";
|
||||
import StaffMembers from "../icons/StaffMembers";
|
||||
import Taxes from "../icons/Taxes";
|
||||
import { maybe } from "../misc";
|
||||
import { menuListUrl } from "../navigation/urls";
|
||||
import { pageListUrl } from "../pages/urls";
|
||||
import { productTypeListUrl } from "../productTypes/urls";
|
||||
import { shippingZonesListUrl } from "../shipping/urls";
|
||||
import { siteSettingsUrl } from "../siteSettings/urls";
|
||||
import { staffListUrl } from "../staff/urls";
|
||||
import { taxSection } from "../taxes/urls";
|
||||
import { PermissionEnum } from "../types/globalTypes";
|
||||
import i18n from "@saleor/i18n";
|
||||
import Navigation from "@saleor/icons/Navigation";
|
||||
import Pages from "@saleor/icons/Pages";
|
||||
import ProductTypes from "@saleor/icons/ProductTypes";
|
||||
import ShippingMethods from "@saleor/icons/ShippingMethods";
|
||||
import SiteSettings from "@saleor/icons/SiteSettings";
|
||||
import StaffMembers from "@saleor/icons/StaffMembers";
|
||||
import Taxes from "@saleor/icons/Taxes";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { menuListUrl } from "@saleor/navigation/urls";
|
||||
import { pageListUrl } from "@saleor/pages/urls";
|
||||
import { productTypeListUrl } from "@saleor/productTypes/urls";
|
||||
import { shippingZonesListUrl } from "@saleor/shipping/urls";
|
||||
import { siteSettingsUrl } from "@saleor/siteSettings/urls";
|
||||
import { staffListUrl } from "@saleor/staff/urls";
|
||||
import { taxSection } from "@saleor/taxes/urls";
|
||||
import { PermissionEnum } from "@saleor/types/globalTypes";
|
||||
import ConfigurationPage, { MenuItem } from "./ConfigurationPage";
|
||||
|
||||
export const configurationMenu: MenuItem[] = [
|
||||
{
|
||||
description: i18n.t("Determine attributes used to create product types"),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
permission: PermissionEnum.MANAGE_PRODUCTS,
|
||||
title: i18n.t("Attributes"),
|
||||
url: attributeListUrl()
|
||||
},
|
||||
{
|
||||
description: i18n.t("Define types of products you sell"),
|
||||
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
|
||||
|
|
|
@ -12,9 +12,11 @@ import ConfirmButton, {
|
|||
ConfirmButtonTransitionState
|
||||
} from "@saleor/components/ConfirmButton";
|
||||
import Form from "@saleor/components/Form";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe } from "../../../misc";
|
||||
import { UserError } from "../../../types";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { UserError } from "@saleor/types";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import { AddressTypeInput } from "../../types";
|
||||
import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses";
|
||||
|
||||
|
@ -50,14 +52,14 @@ const CustomerAddressDialog = withStyles(styles, {})(
|
|||
onClose,
|
||||
onConfirm
|
||||
}: CustomerAddressDialogProps & WithStyles<typeof styles>) => {
|
||||
const [countryDisplayName, setCountryDisplayName] = useStateFromProps(
|
||||
maybe(() => address.country.country, "")
|
||||
);
|
||||
const initialForm: AddressTypeInput = {
|
||||
city: maybe(() => address.city, ""),
|
||||
cityArea: maybe(() => address.cityArea, ""),
|
||||
companyName: maybe(() => address.companyName, ""),
|
||||
country: {
|
||||
label: maybe(() => address.country.country, ""),
|
||||
value: maybe(() => address.country.code, "")
|
||||
},
|
||||
country: maybe(() => address.country.code, ""),
|
||||
countryArea: maybe(() => address.countryArea, ""),
|
||||
firstName: maybe(() => address.firstName, ""),
|
||||
lastName: maybe(() => address.lastName, ""),
|
||||
|
@ -66,6 +68,16 @@ const CustomerAddressDialog = withStyles(styles, {})(
|
|||
streetAddress1: maybe(() => address.streetAddress1, ""),
|
||||
streetAddress2: maybe(() => address.streetAddress2, "")
|
||||
};
|
||||
|
||||
const countryChoices = maybe(
|
||||
() =>
|
||||
countries.map(country => ({
|
||||
label: country.label,
|
||||
value: country.code
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={onClose}
|
||||
|
@ -75,7 +87,14 @@ const CustomerAddressDialog = withStyles(styles, {})(
|
|||
maxWidth="sm"
|
||||
>
|
||||
<Form initial={initialForm} errors={errors} onSubmit={onConfirm}>
|
||||
{({ change, data, errors, submit }) => (
|
||||
{({ change, data, errors, submit }) => {
|
||||
const handleCountrySelect = createSingleAutocompleteSelectHandler(
|
||||
change,
|
||||
setCountryDisplayName,
|
||||
countryChoices
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogTitle>
|
||||
{variant === "create"
|
||||
|
@ -84,10 +103,12 @@ const CustomerAddressDialog = withStyles(styles, {})(
|
|||
</DialogTitle>
|
||||
<DialogContent className={classes.overflow}>
|
||||
<AddressEdit
|
||||
countries={countries}
|
||||
countries={countryChoices}
|
||||
data={data}
|
||||
countryDisplayValue={countryDisplayName}
|
||||
errors={errors}
|
||||
onChange={change}
|
||||
onCountryChange={handleCountrySelect}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
@ -106,7 +127,8 @@ const CustomerAddressDialog = withStyles(styles, {})(
|
|||
</ConfirmButton>
|
||||
</DialogActions>
|
||||
</>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -7,10 +7,10 @@ import React from "react";
|
|||
import AddressEdit from "@saleor/components/AddressEdit";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { FormSpacer } from "@saleor/components/FormSpacer";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import i18n from "../../../i18n";
|
||||
import { FormErrors } from "../../../types";
|
||||
import { AddressTypeInput } from "../../types";
|
||||
import { CustomerCreateData_shop_countries } from "../../types/CustomerCreateData";
|
||||
|
||||
const styles = createStyles({
|
||||
overflow: {
|
||||
|
@ -19,11 +19,13 @@ const styles = createStyles({
|
|||
});
|
||||
|
||||
export interface CustomerCreateAddressProps extends WithStyles<typeof styles> {
|
||||
countries: CustomerCreateData_shop_countries[];
|
||||
countries: SingleAutocompleteChoiceType[];
|
||||
countryDisplayName: string;
|
||||
data: AddressTypeInput;
|
||||
disabled: boolean;
|
||||
errors: FormErrors<keyof AddressTypeInput>;
|
||||
onChange(event: React.ChangeEvent<any>);
|
||||
onCountryChange(event: React.ChangeEvent<any>);
|
||||
}
|
||||
|
||||
const CustomerCreateAddress = withStyles(styles, {
|
||||
|
@ -32,10 +34,12 @@ const CustomerCreateAddress = withStyles(styles, {
|
|||
({
|
||||
classes,
|
||||
countries,
|
||||
countryDisplayName,
|
||||
data,
|
||||
disabled,
|
||||
errors,
|
||||
onChange
|
||||
onChange,
|
||||
onCountryChange
|
||||
}: CustomerCreateAddressProps) => (
|
||||
<Card className={classes.overflow}>
|
||||
<CardTitle title={i18n.t("Primary address")} />
|
||||
|
@ -45,14 +49,13 @@ const CustomerCreateAddress = withStyles(styles, {
|
|||
</Typography>
|
||||
<FormSpacer />
|
||||
<AddressEdit
|
||||
countries={countries.map(country => ({
|
||||
code: country.code,
|
||||
label: country.country
|
||||
}))}
|
||||
countries={countries}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
countryDisplayValue={countryDisplayName}
|
||||
errors={errors}
|
||||
onChange={onChange}
|
||||
onCountryChange={onCountryChange}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
@ -8,6 +8,7 @@ import Form from "@saleor/components/Form";
|
|||
import Grid from "@saleor/components/Grid";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import i18n from "../../../i18n";
|
||||
import { UserError } from "../../../types";
|
||||
import { AddressTypeInput } from "../../types";
|
||||
|
@ -27,10 +28,7 @@ const initialForm: CustomerCreatePageFormData = {
|
|||
city: "",
|
||||
cityArea: "",
|
||||
companyName: "",
|
||||
country: {
|
||||
label: "",
|
||||
value: ""
|
||||
},
|
||||
country: "",
|
||||
countryArea: "",
|
||||
customerFirstName: "",
|
||||
customerLastName: "",
|
||||
|
@ -60,9 +58,28 @@ const CustomerCreatePage: React.StatelessComponent<CustomerCreatePageProps> = ({
|
|||
saveButtonBar,
|
||||
onBack,
|
||||
onSubmit
|
||||
}: CustomerCreatePageProps) => (
|
||||
<Form initial={initialForm} onSubmit={onSubmit} errors={errors} confirmLeave>
|
||||
{({ change, data, errors: formErrors, hasChanged, submit }) => (
|
||||
}: CustomerCreatePageProps) => {
|
||||
const [countryDisplayName, setCountryDisplayName] = React.useState("");
|
||||
const countryChoices = countries.map(country => ({
|
||||
label: country.country,
|
||||
value: country.code
|
||||
}));
|
||||
|
||||
return (
|
||||
<Form
|
||||
initial={initialForm}
|
||||
onSubmit={onSubmit}
|
||||
errors={errors}
|
||||
confirmLeave
|
||||
>
|
||||
{({ change, data, errors: formErrors, hasChanged, submit }) => {
|
||||
const handleCountrySelect = createSingleAutocompleteSelectHandler(
|
||||
change,
|
||||
setCountryDisplayName,
|
||||
countryChoices
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Customers")}</AppHeader>
|
||||
<PageHeader title={i18n.t("Add customer")} />
|
||||
|
@ -76,11 +93,13 @@ const CustomerCreatePage: React.StatelessComponent<CustomerCreatePageProps> = ({
|
|||
/>
|
||||
<CardSpacer />
|
||||
<CustomerCreateAddress
|
||||
countries={countries}
|
||||
countries={countryChoices}
|
||||
countryDisplayName={countryDisplayName}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
onCountryChange={handleCountrySelect}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<CustomerCreateNote
|
||||
|
@ -98,8 +117,10 @@ const CustomerCreatePage: React.StatelessComponent<CustomerCreatePageProps> = ({
|
|||
onCancel={onBack}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
CustomerCreatePage.displayName = "CustomerCreatePage";
|
||||
export default CustomerCreatePage;
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import * as moment from "moment-timezone";
|
||||
import moment from "moment-timezone";
|
||||
import React from "react";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
|
|
|
@ -55,11 +55,11 @@ const CustomerDetailsPage: React.StatelessComponent<
|
|||
<Form
|
||||
errors={errors}
|
||||
initial={{
|
||||
email: maybe(() => customer.email),
|
||||
firstName: maybe(() => customer.firstName),
|
||||
email: maybe(() => customer.email, ""),
|
||||
firstName: maybe(() => customer.firstName, ""),
|
||||
isActive: maybe(() => customer.isActive, false),
|
||||
lastName: maybe(() => customer.lastName),
|
||||
note: maybe(() => customer.note)
|
||||
lastName: maybe(() => customer.lastName, ""),
|
||||
note: maybe(() => customer.note, "")
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
confirmLeave
|
||||
|
|
|
@ -16,9 +16,9 @@ import Checkbox from "@saleor/components/Checkbox";
|
|||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { getUserName, maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions, ListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { getUserName, maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
@ -47,14 +47,18 @@ export interface CustomerListProps
|
|||
customers: ListCustomers_customers_edges_node[];
|
||||
}
|
||||
|
||||
const numberOfColumns = 4;
|
||||
|
||||
const CustomerList = withStyles(styles, { name: "CustomerList" })(
|
||||
({
|
||||
classes,
|
||||
settings,
|
||||
disabled,
|
||||
customers,
|
||||
pageInfo,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
toolbar,
|
||||
toggle,
|
||||
|
@ -65,6 +69,7 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
|
|||
<Card>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={customers}
|
||||
|
@ -84,9 +89,11 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={4}
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
|
@ -112,6 +119,7 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(customer.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
@ -132,7 +140,7 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No customers found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
@ -4,8 +4,8 @@ import React from "react";
|
|||
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
|
||||
import CustomerList from "../CustomerList/CustomerList";
|
||||
|
||||
|
|
|
@ -2,10 +2,7 @@ export interface AddressTypeInput {
|
|||
city: string;
|
||||
cityArea?: string;
|
||||
companyName?: string;
|
||||
country: {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
country: string;
|
||||
countryArea?: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
|
|
|
@ -133,6 +133,16 @@ const CustomerAddresses: React.FC<CustomerAddressesProps> = ({
|
|||
[]
|
||||
)
|
||||
);
|
||||
|
||||
const countryChoices = maybe(
|
||||
() =>
|
||||
shop.countries.map(country => ({
|
||||
code: country.code,
|
||||
label: country.country
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
|
@ -156,14 +166,7 @@ const CustomerAddresses: React.FC<CustomerAddressesProps> = ({
|
|||
<CustomerAddressDialog
|
||||
address={undefined}
|
||||
confirmButtonState={createAddressTransitionState}
|
||||
countries={maybe(
|
||||
() =>
|
||||
shop.countries.map(country => ({
|
||||
code: country.code,
|
||||
label: country.country
|
||||
})),
|
||||
[]
|
||||
)}
|
||||
countries={countryChoices}
|
||||
errors={maybe(
|
||||
() =>
|
||||
createCustomerAddressOpts.data.addressCreate
|
||||
|
@ -173,14 +176,11 @@ const CustomerAddresses: React.FC<CustomerAddressesProps> = ({
|
|||
open={params.action === "add"}
|
||||
variant="create"
|
||||
onClose={closeModal}
|
||||
onConfirm={formData =>
|
||||
onConfirm={input =>
|
||||
createCustomerAddress({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
...formData,
|
||||
country: formData.country.value
|
||||
}
|
||||
input
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -192,7 +192,7 @@ const CustomerAddresses: React.FC<CustomerAddressesProps> = ({
|
|||
)
|
||||
)}
|
||||
confirmButtonState={updateAddressTransitionState}
|
||||
countries={[]}
|
||||
countries={countryChoices}
|
||||
errors={maybe(
|
||||
() =>
|
||||
updateCustomerAddressOpts.data.addressUpdate
|
||||
|
@ -202,14 +202,11 @@ const CustomerAddresses: React.FC<CustomerAddressesProps> = ({
|
|||
open={params.action === "edit"}
|
||||
variant="edit"
|
||||
onClose={closeModal}
|
||||
onConfirm={formData =>
|
||||
onConfirm={input =>
|
||||
updateCustomerAddress({
|
||||
variables: {
|
||||
id: params.id,
|
||||
input: {
|
||||
...formData,
|
||||
country: formData.country.value
|
||||
}
|
||||
input
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -67,14 +67,8 @@ export const CustomerCreate: React.StatelessComponent<{}> = () => {
|
|||
createCustomer({
|
||||
variables: {
|
||||
input: {
|
||||
defaultBillingAddress: {
|
||||
...address,
|
||||
country: address.country.value
|
||||
},
|
||||
defaultShippingAddress: {
|
||||
...address,
|
||||
country: address.country.value
|
||||
},
|
||||
defaultBillingAddress: address,
|
||||
defaultShippingAddress: address,
|
||||
email: formData.email,
|
||||
firstName: formData.customerFirstName,
|
||||
lastName: formData.customerLastName,
|
||||
|
|
|
@ -5,14 +5,15 @@ import React from "react";
|
|||
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useListSettings from "@saleor/hooks/useListSettings";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import usePaginator, {
|
||||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { PAGINATE_BY } from "../../config";
|
||||
import i18n from "../../i18n";
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import CustomerListPage from "../components/CustomerListPage";
|
||||
import { TypedBulkRemoveCustomers } from "../mutations";
|
||||
import { TypedCustomerListQuery } from "../queries";
|
||||
|
@ -37,6 +38,9 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
|
|||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
const { updateListSettings, settings } = useListSettings(
|
||||
ListViews.CUSTOMER_LIST
|
||||
);
|
||||
|
||||
const closeModal = () =>
|
||||
navigate(
|
||||
|
@ -48,7 +52,7 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
|
|||
true
|
||||
);
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||
|
||||
return (
|
||||
<TypedCustomerListQuery displayLoader variables={paginationState}>
|
||||
|
@ -87,11 +91,13 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
|
|||
customers={maybe(() =>
|
||||
data.customers.edges.map(edge => edge.node)
|
||||
)}
|
||||
settings={settings}
|
||||
disabled={loading}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={() => navigate(customerAddUrl)}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onRowClick={id => () => navigate(customerUrl(id))}
|
||||
toolbar={
|
||||
<IconButton
|
||||
|
|
|
@ -50,6 +50,9 @@ const styles = (theme: Theme) =>
|
|||
width: "60%"
|
||||
}
|
||||
});
|
||||
|
||||
const numberOfColumns = 4;
|
||||
|
||||
const DiscountCategories = withStyles(styles, {
|
||||
name: "DiscountCategories"
|
||||
})(
|
||||
|
@ -71,9 +74,7 @@ const DiscountCategories = withStyles(styles, {
|
|||
}: DiscountCategoriesProps & WithStyles<typeof styles>) => (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Categories assigned to {{ saleName }}", {
|
||||
saleName: maybe(() => sale.name)
|
||||
})}
|
||||
title={i18n.t("Eligible Categories")}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={onCategoryAssign}>
|
||||
{i18n.t("Assign categories")}
|
||||
|
@ -82,6 +83,7 @@ const DiscountCategories = withStyles(styles, {
|
|||
/>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={maybe(() => sale.categories.edges.map(edge => edge.node))}
|
||||
|
@ -89,7 +91,6 @@ const DiscountCategories = withStyles(styles, {
|
|||
toolbar={toolbar}
|
||||
>
|
||||
<>
|
||||
<TableCell />
|
||||
<TableCell className={classes.wideColumn}>
|
||||
{i18n.t("Category name")}
|
||||
</TableCell>
|
||||
|
@ -102,7 +103,7 @@ const DiscountCategories = withStyles(styles, {
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={4}
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={
|
||||
|
@ -130,6 +131,7 @@ const DiscountCategories = withStyles(styles, {
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(category.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
@ -158,7 +160,7 @@ const DiscountCategories = withStyles(styles, {
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No categories found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
@ -50,6 +50,9 @@ const styles = (theme: Theme) =>
|
|||
width: "60%"
|
||||
}
|
||||
});
|
||||
|
||||
const numberOfColumns = 4;
|
||||
|
||||
const DiscountCollections = withStyles(styles, {
|
||||
name: "DiscountCollections"
|
||||
})(
|
||||
|
@ -71,9 +74,7 @@ const DiscountCollections = withStyles(styles, {
|
|||
}: DiscountCollectionsProps & WithStyles<typeof styles>) => (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Collections assigned to {{ saleName }}", {
|
||||
saleName: maybe(() => sale.name)
|
||||
})}
|
||||
title={i18n.t("Eligible Collections")}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={onCollectionAssign}>
|
||||
{i18n.t("Assign collections")}
|
||||
|
@ -82,6 +83,7 @@ const DiscountCollections = withStyles(styles, {
|
|||
/>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={maybe(() => sale.collections.edges.map(edge => edge.node))}
|
||||
|
@ -94,11 +96,12 @@ const DiscountCollections = withStyles(styles, {
|
|||
<TableCell className={classes.textRight}>
|
||||
{i18n.t("Products")}
|
||||
</TableCell>
|
||||
<TableCell />
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={4}
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={
|
||||
|
@ -125,6 +128,7 @@ const DiscountCollections = withStyles(styles, {
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(collection.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
@ -156,7 +160,7 @@ const DiscountCollections = withStyles(styles, {
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No collections found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
|
@ -19,7 +19,9 @@ import CardTitle from "@saleor/components/CardTitle";
|
|||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
|
@ -36,28 +38,34 @@ export interface SaleProductsProps extends ListProps, ListActions {
|
|||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colName: {},
|
||||
colPublished: {
|
||||
width: 150
|
||||
},
|
||||
colType: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
colName: {},
|
||||
colPublished: {},
|
||||
colType: {},
|
||||
iconCell: {
|
||||
colActions: {
|
||||
"&:last-child": {
|
||||
paddingRight: 0
|
||||
},
|
||||
width: 48 + theme.spacing.unit / 2
|
||||
},
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colNameLabel: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPublished: {
|
||||
width: 150
|
||||
},
|
||||
colType: {
|
||||
width: 200
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableRow: {
|
||||
cursor: "pointer"
|
||||
}
|
||||
});
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
const DiscountProducts = withStyles(styles, {
|
||||
name: "DiscountProducts"
|
||||
})(
|
||||
|
@ -79,9 +87,7 @@ const DiscountProducts = withStyles(styles, {
|
|||
}: SaleProductsProps & WithStyles<typeof styles>) => (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={i18n.t("Products assigned to {{ saleName }}", {
|
||||
saleName: maybe(() => sale.name)
|
||||
})}
|
||||
title={i18n.t("Eligible Products")}
|
||||
toolbar={
|
||||
<Button color="primary" onClick={onProductAssign}>
|
||||
{i18n.t("Assign products")}
|
||||
|
@ -90,16 +96,17 @@ const DiscountProducts = withStyles(styles, {
|
|||
/>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={maybe(() => sale.products.edges.map(edge => edge.node))}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell />
|
||||
<TableCell />
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameLabel}>
|
||||
{i18n.t("Product name")}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colType}>
|
||||
{i18n.t("Product Type")}
|
||||
|
@ -112,7 +119,7 @@ const DiscountProducts = withStyles(styles, {
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={6}
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={
|
||||
|
@ -139,15 +146,16 @@ const DiscountProducts = withStyles(styles, {
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
/>
|
||||
<TableCell className={classes.colName}>
|
||||
>
|
||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||
</TableCell>
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colType}>
|
||||
{maybe<React.ReactNode>(
|
||||
() => product.productType.name,
|
||||
|
@ -170,7 +178,7 @@ const DiscountProducts = withStyles(styles, {
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.iconCell}>
|
||||
<TableCell className={classes.colActions}>
|
||||
<IconButton
|
||||
disabled={!product || disabled}
|
||||
onClick={event => {
|
||||
|
@ -186,7 +194,9 @@ const DiscountProducts = withStyles(styles, {
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6}>{i18n.t("No products found")}</TableCell>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No products found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -19,10 +19,10 @@ import Percent from "@saleor/components/Percent";
|
|||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions, ListProps } from "../../../types";
|
||||
import { SaleType } from "../../../types/globalTypes";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { SaleType } from "@saleor/types/globalTypes";
|
||||
import { SaleList_sales_edges_node } from "../../types/SaleList";
|
||||
|
||||
export interface SaleListProps extends ListProps, ListActions {
|
||||
|
@ -59,15 +59,19 @@ const styles = (theme: Theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
const SaleList = withStyles(styles, {
|
||||
name: "SaleList"
|
||||
})(
|
||||
({
|
||||
classes,
|
||||
settings,
|
||||
defaultCurrency,
|
||||
disabled,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
pageInfo,
|
||||
sales,
|
||||
|
@ -80,6 +84,7 @@ const SaleList = withStyles(styles, {
|
|||
<Card>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={sales}
|
||||
|
@ -110,9 +115,11 @@ const SaleList = withStyles(styles, {
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={5}
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
|
@ -138,6 +145,7 @@ const SaleList = withStyles(styles, {
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(sale.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
|
@ -184,7 +192,9 @@ const SaleList = withStyles(styles, {
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>{i18n.t("No sales found")}</TableCell>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No sales found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -4,8 +4,8 @@ import React from "react";
|
|||
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { SaleList_sales_edges_node } from "../../types/SaleList";
|
||||
import SaleList from "../SaleList";
|
||||
|
||||
|
|
|
@ -96,6 +96,9 @@ const SalePricing = withStyles(styles, {
|
|||
label={i18n.t("Start Date")}
|
||||
value={data.startDate}
|
||||
type="date"
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField
|
||||
|
@ -107,6 +110,9 @@ const SalePricing = withStyles(styles, {
|
|||
label={i18n.t("End Date")}
|
||||
value={data.endDate}
|
||||
type="date"
|
||||
InputLabelProps={{
|
||||
shrink: true
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</CardContent>
|
||||
|
|
|
@ -11,22 +11,33 @@ import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
|||
import i18n from "../../../i18n";
|
||||
import { UserError } from "../../../types";
|
||||
import {
|
||||
VoucherDiscountValueType,
|
||||
VoucherType
|
||||
DiscountValueTypeEnum,
|
||||
VoucherTypeEnum
|
||||
} from "../../../types/globalTypes";
|
||||
import { RequirementsPicker } from "../../types";
|
||||
import VoucherDates from "../VoucherDates";
|
||||
import VoucherInfo from "../VoucherInfo";
|
||||
import VoucherOptions from "../VoucherOptions";
|
||||
import VoucherLimits from "../VoucherLimits";
|
||||
import VoucherRequirements from "../VoucherRequirements";
|
||||
import VoucherTypes from "../VoucherTypes";
|
||||
|
||||
import VoucherValue from "../VoucherValue";
|
||||
export interface FormData {
|
||||
applyOncePerCustomer: boolean;
|
||||
applyOncePerOrder: boolean;
|
||||
code: string;
|
||||
discountType: VoucherDiscountValueType;
|
||||
discountType: DiscountValueTypeEnum;
|
||||
endDate: string;
|
||||
minAmountSpent: number;
|
||||
name: string;
|
||||
endTime: string;
|
||||
hasEndDate: boolean;
|
||||
hasUsageLimit: boolean;
|
||||
minAmountSpent: string;
|
||||
minCheckoutItemsQuantity: string;
|
||||
requirementsPicker: RequirementsPicker;
|
||||
startDate: string;
|
||||
type: VoucherType;
|
||||
usageLimit: number;
|
||||
startTime: string;
|
||||
type: VoucherTypeEnum;
|
||||
usageLimit: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
|
@ -48,15 +59,21 @@ const VoucherCreatePage: React.StatelessComponent<VoucherCreatePageProps> = ({
|
|||
onSubmit
|
||||
}) => {
|
||||
const initialForm: FormData = {
|
||||
applyOncePerCustomer: false,
|
||||
applyOncePerOrder: false,
|
||||
code: "",
|
||||
discountType: VoucherDiscountValueType.FIXED,
|
||||
discountType: DiscountValueTypeEnum.FIXED,
|
||||
endDate: "",
|
||||
minAmountSpent: 0,
|
||||
name: "",
|
||||
endTime: "",
|
||||
hasEndDate: false,
|
||||
hasUsageLimit: false,
|
||||
minAmountSpent: "0",
|
||||
minCheckoutItemsQuantity: "0",
|
||||
requirementsPicker: RequirementsPicker.NONE,
|
||||
startDate: "",
|
||||
type: VoucherType.VALUE,
|
||||
usageLimit: 0,
|
||||
startTime: "",
|
||||
type: VoucherTypeEnum.ENTIRE_ORDER,
|
||||
usageLimit: "0",
|
||||
value: 0
|
||||
};
|
||||
|
||||
|
@ -72,11 +89,28 @@ const VoucherCreatePage: React.StatelessComponent<VoucherCreatePageProps> = ({
|
|||
data={data}
|
||||
errors={formErrors}
|
||||
disabled={disabled}
|
||||
variant="create"
|
||||
onChange={change}
|
||||
variant="create"
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherOptions
|
||||
<VoucherTypes
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
{data.discountType.toString() !== "SHIPPING" ? (
|
||||
<VoucherValue
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
variant="create"
|
||||
/>
|
||||
) : null}
|
||||
<CardSpacer />
|
||||
<VoucherRequirements
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
|
@ -84,6 +118,21 @@ const VoucherCreatePage: React.StatelessComponent<VoucherCreatePageProps> = ({
|
|||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherLimits
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherDates
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
|
|
|
@ -11,26 +11,32 @@ import Grid from "@saleor/components/Grid";
|
|||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||
import { RequirementsPicker } from "@saleor/discounts/types";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe } from "../../../misc";
|
||||
import { maybe, splitDateTime } from "../../../misc";
|
||||
import { ListProps, TabListActions, UserError } from "../../../types";
|
||||
import {
|
||||
VoucherDiscountValueType,
|
||||
VoucherType
|
||||
DiscountValueTypeEnum,
|
||||
VoucherTypeEnum
|
||||
} from "../../../types/globalTypes";
|
||||
import { VoucherDetails_voucher } from "../../types/VoucherDetails";
|
||||
import DiscountCategories from "../DiscountCategories";
|
||||
import DiscountCollections from "../DiscountCollections";
|
||||
import DiscountProducts from "../DiscountProducts";
|
||||
import VoucherDates from "../VoucherDates";
|
||||
import VoucherInfo from "../VoucherInfo";
|
||||
import VoucherOptions from "../VoucherOptions";
|
||||
import VoucherLimits from "../VoucherLimits";
|
||||
import VoucherRequirements from "../VoucherRequirements";
|
||||
import VoucherSummary from "../VoucherSummary";
|
||||
import VoucherTypes from "../VoucherTypes";
|
||||
import VoucherValue from "../VoucherValue";
|
||||
|
||||
export enum VoucherDetailsPageTab {
|
||||
categories = "categories",
|
||||
collections = "collections",
|
||||
products = "products"
|
||||
}
|
||||
|
||||
export function voucherDetailsPageTab(tab: string): VoucherDetailsPageTab {
|
||||
return tab === VoucherDetailsPageTab.products
|
||||
? VoucherDetailsPageTab.products
|
||||
|
@ -40,15 +46,21 @@ export function voucherDetailsPageTab(tab: string): VoucherDetailsPageTab {
|
|||
}
|
||||
|
||||
export interface FormData {
|
||||
applyOncePerCustomer: boolean;
|
||||
applyOncePerOrder: boolean;
|
||||
code: string;
|
||||
discountType: VoucherDiscountValueType;
|
||||
discountType: DiscountValueTypeEnum;
|
||||
endDate: string;
|
||||
minAmountSpent: number;
|
||||
name: string;
|
||||
endTime: string;
|
||||
hasEndDate: boolean;
|
||||
hasUsageLimit: boolean;
|
||||
minAmountSpent: string;
|
||||
minCheckoutItemsQuantity: string;
|
||||
requirementsPicker: RequirementsPicker;
|
||||
startDate: string;
|
||||
type: VoucherType;
|
||||
usageLimit: number;
|
||||
startTime: string;
|
||||
type: VoucherTypeEnum;
|
||||
usageLimit: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
|
@ -116,19 +128,37 @@ const VoucherDetailsPage: React.StatelessComponent<VoucherDetailsPageProps> = ({
|
|||
collectionListToolbar,
|
||||
productListToolbar
|
||||
}) => {
|
||||
let requirementsPickerInitValue;
|
||||
if (maybe(() => voucher.minAmountSpent.amount) > 0) {
|
||||
requirementsPickerInitValue = RequirementsPicker.ORDER;
|
||||
} else if (maybe(() => voucher.minCheckoutItemsQuantity) > 0) {
|
||||
requirementsPickerInitValue = RequirementsPicker.ITEM;
|
||||
} else {
|
||||
requirementsPickerInitValue = RequirementsPicker.NONE;
|
||||
}
|
||||
|
||||
const initialForm: FormData = {
|
||||
applyOncePerCustomer: maybe(() => voucher.applyOncePerCustomer, false),
|
||||
applyOncePerOrder: maybe(() => voucher.applyOncePerOrder, false),
|
||||
code: maybe(() => voucher.code, ""),
|
||||
discountType: maybe(
|
||||
() => voucher.discountValueType,
|
||||
VoucherDiscountValueType.FIXED
|
||||
DiscountValueTypeEnum.FIXED
|
||||
),
|
||||
endDate: maybe(() => voucher.endDate, ""),
|
||||
minAmountSpent: maybe(() => voucher.minAmountSpent.amount, 0),
|
||||
name: maybe(() => voucher.name, ""),
|
||||
startDate: maybe(() => voucher.startDate, ""),
|
||||
type: maybe(() => voucher.type, VoucherType.VALUE),
|
||||
usageLimit: maybe(() => voucher.usageLimit || 0, 0),
|
||||
endDate: splitDateTime(maybe(() => voucher.endDate, "")).date,
|
||||
endTime: splitDateTime(maybe(() => voucher.endDate, "")).time,
|
||||
hasEndDate: maybe(() => !!voucher.endDate),
|
||||
hasUsageLimit: maybe(() => !!voucher.usageLimit),
|
||||
minAmountSpent: maybe(() => voucher.minAmountSpent.amount.toString(), "0"),
|
||||
minCheckoutItemsQuantity: maybe(
|
||||
() => voucher.minCheckoutItemsQuantity.toString(),
|
||||
"0"
|
||||
),
|
||||
requirementsPicker: requirementsPickerInitValue,
|
||||
startDate: splitDateTime(maybe(() => voucher.startDate, "")).date,
|
||||
startTime: splitDateTime(maybe(() => voucher.startDate, "")).time,
|
||||
type: maybe(() => voucher.type, VoucherTypeEnum.ENTIRE_ORDER),
|
||||
usageLimit: maybe(() => voucher.usageLimit.toString(), "0"),
|
||||
value: maybe(() => voucher.discountValue, 0)
|
||||
};
|
||||
|
||||
|
@ -137,28 +167,37 @@ const VoucherDetailsPage: React.StatelessComponent<VoucherDetailsPageProps> = ({
|
|||
{({ change, data, errors: formErrors, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>{i18n.t("Vouchers")}</AppHeader>
|
||||
<PageHeader title={maybe(() => voucher.name)} />
|
||||
<PageHeader title={maybe(() => voucher.code)} />
|
||||
<Grid>
|
||||
<div>
|
||||
<VoucherInfo
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
variant="update"
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherTypes
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherOptions
|
||||
{data.discountType.toString() !== "SHIPPING" ? (
|
||||
<VoucherValue
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
variant="update"
|
||||
/>
|
||||
) : null}
|
||||
<CardSpacer />
|
||||
{data.type === VoucherType.CATEGORY ||
|
||||
data.type === VoucherType.COLLECTION ||
|
||||
data.type === VoucherType.PRODUCT ? (
|
||||
{data.type === VoucherTypeEnum.SPECIFIC_PRODUCT &&
|
||||
data.discountType.toString() !== "SHIPPING" ? (
|
||||
<>
|
||||
<TabContainer>
|
||||
<CategoriesTab
|
||||
|
@ -246,16 +285,16 @@ const VoucherDetailsPage: React.StatelessComponent<VoucherDetailsPageProps> = ({
|
|||
/>
|
||||
)}
|
||||
</>
|
||||
) : data.type === VoucherType.SHIPPING ? (
|
||||
) : null}
|
||||
<CardSpacer />
|
||||
{data.discountType.toString() === "SHIPPING" ? (
|
||||
<CountryList
|
||||
countries={maybe(() => voucher.countries)}
|
||||
disabled={disabled}
|
||||
emptyText={i18n.t("Voucher applies to all countries")}
|
||||
title={
|
||||
<>
|
||||
{i18n.t("Countries assigned to {{ voucherName }}", {
|
||||
voucherName: maybe(() => voucher.name)
|
||||
})}
|
||||
{i18n.t("Countries")}
|
||||
<Typography variant="caption">
|
||||
{i18n.t("Vouchers limited to these countries")}
|
||||
</Typography>
|
||||
|
@ -265,6 +304,30 @@ const VoucherDetailsPage: React.StatelessComponent<VoucherDetailsPageProps> = ({
|
|||
onCountryUnassign={onCountryUnassign}
|
||||
/>
|
||||
) : null}
|
||||
<CardSpacer />
|
||||
<VoucherRequirements
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherLimits
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<VoucherDates
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
defaultCurrency={defaultCurrency}
|
||||
errors={formErrors}
|
||||
onChange={change}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<VoucherSummary
|
||||
|
|
|
@ -1,79 +1,53 @@
|
|||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import React from "react";
|
||||
|
||||
import Button from "@material-ui/core/Button";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import FormSpacer from "@saleor/components/FormSpacer";
|
||||
import SingleSelectField from "@saleor/components/SingleSelectField";
|
||||
import i18n from "../../../i18n";
|
||||
import { generateCode } from "../../../misc";
|
||||
import { FormErrors } from "../../../types";
|
||||
import { VoucherType } from "../../../types/globalTypes";
|
||||
import { translateVoucherTypes } from "../../translations";
|
||||
import { FormData } from "../VoucherDetailsPage";
|
||||
|
||||
interface VoucherInfoProps {
|
||||
data: FormData;
|
||||
errors: FormErrors<"name" | "code" | "type">;
|
||||
errors: FormErrors<"code">;
|
||||
disabled: boolean;
|
||||
variant: "create" | "update";
|
||||
onChange: (event: React.ChangeEvent<any>) => void;
|
||||
onChange: (event: any) => void;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
nameInput: {
|
||||
gridColumnEnd: "span 2"
|
||||
},
|
||||
root: {
|
||||
display: "grid",
|
||||
gridColumnGap: theme.spacing.unit * 2 + "px",
|
||||
gridTemplateColumns: "1fr 1fr"
|
||||
}
|
||||
});
|
||||
|
||||
const VoucherInfo = withStyles(styles, {
|
||||
name: "VoucherInfo"
|
||||
})(
|
||||
({
|
||||
classes,
|
||||
const VoucherInfo = ({
|
||||
data,
|
||||
disabled,
|
||||
errors,
|
||||
variant,
|
||||
onChange
|
||||
}: VoucherInfoProps & WithStyles<typeof styles>) => {
|
||||
const translatedVoucherTypes = translateVoucherTypes();
|
||||
const voucherTypeChoices = Object.values(VoucherType).map(type => ({
|
||||
label: translatedVoucherTypes[type],
|
||||
value: type
|
||||
}));
|
||||
}: VoucherInfoProps) => {
|
||||
const onGenerateCode = () =>
|
||||
onChange({
|
||||
target: {
|
||||
name: "code",
|
||||
value: generateCode(10)
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle title={i18n.t("General Information")} />
|
||||
<CardTitle
|
||||
title={i18n.t("General Information")}
|
||||
toolbar={
|
||||
variant === "create" && (
|
||||
<Button color="primary" onClick={onGenerateCode}>
|
||||
{i18n.t("Generate Code")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<TextField
|
||||
className={classes.nameInput}
|
||||
disabled={disabled}
|
||||
error={!!errors.name}
|
||||
fullWidth
|
||||
helperText={errors.name}
|
||||
name={"name" as keyof FormData}
|
||||
label={i18n.t("Name")}
|
||||
value={data.name}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<div className={classes.root}>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
disabled={variant === "update" || disabled}
|
||||
error={!!errors.code}
|
||||
fullWidth
|
||||
helperText={errors.code}
|
||||
|
@ -82,20 +56,8 @@ const VoucherInfo = withStyles(styles, {
|
|||
value={data.code}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SingleSelectField
|
||||
choices={voucherTypeChoices}
|
||||
disabled={disabled || variant === "update"}
|
||||
error={!!errors.type}
|
||||
hint={errors.type}
|
||||
name={"type" as keyof FormData}
|
||||
label={i18n.t("Type of Discount")}
|
||||
value={data.type}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
export default VoucherInfo;
|
||||
|
|
|
@ -19,10 +19,10 @@ import Percent from "@saleor/components/Percent";
|
|||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe, renderCollection } from "../../../misc";
|
||||
import { ListActions, ListProps } from "../../../types";
|
||||
import { VoucherDiscountValueType } from "../../../types/globalTypes";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { DiscountValueTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
|
||||
|
||||
export interface VoucherListProps extends ListProps, ListActions {
|
||||
|
@ -74,15 +74,19 @@ const styles = (theme: Theme) =>
|
|||
}
|
||||
});
|
||||
|
||||
const numberOfColumns = 7;
|
||||
|
||||
const VoucherList = withStyles(styles, {
|
||||
name: "VoucherList"
|
||||
})(
|
||||
({
|
||||
classes,
|
||||
settings,
|
||||
defaultCurrency,
|
||||
disabled,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
pageInfo,
|
||||
vouchers,
|
||||
|
@ -95,6 +99,7 @@ const VoucherList = withStyles(styles, {
|
|||
<Card>
|
||||
<Table>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={vouchers}
|
||||
|
@ -102,7 +107,7 @@ const VoucherList = withStyles(styles, {
|
|||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
{i18n.t("Name", {
|
||||
{i18n.t("Code", {
|
||||
context: "voucher list table header"
|
||||
})}
|
||||
</TableCell>
|
||||
|
@ -135,9 +140,11 @@ const VoucherList = withStyles(styles, {
|
|||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={7}
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
|
@ -163,11 +170,12 @@ const VoucherList = withStyles(styles, {
|
|||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(voucher.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colName}>
|
||||
{maybe<React.ReactNode>(() => voucher.name, <Skeleton />)}
|
||||
{maybe<React.ReactNode>(() => voucher.code, <Skeleton />)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colMinSpent}>
|
||||
{voucher && voucher.minAmountSpent ? (
|
||||
|
@ -202,7 +210,7 @@ const VoucherList = withStyles(styles, {
|
|||
voucher.discountValueType &&
|
||||
voucher.discountValue ? (
|
||||
voucher.discountValueType ===
|
||||
VoucherDiscountValueType.FIXED ? (
|
||||
DiscountValueTypeEnum.FIXED ? (
|
||||
<Money
|
||||
money={{
|
||||
amount: voucher.discountValue,
|
||||
|
@ -230,7 +238,9 @@ const VoucherList = withStyles(styles, {
|
|||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7}>{i18n.t("No vouchers found")}</TableCell>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
{i18n.t("No vouchers found")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -4,8 +4,8 @@ import React from "react";
|
|||
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import i18n from "../../../i18n";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import i18n from "@saleor/i18n";
|
||||
import { ListActions, PageListProps } from "@saleor/types";
|
||||
import { VoucherList_vouchers_edges_node } from "../../types/VoucherList";
|
||||
import VoucherList from "../VoucherList";
|
||||
|
||||
|
@ -17,9 +17,11 @@ export interface VoucherListPageProps extends PageListProps, ListActions {
|
|||
const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({
|
||||
defaultCurrency,
|
||||
disabled,
|
||||
settings,
|
||||
onAdd,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick,
|
||||
pageInfo,
|
||||
vouchers,
|
||||
|
@ -38,9 +40,11 @@ const VoucherListPage: React.StatelessComponent<VoucherListPageProps> = ({
|
|||
</PageHeader>
|
||||
<VoucherList
|
||||
defaultCurrency={defaultCurrency}
|
||||
settings={settings}
|
||||
disabled={disabled}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
onRowClick={onRowClick}
|
||||
pageInfo={pageInfo}
|
||||
vouchers={vouchers}
|
||||
|
|
|
@ -13,7 +13,7 @@ import Percent from "@saleor/components/Percent";
|
|||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import i18n from "../../../i18n";
|
||||
import { maybe } from "../../../misc";
|
||||
import { VoucherDiscountValueType } from "../../../types/globalTypes";
|
||||
import { DiscountValueTypeEnum } from "../../../types/globalTypes";
|
||||
import { translateVoucherTypes } from "../../translations";
|
||||
import { VoucherDetails_voucher } from "../../types/VoucherDetails";
|
||||
|
||||
|
@ -32,9 +32,9 @@ const VoucherSummary: React.StatelessComponent<VoucherSummaryProps> = ({
|
|||
<Card>
|
||||
<CardTitle title={i18n.t("Summary")} />
|
||||
<CardContent>
|
||||
<Typography variant="caption">{i18n.t("Name")}</Typography>
|
||||
<Typography variant="caption">{i18n.t("Code")}</Typography>
|
||||
<Typography>
|
||||
{maybe<React.ReactNode>(() => voucher.name, <Skeleton />)}
|
||||
{maybe<React.ReactNode>(() => voucher.code, <Skeleton />)}
|
||||
</Typography>
|
||||
<FormSpacer />
|
||||
|
||||
|
@ -51,7 +51,7 @@ const VoucherSummary: React.StatelessComponent<VoucherSummaryProps> = ({
|
|||
<Typography>
|
||||
{maybe<React.ReactNode>(
|
||||
() =>
|
||||
voucher.discountValueType === VoucherDiscountValueType.FIXED ? (
|
||||
voucher.discountValueType === DiscountValueTypeEnum.FIXED ? (
|
||||
<Money
|
||||
money={{
|
||||
amount: voucher.discountValue,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import placeholderImage from "@assets/images/placeholder60x60.png";
|
||||
import {
|
||||
DiscountValueTypeEnum,
|
||||
SaleType,
|
||||
VoucherDiscountValueType,
|
||||
VoucherType
|
||||
VoucherTypeEnum
|
||||
} from "../types/globalTypes";
|
||||
import { SaleDetails_sale } from "./types/SaleDetails";
|
||||
import { SaleList_sales_edges_node } from "./types/SaleList";
|
||||
|
@ -60,6 +60,7 @@ export const saleList: SaleList_sales_edges_node[] = [
|
|||
export const voucherList: VoucherList_vouchers_edges_node[] = [
|
||||
{
|
||||
__typename: "Voucher" as "Voucher",
|
||||
code: "FREE2019",
|
||||
countries: [
|
||||
{
|
||||
__typename: "CountryDisplay",
|
||||
|
@ -68,19 +69,20 @@ export const voucherList: VoucherList_vouchers_edges_node[] = [
|
|||
}
|
||||
],
|
||||
discountValue: 100,
|
||||
discountValueType: "PERCENTAGE" as VoucherDiscountValueType,
|
||||
discountValueType: "PERCENTAGE" as DiscountValueTypeEnum,
|
||||
endDate: null,
|
||||
id: "Vm91Y2hlcjox",
|
||||
minAmountSpent: null,
|
||||
name: "Free shipping",
|
||||
minCheckoutItemsQuantity: null,
|
||||
startDate: "2019-01-03",
|
||||
usageLimit: null
|
||||
},
|
||||
{
|
||||
__typename: "Voucher" as "Voucher",
|
||||
code: "FREE2020",
|
||||
countries: [],
|
||||
discountValue: 25,
|
||||
discountValueType: "FIXED" as VoucherDiscountValueType,
|
||||
discountValueType: "FIXED" as DiscountValueTypeEnum,
|
||||
endDate: null,
|
||||
id: "Vm91Y2hlcjoy",
|
||||
minAmountSpent: {
|
||||
|
@ -88,7 +90,7 @@ export const voucherList: VoucherList_vouchers_edges_node[] = [
|
|||
amount: 200,
|
||||
currency: "USD"
|
||||
},
|
||||
name: "Big order discount",
|
||||
minCheckoutItemsQuantity: 0,
|
||||
startDate: "2019-01-03",
|
||||
usageLimit: 150
|
||||
}
|
||||
|
@ -241,6 +243,7 @@ export const sale: SaleDetails_sale = {
|
|||
|
||||
export const voucherDetails: VoucherDetails_voucher = {
|
||||
__typename: "Voucher",
|
||||
applyOncePerCustomer: false,
|
||||
applyOncePerOrder: false,
|
||||
categories: {
|
||||
__typename: "CategoryCountableConnection",
|
||||
|
@ -275,7 +278,7 @@ export const voucherDetails: VoucherDetails_voucher = {
|
|||
}
|
||||
],
|
||||
discountValue: 25,
|
||||
discountValueType: VoucherDiscountValueType.FIXED,
|
||||
discountValueType: DiscountValueTypeEnum.FIXED,
|
||||
endDate: null,
|
||||
id: "Vm91Y2hlcjoy",
|
||||
minAmountSpent: {
|
||||
|
@ -283,7 +286,7 @@ export const voucherDetails: VoucherDetails_voucher = {
|
|||
amount: 200,
|
||||
currency: "USD"
|
||||
},
|
||||
name: "Big order discount",
|
||||
minCheckoutItemsQuantity: 0,
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [],
|
||||
|
@ -297,7 +300,7 @@ export const voucherDetails: VoucherDetails_voucher = {
|
|||
totalCount: 0
|
||||
},
|
||||
startDate: "2018-11-27",
|
||||
type: VoucherType.VALUE,
|
||||
type: VoucherTypeEnum.ENTIRE_ORDER,
|
||||
usageLimit: null,
|
||||
used: 0
|
||||
};
|
||||
|
|
|
@ -81,7 +81,7 @@ export const saleDetailsFragment = gql`
|
|||
export const voucherFragment = gql`
|
||||
fragment VoucherFragment on Voucher {
|
||||
id
|
||||
name
|
||||
code
|
||||
startDate
|
||||
endDate
|
||||
usageLimit
|
||||
|
@ -95,6 +95,7 @@ export const voucherFragment = gql`
|
|||
currency
|
||||
amount
|
||||
}
|
||||
minCheckoutItemsQuantity
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -108,6 +109,7 @@ export const voucherDetailsFragment = gql`
|
|||
usageLimit
|
||||
used
|
||||
applyOncePerOrder
|
||||
applyOncePerCustomer
|
||||
products(after: $after, before: $before, first: $first, last: $last) {
|
||||
edges {
|
||||
node {
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import i18n from "../i18n";
|
||||
import { VoucherType } from "../types/globalTypes";
|
||||
import { VoucherTypeEnum } from "../types/globalTypes";
|
||||
|
||||
export const translateVoucherTypes = () => ({
|
||||
[VoucherType.CATEGORY]: i18n.t("Selected Categories"),
|
||||
[VoucherType.COLLECTION]: i18n.t("Selected Collections"),
|
||||
[VoucherType.PRODUCT]: i18n.t("Selected Products"),
|
||||
[VoucherType.SHIPPING]: i18n.t("Shipment"),
|
||||
[VoucherType.VALUE]: i18n.t("All Products")
|
||||
[VoucherTypeEnum.SHIPPING]: i18n.t("Shipment"),
|
||||
[VoucherTypeEnum.ENTIRE_ORDER]: i18n.t("Entire order"),
|
||||
[VoucherTypeEnum.SPECIFIC_PRODUCT]: i18n.t("Specific Products")
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CatalogueInput, VoucherDiscountValueType, VoucherType } from "./../../types/globalTypes";
|
||||
import { CatalogueInput, DiscountValueTypeEnum, VoucherTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: VoucherCataloguesAdd
|
||||
|
@ -133,18 +133,19 @@ export interface VoucherCataloguesAdd_voucherCataloguesAdd_voucher_categories {
|
|||
export interface VoucherCataloguesAdd_voucherCataloguesAdd_voucher {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherCataloguesAdd_voucherCataloguesAdd_voucher_countries | null)[] | null;
|
||||
minAmountSpent: VoucherCataloguesAdd_voucherCataloguesAdd_voucher_minAmountSpent | null;
|
||||
type: VoucherType;
|
||||
code: string;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
type: VoucherTypeEnum;
|
||||
used: number;
|
||||
applyOncePerOrder: boolean;
|
||||
applyOncePerCustomer: boolean;
|
||||
products: VoucherCataloguesAdd_voucherCataloguesAdd_voucher_products | null;
|
||||
collections: VoucherCataloguesAdd_voucherCataloguesAdd_voucher_collections | null;
|
||||
categories: VoucherCataloguesAdd_voucherCataloguesAdd_voucher_categories | null;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { CatalogueInput, VoucherDiscountValueType, VoucherType } from "./../../types/globalTypes";
|
||||
import { CatalogueInput, DiscountValueTypeEnum, VoucherTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: VoucherCataloguesRemove
|
||||
|
@ -133,18 +133,19 @@ export interface VoucherCataloguesRemove_voucherCataloguesRemove_voucher_categor
|
|||
export interface VoucherCataloguesRemove_voucherCataloguesRemove_voucher {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherCataloguesRemove_voucherCataloguesRemove_voucher_countries | null)[] | null;
|
||||
minAmountSpent: VoucherCataloguesRemove_voucherCataloguesRemove_voucher_minAmountSpent | null;
|
||||
type: VoucherType;
|
||||
code: string;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
type: VoucherTypeEnum;
|
||||
used: number;
|
||||
applyOncePerOrder: boolean;
|
||||
applyOncePerCustomer: boolean;
|
||||
products: VoucherCataloguesRemove_voucherCataloguesRemove_voucher_products | null;
|
||||
collections: VoucherCataloguesRemove_voucherCataloguesRemove_voucher_collections | null;
|
||||
categories: VoucherCataloguesRemove_voucherCataloguesRemove_voucher_categories | null;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { VoucherInput, VoucherDiscountValueType } from "./../../types/globalTypes";
|
||||
import { VoucherInput, DiscountValueTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL mutation operation: VoucherCreate
|
||||
|
@ -29,14 +29,15 @@ export interface VoucherCreate_voucherCreate_voucher_minAmountSpent {
|
|||
export interface VoucherCreate_voucherCreate_voucher {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherCreate_voucherCreate_voucher_countries | null)[] | null;
|
||||
minAmountSpent: VoucherCreate_voucherCreate_voucher_minAmountSpent | null;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
}
|
||||
|
||||
export interface VoucherCreate_voucherCreate {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { VoucherDiscountValueType, VoucherType } from "./../../types/globalTypes";
|
||||
import { DiscountValueTypeEnum, VoucherTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: VoucherDetails
|
||||
|
@ -127,18 +127,19 @@ export interface VoucherDetails_voucher_categories {
|
|||
export interface VoucherDetails_voucher {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherDetails_voucher_countries | null)[] | null;
|
||||
minAmountSpent: VoucherDetails_voucher_minAmountSpent | null;
|
||||
type: VoucherType;
|
||||
code: string;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
type: VoucherTypeEnum;
|
||||
used: number;
|
||||
applyOncePerOrder: boolean;
|
||||
applyOncePerCustomer: boolean;
|
||||
products: VoucherDetails_voucher_products | null;
|
||||
collections: VoucherDetails_voucher_collections | null;
|
||||
categories: VoucherDetails_voucher_categories | null;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { VoucherDiscountValueType, VoucherType } from "./../../types/globalTypes";
|
||||
import { DiscountValueTypeEnum, VoucherTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: VoucherDetailsFragment
|
||||
|
@ -127,18 +127,19 @@ export interface VoucherDetailsFragment_categories {
|
|||
export interface VoucherDetailsFragment {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherDetailsFragment_countries | null)[] | null;
|
||||
minAmountSpent: VoucherDetailsFragment_minAmountSpent | null;
|
||||
type: VoucherType;
|
||||
code: string;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
type: VoucherTypeEnum;
|
||||
used: number;
|
||||
applyOncePerOrder: boolean;
|
||||
applyOncePerCustomer: boolean;
|
||||
products: VoucherDetailsFragment_products | null;
|
||||
collections: VoucherDetailsFragment_collections | null;
|
||||
categories: VoucherDetailsFragment_categories | null;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { VoucherDiscountValueType } from "./../../types/globalTypes";
|
||||
import { DiscountValueTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL fragment: VoucherFragment
|
||||
|
@ -23,12 +23,13 @@ export interface VoucherFragment_minAmountSpent {
|
|||
export interface VoucherFragment {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherFragment_countries | null)[] | null;
|
||||
minAmountSpent: VoucherFragment_minAmountSpent | null;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { VoucherDiscountValueType } from "./../../types/globalTypes";
|
||||
import { DiscountValueTypeEnum } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: VoucherList
|
||||
|
@ -23,14 +23,15 @@ export interface VoucherList_vouchers_edges_node_minAmountSpent {
|
|||
export interface VoucherList_vouchers_edges_node {
|
||||
__typename: "Voucher";
|
||||
id: string;
|
||||
name: string | null;
|
||||
code: string;
|
||||
startDate: any;
|
||||
endDate: any | null;
|
||||
usageLimit: number | null;
|
||||
discountValueType: VoucherDiscountValueType;
|
||||
discountValueType: DiscountValueTypeEnum;
|
||||
discountValue: number;
|
||||
countries: (VoucherList_vouchers_edges_node_countries | null)[] | null;
|
||||
minAmountSpent: VoucherList_vouchers_edges_node_minAmountSpent | null;
|
||||
minCheckoutItemsQuantity: number | null;
|
||||
}
|
||||
|
||||
export interface VoucherList_vouchers_edges {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue