Add search to attribute list
This commit is contained in:
parent
249647cafa
commit
dde0ec06d2
8 changed files with 191 additions and 10 deletions
|
@ -4,19 +4,37 @@ import Card from "@material-ui/core/Card";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import SearchBar from "@saleor/components/SearchBar";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import Container from "../../../components/Container";
|
import Container from "../../../components/Container";
|
||||||
import PageHeader from "../../../components/PageHeader";
|
import PageHeader from "../../../components/PageHeader";
|
||||||
import { ListActions, PageListProps } from "../../../types";
|
import {
|
||||||
|
ListActions,
|
||||||
|
PageListProps,
|
||||||
|
SearchPageProps,
|
||||||
|
TabPageProps
|
||||||
|
} from "../../../types";
|
||||||
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
|
import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
|
||||||
import AttributeList from "../AttributeList/AttributeList";
|
import AttributeList from "../AttributeList/AttributeList";
|
||||||
|
|
||||||
export interface AttributeListPageProps extends PageListProps, ListActions {
|
export interface AttributeListPageProps
|
||||||
|
extends PageListProps,
|
||||||
|
ListActions,
|
||||||
|
SearchPageProps,
|
||||||
|
TabPageProps {
|
||||||
attributes: AttributeList_attributes_edges_node[];
|
attributes: AttributeList_attributes_edges_node[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
onAdd,
|
onAdd,
|
||||||
|
initialSearch,
|
||||||
|
onSearchChange,
|
||||||
|
currentTab,
|
||||||
|
onAll,
|
||||||
|
onTabChange,
|
||||||
|
onTabDelete,
|
||||||
|
onTabSave,
|
||||||
|
tabs,
|
||||||
...listProps
|
...listProps
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
@ -32,6 +50,19 @@ const AttributeListPage: React.FC<AttributeListPageProps> = ({
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Card>
|
<Card>
|
||||||
|
<SearchBar
|
||||||
|
currentTab={currentTab}
|
||||||
|
initialSearch={initialSearch}
|
||||||
|
searchPlaceholder={intl.formatMessage({
|
||||||
|
defaultMessage: "Search Attribute"
|
||||||
|
})}
|
||||||
|
tabs={tabs}
|
||||||
|
onAll={onAll}
|
||||||
|
onSearchChange={onSearchChange}
|
||||||
|
onTabChange={onTabChange}
|
||||||
|
onTabDelete={onTabDelete}
|
||||||
|
onTabSave={onTabSave}
|
||||||
|
/>
|
||||||
<AttributeList {...listProps} />
|
<AttributeList {...listProps} />
|
||||||
</Card>
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -52,7 +52,7 @@ const attributeList = gql`
|
||||||
${attributeFragment}
|
${attributeFragment}
|
||||||
${pageInfoFragment}
|
${pageInfoFragment}
|
||||||
query AttributeList(
|
query AttributeList(
|
||||||
$query: String
|
$filter: AttributeFilterInput
|
||||||
$inCategory: ID
|
$inCategory: ID
|
||||||
$inCollection: ID
|
$inCollection: ID
|
||||||
$before: String
|
$before: String
|
||||||
|
@ -61,7 +61,7 @@ const attributeList = gql`
|
||||||
$last: Int
|
$last: Int
|
||||||
) {
|
) {
|
||||||
attributes(
|
attributes(
|
||||||
query: $query
|
filter: $filter
|
||||||
inCategory: $inCategory
|
inCategory: $inCategory
|
||||||
inCollection: $inCollection
|
inCollection: $inCollection
|
||||||
before: $before
|
before: $before
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { AttributeFilterInput } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL query operation: AttributeList
|
// GraphQL query operation: AttributeList
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
@ -40,7 +42,7 @@ export interface AttributeList {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeListVariables {
|
export interface AttributeListVariables {
|
||||||
query?: string | null;
|
filter?: AttributeFilterInput | null;
|
||||||
inCategory?: string | null;
|
inCategory?: string | null;
|
||||||
inCollection?: string | null;
|
inCollection?: string | null;
|
||||||
before?: string | null;
|
before?: string | null;
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
import { stringify as stringifyQs } from "qs";
|
import { stringify as stringifyQs } from "qs";
|
||||||
import urlJoin from "url-join";
|
import urlJoin from "url-join";
|
||||||
|
|
||||||
import { BulkAction, Dialog, Pagination, SingleAction } from "../types";
|
import {
|
||||||
|
ActiveTab,
|
||||||
|
BulkAction,
|
||||||
|
Dialog,
|
||||||
|
Filters,
|
||||||
|
Pagination,
|
||||||
|
SingleAction,
|
||||||
|
TabActionDialog
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
export const attributeSection = "/attributes/";
|
export const attributeSection = "/attributes/";
|
||||||
|
|
||||||
export type AttributeListUrlDialog = "remove";
|
export enum AttributeListUrlFiltersEnum {
|
||||||
export type AttributeListUrlQueryParams = BulkAction &
|
query = "query"
|
||||||
|
}
|
||||||
|
export type AttributeListUrlFilters = Filters<AttributeListUrlFiltersEnum>;
|
||||||
|
export type AttributeListUrlDialog = "remove" | TabActionDialog;
|
||||||
|
export type AttributeListUrlQueryParams = ActiveTab &
|
||||||
|
AttributeListUrlFilters &
|
||||||
|
BulkAction &
|
||||||
Dialog<AttributeListUrlDialog> &
|
Dialog<AttributeListUrlDialog> &
|
||||||
Pagination;
|
Pagination;
|
||||||
export const attributeListPath = attributeSection;
|
export const attributeListPath = attributeSection;
|
||||||
|
|
|
@ -3,6 +3,18 @@ import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import {
|
||||||
|
areFiltersApplied,
|
||||||
|
deleteFilterTab,
|
||||||
|
getActiveFilters,
|
||||||
|
getFilterTabs,
|
||||||
|
saveFilterTab,
|
||||||
|
getFilterVariables
|
||||||
|
} from "@saleor/attributes/views/AttributeList/filters";
|
||||||
|
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||||
|
import SaveFilterTabDialog, {
|
||||||
|
SaveFilterTabDialogFormData
|
||||||
|
} from "@saleor/components/SaveFilterTabDialog";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import usePaginator, {
|
import usePaginator, {
|
||||||
|
@ -20,6 +32,7 @@ import {
|
||||||
attributeAddUrl,
|
attributeAddUrl,
|
||||||
attributeListUrl,
|
attributeListUrl,
|
||||||
AttributeListUrlDialog,
|
AttributeListUrlDialog,
|
||||||
|
AttributeListUrlFilters,
|
||||||
AttributeListUrlQueryParams,
|
AttributeListUrlQueryParams,
|
||||||
attributeUrl
|
attributeUrl
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
|
@ -37,6 +50,15 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
);
|
);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const tabs = getFilterTabs();
|
||||||
|
|
||||||
|
const currentTab =
|
||||||
|
params.activeTab === undefined
|
||||||
|
? areFiltersApplied(params)
|
||||||
|
? tabs.length + 1
|
||||||
|
: 0
|
||||||
|
: parseInt(params.activeTab, 0);
|
||||||
|
|
||||||
const closeModal = () =>
|
const closeModal = () =>
|
||||||
navigate(
|
navigate(
|
||||||
attributeListUrl({
|
attributeListUrl({
|
||||||
|
@ -56,8 +78,46 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const changeFilterField = (filter: AttributeListUrlFilters) => {
|
||||||
|
reset();
|
||||||
|
navigate(
|
||||||
|
attributeListUrl({
|
||||||
|
...getActiveFilters(params),
|
||||||
|
...filter,
|
||||||
|
activeTab: undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabChange = (tab: number) => {
|
||||||
|
reset();
|
||||||
|
navigate(
|
||||||
|
attributeListUrl({
|
||||||
|
activeTab: tab.toString(),
|
||||||
|
...getFilterTabs()[tab - 1].data
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabDelete = () => {
|
||||||
|
deleteFilterTab(currentTab);
|
||||||
|
reset();
|
||||||
|
navigate(attributeListUrl());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
|
||||||
|
saveFilterTab(data.name, getActiveFilters(params));
|
||||||
|
handleTabChange(tabs.length + 1);
|
||||||
|
};
|
||||||
|
|
||||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||||
const queryVariables = React.useMemo(() => paginationState, [params]);
|
const queryVariables = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
...paginationState,
|
||||||
|
filter: getFilterVariables(params)
|
||||||
|
}),
|
||||||
|
[params]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AttributeListQuery variables={queryVariables}>
|
<AttributeListQuery variables={queryVariables}>
|
||||||
|
@ -99,14 +159,22 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
attributes={maybe(() =>
|
attributes={maybe(() =>
|
||||||
data.attributes.edges.map(edge => edge.node)
|
data.attributes.edges.map(edge => edge.node)
|
||||||
)}
|
)}
|
||||||
|
currentTab={currentTab}
|
||||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||||
|
initialSearch={params.query || ""}
|
||||||
isChecked={isSelected}
|
isChecked={isSelected}
|
||||||
onAdd={() => navigate(attributeAddUrl())}
|
onAdd={() => navigate(attributeAddUrl())}
|
||||||
|
onAll={() => navigate(attributeListUrl())}
|
||||||
onNextPage={loadNextPage}
|
onNextPage={loadNextPage}
|
||||||
onPreviousPage={loadPreviousPage}
|
onPreviousPage={loadPreviousPage}
|
||||||
onRowClick={id => () => navigate(attributeUrl(id))}
|
onRowClick={id => () => navigate(attributeUrl(id))}
|
||||||
|
onSearchChange={query => changeFilterField({ query })}
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
onTabDelete={() => openModal("delete-search")}
|
||||||
|
onTabSave={() => openModal("save-search")}
|
||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
selected={listElements.length}
|
selected={listElements.length}
|
||||||
|
tabs={tabs.map(tab => tab.name)}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
toolbar={
|
toolbar={
|
||||||
|
@ -130,6 +198,19 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
quantity={maybe(() => params.ids.length)}
|
quantity={maybe(() => params.ids.length)}
|
||||||
/>
|
/>
|
||||||
|
<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={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
31
src/attributes/views/AttributeList/filters.ts
Normal file
31
src/attributes/views/AttributeList/filters.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { AttributeFilterInput } from "@saleor/types/globalTypes";
|
||||||
|
import {
|
||||||
|
createFilterTabUtils,
|
||||||
|
createFilterUtils
|
||||||
|
} from "../../../utils/filters";
|
||||||
|
import {
|
||||||
|
AttributeListUrlFilters,
|
||||||
|
AttributeListUrlFiltersEnum,
|
||||||
|
AttributeListUrlQueryParams
|
||||||
|
} from "../../urls";
|
||||||
|
|
||||||
|
export const PRODUCT_FILTERS_KEY = "productFilters";
|
||||||
|
|
||||||
|
export function getFilterVariables(
|
||||||
|
params: AttributeListUrlFilters
|
||||||
|
): AttributeFilterInput {
|
||||||
|
return {
|
||||||
|
search: params.query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const {
|
||||||
|
deleteFilterTab,
|
||||||
|
getFilterTabs,
|
||||||
|
saveFilterTab
|
||||||
|
} = createFilterTabUtils<AttributeListUrlFilters>(PRODUCT_FILTERS_KEY);
|
||||||
|
|
||||||
|
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
|
||||||
|
AttributeListUrlQueryParams,
|
||||||
|
AttributeListUrlFilters
|
||||||
|
>(AttributeListUrlFiltersEnum);
|
|
@ -5,12 +5,19 @@ import AttributeListPage, {
|
||||||
AttributeListPageProps
|
AttributeListPageProps
|
||||||
} from "@saleor/attributes/components/AttributeListPage";
|
} from "@saleor/attributes/components/AttributeListPage";
|
||||||
import { attributes } from "@saleor/attributes/fixtures";
|
import { attributes } from "@saleor/attributes/fixtures";
|
||||||
import { listActionsProps, pageListProps } from "@saleor/fixtures";
|
import {
|
||||||
|
listActionsProps,
|
||||||
|
pageListProps,
|
||||||
|
searchPageProps,
|
||||||
|
tabPageProps
|
||||||
|
} from "@saleor/fixtures";
|
||||||
import Decorator from "../../Decorator";
|
import Decorator from "../../Decorator";
|
||||||
|
|
||||||
const props: AttributeListPageProps = {
|
const props: AttributeListPageProps = {
|
||||||
...pageListProps.default,
|
...pageListProps.default,
|
||||||
...listActionsProps,
|
...listActionsProps,
|
||||||
|
...tabPageProps,
|
||||||
|
...searchPageProps,
|
||||||
attributes
|
attributes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,7 @@ export enum PermissionEnum {
|
||||||
MANAGE_PAGES = "MANAGE_PAGES",
|
MANAGE_PAGES = "MANAGE_PAGES",
|
||||||
MANAGE_PLUGINS = "MANAGE_PLUGINS",
|
MANAGE_PLUGINS = "MANAGE_PLUGINS",
|
||||||
MANAGE_PRODUCTS = "MANAGE_PRODUCTS",
|
MANAGE_PRODUCTS = "MANAGE_PRODUCTS",
|
||||||
|
MANAGE_SERVICE_ACCOUNTS = "MANAGE_SERVICE_ACCOUNTS",
|
||||||
MANAGE_SETTINGS = "MANAGE_SETTINGS",
|
MANAGE_SETTINGS = "MANAGE_SETTINGS",
|
||||||
MANAGE_SHIPPING = "MANAGE_SHIPPING",
|
MANAGE_SHIPPING = "MANAGE_SHIPPING",
|
||||||
MANAGE_STAFF = "MANAGE_STAFF",
|
MANAGE_STAFF = "MANAGE_STAFF",
|
||||||
|
@ -264,6 +265,17 @@ export interface AttributeCreateInput {
|
||||||
availableInGrid?: boolean | null;
|
availableInGrid?: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AttributeFilterInput {
|
||||||
|
valueRequired?: boolean | null;
|
||||||
|
isVariantOnly?: boolean | null;
|
||||||
|
visibleInStorefront?: boolean | null;
|
||||||
|
filterableInStorefront?: boolean | null;
|
||||||
|
filterableInDashboard?: boolean | null;
|
||||||
|
availableInGrid?: boolean | null;
|
||||||
|
search?: string | null;
|
||||||
|
ids?: (string | null)[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeInput {
|
export interface AttributeInput {
|
||||||
slug: string;
|
slug: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -431,6 +443,7 @@ export interface OrderFilterInput {
|
||||||
status?: (OrderStatusFilter | null)[] | null;
|
status?: (OrderStatusFilter | null)[] | null;
|
||||||
customer?: string | null;
|
customer?: string | null;
|
||||||
created?: DateRangeInput | null;
|
created?: DateRangeInput | null;
|
||||||
|
search?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderLineCreateInput {
|
export interface OrderLineCreateInput {
|
||||||
|
@ -577,6 +590,7 @@ export interface StaffCreateInput {
|
||||||
note?: string | null;
|
note?: string | null;
|
||||||
permissions?: (PermissionEnum | null)[] | null;
|
permissions?: (PermissionEnum | null)[] | null;
|
||||||
sendPasswordEmail?: boolean | null;
|
sendPasswordEmail?: boolean | null;
|
||||||
|
redirectUrl?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StaffInput {
|
export interface StaffInput {
|
||||||
|
@ -605,6 +619,7 @@ export interface UserCreateInput {
|
||||||
isActive?: boolean | null;
|
isActive?: boolean | null;
|
||||||
note?: string | null;
|
note?: string | null;
|
||||||
sendPasswordEmail?: boolean | null;
|
sendPasswordEmail?: boolean | null;
|
||||||
|
redirectUrl?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VoucherInput {
|
export interface VoucherInput {
|
||||||
|
|
Loading…
Reference in a new issue