From 35324951ecbeb15dfcfc06125f1bb4478004e38f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 10 Jan 2020 17:30:42 +0100 Subject: [PATCH] Add tabs and filters to plugins --- .../components/PluginsList/PluginsList.tsx | 195 +++++++++--------- .../PluginsListPage/PluginsListPage.tsx | 56 ++++- .../components/PluginsListPage/filters.ts | 45 ++++ .../components/PluginsListPage/index.ts | 1 + src/plugins/queries.ts | 2 + src/plugins/types/Plugins.ts | 3 +- src/plugins/urls.ts | 21 +- src/plugins/views/PluginList/PluginList.tsx | 95 ++++++++- src/plugins/views/PluginList/filters.ts | 71 +++++++ src/types/globalTypes.ts | 5 + 10 files changed, 388 insertions(+), 106 deletions(-) create mode 100644 src/plugins/components/PluginsListPage/filters.ts create mode 100644 src/plugins/views/PluginList/filters.ts diff --git a/src/plugins/components/PluginsList/PluginsList.tsx b/src/plugins/components/PluginsList/PluginsList.tsx index 9fa315136..568e88910 100644 --- a/src/plugins/components/PluginsList/PluginsList.tsx +++ b/src/plugins/components/PluginsList/PluginsList.tsx @@ -1,4 +1,3 @@ -import Card from "@material-ui/core/Card"; import { makeStyles } from "@material-ui/core/styles"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; @@ -65,107 +64,105 @@ const PluginList: React.FC = props => { onPreviousPage } = props; const classes = useStyles(props); - const intl = useIntl(); + return ( - - - - + + onSort(PluginListUrlSortField.name)} + className={classes.colName} + > + {intl.formatMessage({ + defaultMessage: "Name", + description: "plugin name" + })} + + onSort(PluginListUrlSortField.active)} + className={classes.colActive} + > + {intl.formatMessage({ + defaultMessage: "Active", + description: "plugin status" + })} + + + {intl.formatMessage({ + defaultMessage: "Action", + description: "user action bar" + })} + + + + + onSort(PluginListUrlSortField.name)} - className={classes.colName} - > - {intl.formatMessage({ - defaultMessage: "Name", - description: "plugin name" - })} - - onSort(PluginListUrlSortField.active)} - className={classes.colActive} - > - {intl.formatMessage({ - defaultMessage: "Active", - description: "plugin status" - })} - - - {intl.formatMessage({ - defaultMessage: "Action", - description: "user action bar" - })} - - - - - - - - - {renderCollection( - plugins, - plugin => ( - - - {maybe(() => plugin.name, )} - - - {maybe( - () => ( - - ), - - )} - - -
- -
-
-
- ), - () => ( - - - {intl.formatMessage({ - defaultMessage: "No plugins found" - })} - - - ) - )} -
-
-
+ onPreviousPage={onPreviousPage} + /> + + + + {renderCollection( + plugins, + plugin => ( + + + {maybe(() => plugin.name, )} + + + {maybe( + () => ( + + ), + + )} + + +
+ +
+
+
+ ), + () => ( + + + {intl.formatMessage({ + defaultMessage: "No plugins found" + })} + + + ) + )} +
+ ); }; PluginList.displayName = "PluginList"; diff --git a/src/plugins/components/PluginsListPage/PluginsListPage.tsx b/src/plugins/components/PluginsListPage/PluginsListPage.tsx index efcb3372b..da4ead724 100644 --- a/src/plugins/components/PluginsListPage/PluginsListPage.tsx +++ b/src/plugins/components/PluginsListPage/PluginsListPage.tsx @@ -1,35 +1,85 @@ import React from "react"; +import Card from "@material-ui/core/Card"; import { useIntl } from "react-intl"; import AppHeader from "@saleor/components/AppHeader"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; import { sectionNames } from "@saleor/intl"; -import { PageListProps, SortPage } from "@saleor/types"; +import { + PageListProps, + SortPage, + FilterPageProps, + TabPageProps +} from "@saleor/types"; import { PluginListUrlSortField } from "@saleor/plugins/urls"; +import FilterBar from "@saleor/components/FilterBar"; import { Plugins_plugins_edges_node } from "../../types/Plugins"; import PluginsList from "../PluginsList/PluginsList"; +import { + createFilterStructure, + PluginFilterKeys, + PluginListFilterOpts +} from "./filters"; export interface PluginsListPageProps extends PageListProps, - SortPage { + FilterPageProps, + SortPage, + TabPageProps { plugins: Plugins_plugins_edges_node[]; onBack: () => void; } const PluginsListPage: React.FC = ({ + currencySymbol, + currentTab, + initialSearch, + filterOpts, + tabs, + onAdd, + onAll, onBack, + onSearchChange, + onFilterChange, + onTabChange, + onTabDelete, + onTabSave, ...listProps }) => { const intl = useIntl(); + const filterStructure = createFilterStructure(intl, filterOpts); + return ( {intl.formatMessage(sectionNames.configuration)} - + + + + ); }; diff --git a/src/plugins/components/PluginsListPage/filters.ts b/src/plugins/components/PluginsListPage/filters.ts new file mode 100644 index 000000000..d4d1a384b --- /dev/null +++ b/src/plugins/components/PluginsListPage/filters.ts @@ -0,0 +1,45 @@ +import { defineMessages, IntlShape } from "react-intl"; + +import { FilterOpts } from "@saleor/types"; +import { IFilter } from "@saleor/components/Filter"; +import { createBooleanField } from "@saleor/utils/filters/fields"; +import { commonMessages } from "@saleor/intl"; + +export enum PluginFilterKeys { + active = "active" +} + +export interface PluginListFilterOpts { + isActive: FilterOpts; +} + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "plugin" + }, + deactivated: { + defaultMessage: "Inactive", + description: "plugin" + } +}); + +export function createFilterStructure( + intl: IntlShape, + opts: PluginListFilterOpts +): IFilter { + return [ + { + ...createBooleanField( + PluginFilterKeys.active, + intl.formatMessage(commonMessages.status), + opts.isActive.value, + { + negative: intl.formatMessage(messages.deactivated), + positive: intl.formatMessage(messages.active) + } + ), + active: opts.isActive.active + } + ]; +} diff --git a/src/plugins/components/PluginsListPage/index.ts b/src/plugins/components/PluginsListPage/index.ts index 37ed6f973..a378b1ac7 100644 --- a/src/plugins/components/PluginsListPage/index.ts +++ b/src/plugins/components/PluginsListPage/index.ts @@ -1,2 +1,3 @@ export { default } from "./PluginsListPage"; export * from "./PluginsListPage"; +export * from "./filters"; diff --git a/src/plugins/queries.ts b/src/plugins/queries.ts index dc518f6a5..c3003fa81 100644 --- a/src/plugins/queries.ts +++ b/src/plugins/queries.ts @@ -35,6 +35,7 @@ const pluginsList = gql` $after: String $last: Int $before: String + $filter: PluginFilterInput $sort: PluginSortingInput ) { plugins( @@ -42,6 +43,7 @@ const pluginsList = gql` after: $after first: $first last: $last + filter: $filter sortBy: $sort ) { edges { diff --git a/src/plugins/types/Plugins.ts b/src/plugins/types/Plugins.ts index a8d888402..59b578762 100644 --- a/src/plugins/types/Plugins.ts +++ b/src/plugins/types/Plugins.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. -import { PluginSortingInput } from "./../../types/globalTypes"; +import { PluginFilterInput, PluginSortingInput } from "./../../types/globalTypes"; // ==================================================== // GraphQL query operation: Plugins @@ -44,5 +44,6 @@ export interface PluginsVariables { after?: string | null; last?: number | null; before?: string | null; + filter?: PluginFilterInput | null; sort?: PluginSortingInput | null; } diff --git a/src/plugins/urls.ts b/src/plugins/urls.ts index 11a6b15f8..ab0faca5f 100644 --- a/src/plugins/urls.ts +++ b/src/plugins/urls.ts @@ -1,17 +1,34 @@ import { stringify as stringifyQs } from "qs"; import urlJoin from "url-join"; -import { Dialog, Pagination, SingleAction, Sort } from "../types"; +import { + Dialog, + Pagination, + SingleAction, + Sort, + Filters, + ActiveTab, + TabActionDialog +} from "../types"; export const pluginSection = "/plugins/"; export const pluginListPath = pluginSection; +export enum PluginListUrlFiltersEnum { + active = "active", + query = "query" +} +export type PluginListUrlFilters = Filters; +export type PluginListUrlDialog = TabActionDialog; export enum PluginListUrlSortField { name = "name", active = "active" } export type PluginListUrlSort = Sort; -export type PluginListUrlQueryParams = Pagination & +export type PluginListUrlQueryParams = ActiveTab & + Dialog & + PluginListUrlFilters & + Pagination & PluginListUrlSort & SingleAction; export const pluginListUrl = (params?: PluginListUrlQueryParams) => diff --git a/src/plugins/views/PluginList/PluginList.tsx b/src/plugins/views/PluginList/PluginList.tsx index 60482660a..e46efa943 100644 --- a/src/plugins/views/PluginList/PluginList.tsx +++ b/src/plugins/views/PluginList/PluginList.tsx @@ -10,10 +10,32 @@ import React from "react"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; +import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import useShop from "@saleor/hooks/useShop"; +import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import SaveFilterTabDialog, { + SaveFilterTabDialogFormData +} from "@saleor/components/SaveFilterTabDialog"; +import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import PluginsListPage from "../../components/PluginsListPage/PluginsListPage"; import { usePluginsListQuery } from "../../queries"; -import { PluginListUrlQueryParams, pluginListUrl, pluginUrl } from "../../urls"; +import { + PluginListUrlQueryParams, + pluginListUrl, + pluginUrl, + PluginListUrlDialog +} from "../../urls"; import { getSortQueryVariables } from "./sort"; +import { + getFilterQueryParam, + getFilterOpts, + getFilterTabs, + areFiltersApplied, + saveFilterTab, + getActiveFilters, + deleteFilterTab, + getFilterVariables +} from "./filters"; interface PluginsListProps { params: PluginListUrlQueryParams; @@ -22,6 +44,7 @@ interface PluginsListProps { export const PluginsList: React.FC = ({ params }) => { const navigate = useNavigator(); const paginate = usePaginator(); + const shop = useShop(); const { updateListSettings, settings } = useListSettings( ListViews.PLUGINS_LIST ); @@ -30,6 +53,7 @@ export const PluginsList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, + filter: getFilterVariables(params), sort: getSortQueryVariables(params) }), [params] @@ -39,6 +63,50 @@ export const PluginsList: React.FC = ({ params }) => { variables: queryVariables }); + const tabs = getFilterTabs(); + + const currentTab = + params.activeTab === undefined + ? areFiltersApplied(params) + ? tabs.length + 1 + : 0 + : parseInt(params.activeTab, 0); + + const [ + changeFilters, + resetFilters, + handleSearchChange + ] = createFilterHandlers({ + createUrl: pluginListUrl, + getFilterQueryParam, + navigate, + params + }); + + const [openModal, closeModal] = createDialogActionHandlers< + PluginListUrlDialog, + PluginListUrlQueryParams + >(navigate, pluginListUrl, params); + + const handleTabChange = (tab: number) => { + navigate( + pluginListUrl({ + activeTab: tab.toString(), + ...getFilterTabs()[tab - 1].data + }) + ); + }; + + const handleFilterTabDelete = () => { + deleteFilterTab(currentTab); + navigate(pluginListUrl()); + }; + + const handleFilterTabSave = (data: SaveFilterTabDialogFormData) => { + saveFilterTab(data.name, getActiveFilters(params)); + handleTabChange(tabs.length + 1); + }; + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.plugins.pageInfo), paginationState, @@ -46,23 +114,48 @@ export const PluginsList: React.FC = ({ params }) => { ); const handleSort = createSortHandler(navigate, pluginListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( <> data.plugins.edges.map(edge => edge.node))} pageInfo={pageInfo} sort={getSortParams(params)} + tabs={getFilterTabs().map(tab => tab.name)} onAdd={() => navigate(configurationMenuUrl)} + onAll={resetFilters} onBack={() => navigate(configurationMenuUrl)} + onFilterChange={changeFilters} + onSearchChange={handleSearchChange} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} onSort={handleSort} + onTabSave={() => openModal("save-search")} + onTabDelete={() => openModal("delete-search")} + onTabChange={handleTabChange} onUpdateListSettings={updateListSettings} onRowClick={id => () => navigate(pluginUrl(id))} /> + + tabs[currentTab - 1].name, "...")} + /> ); }; diff --git a/src/plugins/views/PluginList/filters.ts b/src/plugins/views/PluginList/filters.ts new file mode 100644 index 000000000..0a995fffa --- /dev/null +++ b/src/plugins/views/PluginList/filters.ts @@ -0,0 +1,71 @@ +import { PluginFilterInput } from "@saleor/types/globalTypes"; +import { + PluginListFilterOpts, + PluginFilterKeys +} from "@saleor/plugins/components/PluginsListPage"; +import { maybe, parseBoolean } from "@saleor/misc"; +import { IFilterElement } from "@saleor/components/Filter"; +import { + PluginListUrlFilters, + PluginListUrlFiltersEnum, + PluginListUrlQueryParams +} from "../../urls"; +import { + createFilterTabUtils, + createFilterUtils +} from "../../../utils/filters"; + +export const PLUGIN_FILTERS_KEY = "pluginFilters"; + +export function getFilterOpts( + params: PluginListUrlFilters +): PluginListFilterOpts { + return { + isActive: { + active: maybe(() => params.active !== undefined, false), + value: + params.active !== undefined ? parseBoolean(params.active, true) : true + } + }; +} + +export function getFilterVariables( + params: PluginListUrlFilters +): PluginFilterInput { + return { + active: + params.active !== undefined + ? parseBoolean(params.active, true) + : undefined, + search: params.query + }; +} + +export function getFilterQueryParam( + filter: IFilterElement +): PluginListUrlFilters { + const { active, name, value } = filter; + + switch (name) { + case PluginFilterKeys.active: + if (!active) { + return { + active: undefined + }; + } + return { + active: value[0] + }; + } +} + +export const { + deleteFilterTab, + getFilterTabs, + saveFilterTab +} = createFilterTabUtils(PLUGIN_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + PluginListUrlQueryParams, + PluginListUrlFilters +>(PluginListUrlFiltersEnum); diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 136badcd5..bc8d960d2 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -986,6 +986,11 @@ export interface PageTranslationInput { contentJson?: any | null; } +export interface PluginFilterInput { + active?: boolean | null; + search?: string | null; +} + export interface PluginSortingInput { direction: OrderDirection; field: PluginSortField;