Split filters, searches and tabs

This commit is contained in:
dominik-zeglen 2019-09-10 17:14:11 +02:00
parent f4e3149f89
commit 544d13754a
16 changed files with 392 additions and 178 deletions

View file

@ -0,0 +1,109 @@
import { Theme } from "@material-ui/core/styles";
import TextField, { TextFieldProps } from "@material-ui/core/TextField";
import { makeStyles } from "@material-ui/styles";
import React from "react";
import { FilterContentSubmitData, IFilter } from "../Filter";
import Filter from "./Filter";
const useInputStyles = makeStyles({
input: {
padding: "10px 12px"
},
root: {
flex: 1
}
});
const Search: React.FC<TextFieldProps> = props => {
const classes = useInputStyles({});
return (
<TextField
{...props}
className={classes.root}
inputProps={{
className: classes.input
}}
/>
);
};
const useStyles = makeStyles(
(theme: Theme) => ({
actionContainer: {
display: "flex",
flexWrap: "wrap",
padding: `${theme.spacing.unit * 1.5}px ${theme.spacing.unit * 3}px ${
theme.spacing.unit
}px`
}
}),
{
name: "FilterActions"
}
);
export interface FilterActionsPropsSearch {
placeholder: string;
search: string;
onSearchChange: (event: React.ChangeEvent<any>) => void;
}
export interface FilterActionsPropsFilters {
currencySymbol: string;
menu: IFilter;
filterLabel: string;
onFilterAdd: (filter: FilterContentSubmitData) => void;
}
export const FilterActionsOnlySearch: React.FC<
FilterActionsPropsSearch
> = props => {
const { onSearchChange, placeholder, search } = props;
const classes = useStyles(props);
return (
<div className={classes.actionContainer}>
<Search
fullWidth
placeholder={placeholder}
value={search}
onChange={onSearchChange}
/>
</div>
);
};
export type FilterActionsProps = FilterActionsPropsSearch &
FilterActionsPropsFilters;
const FilterActions: React.FC<FilterActionsProps> = props => {
const {
currencySymbol,
filterLabel,
menu,
onFilterAdd,
onSearchChange,
placeholder,
search
} = props;
const classes = useStyles(props);
return (
<div className={classes.actionContainer}>
<Filter
currencySymbol={currencySymbol}
menu={menu}
filterLabel={filterLabel}
onFilterAdd={onFilterAdd}
/>
<Search
fullWidth
placeholder={placeholder}
value={search}
onChange={onSearchChange}
/>
</div>
);
};
FilterActions.displayName = "FilterActions";
export default FilterActions;

View file

