Add filtering to attributes

This commit is contained in:
dominik-zeglen 2020-01-10 12:34:49 +01:00
parent 04a633bd32
commit d414729f01
6 changed files with 287 additions and 19 deletions

View file

@ -4,25 +4,28 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader";
import SearchBar from "@saleor/components/SearchBar";
import FilterBar from "@saleor/components/FilterBar";
import { sectionNames } from "@saleor/intl";
import { AttributeListUrlSortField } from "@saleor/attributes/urls";
import { AttributeFilterKeys } from "@saleor/attributes/views/AttributeList/filters";
import { AttributeListFilterOpts } from "@saleor/attributes/types";
import Container from "../../../components/Container";
import PageHeader from "../../../components/PageHeader";
import {
ListActions,
PageListProps,
SearchPageProps,
FilterPageProps,
TabPageProps,
SortPage
} from "../../../types";
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
import AttributeList from "../AttributeList/AttributeList";
import { createFilterStructure } from "../../views/AttributeList/filters";
export interface AttributeListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
FilterPageProps<AttributeFilterKeys, AttributeListFilterOpts>,
SortPage<AttributeListUrlSortField>,
TabPageProps {
attributes: AttributeList_attributes_edges_node[];
@ -30,9 +33,12 @@ export interface AttributeListPageProps
}
const AttributeListPage: React.FC<AttributeListPageProps> = ({
currencySymbol,
filterOpts,
initialSearch,
onAdd,
onBack,
initialSearch,
onFilterChange,
onSearchChange,
currentTab,
onAll,
@ -44,6 +50,8 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
}) => {
const intl = useIntl();
const structure = createFilterStructure(intl, filterOpts);
return (
<Container>
<AppHeader onBack={onBack}>
@ -58,18 +66,21 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
</Button>
</PageHeader>
<Card>
<SearchBar
<FilterBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Attributes",
description: "tab name"
})}
currencySymbol={currencySymbol}
currentTab={currentTab}
filterStructure={structure}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Attribute"
})}
tabs={tabs}
onAll={onAll}
onFilterChange={onFilterChange}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}

10
src/attributes/types.ts Normal file
View file

@ -0,0 +1,10 @@
import { FilterOpts } from "@saleor/types";
export interface AttributeListFilterOpts {
availableInGrid: FilterOpts<boolean>;
filterableInDashboard: FilterOpts<boolean>;
filterableInStorefront: FilterOpts<boolean>;
isVariantOnly: FilterOpts<boolean>;
valueRequired: FilterOpts<boolean>;
visibleInStorefront: FilterOpts<boolean>;
}

View file

