Add search bar to category list
This commit is contained in:
parent
b842e85759
commit
53ac48062f
13 changed files with 442 additions and 224 deletions
|
@ -1,5 +1,3 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import Card from "@material-ui/core/Card";
|
|
||||||
import {
|
import {
|
||||||
createStyles,
|
createStyles,
|
||||||
Theme,
|
Theme,
|
||||||
|
@ -12,9 +10,9 @@ import TableCell from "@material-ui/core/TableCell";
|
||||||
import TableFooter from "@material-ui/core/TableFooter";
|
import TableFooter from "@material-ui/core/TableFooter";
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import { CategoryFragment } from "@saleor/categories/types/CategoryFragment";
|
||||||
import Checkbox from "@saleor/components/Checkbox";
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
import TableHead from "@saleor/components/TableHead";
|
import TableHead from "@saleor/components/TableHead";
|
||||||
|
@ -49,20 +47,8 @@ const styles = (theme: Theme) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
interface CategoryListProps
|
interface CategoryListProps extends ListProps, ListActions {
|
||||||
extends ListProps,
|
categories?: CategoryFragment[];
|
||||||
ListActions,
|
|
||||||
WithStyles<typeof styles> {
|
|
||||||
categories?: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
children: {
|
|
||||||
totalCount: number;
|
|
||||||
};
|
|
||||||
products: {
|
|
||||||
totalCount: number;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
isRoot: boolean;
|
isRoot: boolean;
|
||||||
onAdd?();
|
onAdd?();
|
||||||
}
|
}
|
||||||
|
@ -75,144 +61,119 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
|
||||||
classes,
|
classes,
|
||||||
disabled,
|
disabled,
|
||||||
settings,
|
settings,
|
||||||
isRoot,
|
|
||||||
pageInfo,
|
pageInfo,
|
||||||
isChecked,
|
isChecked,
|
||||||
|
isRoot,
|
||||||
selected,
|
selected,
|
||||||
toggle,
|
toggle,
|
||||||
toggleAll,
|
toggleAll,
|
||||||
toolbar,
|
toolbar,
|
||||||
onAdd,
|
|
||||||
onNextPage,
|
onNextPage,
|
||||||
onPreviousPage,
|
onPreviousPage,
|
||||||
onUpdateListSettings,
|
onUpdateListSettings,
|
||||||
onRowClick
|
onRowClick
|
||||||
}: CategoryListProps) => {
|
}: CategoryListProps & WithStyles<typeof styles>) => (
|
||||||
const intl = useIntl();
|
<Table>
|
||||||
|
<TableHead
|
||||||
return (
|
colSpan={numberOfColumns}
|
||||||
<Card>
|
selected={selected}
|
||||||
{!isRoot && (
|
disabled={disabled}
|
||||||
<CardTitle
|
items={categories}
|
||||||
title={intl.formatMessage({
|
toggleAll={toggleAll}
|
||||||
defaultMessage: "All Subcategories",
|
toolbar={toolbar}
|
||||||
description: "section header"
|
>
|
||||||
})}
|
<TableCell className={classes.colName}>
|
||||||
toolbar={
|
<FormattedMessage defaultMessage="Category Name" />
|
||||||
<Button color="primary" variant="text" onClick={onAdd}>
|
</TableCell>
|
||||||
<FormattedMessage
|
<TableCell className={classes.colSubcategories}>
|
||||||
defaultMessage="Create subcategory"
|
<FormattedMessage
|
||||||
description="button"
|
defaultMessage="Subcategories"
|
||||||
/>
|
description="number of subcategories"
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</TableCell>
|
||||||
<Table>
|
<TableCell className={classes.colProducts}>
|
||||||
<TableHead
|
<FormattedMessage
|
||||||
|
defaultMessage="No. of Products"
|
||||||
|
description="number of products"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableHead>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TablePagination
|
||||||
colSpan={numberOfColumns}
|
colSpan={numberOfColumns}
|
||||||
selected={selected}
|
settings={settings}
|
||||||
disabled={disabled}
|
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||||
items={categories}
|
onNextPage={onNextPage}
|
||||||
toggleAll={toggleAll}
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
toolbar={toolbar}
|
hasPreviousPage={
|
||||||
>
|
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||||
<TableCell className={classes.colName}>
|
}
|
||||||
<FormattedMessage defaultMessage="Category Name" />
|
onPreviousPage={onPreviousPage}
|
||||||
</TableCell>
|
/>
|
||||||
<TableCell className={classes.colSubcategories}>
|
</TableRow>
|
||||||
<FormattedMessage
|
</TableFooter>
|
||||||
defaultMessage="Subcategories"
|
<TableBody>
|
||||||
description="number of subcategories"
|
{renderCollection(
|
||||||
/>
|
categories,
|
||||||
</TableCell>
|
category => {
|
||||||
<TableCell className={classes.colProducts}>
|
const isSelected = category ? isChecked(category.id) : false;
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="No. of Products"
|
|
||||||
description="number of products"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
</TableHead>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TablePagination
|
|
||||||
colSpan={numberOfColumns}
|
|
||||||
settings={settings}
|
|
||||||
hasNextPage={
|
|
||||||
pageInfo && !disabled ? pageInfo.hasNextPage : false
|
|
||||||
}
|
|
||||||
onNextPage={onNextPage}
|
|
||||||
onUpdateListSettings={onUpdateListSettings}
|
|
||||||
hasPreviousPage={
|
|
||||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
|
||||||
}
|
|
||||||
onPreviousPage={onPreviousPage}
|
|
||||||
/>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
<TableBody>
|
|
||||||
{renderCollection(
|
|
||||||
categories,
|
|
||||||
category => {
|
|
||||||
const isSelected = category ? isChecked(category.id) : false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={classes.tableRow}
|
className={classes.tableRow}
|
||||||
hover={!!category}
|
hover={!!category}
|
||||||
onClick={category ? onRowClick(category.id) : undefined}
|
onClick={category ? onRowClick(category.id) : undefined}
|
||||||
key={category ? category.id : "skeleton"}
|
key={category ? category.id : "skeleton"}
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
>
|
>
|
||||||
<TableCell padding="checkbox">
|
<TableCell padding="checkbox">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
disableClickPropagation
|
disableClickPropagation
|
||||||
onChange={() => toggle(category.id)}
|
onChange={() => toggle(category.id)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell className={classes.colName}>
|
||||||
{category && category.name ? category.name : <Skeleton />}
|
{category && category.name ? category.name : <Skeleton />}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colSubcategories}>
|
<TableCell className={classes.colSubcategories}>
|
||||||
{category &&
|
{category &&
|
||||||
category.children &&
|
category.children &&
|
||||||
category.children.totalCount !== undefined ? (
|
category.children.totalCount !== undefined ? (
|
||||||
category.children.totalCount
|
category.children.totalCount
|
||||||
) : (
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className={classes.colProducts}>
|
<TableCell className={classes.colProducts}>
|
||||||
{category &&
|
{category &&
|
||||||
category.products &&
|
category.products &&
|
||||||
category.products.totalCount !== undefined ? (
|
category.products.totalCount !== undefined ? (
|
||||||
category.products.totalCount
|
category.products.totalCount
|
||||||
) : (
|
) : (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
() => (
|
() => (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={numberOfColumns}>
|
<TableCell colSpan={numberOfColumns}>
|
||||||
{isRoot ? (
|
{isRoot ? (
|
||||||
<FormattedMessage defaultMessage="No categories found" />
|
<FormattedMessage defaultMessage="No categories found" />
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage defaultMessage="No subcategories found" />
|
<FormattedMessage defaultMessage="No subcategories found" />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Card>
|
)
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
CategoryList.displayName = "CategoryList";
|
CategoryList.displayName = "CategoryList";
|
||||||
export default CategoryList;
|
export default CategoryList;
|
||||||
|
|
|
@ -1,44 +1,55 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
|
import Card from "@material-ui/core/Card";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { CategoryFragment } from "@saleor/categories/types/CategoryFragment";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
|
import SearchBar from "@saleor/components/SearchBar";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ListActions, PageListProps } from "@saleor/types";
|
import {
|
||||||
|
ListActions,
|
||||||
|
PageListProps,
|
||||||
|
SearchPageProps,
|
||||||
|
TabPageProps
|
||||||
|
} from "@saleor/types";
|
||||||
import CategoryList from "../CategoryList";
|
import CategoryList from "../CategoryList";
|
||||||
|
|
||||||
export interface CategoryTableProps extends PageListProps, ListActions {
|
export interface CategoryTableProps
|
||||||
categories: Array<{
|
extends PageListProps,
|
||||||
id: string;
|
ListActions,
|
||||||
name: string;
|
SearchPageProps,
|
||||||
children: {
|
TabPageProps {
|
||||||
totalCount: number;
|
categories: CategoryFragment[];
|
||||||
};
|
|
||||||
products: {
|
|
||||||
totalCount: number;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
|
export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
|
||||||
categories,
|
categories,
|
||||||
|
currentTab,
|
||||||
disabled,
|
disabled,
|
||||||
settings,
|
initialSearch,
|
||||||
onAdd,
|
|
||||||
onNextPage,
|
|
||||||
onPreviousPage,
|
|
||||||
onUpdateListSettings,
|
|
||||||
onRowClick,
|
|
||||||
pageInfo,
|
|
||||||
isChecked,
|
isChecked,
|
||||||
|
pageInfo,
|
||||||
selected,
|
selected,
|
||||||
|
settings,
|
||||||
|
tabs,
|
||||||
toggle,
|
toggle,
|
||||||
toggleAll,
|
toggleAll,
|
||||||
toolbar
|
toolbar,
|
||||||
|
onAdd,
|
||||||
|
onAll,
|
||||||
|
onNextPage,
|
||||||
|
onPreviousPage,
|
||||||
|
onRowClick,
|
||||||
|
onSearchChange,
|
||||||
|
onTabChange,
|
||||||
|
onTabDelete,
|
||||||
|
onTabSave,
|
||||||
|
onUpdateListSettings
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<PageHeader title={intl.formatMessage(sectionNames.categories)}>
|
<PageHeader title={intl.formatMessage(sectionNames.categories)}>
|
||||||
|
@ -49,23 +60,38 @@ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<CategoryList
|
<Card>
|
||||||
categories={categories}
|
<SearchBar
|
||||||
onAdd={onAdd}
|
currentTab={currentTab}
|
||||||
onRowClick={onRowClick}
|
initialSearch={initialSearch}
|
||||||
disabled={disabled}
|
searchPlaceholder={intl.formatMessage({
|
||||||
settings={settings}
|
defaultMessage: "Search Attribute"
|
||||||
isRoot={true}
|
})}
|
||||||
onNextPage={onNextPage}
|
tabs={tabs}
|
||||||
onPreviousPage={onPreviousPage}
|
onAll={onAll}
|
||||||
onUpdateListSettings={onUpdateListSettings}
|
onSearchChange={onSearchChange}
|
||||||
pageInfo={pageInfo}
|
onTabChange={onTabChange}
|
||||||
isChecked={isChecked}
|
onTabDelete={onTabDelete}
|
||||||
selected={selected}
|
onTabSave={onTabSave}
|
||||||
toggle={toggle}
|
/>
|
||||||
toggleAll={toggleAll}
|
<CategoryList
|
||||||
toolbar={toolbar}
|
categories={categories}
|
||||||
/>
|
disabled={disabled}
|
||||||
|
isChecked={isChecked}
|
||||||
|
isRoot={true}
|
||||||
|
pageInfo={pageInfo}
|
||||||
|
selected={selected}
|
||||||
|
settings={settings}
|
||||||
|
toggle={toggle}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
toolbar={toolbar}
|
||||||
|
onAdd={onAdd}
|
||||||
|
onNextPage={onNextPage}
|
||||||
|
onPreviousPage={onPreviousPage}
|
||||||
|
onRowClick={onRowClick}
|
||||||
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import Card from "@material-ui/core/Card";
|
||||||
import { RawDraftContentState } from "draft-js";
|
import { RawDraftContentState } from "draft-js";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import { CardSpacer } from "@saleor/components/CardSpacer";
|
import { CardSpacer } from "@saleor/components/CardSpacer";
|
||||||
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
|
@ -178,21 +181,40 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
{currentTab === CategoryPageTab.categories && (
|
{currentTab === CategoryPageTab.categories && (
|
||||||
<CategoryList
|
<Card>
|
||||||
disabled={disabled}
|
<CardTitle
|
||||||
isRoot={false}
|
title={intl.formatMessage({
|
||||||
categories={subcategories}
|
defaultMessage: "All Subcategories",
|
||||||
onAdd={onAddCategory}
|
description: "section header"
|
||||||
onRowClick={onCategoryClick}
|
})}
|
||||||
onNextPage={onNextPage}
|
toolbar={
|
||||||
onPreviousPage={onPreviousPage}
|
<Button
|
||||||
pageInfo={pageInfo}
|
color="primary"
|
||||||
toggle={toggle}
|
variant="text"
|
||||||
toggleAll={toggleAll}
|
onClick={onAddCategory}
|
||||||
selected={selected}
|
>
|
||||||
isChecked={isChecked}
|
<FormattedMessage
|
||||||
toolbar={subcategoryListToolbar}
|
defaultMessage="Create subcategory"
|
||||||
/>
|
description="button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CategoryList
|
||||||
|
categories={subcategories}
|
||||||
|
disabled={disabled}
|
||||||
|
isChecked={isChecked}
|
||||||
|
isRoot={false}
|
||||||
|
pageInfo={pageInfo}
|
||||||
|
selected={selected}
|
||||||
|
toggle={toggle}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
toolbar={subcategoryListToolbar}
|
||||||
|
onNextPage={onNextPage}
|
||||||
|
onPreviousPage={onPreviousPage}
|
||||||
|
onRowClick={onCategoryClick}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
{currentTab === CategoryPageTab.products && (
|
{currentTab === CategoryPageTab.products && (
|
||||||
<CategoryProducts
|
<CategoryProducts
|
||||||
|
|
|
@ -1,64 +1,83 @@
|
||||||
import { content } from "../storybook/stories/components/RichTextEditor";
|
import { content } from "../storybook/stories/components/RichTextEditor";
|
||||||
import { CategoryDetails_category } from "./types/CategoryDetails";
|
import { CategoryDetails_category } from "./types/CategoryDetails";
|
||||||
|
import { CategoryFragment } from "./types/CategoryFragment";
|
||||||
|
|
||||||
export const categories = [
|
export const categories: CategoryFragment[] = [
|
||||||
{
|
{
|
||||||
|
__typename: "Category",
|
||||||
children: {
|
children: {
|
||||||
|
__typename: "CategoryCountableConnection",
|
||||||
totalCount: 2
|
totalCount: 2
|
||||||
},
|
},
|
||||||
id: "123123",
|
id: "123123",
|
||||||
name: "Lorem ipsum dolor",
|
name: "Lorem ipsum dolor",
|
||||||
products: {
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
totalCount: 4
|
totalCount: 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
__typename: "Category",
|
||||||
children: {
|
children: {
|
||||||
|
__typename: "CategoryCountableConnection",
|
||||||
totalCount: 54
|
totalCount: 54
|
||||||
},
|
},
|
||||||
id: "876752",
|
id: "876752",
|
||||||
name: "Mauris vehicula tortor vulputate",
|
name: "Mauris vehicula tortor vulputate",
|
||||||
products: {
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
totalCount: 3
|
totalCount: 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
__typename: "Category",
|
||||||
children: {
|
children: {
|
||||||
|
__typename: "CategoryCountableConnection",
|
||||||
totalCount: 2
|
totalCount: 2
|
||||||
},
|
},
|
||||||
id: "876542",
|
id: "876542",
|
||||||
name: "Excepteur sint occaecat cupidatat non proident",
|
name: "Excepteur sint occaecat cupidatat non proident",
|
||||||
products: {
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
totalCount: 6
|
totalCount: 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
__typename: "Category",
|
||||||
children: {
|
children: {
|
||||||
|
__typename: "CategoryCountableConnection",
|
||||||
totalCount: 6
|
totalCount: 6
|
||||||
},
|
},
|
||||||
id: "875352",
|
id: "875352",
|
||||||
name: "Ut enim ad minim veniam",
|
name: "Ut enim ad minim veniam",
|
||||||
products: {
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
totalCount: 12
|
totalCount: 12
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
__typename: "Category",
|
||||||
children: {
|
children: {
|
||||||
|
__typename: "CategoryCountableConnection",
|
||||||
totalCount: 76
|
totalCount: 76
|
||||||
},
|
},
|
||||||
id: "865752",
|
id: "865752",
|
||||||
name: "Duis aute irure dolor in reprehenderit",
|
name: "Duis aute irure dolor in reprehenderit",
|
||||||
products: {
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
totalCount: 43
|
totalCount: 43
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
__typename: "Category",
|
||||||
children: {
|
children: {
|
||||||
|
__typename: "CategoryCountableConnection",
|
||||||
totalCount: 11
|
totalCount: 11
|
||||||
},
|
},
|
||||||
id: "878752",
|
id: "878752",
|
||||||
name: "Neque porro quisquam est",
|
name: "Neque porro quisquam est",
|
||||||
products: {
|
products: {
|
||||||
|
__typename: "ProductCountableConnection",
|
||||||
totalCount: 21
|
totalCount: 21
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,18 @@ import {
|
||||||
} from "./types/CategoryDetails";
|
} from "./types/CategoryDetails";
|
||||||
import { RootCategories } from "./types/RootCategories";
|
import { RootCategories } from "./types/RootCategories";
|
||||||
|
|
||||||
|
export const categoryFragment = gql`
|
||||||
|
fragment CategoryFragment on Category {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
children {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
products {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const categoryDetailsFragment = gql`
|
export const categoryDetailsFragment = gql`
|
||||||
fragment CategoryDetailsFragment on Category {
|
fragment CategoryDetailsFragment on Category {
|
||||||
id
|
id
|
||||||
|
@ -25,11 +37,13 @@ export const categoryDetailsFragment = gql`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const rootCategories = gql`
|
export const rootCategories = gql`
|
||||||
|
${categoryFragment}
|
||||||
query RootCategories(
|
query RootCategories(
|
||||||
$first: Int
|
$first: Int
|
||||||
$after: String
|
$after: String
|
||||||
$last: Int
|
$last: Int
|
||||||
$before: String
|
$before: String
|
||||||
|
$filter: CategoryFilterInput
|
||||||
) {
|
) {
|
||||||
categories(
|
categories(
|
||||||
level: 0
|
level: 0
|
||||||
|
@ -37,17 +51,11 @@ export const rootCategories = gql`
|
||||||
after: $after
|
after: $after
|
||||||
last: $last
|
last: $last
|
||||||
before: $before
|
before: $before
|
||||||
|
filter: $filter
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
...CategoryFragment
|
||||||
name
|
|
||||||
children {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
products {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
|
@ -64,6 +72,7 @@ export const TypedRootCategoriesQuery = TypedQuery<RootCategories, {}>(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const categoryDetails = gql`
|
export const categoryDetails = gql`
|
||||||
|
${categoryFragment}
|
||||||
${categoryDetailsFragment}
|
${categoryDetailsFragment}
|
||||||
query CategoryDetails(
|
query CategoryDetails(
|
||||||
$id: ID!
|
$id: ID!
|
||||||
|
@ -77,14 +86,7 @@ export const categoryDetails = gql`
|
||||||
children(first: 20) {
|
children(first: 20) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
...CategoryFragment
|
||||||
name
|
|
||||||
children {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
products {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
src/categories/types/CategoryFragment.ts
Normal file
25
src/categories/types/CategoryFragment.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL fragment: CategoryFragment
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CategoryFragment_children {
|
||||||
|
__typename: "CategoryCountableConnection";
|
||||||
|
totalCount: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryFragment_products {
|
||||||
|
__typename: "ProductCountableConnection";
|
||||||
|
totalCount: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryFragment {
|
||||||
|
__typename: "Category";
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
children: CategoryFragment_children | null;
|
||||||
|
products: CategoryFragment_products | null;
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { CategoryFilterInput } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL query operation: RootCategories
|
// GraphQL query operation: RootCategories
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
@ -52,4 +54,5 @@ export interface RootCategoriesVariables {
|
||||||
after?: string | null;
|
after?: string | null;
|
||||||
last?: number | null;
|
last?: number | null;
|
||||||
before?: string | null;
|
before?: string | null;
|
||||||
|
filter?: CategoryFilterInput | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,27 @@
|
||||||
import { stringify as stringifyQs } from "qs";
|
import { stringify as stringifyQs } from "qs";
|
||||||
import urlJoin from "url-join";
|
import urlJoin from "url-join";
|
||||||
|
|
||||||
import { ActiveTab, BulkAction, Dialog, Pagination } from "../types";
|
import {
|
||||||
|
ActiveTab,
|
||||||
|
BulkAction,
|
||||||
|
Dialog,
|
||||||
|
Filters,
|
||||||
|
Pagination,
|
||||||
|
TabActionDialog
|
||||||
|
} from "../types";
|
||||||
import { CategoryPageTab } from "./components/CategoryUpdatePage";
|
import { CategoryPageTab } from "./components/CategoryUpdatePage";
|
||||||
|
|
||||||
const categorySectionUrl = "/categories/";
|
const categorySectionUrl = "/categories/";
|
||||||
|
|
||||||
export const categoryListPath = categorySectionUrl;
|
export const categoryListPath = categorySectionUrl;
|
||||||
export type CategoryListUrlDialog = "delete";
|
export enum CategoryListUrlFiltersEnum {
|
||||||
export type CategoryListUrlQueryParams = BulkAction &
|
query = "query"
|
||||||
|
}
|
||||||
|
export type CategoryListUrlFilters = Filters<CategoryListUrlFiltersEnum>;
|
||||||
|
export type CategoryListUrlDialog = "delete" | TabActionDialog;
|
||||||
|
export type CategoryListUrlQueryParams = ActiveTab &
|
||||||
|
BulkAction &
|
||||||
|
CategoryListUrlFilters &
|
||||||
Dialog<CategoryListUrlDialog> &
|
Dialog<CategoryListUrlDialog> &
|
||||||
Pagination;
|
Pagination;
|
||||||
export const categoryListUrl = (params?: CategoryListUrlQueryParams) =>
|
export const categoryListUrl = (params?: CategoryListUrlQueryParams) =>
|
||||||
|
|
|
@ -5,6 +5,10 @@ import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
|
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||||
|
import SaveFilterTabDialog, {
|
||||||
|
SaveFilterTabDialogFormData
|
||||||
|
} from "@saleor/components/SaveFilterTabDialog";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||||
import useListSettings from "@saleor/hooks/useListSettings";
|
import useListSettings from "@saleor/hooks/useListSettings";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
|
@ -13,16 +17,26 @@ import usePaginator, {
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import { getMutationState, maybe } from "@saleor/misc";
|
import { getMutationState, maybe } from "@saleor/misc";
|
||||||
import { ListViews } from "@saleor/types";
|
import { ListViews } from "@saleor/types";
|
||||||
import { CategoryListPage } from "../components/CategoryListPage/CategoryListPage";
|
import { CategoryListPage } from "../../components/CategoryListPage/CategoryListPage";
|
||||||
import { TypedCategoryBulkDeleteMutation } from "../mutations";
|
import { TypedCategoryBulkDeleteMutation } from "../../mutations";
|
||||||
import { TypedRootCategoriesQuery } from "../queries";
|
import { TypedRootCategoriesQuery } from "../../queries";
|
||||||
import { CategoryBulkDelete } from "../types/CategoryBulkDelete";
|
import { CategoryBulkDelete } from "../../types/CategoryBulkDelete";
|
||||||
import {
|
import {
|
||||||
categoryAddUrl,
|
categoryAddUrl,
|
||||||
categoryListUrl,
|
categoryListUrl,
|
||||||
|
CategoryListUrlDialog,
|
||||||
|
CategoryListUrlFilters,
|
||||||
CategoryListUrlQueryParams,
|
CategoryListUrlQueryParams,
|
||||||
categoryUrl
|
categoryUrl
|
||||||
} from "../urls";
|
} from "../../urls";
|
||||||
|
import {
|
||||||
|
areFiltersApplied,
|
||||||
|
deleteFilterTab,
|
||||||
|
getActiveFilters,
|
||||||
|
getFilterTabs,
|
||||||
|
getFilterVariables,
|
||||||
|
saveFilterTab
|
||||||
|
} from "./filter";
|
||||||
|
|
||||||
interface CategoryListProps {
|
interface CategoryListProps {
|
||||||
params: CategoryListUrlQueryParams;
|
params: CategoryListUrlQueryParams;
|
||||||
|
@ -41,9 +55,77 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
||||||
);
|
);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const tabs = getFilterTabs();
|
||||||
|
|
||||||
|
const currentTab =
|
||||||
|
params.activeTab === undefined
|
||||||
|
? areFiltersApplied(params)
|
||||||
|
? tabs.length + 1
|
||||||
|
: 0
|
||||||
|
: parseInt(params.activeTab, 0);
|
||||||
|
|
||||||
|
const changeFilterField = (filter: CategoryListUrlFilters) => {
|
||||||
|
reset();
|
||||||
|
navigate(
|
||||||
|
categoryListUrl({
|
||||||
|
...getActiveFilters(params),
|
||||||
|
...filter,
|
||||||
|
activeTab: undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () =>
|
||||||
|
navigate(
|
||||||
|
categoryListUrl({
|
||||||
|
...params,
|
||||||
|
action: undefined,
|
||||||
|
ids: undefined
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const openModal = (action: CategoryListUrlDialog, ids?: string[]) =>
|
||||||
|
navigate(
|
||||||
|
categoryListUrl({
|
||||||
|
...params,
|
||||||
|
action,
|
||||||
|
ids
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabChange = (tab: number) => {
|
||||||
|
reset();
|
||||||
|
navigate(
|
||||||
|
categoryListUrl({
|
||||||
|
activeTab: tab.toString(),
|
||||||
|
...getFilterTabs()[tab - 1].data
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabDelete = () => {
|
||||||
|
deleteFilterTab(currentTab);
|
||||||
|
reset();
|
||||||
|
navigate(categoryListUrl());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
|
||||||
|
saveFilterTab(data.name, getActiveFilters(params));
|
||||||
|
handleTabChange(tabs.length + 1);
|
||||||
|
};
|
||||||
|
|
||||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
|
const queryVariables = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
...paginationState,
|
||||||
|
filter: getFilterVariables(params)
|
||||||
|
}),
|
||||||
|
[params]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedRootCategoriesQuery displayLoader variables={paginationState}>
|
<TypedRootCategoriesQuery displayLoader variables={queryVariables}>
|
||||||
{({ data, loading, refetch }) => {
|
{({ data, loading, refetch }) => {
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
maybe(() => data.categories.pageInfo),
|
maybe(() => data.categories.pageInfo),
|
||||||
|
@ -78,6 +160,14 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
||||||
() => data.categories.edges.map(edge => edge.node),
|
() => data.categories.edges.map(edge => edge.node),
|
||||||
[]
|
[]
|
||||||
)}
|
)}
|
||||||
|
currentTab={currentTab}
|
||||||
|
initialSearch={params.query || ""}
|
||||||
|
onSearchChange={query => changeFilterField({ query })}
|
||||||
|
onAll={() => navigate(categoryListUrl())}
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
onTabDelete={() => openModal("delete-search")}
|
||||||
|
onTabSave={() => openModal("save-search")}
|
||||||
|
tabs={tabs.map(tab => tab.name)}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
onAdd={() => navigate(categoryAddUrl())}
|
onAdd={() => navigate(categoryAddUrl())}
|
||||||
onRowClick={id => () => navigate(categoryUrl(id))}
|
onRowClick={id => () => navigate(categoryUrl(id))}
|
||||||
|
@ -134,7 +224,7 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||||
one {this attribute}
|
one {this category}
|
||||||
other {{displayQuantity} categories}
|
other {{displayQuantity} categories}
|
||||||
}?"
|
}?"
|
||||||
values={{
|
values={{
|
||||||
|
@ -148,6 +238,19 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
|
||||||
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</ActionDialog>
|
</ActionDialog>
|
||||||
|
<SaveFilterTabDialog
|
||||||
|
open={params.action === "save-search"}
|
||||||
|
confirmButtonState="default"
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={handleTabSave}
|
||||||
|
/>
|
||||||
|
<DeleteFilterTabDialog
|
||||||
|
open={params.action === "delete-search"}
|
||||||
|
confirmButtonState="default"
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={handleTabDelete}
|
||||||
|
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
31
src/categories/views/CategoryList/filter.ts
Normal file
31
src/categories/views/CategoryList/filter.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { CategoryFilterInput } from "@saleor/types/globalTypes";
|
||||||
|
import {
|
||||||
|
createFilterTabUtils,
|
||||||
|
createFilterUtils
|
||||||
|
} from "../../../utils/filters";
|
||||||
|
import {
|
||||||
|
CategoryListUrlFilters,
|
||||||
|
CategoryListUrlFiltersEnum,
|
||||||
|
CategoryListUrlQueryParams
|
||||||
|
} from "../../urls";
|
||||||
|
|
||||||
|
export const PRODUCT_FILTERS_KEY = "productFilters";
|
||||||
|
|
||||||
|
export function getFilterVariables(
|
||||||
|
params: CategoryListUrlFilters
|
||||||
|
): CategoryFilterInput {
|
||||||
|
return {
|
||||||
|
search: params.query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const {
|
||||||
|
deleteFilterTab,
|
||||||
|
getFilterTabs,
|
||||||
|
saveFilterTab
|
||||||
|
} = createFilterTabUtils<CategoryListUrlFilters>(PRODUCT_FILTERS_KEY);
|
||||||
|
|
||||||
|
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
|
||||||
|
CategoryListUrlQueryParams,
|
||||||
|
CategoryListUrlFilters
|
||||||
|
>(CategoryListUrlFiltersEnum);
|
2
src/categories/views/CategoryList/index.ts
Normal file
2
src/categories/views/CategoryList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./CategoryList";
|
||||||
|
export * from "./CategoryList";
|
|
@ -3,7 +3,12 @@ import React from "react";
|
||||||
|
|
||||||
import CategoryListPage from "../../../categories/components/CategoryListPage";
|
import CategoryListPage from "../../../categories/components/CategoryListPage";
|
||||||
import { categories } from "../../../categories/fixtures";
|
import { categories } from "../../../categories/fixtures";
|
||||||
import { listActionsProps, pageListProps } from "../../../fixtures";
|
import {
|
||||||
|
listActionsProps,
|
||||||
|
pageListProps,
|
||||||
|
searchPageProps,
|
||||||
|
tabPageProps
|
||||||
|
} from "../../../fixtures";
|
||||||
import Decorator from "../../Decorator";
|
import Decorator from "../../Decorator";
|
||||||
|
|
||||||
const categoryTableProps = {
|
const categoryTableProps = {
|
||||||
|
@ -11,6 +16,8 @@ const categoryTableProps = {
|
||||||
onAddCategory: undefined,
|
onAddCategory: undefined,
|
||||||
onCategoryClick: () => undefined,
|
onCategoryClick: () => undefined,
|
||||||
...listActionsProps,
|
...listActionsProps,
|
||||||
|
...tabPageProps,
|
||||||
|
...searchPageProps,
|
||||||
...pageListProps.default
|
...pageListProps.default
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,10 @@ export interface CatalogueInput {
|
||||||
collections?: (string | null)[] | null;
|
collections?: (string | null)[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CategoryFilterInput {
|
||||||
|
search?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CategoryInput {
|
export interface CategoryInput {
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
descriptionJson?: any | null;
|
descriptionJson?: any | null;
|
||||||
|
|
Loading…
Reference in a new issue