diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 1069967c5..c932eac58 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -5671,6 +5671,14 @@ "context": "button", "string": "Create page" }, + "src_dot_pages_dot_components_dot_PageListPage_dot_pageType": { + "context": "Types", + "string": "Page Types" + }, + "src_dot_pages_dot_components_dot_PageListPage_dot_searchPlaceholder": { + "context": "search pages placeholder", + "string": "Search Pages" + }, "src_dot_pages_dot_components_dot_PageList_dot_1124600214": { "context": "dialog header", "string": "Title" diff --git a/src/pages/components/PageListPage/PageListPage.tsx b/src/pages/components/PageListPage/PageListPage.tsx index c231876f1..06687c68a 100644 --- a/src/pages/components/PageListPage/PageListPage.tsx +++ b/src/pages/components/PageListPage/PageListPage.tsx @@ -1,23 +1,40 @@ +import { Card } from "@material-ui/core"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import { PageFragment } from "@saleor/graphql"; import { sectionNames } from "@saleor/intl"; import { Button } from "@saleor/macaw-ui"; -import { PageListUrlSortField } from "@saleor/pages/urls"; +import { + PageListUrlDialog, + PageListUrlQueryParams, + PageListUrlSortField +} from "@saleor/pages/urls"; import { ListActions, PageListProps, SortPage } from "@saleor/types"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import PageList from "../PageList"; +import PageListSearchAndFilters from "./PageListSearchAndFilters"; +export interface PageListActionDialogOpts { + open: (action: PageListUrlDialog, newParams?: PageListUrlQueryParams) => void; + close: () => void; +} export interface PageListPageProps extends PageListProps, ListActions, SortPage { pages: PageFragment[]; + params: PageListUrlQueryParams; + actionDialogOpts: PageListActionDialogOpts; } -const PageListPage: React.FC = ({ onAdd, ...listProps }) => { +const PageListPage: React.FC = ({ + onAdd, + params, + actionDialogOpts, + ...listProps +}) => { const intl = useIntl(); return ( @@ -27,7 +44,13 @@ const PageListPage: React.FC = ({ onAdd, ...listProps }) => { - + + + + ); }; diff --git a/src/pages/components/PageListPage/PageListSearchAndFilters.tsx b/src/pages/components/PageListPage/PageListSearchAndFilters.tsx new file mode 100644 index 000000000..788e0f55f --- /dev/null +++ b/src/pages/components/PageListPage/PageListSearchAndFilters.tsx @@ -0,0 +1,138 @@ +import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; +import FilterBar from "@saleor/components/FilterBar"; +import SaveFilterTabDialog, { + SaveFilterTabDialogFormData +} from "@saleor/components/SaveFilterTabDialog"; +import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; +import { getSearchFetchMoreProps } from "@saleor/hooks/makeTopLevelSearch/utils"; +import useBulkActions from "@saleor/hooks/useBulkActions"; +import useNavigator from "@saleor/hooks/useNavigator"; +import { pageListUrl, PageListUrlQueryParams } from "@saleor/pages/urls"; +import usePageTypeSearch from "@saleor/searches/usePageTypeSearch"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import { mapEdgesToItems } from "@saleor/utils/maps"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { + createFilterStructure, + deleteFilterTab, + getActiveFilters, + getFilterOpts, + getFilterQueryParam, + getFiltersCurrentTab, + getFilterTabs, + saveFilterTab +} from "./filters"; +import { pagesListSearchAndFiltersMessages as messages } from "./messages"; +import { PageListActionDialogOpts } from "./PageListPage"; + +interface PageListSearchAndFiltersProps { + params: PageListUrlQueryParams; + actionDialogOpts: PageListActionDialogOpts; +} + +const PageListSearchAndFilters: React.FC = ({ + params, + actionDialogOpts +}) => { + const navigate = useNavigator(); + const intl = useIntl(); + + const defaultSearchVariables = { + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 5 + } + }; + + const { reset } = useBulkActions(params.ids); + + const { + loadMore: fetchMorePageTypes, + search: searchPageTypes, + result: searchPageTypesResult + } = usePageTypeSearch(defaultSearchVariables); + + const filterOpts = getFilterOpts({ + params, + pageTypes: mapEdgesToItems(searchPageTypesResult?.data?.search), + pageTypesProps: { + ...getSearchFetchMoreProps(searchPageTypesResult, fetchMorePageTypes), + onSearchChange: searchPageTypes + } + }); + + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + createUrl: pageListUrl, + getFilterQueryParam, + navigate, + params, + cleanupFn: reset + }); + + const filterStrucutre = createFilterStructure(intl, filterOpts); + + const { open: openModal, close: closeModal } = actionDialogOpts; + + const handleTabChange = (tab: number) => { + navigate( + pageListUrl({ + activeTab: tab.toString(), + ...getFilterTabs()[tab - 1].data + }) + ); + }; + + const tabs = getFilterTabs(); + const currentTab = getFiltersCurrentTab(params, tabs); + + const handleTabSave = (data: SaveFilterTabDialogFormData) => { + saveFilterTab(data.name, getActiveFilters(params)); + handleTabChange(tabs.length + 1); + }; + + const handleTabDelete = () => { + deleteFilterTab(currentTab); + reset(); + navigate(pageListUrl()); + }; + + return ( + <> + name)} + currentTab={currentTab} + onTabDelete={handleTabDelete} + onTabChange={handleTabChange} + onTabSave={() => openModal("save-search")} + /> + + + + ); +}; + +export default PageListSearchAndFilters; diff --git a/src/pages/components/PageListPage/filters.ts b/src/pages/components/PageListPage/filters.ts new file mode 100644 index 000000000..6c98876a0 --- /dev/null +++ b/src/pages/components/PageListPage/filters.ts @@ -0,0 +1,126 @@ +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { SearchWithFetchMoreProps } from "@saleor/giftCards/GiftCardsList/GiftCardListSearchAndFilters/types"; +import { SearchPageTypesQuery } from "@saleor/graphql"; +import { + PageListUrlFilters, + PageListUrlFiltersWithMultipleValues, + PageListUrlSort +} from "@saleor/pages/urls"; +import { + ActiveTab, + AutocompleteFilterOpts, + FilterOpts, + Pagination, + Search +} from "@saleor/types"; +import { + createFilterTabUtils, + createFilterUtils, + getMultipleValueQueryParam +} from "@saleor/utils/filters"; +import { createAutocompleteField } from "@saleor/utils/filters/fields"; +import { + mapNodeToChoice, + mapSingleValueNodeToChoice +} from "@saleor/utils/maps"; +import { defineMessages, IntlShape } from "react-intl"; + +export enum PageListFilterKeys { + pageTypes = "pageTypes" +} + +export const PAGES_FILTERS_KEY = "pagesFilters"; + +export interface PageListFilterOpts { + pageType: FilterOpts & AutocompleteFilterOpts; +} + +const messages = defineMessages({ + pageType: { + defaultMessage: "Page Types", + description: "Types" + } +}); + +interface PageListFilterOptsProps { + params: PageListUrlFilters; + pageTypes: Array; + pageTypesProps: SearchWithFetchMoreProps; +} + +export const getFilterOpts = ({ + params, + pageTypes, + pageTypesProps +}: PageListFilterOptsProps): PageListFilterOpts => ({ + pageType: { + active: !!params?.pageTypes, + value: params?.pageTypes, + choices: mapNodeToChoice(pageTypes), + displayValues: mapSingleValueNodeToChoice(pageTypes), + initialSearch: "", + hasMore: pageTypesProps.hasMore, + loading: pageTypesProps.loading, + onFetchMore: pageTypesProps.onFetchMore, + onSearchChange: pageTypesProps.onSearchChange + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: PageListFilterOpts +): IFilter { + return [ + { + ...createAutocompleteField( + PageListFilterKeys.pageTypes, + intl.formatMessage(messages.pageType), + opts.pageType.value, + opts.pageType.displayValues, + true, + opts.pageType.choices, + { + hasMore: opts.pageType.hasMore, + initialSearch: "", + loading: opts.pageType.loading, + onFetchMore: opts.pageType.onFetchMore, + onSearchChange: opts.pageType.onSearchChange + } + ), + active: opts.pageType.active + } + ]; +} + +export function getFilterQueryParam( + filter: IFilterElement +): PageListUrlFilters { + const { name } = filter; + + const { pageTypes } = PageListFilterKeys; + + switch (name) { + case pageTypes: + return getMultipleValueQueryParam(filter, name); + } +} + +export type PageListUrlQueryParams = Pagination & + PageListUrlFilters & + PageListUrlSort & + ActiveTab & + Search; + +export const { + deleteFilterTab, + getFilterTabs, + saveFilterTab +} = createFilterTabUtils(PAGES_FILTERS_KEY); + +export const { + areFiltersApplied, + getActiveFilters, + getFiltersCurrentTab +} = createFilterUtils( + PageListUrlFiltersWithMultipleValues +); diff --git a/src/pages/components/PageListPage/messages.ts b/src/pages/components/PageListPage/messages.ts new file mode 100644 index 000000000..dc300cacd --- /dev/null +++ b/src/pages/components/PageListPage/messages.ts @@ -0,0 +1,8 @@ +import { defineMessages } from "react-intl"; + +export const pagesListSearchAndFiltersMessages = defineMessages({ + searchPlaceholder: { + defaultMessage: "Search Pages", + description: "search pages placeholder" + } +}); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 11bbcdb84..5d3e039f2 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -18,7 +18,7 @@ import PageCreateComponent from "./views/PageCreate"; import PageDetailsComponent from "./views/PageDetails"; import PageListComponent from "./views/PageList"; -const PageList: React.FC> = ({ location }) => { +const PageList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)); const params: PageListUrlQueryParams = asSortParams( qs, diff --git a/src/pages/queries.ts b/src/pages/queries.ts index 752de57fd..27ecff581 100644 --- a/src/pages/queries.ts +++ b/src/pages/queries.ts @@ -7,6 +7,7 @@ export const pageList = gql` $last: Int $before: String $sort: PageSortingInput + $filter: PageFilterInput ) { pages( before: $before @@ -14,6 +15,7 @@ export const pageList = gql` first: $first last: $last sortBy: $sort + filter: $filter ) { edges { node { diff --git a/src/pages/urls.ts b/src/pages/urls.ts index 617a54379..1388ffc25 100644 --- a/src/pages/urls.ts +++ b/src/pages/urls.ts @@ -2,37 +2,48 @@ import { stringifyQs } from "@saleor/utils/urls"; import urlJoin from "url-join"; import { + ActiveTab, BulkAction, Dialog, + Filters, FiltersWithMultipleValues, Pagination, SingleAction, - Sort + Sort, + TabActionDialog } from "../types"; export const pagesSection = "/pages/"; export const pageListPath = pagesSection; -export type PageListUrlDialog = "publish" | "unpublish" | "remove"; +export type PageListUrlDialog = + | "publish" + | "unpublish" + | "remove" + | TabActionDialog; export enum PageListUrlSortField { title = "title", slug = "slug", visible = "visible" } +export enum PageListUrlFiltersEnum { + query = "query" +} + export enum PageListUrlFiltersWithMultipleValues { pageTypes = "pageTypes" } -export type PageListUrlFilters = FiltersWithMultipleValues< - PageListUrlFiltersWithMultipleValues ->; +export type PageListUrlFilters = Filters & + FiltersWithMultipleValues; export type PageListUrlSort = Sort; export type PageListUrlQueryParams = BulkAction & PageListUrlFilters & Dialog & PageListUrlSort & - Pagination; + Pagination & + ActiveTab; export const pageListUrl = (params?: PageListUrlQueryParams) => pageListPath + "?" + stringifyQs(params); diff --git a/src/pages/views/PageList/PageList.tsx b/src/pages/views/PageList/PageList.tsx index f04e6ddda..9a1a6dbb8 100644 --- a/src/pages/views/PageList/PageList.tsx +++ b/src/pages/views/PageList/PageList.tsx @@ -31,7 +31,7 @@ import { PageListUrlQueryParams, pageUrl } from "../../urls"; -import { getSortQueryVariables } from "./sort"; +import { getFilterVariables, getSortQueryVariables } from "./sort"; interface PageListProps { params: PageListUrlQueryParams; @@ -56,6 +56,7 @@ export const PageList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, + filter: getFilterVariables(params), sort: getSortQueryVariables(params) }), [params, settings.rowNumber] @@ -125,6 +126,11 @@ export const PageList: React.FC = ({ params }) => { onUpdateListSettings={updateListSettings} onRowClick={id => () => navigate(pageUrl(id))} onSort={handleSort} + actionDialogOpts={{ + open: openModal, + close: closeModal + }} + params={params} toolbar={ <>