Add search to product types
This commit is contained in:
parent
f9285cec60
commit
490c3b7543
8 changed files with 341 additions and 154 deletions
|
@ -1,4 +1,3 @@
|
||||||
import Card from "@material-ui/core/Card";
|
|
||||||
import {
|
import {
|
||||||
createStyles,
|
createStyles,
|
||||||
Theme,
|
Theme,
|
||||||
|
@ -70,7 +69,6 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })(
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead
|
<TableHead
|
||||||
colSpan={numberOfColumns}
|
colSpan={numberOfColumns}
|
||||||
|
@ -103,9 +101,7 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })(
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
colSpan={numberOfColumns}
|
colSpan={numberOfColumns}
|
||||||
hasNextPage={
|
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false}
|
||||||
pageInfo && !disabled ? pageInfo.hasNextPage : false
|
|
||||||
}
|
|
||||||
onNextPage={onNextPage}
|
onNextPage={onNextPage}
|
||||||
hasPreviousPage={
|
hasPreviousPage={
|
||||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||||
|
@ -126,9 +122,7 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })(
|
||||||
className={!!productType ? classes.link : undefined}
|
className={!!productType ? classes.link : undefined}
|
||||||
hover={!!productType}
|
hover={!!productType}
|
||||||
key={productType ? productType.id : "skeleton"}
|
key={productType ? productType.id : "skeleton"}
|
||||||
onClick={
|
onClick={productType ? onRowClick(productType.id) : undefined}
|
||||||
productType ? onRowClick(productType.id) : undefined
|
|
||||||
}
|
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
>
|
>
|
||||||
<TableCell padding="checkbox">
|
<TableCell padding="checkbox">
|
||||||
|
@ -201,7 +195,6 @@ const ProductTypeList = withStyles(styles, { name: "ProductTypeList" })(
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,23 +1,46 @@
|
||||||
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 AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
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 "../../../types";
|
import {
|
||||||
|
ListActions,
|
||||||
|
PageListProps,
|
||||||
|
SearchPageProps,
|
||||||
|
TabPageProps
|
||||||
|
} from "../../../types";
|
||||||
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
|
import { ProductTypeList_productTypes_edges_node } from "../../types/ProductTypeList";
|
||||||
import ProductTypeList from "../ProductTypeList";
|
import ProductTypeList from "../ProductTypeList";
|
||||||
|
|
||||||
interface ProductTypeListPageProps extends PageListProps, ListActions {
|
interface ProductTypeListPageProps
|
||||||
|
extends PageListProps,
|
||||||
|
ListActions,
|
||||||
|
SearchPageProps,
|
||||||
|
TabPageProps {
|
||||||
productTypes: ProductTypeList_productTypes_edges_node[];
|
productTypes: ProductTypeList_productTypes_edges_node[];
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductTypeListPage: React.StatelessComponent<
|
const ProductTypeListPage: React.StatelessComponent<
|
||||||
ProductTypeListPageProps
|
ProductTypeListPageProps
|
||||||
> = ({ disabled, onAdd, onBack, ...listProps }) => {
|
> = ({
|
||||||
|
currentTab,
|
||||||
|
initialSearch,
|
||||||
|
onAdd,
|
||||||
|
onAll,
|
||||||
|
onBack,
|
||||||
|
onSearchChange,
|
||||||
|
onTabChange,
|
||||||
|
onTabDelete,
|
||||||
|
onTabSave,
|
||||||
|
tabs,
|
||||||
|
...listProps
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -26,19 +49,33 @@ const ProductTypeListPage: React.StatelessComponent<
|
||||||
{intl.formatMessage(sectionNames.configuration)}
|
{intl.formatMessage(sectionNames.configuration)}
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
<PageHeader title={intl.formatMessage(sectionNames.productTypes)}>
|
<PageHeader title={intl.formatMessage(sectionNames.productTypes)}>
|
||||||
<Button
|
<Button color="primary" variant="contained" onClick={onAdd}>
|
||||||
color="primary"
|
|
||||||
variant="contained"
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={onAdd}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="create product type"
|
defaultMessage="create product type"
|
||||||
description="button"
|
description="button"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<ProductTypeList disabled={disabled} {...listProps} />
|
<Card>
|
||||||
|
<SearchBar
|
||||||
|
allTabLabel={intl.formatMessage({
|
||||||
|
defaultMessage: "All Product Types",
|
||||||
|
description: "tab name"
|
||||||
|
})}
|
||||||
|
currentTab={currentTab}
|
||||||
|
initialSearch={initialSearch}
|
||||||
|
searchPlaceholder={intl.formatMessage({
|
||||||
|
defaultMessage: "Search Product Type"
|
||||||
|
})}
|
||||||
|
tabs={tabs}
|
||||||
|
onAll={onAll}
|
||||||
|
onSearchChange={onSearchChange}
|
||||||
|
onTabChange={onTabChange}
|
||||||
|
onTabDelete={onTabDelete}
|
||||||
|
onTabSave={onTabSave}
|
||||||
|
/>
|
||||||
|
<ProductTypeList {...listProps} />
|
||||||
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,8 +51,15 @@ export const productTypeListQuery = gql`
|
||||||
$before: String
|
$before: String
|
||||||
$first: Int
|
$first: Int
|
||||||
$last: Int
|
$last: Int
|
||||||
|
$filter: ProductTypeFilterInput
|
||||||
|
) {
|
||||||
|
productTypes(
|
||||||
|
after: $after
|
||||||
|
before: $before
|
||||||
|
first: $first
|
||||||
|
last: $last
|
||||||
|
filter: $filter
|
||||||
) {
|
) {
|
||||||
productTypes(after: $after, before: $before, first: $first, last: $last) {
|
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...ProductTypeFragment
|
...ProductTypeFragment
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
import { stringify as stringifyQs } from "qs";
|
import { stringify as stringifyQs } from "qs";
|
||||||
import urlJoin from "url-join";
|
import urlJoin from "url-join";
|
||||||
|
|
||||||
import { BulkAction, Dialog, Pagination, SingleAction } from "../types";
|
import {
|
||||||
|
ActiveTab,
|
||||||
|
BulkAction,
|
||||||
|
Dialog,
|
||||||
|
Filters,
|
||||||
|
Pagination,
|
||||||
|
SingleAction,
|
||||||
|
TabActionDialog
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
const productTypeSection = "/product-types/";
|
const productTypeSection = "/product-types/";
|
||||||
|
|
||||||
export const productTypeListPath = productTypeSection;
|
export const productTypeListPath = productTypeSection;
|
||||||
export type ProductTypeListUrlDialog = "remove";
|
export enum ProductTypeListUrlFiltersEnum {
|
||||||
export type ProductTypeListUrlQueryParams = BulkAction &
|
query = "query"
|
||||||
|
}
|
||||||
|
export type ProductTypeListUrlFilters = Filters<ProductTypeListUrlFiltersEnum>;
|
||||||
|
export type ProductTypeListUrlDialog = "remove" | TabActionDialog;
|
||||||
|
export type ProductTypeListUrlQueryParams = ActiveTab &
|
||||||
|
BulkAction &
|
||||||
Dialog<ProductTypeListUrlDialog> &
|
Dialog<ProductTypeListUrlDialog> &
|
||||||
Pagination;
|
Pagination &
|
||||||
|
ProductTypeListUrlFilters;
|
||||||
export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) =>
|
export const productTypeListUrl = (params?: ProductTypeListUrlQueryParams) =>
|
||||||
productTypeListPath + "?" + stringifyQs(params);
|
productTypeListPath + "?" + stringifyQs(params);
|
||||||
|
|
||||||
|
|
|
@ -5,26 +5,41 @@ 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 useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
createPaginationState
|
createPaginationState
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { PAGINATE_BY } from "../../config";
|
import { ListViews } from "@saleor/types";
|
||||||
import { configurationMenuUrl } from "../../configuration";
|
import { configurationMenuUrl } from "../../../configuration";
|
||||||
import { getMutationState, maybe } from "../../misc";
|
import { getMutationState, maybe } from "../../../misc";
|
||||||
import ProductTypeListPage from "../components/ProductTypeListPage";
|
import ProductTypeListPage from "../../components/ProductTypeListPage";
|
||||||
import { TypedProductTypeBulkDeleteMutation } from "../mutations";
|
import { TypedProductTypeBulkDeleteMutation } from "../../mutations";
|
||||||
import { TypedProductTypeListQuery } from "../queries";
|
import { TypedProductTypeListQuery } from "../../queries";
|
||||||
import { ProductTypeBulkDelete } from "../types/ProductTypeBulkDelete";
|
import { ProductTypeBulkDelete } from "../../types/ProductTypeBulkDelete";
|
||||||
import {
|
import {
|
||||||
productTypeAddUrl,
|
productTypeAddUrl,
|
||||||
productTypeListUrl,
|
productTypeListUrl,
|
||||||
|
ProductTypeListUrlDialog,
|
||||||
|
ProductTypeListUrlFilters,
|
||||||
ProductTypeListUrlQueryParams,
|
ProductTypeListUrlQueryParams,
|
||||||
productTypeUrl
|
productTypeUrl
|
||||||
} from "../urls";
|
} from "../../urls";
|
||||||
|
import {
|
||||||
|
areFiltersApplied,
|
||||||
|
deleteFilterTab,
|
||||||
|
getActiveFilters,
|
||||||
|
getFilterTabs,
|
||||||
|
getFilterVariables,
|
||||||
|
saveFilterTab
|
||||||
|
} from "./filter";
|
||||||
|
|
||||||
interface ProductTypeListProps {
|
interface ProductTypeListProps {
|
||||||
params: ProductTypeListUrlQueryParams;
|
params: ProductTypeListUrlQueryParams;
|
||||||
|
@ -39,13 +54,79 @@ export const ProductTypeList: React.StatelessComponent<
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||||
params.ids
|
params.ids
|
||||||
);
|
);
|
||||||
|
const { settings } = useListSettings(ListViews.PRODUCT_LIST);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const closeModal = () => navigate(productTypeListUrl(), true);
|
const tabs = getFilterTabs();
|
||||||
|
|
||||||
|
const currentTab =
|
||||||
|
params.activeTab === undefined
|
||||||
|
? areFiltersApplied(params)
|
||||||
|
? tabs.length + 1
|
||||||
|
: 0
|
||||||
|
: parseInt(params.activeTab, 0);
|
||||||
|
|
||||||
|
const changeFilterField = (filter: ProductTypeListUrlFilters) => {
|
||||||
|
reset();
|
||||||
|
navigate(
|
||||||
|
productTypeListUrl({
|
||||||
|
...getActiveFilters(params),
|
||||||
|
...filter,
|
||||||
|
activeTab: undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () =>
|
||||||
|
navigate(
|
||||||
|
productTypeListUrl({
|
||||||
|
...params,
|
||||||
|
action: undefined,
|
||||||
|
ids: undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const openModal = (action: ProductTypeListUrlDialog, ids?: string[]) =>
|
||||||
|
navigate(
|
||||||
|
productTypeListUrl({
|
||||||
|
...params,
|
||||||
|
action,
|
||||||
|
ids
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTabChange = (tab: number) => {
|
||||||
|
reset();
|
||||||
|
navigate(
|
||||||
|
productTypeListUrl({
|
||||||
|
activeTab: tab.toString(),
|
||||||
|
...getFilterTabs()[tab - 1].data
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabDelete = () => {
|
||||||
|
deleteFilterTab(currentTab);
|
||||||
|
reset();
|
||||||
|
navigate(productTypeListUrl());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
|
||||||
|
saveFilterTab(data.name, getActiveFilters(params));
|
||||||
|
handleTabChange(tabs.length + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
|
const queryVariables = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
...paginationState,
|
||||||
|
filter: getFilterVariables(params)
|
||||||
|
}),
|
||||||
|
[params]
|
||||||
|
);
|
||||||
|
|
||||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
|
||||||
return (
|
return (
|
||||||
<TypedProductTypeListQuery displayLoader variables={paginationState}>
|
<TypedProductTypeListQuery displayLoader variables={queryVariables}>
|
||||||
{({ data, loading, refetch }) => {
|
{({ data, loading, refetch }) => {
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
maybe(() => data.productTypes.pageInfo),
|
maybe(() => data.productTypes.pageInfo),
|
||||||
|
@ -93,6 +174,14 @@ export const ProductTypeList: React.StatelessComponent<
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProductTypeListPage
|
<ProductTypeListPage
|
||||||
|
currentTab={currentTab}
|
||||||
|
initialSearch={params.query || ""}
|
||||||
|
onSearchChange={query => changeFilterField({ query })}
|
||||||
|
onAll={() => navigate(productTypeListUrl())}
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
onTabDelete={() => openModal("delete-search")}
|
||||||
|
onTabSave={() => openModal("save-search")}
|
||||||
|
tabs={tabs.map(tab => tab.name)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
productTypes={maybe(() =>
|
productTypes={maybe(() =>
|
||||||
data.productTypes.edges.map(edge => edge.node)
|
data.productTypes.edges.map(edge => edge.node)
|
||||||
|
@ -150,6 +239,19 @@ export const ProductTypeList: React.StatelessComponent<
|
||||||
/>
|
/>
|
||||||
</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/productTypes/views/ProductTypeList/filter.ts
Normal file
31
src/productTypes/views/ProductTypeList/filter.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { ProductTypeFilterInput } from "@saleor/types/globalTypes";
|
||||||
|
import {
|
||||||
|
createFilterTabUtils,
|
||||||
|
createFilterUtils
|
||||||
|
} from "../../../utils/filters";
|
||||||
|
import {
|
||||||
|
ProductTypeListUrlFilters,
|
||||||
|
ProductTypeListUrlFiltersEnum,
|
||||||
|
ProductTypeListUrlQueryParams
|
||||||
|
} from "../../urls";
|
||||||
|
|
||||||
|
export const PRODUCT_TYPE_FILTERS_KEY = "productTypeFilters";
|
||||||
|
|
||||||
|
export function getFilterVariables(
|
||||||
|
params: ProductTypeListUrlFilters
|
||||||
|
): ProductTypeFilterInput {
|
||||||
|
return {
|
||||||
|
search: params.query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const {
|
||||||
|
deleteFilterTab,
|
||||||
|
getFilterTabs,
|
||||||
|
saveFilterTab
|
||||||
|
} = createFilterTabUtils<ProductTypeListUrlFilters>(PRODUCT_TYPE_FILTERS_KEY);
|
||||||
|
|
||||||
|
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
|
||||||
|
ProductTypeListUrlQueryParams,
|
||||||
|
ProductTypeListUrlFilters
|
||||||
|
>(ProductTypeListUrlFiltersEnum);
|
2
src/productTypes/views/ProductTypeList/index.ts
Normal file
2
src/productTypes/views/ProductTypeList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export { default } from "./ProductTypeList";
|
||||||
|
export * from "./ProductTypeList";
|
|
@ -24,6 +24,7 @@ export enum ListViews {
|
||||||
PAGES_LIST = "PAGES_LIST",
|
PAGES_LIST = "PAGES_LIST",
|
||||||
PLUGINS_LIST = "PLUGIN_LIST",
|
PLUGINS_LIST = "PLUGIN_LIST",
|
||||||
PRODUCT_LIST = "PRODUCT_LIST",
|
PRODUCT_LIST = "PRODUCT_LIST",
|
||||||
|
PRODUCT_TYPE_LIST = "PRODUCT_TYPE_LIST",
|
||||||
SALES_LIST = "SALES_LIST",
|
SALES_LIST = "SALES_LIST",
|
||||||
SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST",
|
SHIPPING_METHODS_LIST = "SHIPPING_METHODS_LIST",
|
||||||
STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST",
|
STAFF_MEMBERS_LIST = "STAFF_MEMBERS_LIST",
|
||||||
|
|
Loading…
Reference in a new issue