@ -6,9 +6,8 @@ import Debounce from "../Debounce";
import { IFilter } from "../Filter/types";
import FilterTabs, { FilterChips, FilterTab } from "../TableFilter";
export interface FilterBarProps<TUrlFilters = object, TFilterKeys = any>
extends FilterProps<TUrlFilters, TFilterKeys> {
filterMenu: IFilter<TFilterKeys>;
export interface FilterBarProps extends FilterProps {
filterMenu: IFilter;
}
const FilterBar: React.FC<FilterBarProps> = ({
@ -16,32 +15,32 @@ const FilterBar: React.FC<FilterBarProps> = ({
currencySymbol,
filterLabel,
filtersList,
filterTabs,
filterMenu,
currentTab,
initialSearch,
searchPlaceholder,
tabs,
onAll,
onSearchChange,
onFilterAdd,
onFilterSave,
onTabChange,
onFilterDelete
onTabDelete,
onTabSave
}) => {
const intl = useIntl();
const [search, setSearch] = React.useState(initialSearch);
React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]);
const isCustom = currentTab === filterTabs.length + 1;
const isCustom = currentTab === tabs.length + 1;
return (
<>
<FilterTabs currentTab={currentTab}>
<FilterTab label={allTabLabel} onClick={onAll} />
{filterTabs.map((tab, tabIndex) => (
{tabs.map((tab, tabIndex) => (
<FilterTab
onClick={() => onTabChange(tabIndex + 1)}
label={tab.name}
label={tab}
key={tabIndex}
/>
))}
@ -72,9 +71,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
search={search}
onSearchChange={handleSearchChange}
onFilterAdd={onFilterAdd}
onFilterSave={onFilterSave}
onFilterSave={onTabDelete}
isCustomSearch={isCustom}
onFilterDelete={onFilterDelete}
onFilterDelete={onTabSave}
/>
);
}}

View file

@ -0,0 +1,122 @@
import { Theme } from "@material-ui/core/styles";
import { makeStyles } from "@material-ui/styles";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { SearchPageProps, TabPageProps } from "@saleor/types";
import Debounce from "../Debounce";
import { FilterActionsOnlySearch } from "../Filter/FilterActions";
import Hr from "../Hr";
import Link from "../Link";
import FilterTabs, { FilterTab } from "../TableFilter";
export interface SearchBarProps extends SearchPageProps, TabPageProps {
searchPlaceholder: string;
}
const useStyles = makeStyles((theme: Theme) => ({
tabAction: {
display: "inline-block"
},
tabActionContainer: {
borderBottom: "1px solid #e8e8e8",
display: "flex",
justifyContent: "flex-end",
marginTop: theme.spacing.unit,
padding: `0 ${theme.spacing.unit * 3}px ${theme.spacing.unit}px`
}
}));
const SearchBar: React.FC<SearchBarProps> = props => {
const {
currentTab,
initialSearch,
onSearchChange,
searchPlaceholder,
tabs,
onAll,
onTabChange,
onTabDelete,
onTabSave
} = props;
const classes = useStyles(props);
const [search, setSearch] = React.useState(initialSearch);
const intl = useIntl();
React.useEffect(() => setSearch(initialSearch), [initialSearch]);
const isCustom = currentTab === tabs.length + 1;
return (
<>
<FilterTabs currentTab={currentTab}>
<FilterTab
label={intl.formatMessage({
defaultMessage: "All Attributes",
description: "tab name"
})}
onClick={onAll}
/>
{tabs.map((tab, tabIndex) => (
<FilterTab
onClick={() => onTabChange(tabIndex + 1)}
label={tab}
key={tabIndex}
/>
))}
{isCustom && (
<FilterTab
onClick={() => undefined}
label={intl.formatMessage({
defaultMessage: "Custom Filter"
})}
/>
)}
</FilterTabs>
<Debounce debounceFn={onSearchChange}>
{debounceSearchChange => {
const handleSearchChange = (event: React.ChangeEvent<any>) => {
const value = event.target.value;
setSearch(value);
debounceSearchChange(value);
};
return (
<>
<FilterActionsOnlySearch
{...props}
placeholder={searchPlaceholder}
search={search}
onSearchChange={handleSearchChange}
/>
<div className={classes.tabActionContainer}>
<div className={classes.tabAction}>
{search || (tabs && tabs.length) > 0 ? (
isCustom ? (
<Link onClick={onTabSave}>
<FormattedMessage
defaultMessage="Save Custom Search"
description="button"
/>
</Link>
) : (
<Link onClick={onTabDelete}>
<FormattedMessage
defaultMessage="Delete Search"
description="button"
/>
</Link>
)
) : (
<Hr />
)}
</div>
</div>
</>
);
}}
</Debounce>
</>
);
};
SearchBar.displayName = "SearchBar";
export default SearchBar;

View file

@ -0,0 +1,2 @@
export { default } from "./SearchBar";
export * from "./SearchBar";

View file

@ -1,14 +1,14 @@
import ButtonBase from "@material-ui/core/ButtonBase";
import { Theme } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import TextField, { TextFieldProps } from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import ClearIcon from "@material-ui/icons/Clear";
import { createStyles, makeStyles, useTheme } from "@material-ui/styles";
import { makeStyles, useTheme } from "@material-ui/styles";
import React from "react";
import { FormattedMessage } from "react-intl";
import Filter, { FilterContentSubmitData, IFilter } from "../Filter";
import Filter from "../Filter";
import FilterActions, { FilterActionsProps } from "../Filter/FilterActions";
import Hr from "../Hr";
import Link from "../Link";
@ -17,38 +17,8 @@ export interface Filter {
onClick: () => void;
}
const useInputStyles = makeStyles({
input: {
padding: "10px 12px"
},
root: {
flex: 1
}
});
const Search: React.FC<TextFieldProps> = props => {
const classes = useInputStyles({});
return (
<TextField
{...props}
className={classes.root}
inputProps={{
className: classes.input
}}
/>
);
};
const useStyles = makeStyles(
(theme: Theme) =>
createStyles({
actionContainer: {
display: "flex",
flexWrap: "wrap",
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${
theme.spacing.unit
}px ${theme.spacing.unit * 3}px`
},
(theme: Theme) => ({
filterButton: {
alignItems: "center",
backgroundColor: fade(theme.palette.primary.main, 0.8),
@ -105,16 +75,10 @@ const useStyles = makeStyles(
}
);
interface FilterChipProps<TFilterKeys = string> {
currencySymbol: string;
menu: IFilter<TFilterKeys>;
interface FilterChipProps extends FilterActionsProps {
filtersList: Filter[];
filterLabel: string;
placeholder: string;
search: string;
isCustomSearch: boolean;
onSearchChange: (event: React.ChangeEvent<any>) => void;
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
onFilterDelete: () => void;
onFilterSave: () => void;
}
@ -137,20 +101,15 @@ export const FilterChips: React.FC<FilterChipProps> = ({
return (
<>
<div className={classes.actionContainer}>
<Filter
<FilterActions
currencySymbol={currencySymbol}
menu={menu}
filterLabel={filterLabel}
placeholder={placeholder}
search={search}
onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd}
/>
<Search
fullWidth
placeholder={placeholder}
value={search}
onChange={onSearchChange}
/>
</div>
{search || (filtersList && filtersList.length) ? (
<div className={classes.filterContainer}>
<div className={classes.filterChipContainer}>

View file

@ -3,7 +3,9 @@ import {
FetchMoreProps,
FilterPageProps,
ListActions,
PageListProps
PageListProps,
SearchPageProps,
TabPageProps
} from "./types";
const pageInfo = {
@ -46,23 +48,26 @@ export const countries = [
{ code: "AS", label: "American Samoa" }
];
export const filterPageProps: FilterPageProps<{}, unknown> = {
currencySymbol: "USD",
export const tabPageProps: TabPageProps = {
currentTab: 0,
filterTabs: [
{
data: {},
name: "Tab X"
}
],
filtersList: [],
initialSearch: "",
onAll: () => undefined,
onFilterAdd: () => undefined,
onFilterDelete: () => undefined,
onFilterSave: () => undefined,
onSearchChange: () => undefined,
onTabChange: () => undefined
onTabChange: () => undefined,
onTabDelete: () => undefined,
onTabSave: () => undefined,
tabs: ["Tab X"]
};
export const searchPageProps: SearchPageProps = {
initialSearch: "",
onSearchChange: () => undefined
};
export const filterPageProps: FilterPageProps = {
...searchPageProps,
...tabPageProps,
currencySymbol: "USD",
filtersList: [],
onFilterAdd: () => undefined
};
export const filters: Filter[] = [

View file

@ -8,9 +8,8 @@ import FilterBar from "@saleor/components/FilterBar";
import TimezoneContext from "@saleor/components/Timezone";
import { FilterProps } from "../../../types";
import { OrderStatusFilter } from "../../../types/globalTypes";
import { OrderListUrlFilters } from "../../urls";
type OrderListFilterProps = FilterProps<OrderListUrlFilters, OrderFilterKeys>;
type OrderListFilterProps = FilterProps;
export enum OrderFilterKeys {
date = "date",

View file

@ -10,14 +10,13 @@ import { sectionNames } from "@saleor/intl";
import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter";
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
import { OrderList_orders_edges_node } from "../../types/OrderList";
import { OrderListUrlFilters } from "../../urls";
import OrderList from "../OrderList";
import OrderListFilter from "../OrderListFilter";
export interface OrderListPageProps
extends PageListProps,
ListActions,
FilterPageProps<OrderListUrlFilters, OrderFilterKeys> {
FilterPageProps {
orders: OrderList_orders_edges_node[];
}
@ -25,8 +24,8 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
currencySymbol,
currentTab,
filtersList,
filterTabs,
initialSearch,
tabs,
onAdd,
onAll,
onSearchChange,
@ -59,7 +58,7 @@ const OrderListPage: React.FC<OrderListPageProps> = ({
filterLabel={intl.formatMessage({
defaultMessage: "Select all orders where:"
})}
filterTabs={filterTabs}
filterTabs={tabs}
filtersList={filtersList}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({

View file

@ -8,7 +8,8 @@ import {
Filters,
FiltersWithMultipleValues,
Pagination,
SingleAction
SingleAction,
TabActionDialog
} from "../types";
const orderSectionUrl = "/orders";
@ -25,7 +26,7 @@ export enum OrderListUrlFiltersWithMultipleValuesEnum {
}
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> &
FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>;
export type OrderListUrlDialog = "cancel" | "save-search" | "delete-search";
export type OrderListUrlDialog = "cancel" | TabActionDialog;
export type OrderListUrlQueryParams = BulkAction &
Dialog<OrderListUrlDialog> &
OrderListUrlFilters &

View file

@ -236,7 +236,7 @@ export const OrderList: React.StatelessComponent<OrderListProps> = ({
onFilterDelete={() => openModal("delete-search")}
onTabChange={handleTabChange}
initialSearch={params.email || ""}
filterTabs={getFilterTabs()}
filterTabs={getFilterTabs().map(tab => tab.name)}
onAll={() =>
changeFilters({
status: undefined

View file

@ -5,11 +5,10 @@ import { FieldType, IFilter } from "@saleor/components/Filter";
import FilterBar from "@saleor/components/FilterBar";
import { FilterProps } from "@saleor/types";
import { StockAvailability } from "@saleor/types/globalTypes";
import { ProductListUrlFilters } from "../../urls";
type ProductListFilterProps = FilterProps<
ProductListUrlFilters,
ProductFilterKeys
type ProductListFilterProps = Omit<
FilterProps,
"allTabLabel" | "filterLabel" | "searchPlaceholder"
>;
export enum ProductFilterKeys {
@ -133,7 +132,22 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
}
];
return <FilterBar {...props} filterMenu={filterMenu} />;
return (
<FilterBar
{...props}
allTabLabel={intl.formatMessage({
defaultMessage: "All Products",
description: "tab name"
})}
filterMenu={filterMenu}
filterLabel={intl.formatMessage({
defaultMessage: "Select all products where:"
})}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Products..."
})}
/>
);
};
ProductListFilter.displayName = "ProductListFilter";
export default ProductListFilter;

View file

@ -24,14 +24,13 @@ import {
ListActions,
PageListProps
} from "@saleor/types";
import { ProductListUrlFilters } from "../../urls";
import ProductList from "../ProductList";
import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter";
export interface ProductListPageProps
extends PageListProps<ProductListColumns>,
ListActions,
FilterPageProps<ProductListUrlFilters, ProductFilterKeys>,
FilterPageProps,
FetchMoreProps {
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
currencySymbol: string;
@ -52,13 +51,13 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
currentTab,
defaultSettings,
filtersList,
filterTabs,
gridAttributes,
availableInGridAttributes,
hasMore,
initialSearch,
loading,
settings,
tabs,
totalGridAttributes,
onAdd,
onAll,
@ -137,21 +136,11 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
</PageHeader>
<Card>
<ProductListFilter
allTabLabel={intl.formatMessage({
defaultMessage: "All Products",
description: "tab name"
})}
currencySymbol={currencySymbol}
currentTab={currentTab}
filterLabel={intl.formatMessage({
defaultMessage: "Select all products where:"
})}
filterTabs={filterTabs}
filtersList={filtersList}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Products..."
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd}

View file

@ -1,7 +1,14 @@
import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join";
import { ActiveTab, BulkAction, Dialog, Filters, Pagination } from "../types";
import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
TabActionDialog
} from "../types";
const productSection = "/products/";
@ -13,8 +20,7 @@ export type ProductListUrlDialog =
| "publish"
| "unpublish"
| "delete"
| "save-search"
| "delete-search";
| TabActionDialog;
export enum ProductListUrlFiltersEnum {
isPublished = "isPublished",
priceFrom = "priceFrom",

View file

@ -355,7 +355,7 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
onFilterDelete={() => openModal("delete-search")}
onTabChange={handleTabChange}
initialSearch={params.query || ""}
filterTabs={getFilterTabs()}
tabs={getFilterTabs().map(tab => tab.name)}
/>
<ActionDialog
open={params.action === "delete"}

View file

@ -1,4 +1,5 @@
import { defineMessages, IntlShape } from "react-intl";
import { FilterContentSubmitData } from "../../../components/Filter";
import { Filter } from "../../../components/TableFilter";
import {

View file

@ -2,7 +2,6 @@ import { MutationResult } from "react-apollo";
import { FilterContentSubmitData } from "./components/Filter";
import { Filter } from "./components/TableFilter";
import { GetFilterTabsOutput } from "./utils/filters";
export interface UserError {
field: string;
@ -65,24 +64,32 @@ export interface PageListProps<TColumns extends string = string>
defaultSettings?: ListSettings<TColumns>;
onAdd: () => void;
}
export interface FilterPageProps<TUrlFilters, TFilterKeys> {
currencySymbol: string;
currentTab: number;
filterTabs: GetFilterTabsOutput<TUrlFilters>;
filtersList: Filter[];
export interface SearchPageProps {
initialSearch: string;
onAll: () => void;
onSearchChange: (value: string) => void;
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
onFilterDelete: () => void;
onFilterSave: () => void;
onTabChange: (tab: number) => void;
}
export interface FilterProps<TUrlFilters, TFilterKeys>
extends FilterPageProps<TUrlFilters, TFilterKeys> {
export interface FilterPageProps extends SearchPageProps, TabPageProps {
currencySymbol: string;
filtersList: Filter[];
onFilterAdd: (filter: FilterContentSubmitData) => void;
}
export interface SearchProps {
searchPlaceholder: string;
}
export interface FilterProps extends FilterPageProps, SearchProps {
allTabLabel: string;
filterLabel: string;
searchPlaceholder: string;
}
export interface TabPageProps {
currentTab: number;
tabs: string[];
onAll: () => void;
onTabChange: (tab: number) => void;
onTabDelete: () => void;
onTabSave: () => void;
}
export interface PartialMutationProviderOutput<
@ -134,3 +141,5 @@ export interface FetchMoreProps {
hasMore: boolean;
onFetchMore: () => void;
}
export type TabActionDialog = "save-search" | "delete-search";