@ -15,6 +15,12 @@ import {
export const attributeSection = "/attributes/";
export enum AttributeListUrlFiltersEnum {
availableInGrid = "availableInGrid",
filterableInDashboard = "filterableInDashboard",
filterableInStorefront = "filterableInStorefront",
isVariantOnly = "isVariantOnly",
valueRequired = "valueRequired",
visibleInStorefront = "visibleInStorefront",
query = "query"
}
export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>;

View file

@ -9,7 +9,8 @@ import {
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
saveFilterTab,
getFilterOpts
} from "@saleor/attributes/views/AttributeList/filters";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
@ -24,6 +25,7 @@ import usePaginator, {
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
import { PAGINATE_BY } from "../../../config";
import useBulkActions from "../../../hooks/useBulkActions";
import { maybe } from "../../../misc";
@ -35,12 +37,12 @@ import { AttributeBulkDelete } from "../../types/AttributeBulkDelete";
import {
attributeAddUrl,
attributeListUrl,
AttributeListUrlFilters,
AttributeListUrlQueryParams,
attributeUrl,
AttributeListUrlDialog
} from "../../urls";
import { getSortQueryVariables } from "./sort";
import { getFilterQueryParam } from "./filters";
interface AttributeListProps {
params: AttributeListUrlQueryParams;
@ -82,16 +84,17 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
AttributeListUrlQueryParams
>(navigate, attributeListUrl, params);
const changeFilterField = (filter: AttributeListUrlFilters) => {
reset();
navigate(
attributeListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const [
changeFilters,
resetFilters,
handleSearchChange
] = createFilterHandlers({
cleanupFn: reset,
createUrl: attributeListUrl,
getFilterQueryParam,
navigate,
params
});
const handleTabChange = (tab: number) => {
reset();
@ -146,15 +149,17 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
)}
currentTab={currentTab}
disabled={loading || attributeBulkDeleteOpts.loading}
filterOpts={getFilterOpts(params)}
initialSearch={params.query || ""}
isChecked={isSelected}
onAdd={() => navigate(attributeAddUrl())}
onAll={() => navigate(attributeListUrl())}
onAll={resetFilters}
onBack={() => navigate(configurationMenuUrl)}
onFilterChange={changeFilters}
onNextPage={loadNextPage}
onPreviousPage={loadPreviousPage}
onRowClick={id => () => navigate(attributeUrl(id))}
onSearchChange={query => changeFilterField({ query })}
onSearchChange={handleSearchChange}
onSort={handleSort}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}

View file

@ -1,4 +1,10 @@
import { IntlShape } from "react-intl";
import { AttributeFilterInput } from "@saleor/types/globalTypes";
import { maybe, parseBoolean } from "@saleor/misc";
import { createBooleanField } from "@saleor/utils/filters/fields";
import { commonMessages } from "@saleor/intl";
import { IFilter, IFilterElement } from "@saleor/components/Filter";
import {
createFilterTabUtils,
createFilterUtils
@ -8,17 +14,217 @@ import {
AttributeListUrlFiltersEnum,
AttributeListUrlQueryParams
} from "../../urls";
import { AttributeListFilterOpts } from "../../types";
import messages from "./messages";
export const PRODUCT_FILTERS_KEY = "productFilters";
export enum AttributeFilterKeys {
availableInGrid = "availableInGrud",
filterableInDashboard = "filterableInDashboard",
filterableInStorefront = "filterableInStorefront",
isVariantOnly = "isVariantOnly",
valueRequired = "valueRequired",
visibleInStorefront = "visibleInStorefront"
}
export function getFilterOpts(
params: AttributeListUrlFilters
): AttributeListFilterOpts {
return {
availableInGrid: {
active: params.availableInGrid !== undefined,
value: maybe(() => parseBoolean(params.availableInGrid, true))
},
filterableInDashboard: {
active: params.filterableInDashboard !== undefined,
value: maybe(() => parseBoolean(params.filterableInDashboard, true))
},
filterableInStorefront: {
active: params.filterableInStorefront !== undefined,
value: maybe(() => parseBoolean(params.filterableInStorefront, true))
},
isVariantOnly: {
active: params.isVariantOnly !== undefined,
value: maybe(() => parseBoolean(params.isVariantOnly, true))
},
valueRequired: {
active: params.valueRequired !== undefined,
value: maybe(() => parseBoolean(params.valueRequired, true))
},
visibleInStorefront: {
active: params.visibleInStorefront !== undefined,
value: maybe(() => parseBoolean(params.visibleInStorefront, true))
}
};
}
export function createFilterStructure(
intl: IntlShape,
opts: AttributeListFilterOpts
): IFilter<AttributeFilterKeys> {
return [
{
...createBooleanField(
AttributeFilterKeys.availableInGrid,
intl.formatMessage(messages.availableInGrid),
opts.availableInGrid.value,
{
negative: intl.formatMessage(commonMessages.no),
positive: intl.formatMessage(commonMessages.yes)
}
),
active: opts.availableInGrid.active
},
{
...createBooleanField(
AttributeFilterKeys.filterableInDashboard,
intl.formatMessage(messages.filterableInDashboard),
opts.filterableInDashboard.value,
{
negative: intl.formatMessage(commonMessages.no),
positive: intl.formatMessage(commonMessages.yes)
}
),
active: opts.filterableInDashboard.active
},
{
...createBooleanField(
AttributeFilterKeys.filterableInStorefront,
intl.formatMessage(messages.filterableInStorefront),
opts.filterableInStorefront.value,
{
negative: intl.formatMessage(commonMessages.no),
positive: intl.formatMessage(commonMessages.yes)
}
),
active: opts.filterableInStorefront.active
},
{
...createBooleanField(
AttributeFilterKeys.isVariantOnly,
intl.formatMessage(messages.isVariantOnly),
opts.isVariantOnly.value,
{
negative: intl.formatMessage(commonMessages.no),
positive: intl.formatMessage(commonMessages.yes)
}
),
active: opts.isVariantOnly.active
},
{
...createBooleanField(
AttributeFilterKeys.valueRequired,
intl.formatMessage(messages.valueRequired),
opts.valueRequired.value,
{
negative: intl.formatMessage(commonMessages.no),
positive: intl.formatMessage(commonMessages.yes)
}
),
active: opts.valueRequired.active
},
{
...createBooleanField(
AttributeFilterKeys.visibleInStorefront,
intl.formatMessage(messages.visibleInStorefront),
opts.visibleInStorefront.value,
{
negative: intl.formatMessage(commonMessages.no),
positive: intl.formatMessage(commonMessages.yes)
}
),
active: opts.visibleInStorefront.active
}
];
}
export function getFilterVariables(
params: AttributeListUrlFilters
): AttributeFilterInput {
return {
availableInGrid:
params.availableInGrid !== undefined
? parseBoolean(params.availableInGrid, false)
: undefined,
search: params.query
};
}
export function getFilterQueryParam(
filter: IFilterElement<AttributeFilterKeys>
): AttributeListUrlFilters {
const { active, name, value } = filter;
switch (name) {
case AttributeFilterKeys.availableInGrid:
if (!active) {
return {
availableInGrid: undefined
};
}
return {
availableInGrid: value[0]
};
case AttributeFilterKeys.filterableInDashboard:
if (!active) {
return {
filterableInDashboard: undefined
};
}
return {
filterableInDashboard: value[0]
};
case AttributeFilterKeys.filterableInStorefront:
if (!active) {
return {
filterableInStorefront: undefined
};
}
return {
filterableInStorefront: value[0]
};
case AttributeFilterKeys.isVariantOnly:
if (!active) {
return {
isVariantOnly: undefined
};
}
return {
isVariantOnly: value[0]
};
case AttributeFilterKeys.valueRequired:
if (!active) {
return {
valueRequired: undefined
};
}
return {
valueRequired: value[0]
};
case AttributeFilterKeys.visibleInStorefront:
if (!active) {
return {
visibleInStorefront: undefined
};
}
return {
visibleInStorefront: value[0]
};
}
}
export const {
deleteFilterTab,
getFilterTabs,

View file

@ -0,0 +1,30 @@
import { defineMessages } from "react-intl";
const messages = defineMessages({
availableInGrid: {
defaultMessage: "Can be used as column",
description: "attribute can be column in product list table"
},
filterableInDashboard: {
defaultMessage: "Filterable in Dashboard",
description: "use attribute in filtering"
},
filterableInStorefront: {
defaultMessage: "Filterable in Storefront",
description: "use attribute in filtering"
},
isVariantOnly: {
defaultMessage: "Variant Only",
description: "attribute can be used only in variants"
},
valueRequired: {
defaultMessage: "Value Required",
description: "attribute value is required"
},
visibleInStorefront: {
defaultMessage: "Visible on Product Page in Storefront",
description: "attribute"
}
});
export default messages;