diff --git a/src/attributes/components/AttributeListPage/AttributeListPage.tsx b/src/attributes/components/AttributeListPage/AttributeListPage.tsx index aadb1d933..57e2c97fa 100644 --- a/src/attributes/components/AttributeListPage/AttributeListPage.tsx +++ b/src/attributes/components/AttributeListPage/AttributeListPage.tsx @@ -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, SortPage, TabPageProps { attributes: AttributeList_attributes_edges_node[]; @@ -30,9 +33,12 @@ export interface AttributeListPageProps } const AttributeListPage: React.FC = ({ + currencySymbol, + filterOpts, + initialSearch, onAdd, onBack, - initialSearch, + onFilterChange, onSearchChange, currentTab, onAll, @@ -44,6 +50,8 @@ const AttributeListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -58,18 +66,21 @@ const AttributeListPage: React.FC = ({ - ; + filterableInDashboard: FilterOpts; + filterableInStorefront: FilterOpts; + isVariantOnly: FilterOpts; + valueRequired: FilterOpts; + visibleInStorefront: FilterOpts; +} diff --git a/src/attributes/urls.ts b/src/attributes/urls.ts index fc0312068..9e498a6d9 100644 --- a/src/attributes/urls.ts +++ b/src/attributes/urls.ts @@ -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; diff --git a/src/attributes/views/AttributeList/AttributeList.tsx b/src/attributes/views/AttributeList/AttributeList.tsx index 81e49d3f0..12f6207d5 100644 --- a/src/attributes/views/AttributeList/AttributeList.tsx +++ b/src/attributes/views/AttributeList/AttributeList.tsx @@ -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 = ({ 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 = ({ 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")} diff --git a/src/attributes/views/AttributeList/filters.ts b/src/attributes/views/AttributeList/filters.ts index e6f2b9d1d..189d92262 100644 --- a/src/attributes/views/AttributeList/filters.ts +++ b/src/attributes/views/AttributeList/filters.ts @@ -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 { + 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 +): 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, diff --git a/src/attributes/views/AttributeList/messages.ts b/src/attributes/views/AttributeList/messages.ts new file mode 100644 index 000000000..be243a9a3 --- /dev/null +++ b/src/attributes/views/AttributeList/messages.ts @@ -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;