Add tabs and filters to plugins
This commit is contained in:
parent
e4397b1f75
commit
35324951ec
10 changed files with 388 additions and 106 deletions
|
@ -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,10 +64,9 @@ const PluginList: React.FC<PluginListProps> = props => {
|
|||
onPreviousPage
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<ResponsiveTable>
|
||||
<TableHead>
|
||||
<TableCellHeader
|
||||
|
@ -165,7 +163,6 @@ const PluginList: React.FC<PluginListProps> = props => {
|
|||
)}
|
||||
</TableBody>
|
||||
</ResponsiveTable>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
PluginList.displayName = "PluginList";
|
||||
|
|
|
@ -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<PluginListUrlSortField> {
|
||||
FilterPageProps<PluginFilterKeys, PluginListFilterOpts>,
|
||||
SortPage<PluginListUrlSortField>,
|
||||
TabPageProps {
|
||||
plugins: Plugins_plugins_edges_node[];
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
const PluginsListPage: React.FC<PluginsListPageProps> = ({
|
||||
currencySymbol,
|
||||
currentTab,
|
||||
initialSearch,
|
||||
filterOpts,
|
||||
tabs,
|
||||
onAdd,
|
||||
onAll,
|
||||
onBack,
|
||||
onSearchChange,
|
||||
onFilterChange,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
onTabSave,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const filterStructure = createFilterStructure(intl, filterOpts);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.configuration)}
|
||||
</AppHeader>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.plugins)} />
|
||||
<Card>
|
||||
<FilterBar
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
initialSearch={initialSearch}
|
||||
onAll={onAll}
|
||||
onFilterChange={onFilterChange}
|
||||
onSearchChange={onSearchChange}
|
||||
onTabChange={onTabChange}
|
||||
onTabDelete={onTabDelete}
|
||||
onTabSave={onTabSave}
|
||||
tabs={tabs}
|
||||
allTabLabel={intl.formatMessage({
|
||||
defaultMessage: "All Plugins",
|
||||
description: "tab name"
|
||||
})}
|
||||
filterStructure={filterStructure}
|
||||
searchPlaceholder={intl.formatMessage({
|
||||
defaultMessage: "Search Plugins..."
|
||||
})}
|
||||
/>
|
||||
<PluginsList {...listProps} />
|
||||
</Card>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
|
45
src/plugins/components/PluginsListPage/filters.ts
Normal file
45
src/plugins/components/PluginsListPage/filters.ts
Normal file
|
@ -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<boolean>;
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
active: {
|
||||
defaultMessage: "Active",
|
||||
description: "plugin"
|
||||
},
|
||||
deactivated: {
|
||||
defaultMessage: "Inactive",
|
||||
description: "plugin"
|
||||
}
|
||||
});
|
||||
|
||||
export function createFilterStructure(
|
||||
intl: IntlShape,
|
||||
opts: PluginListFilterOpts
|
||||
): IFilter<PluginFilterKeys> {
|
||||
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
|
||||
}
|
||||
];
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export { default } from "./PluginsListPage";
|
||||
export * from "./PluginsListPage";
|
||||
export * from "./filters";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<PluginListUrlFiltersEnum>;
|
||||
export type PluginListUrlDialog = TabActionDialog;
|
||||
export enum PluginListUrlSortField {
|
||||
name = "name",
|
||||
active = "active"
|
||||
}
|
||||
export type PluginListUrlSort = Sort<PluginListUrlSortField>;
|
||||
export type PluginListUrlQueryParams = Pagination &
|
||||
export type PluginListUrlQueryParams = ActiveTab &
|
||||
Dialog<PluginListUrlDialog> &
|
||||
PluginListUrlFilters &
|
||||
Pagination &
|
||||
PluginListUrlSort &
|
||||
SingleAction;
|
||||
export const pluginListUrl = (params?: PluginListUrlQueryParams) =>
|
||||
|
|
|
@ -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<PluginsListProps> = ({ 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<PluginsListProps> = ({ params }) => {
|
|||
const queryVariables = React.useMemo(
|
||||
() => ({
|
||||
...paginationState,
|
||||
filter: getFilterVariables(params),
|
||||
sort: getSortQueryVariables(params)
|
||||
}),
|
||||
[params]
|
||||
|
@ -39,6 +63,50 @@ export const PluginsList: React.FC<PluginsListProps> = ({ 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<PluginsListProps> = ({ params }) => {
|
|||
);
|
||||
|
||||
const handleSort = createSortHandler(navigate, pluginListUrl, params);
|
||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||
|
||||
return (
|
||||
<>
|
||||
<PluginsListPage
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
disabled={loading}
|
||||
filterOpts={getFilterOpts(params)}
|
||||
initialSearch={params.query || ""}
|
||||
settings={settings}
|
||||
plugins={maybe(() => 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))}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabSave}
|
||||
/>
|
||||
<DeleteFilterTabDialog
|
||||
open={params.action === "delete-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabDelete}
|
||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
71
src/plugins/views/PluginList/filters.ts
Normal file
71
src/plugins/views/PluginList/filters.ts
Normal file
|
@ -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<PluginFilterKeys>
|
||||
): 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<PluginListUrlFilters>(PLUGIN_FILTERS_KEY);
|
||||
|
||||
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
|
||||
PluginListUrlQueryParams,
|
||||
PluginListUrlFilters
|
||||
>(PluginListUrlFiltersEnum);
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue