Add search and filters to Pages list page (#1901)

* Uppdate queries

* Add page list search and filters

* Update types

* Update tests

* Update messages

* Change pageType -> pageTypes

* Updae types for page list

* run lint
This commit is contained in:
Wojciech Mista 2022-03-22 09:53:31 +01:00 committed by GitHub
parent d35ca150dc
commit 5059557987
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1351 additions and 779 deletions

View file

@ -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"

View file

@ -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<PageListUrlSortField> {
pages: PageFragment[];
params: PageListUrlQueryParams;
actionDialogOpts: PageListActionDialogOpts;
}
const PageListPage: React.FC<PageListPageProps> = ({ onAdd, ...listProps }) => {
const PageListPage: React.FC<PageListPageProps> = ({
onAdd,
params,
actionDialogOpts,
...listProps
}) => {
const intl = useIntl();
return (
@ -27,7 +44,13 @@ const PageListPage: React.FC<PageListPageProps> = ({ onAdd, ...listProps }) => {
<FormattedMessage defaultMessage="Create page" description="button" />
</Button>
</PageHeader>
<PageList {...listProps} />
<Card>
<PageListSearchAndFilters
params={params}
actionDialogOpts={actionDialogOpts}
/>
<PageList {...listProps} />
</Card>
</Container>
);
};

View file

@ -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<PageListSearchAndFiltersProps> = ({
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 (
<>
<FilterBar
filterStructure={filterStrucutre}
initialSearch={""}
onAll={resetFilters}
onFilterChange={changeFilters}
onSearchChange={handleSearchChange}
searchPlaceholder={intl.formatMessage(messages.searchPlaceholder)}
allTabLabel={"All Pages"}
tabs={tabs.map(({ name }) => name)}
currentTab={currentTab}
onTabDelete={handleTabDelete}
onTabChange={handleTabChange}
onTabSave={() => openModal("save-search")}
/>
<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={tabs[currentTab - 1]?.name ?? "..."}
/>
</>
);
};
export default PageListSearchAndFilters;

View file

@ -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<string[]> & AutocompleteFilterOpts;
}
const messages = defineMessages({
pageType: {
defaultMessage: "Page Types",
description: "Types"
}
});
interface PageListFilterOptsProps {
params: PageListUrlFilters;
pageTypes: Array<SearchPageTypesQuery["search"]["edges"][0]["node"]>;
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<PageListFilterKeys> {
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<PageListFilterKeys>
): 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<PageListUrlFilters>(PAGES_FILTERS_KEY);
export const {
areFiltersApplied,
getActiveFilters,
getFiltersCurrentTab
} = createFilterUtils<PageListUrlQueryParams, PageListUrlFilters>(
PageListUrlFiltersWithMultipleValues
);

View file

@ -0,0 +1,8 @@
import { defineMessages } from "react-intl";
export const pagesListSearchAndFiltersMessages = defineMessages({
searchPlaceholder: {
defaultMessage: "Search Pages",
description: "search pages placeholder"
}
});

View file

@ -18,7 +18,7 @@ import PageCreateComponent from "./views/PageCreate";
import PageDetailsComponent from "./views/PageDetails";
import PageListComponent from "./views/PageList";
const PageList: React.FC<RouteComponentProps<any>> = ({ location }) => {
const PageList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
const qs = parseQs(location.search.substr(1));
const params: PageListUrlQueryParams = asSortParams(
qs,

View file

@ -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 {

View file

@ -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<PageListUrlFiltersEnum> &
FiltersWithMultipleValues<PageListUrlFiltersWithMultipleValues>;
export type PageListUrlSort = Sort<PageListUrlSortField>;
export type PageListUrlQueryParams = BulkAction &
PageListUrlFilters &
Dialog<PageListUrlDialog> &
PageListUrlSort &
Pagination;
Pagination &
ActiveTab;
export const pageListUrl = (params?: PageListUrlQueryParams) =>
pageListPath + "?" + stringifyQs(params);

View file

@ -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<PageListProps> = ({ params }) => {
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params),
sort: getSortQueryVariables(params)
}),
[params, settings.rowNumber]
@ -125,6 +126,11 @@ export const PageList: React.FC<PageListProps> = ({ params }) => {
onUpdateListSettings={updateListSettings}
onRowClick={id => () => navigate(pageUrl(id))}
onSort={handleSort}
actionDialogOpts={{
open: openModal,
close: closeModal
}}
params={params}
toolbar={
<>
<Button

View file

@ -1,5 +1,5 @@
import { PageSortField } from "@saleor/graphql";
import { PageListUrlSortField } from "@saleor/pages/urls";
import { PageFilterInput, PageSortField } from "@saleor/graphql";
import { PageListUrlFilters, PageListUrlSortField } from "@saleor/pages/urls";
import { createGetSortQueryVariables } from "@saleor/utils/sort";
export function getSortQueryField(sort: PageListUrlSortField): PageSortField {
@ -15,6 +15,15 @@ export function getSortQueryField(sort: PageListUrlSortField): PageSortField {
}
}
export function getFilterVariables(
params: PageListUrlFilters
): PageFilterInput {
return {
search: params.query,
pageTypes: params.pageTypes
};
}
export const getSortQueryVariables = createGetSortQueryVariables(
getSortQueryField
);

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,13 @@ const props: PageListPageProps = {
sort: {
...sortPageProps.sort,
sort: PageListUrlSortField.title
},
actionDialogOpts: {
open: () => undefined,
close: () => undefined
},
params: {
ids: []
}
};