Saleor 1636 add possibility to filter through channels in order view (#929)

* Add possibility to filter through channels in order view

* Update storybook and locale

* Refactor

* Refactor to mapNodetoChoice

* Fix conditional value in array implicit syntax

Co-authored-by: Jakub Majorek <majorek.jakub@gmail.com>
This commit is contained in:
Marek Choiński 2021-01-13 10:50:54 +01:00 committed by GitHub
parent d0be941ade
commit 4a8ebd5b1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 45 deletions

View file

@ -3550,6 +3550,10 @@
"context": "tab name", "context": "tab name",
"string": "All Orders" "string": "All Orders"
}, },
"src_dot_orders_dot_components_dot_OrderListPage_dot_channel": {
"context": "order",
"string": "Channel"
},
"src_dot_orders_dot_components_dot_OrderListPage_dot_customer": { "src_dot_orders_dot_components_dot_OrderListPage_dot_customer": {
"context": "order", "context": "order",
"string": "Customer" "string": "Customer"

View file

@ -3042,6 +3042,7 @@ input OrderFilterInput {
customer: String customer: String
created: DateRangeInput created: DateRangeInput
search: String search: String
channels: [ID]
} }
type OrderFulfill { type OrderFulfill {

View file

@ -1,4 +1,5 @@
import Decorator from "@saleor/storybook/Decorator"; import Decorator from "@saleor/storybook/Decorator";
import { mapNodeToChoice } from "@saleor/utils/maps";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
@ -8,10 +9,7 @@ import ChannelDeleteDialog, {
} from "./ChannelDeleteDialog"; } from "./ChannelDeleteDialog";
const props: ChannelDeleteDialogProps = { const props: ChannelDeleteDialogProps = {
channelsChoices: channelsList.map(channel => ({ channelsChoices: mapNodeToChoice(channelsList),
label: channel.name,
value: channel.id
})),
hasOrders: true, hasOrders: true,
confirmButtonState: "default", confirmButtonState: "default",
onBack: () => undefined, onBack: () => undefined,

View file

@ -1,4 +1,5 @@
import Decorator from "@saleor/storybook/Decorator"; import Decorator from "@saleor/storybook/Decorator";
import { mapNodeToChoice } from "@saleor/utils/maps";
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React from "react"; import React from "react";
@ -7,10 +8,7 @@ import ChannelPickerDialog, {
ChannelPickerDialogProps ChannelPickerDialogProps
} from "./ChannelPickerDialog"; } from "./ChannelPickerDialog";
const channelsChoices = channelsList.map(channel => ({ const channelsChoices = mapNodeToChoice(channelsList);
label: channel.name,
value: channel.id
}));
const props: ChannelPickerDialogProps = { const props: ChannelPickerDialogProps = {
channelsChoices, channelsChoices,

View file

@ -157,6 +157,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
</div> </div>
<Hr /> <Hr />
{filters {filters
.filter(filter => !!filter)
.sort((a, b) => (a.name > b.name ? 1 : -1)) .sort((a, b) => (a.name > b.name ? 1 : -1))
.map(filterField => ( .map(filterField => (
<React.Fragment key={filterField.name}> <React.Fragment key={filterField.name}>

View file

@ -1,4 +1,5 @@
import { IFilter } from "@saleor/components/Filter"; import { IFilter } from "@saleor/components/Filter";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { orderStatusMessages } from "@saleor/misc"; import { orderStatusMessages } from "@saleor/misc";
import { FilterOpts, MinMax } from "@saleor/types"; import { FilterOpts, MinMax } from "@saleor/types";
@ -13,16 +14,22 @@ import { defineMessages, IntlShape } from "react-intl";
export enum OrderFilterKeys { export enum OrderFilterKeys {
created = "created", created = "created",
customer = "customer", customer = "customer",
status = "status" status = "status",
channel = "channel"
} }
export interface OrderListFilterOpts { export interface OrderListFilterOpts {
created: FilterOpts<MinMax>; created: FilterOpts<MinMax>;
customer: FilterOpts<string>; customer: FilterOpts<string>;
status: FilterOpts<OrderStatusFilter[]>; status: FilterOpts<OrderStatusFilter[]>;
channel?: FilterOpts<MultiAutocompleteChoiceType[]>;
} }
const messages = defineMessages({ const messages = defineMessages({
channel: {
defaultMessage: "Channel",
description: "order"
},
customer: { customer: {
defaultMessage: "Customer", defaultMessage: "Customer",
description: "order" description: "order"
@ -88,6 +95,20 @@ export function createFilterStructure(
] ]
), ),
active: opts.status.active active: opts.status.active
} },
...(opts?.channel?.value.length
? [
{
...createOptionsField(
OrderFilterKeys.channel,
intl.formatMessage(messages.channel),
[],
true,
opts.channel.value
),
active: opts.channel.active
}
]
: [])
]; ];
} }

View file

@ -6,6 +6,7 @@ import {
BulkAction, BulkAction,
Dialog, Dialog,
Filters, Filters,
FiltersAsDictWithMultipleValues,
FiltersWithMultipleValues, FiltersWithMultipleValues,
Pagination, Pagination,
SingleAction, SingleAction,
@ -25,11 +26,17 @@ export enum OrderListUrlFiltersEnum {
payment = "payment", payment = "payment",
query = "query" query = "query"
} }
export enum OrderListUrlFiltersWithMultipleValuesEnum { export enum OrderListUrlFiltersWithMultipleValues {
status = "status" status = "status"
} }
export enum OrderListUrlFiltersDictWithMultipleValues {
channel = "channel"
}
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> & export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> &
FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>; FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValues> &
FiltersAsDictWithMultipleValues<OrderListUrlFiltersDictWithMultipleValues>;
export type OrderListUrlDialog = "cancel" | CreateOrderDialog | TabActionDialog; export type OrderListUrlDialog = "cancel" | CreateOrderDialog | TabActionDialog;
export enum OrderListUrlSortField { export enum OrderListUrlSortField {
number = "number", number = "number",

View file

@ -15,6 +15,7 @@ import { ListViews } from "@saleor/types";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createSortHandler from "@saleor/utils/handlers/sortHandler";
import { mapNodeToChoice } from "@saleor/utils/maps";
import { getSortParams } from "@saleor/utils/sort"; import { getSortParams } from "@saleor/utils/sort";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -72,6 +73,9 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
const { channel, availableChannels } = useAppChannel(); const { channel, availableChannels } = useAppChannel();
const noChannel = !channel && typeof channel !== "undefined"; const noChannel = !channel && typeof channel !== "undefined";
const channelOpts = availableChannels
? mapNodeToChoice(availableChannels)
: null;
const tabs = getFilterTabs(); const tabs = getFilterTabs();
@ -132,7 +136,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
}); });
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.orders.pageInfo), data?.orders?.pageInfo,
paginationState, paginationState,
params params
); );
@ -145,7 +149,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
settings={settings} settings={settings}
currentTab={currentTab} currentTab={currentTab}
disabled={loading} disabled={loading}
filterOpts={getFilterOpts(params)} filterOpts={getFilterOpts(params, channelOpts)}
orders={maybe(() => data.orders.edges.map(edge => edge.node))} orders={maybe(() => data.orders.edges.map(edge => edge.node))}
pageInfo={pageInfo} pageInfo={pageInfo}
sort={getSortParams(params)} sort={getSortParams(params)}
@ -180,10 +184,7 @@ export const OrderList: React.FC<OrderListProps> = ({ params }) => {
/> />
{!noChannel && ( {!noChannel && (
<ChannelPickerDialog <ChannelPickerDialog
channelsChoices={availableChannels.map(channel => ({ channelsChoices={mapNodeToChoice(availableChannels)}
label: channel.name,
value: channel.id
}))}
confirmButtonState="success" confirmButtonState="success"
defaultChoice={channel.id} defaultChoice={channel.id}
open={params.action === "create-order"} open={params.action === "create-order"}

View file

@ -2,6 +2,7 @@
exports[`Filtering URL params should not be empty if active filters are present 1`] = ` exports[`Filtering URL params should not be empty if active filters are present 1`] = `
Object { Object {
"channel": Array [],
"createdFrom": "2019-12-09", "createdFrom": "2019-12-09",
"createdTo": "2019-12-38", "createdTo": "2019-12-38",
"customer": "email@example.com", "customer": "email@example.com",

View file

@ -38,6 +38,15 @@ describe("Filtering URL params", () => {
const intl = createIntl(config); const intl = createIntl(config);
const filters = createFilterStructure(intl, { const filters = createFilterStructure(intl, {
channel: {
active: false,
value: [
{
label: "Channel PLN",
value: "channelId"
}
]
},
created: { created: {
active: false, active: false,
value: { value: {

View file

@ -1,4 +1,5 @@
import { findInEnum, findValueInEnum, maybe } from "@saleor/misc"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import { findInEnum, findValueInEnum } from "@saleor/misc";
import { import {
OrderFilterKeys, OrderFilterKeys,
OrderListFilterOpts OrderListFilterOpts
@ -16,48 +17,49 @@ import {
getGteLteVariables, getGteLteVariables,
getMinMaxQueryParam, getMinMaxQueryParam,
getMultipleEnumValueQueryParam, getMultipleEnumValueQueryParam,
getMultipleValueQueryParam,
getSingleValueQueryParam getSingleValueQueryParam
} from "../../../utils/filters"; } from "../../../utils/filters";
import { import {
OrderListUrlFilters, OrderListUrlFilters,
OrderListUrlFiltersDictWithMultipleValues,
OrderListUrlFiltersEnum, OrderListUrlFiltersEnum,
OrderListUrlFiltersWithMultipleValuesEnum, OrderListUrlFiltersWithMultipleValues,
OrderListUrlQueryParams OrderListUrlQueryParams
} from "../../urls"; } from "../../urls";
export const ORDER_FILTERS_KEY = "orderFilters"; export const ORDER_FILTERS_KEY = "orderFilters";
export function getFilterOpts( export function getFilterOpts(
params: OrderListUrlFilters params: OrderListUrlFilters,
channels: MultiAutocompleteChoiceType[]
): OrderListFilterOpts { ): OrderListFilterOpts {
return { return {
channel: channels
? {
active: params?.channel !== undefined,
value: channels
}
: null,
created: { created: {
active: maybe( active: [params?.createdFrom, params?.createdTo].some(
() => field => field !== undefined
[params.createdFrom, params.createdTo].some(
field => field !== undefined
),
false
), ),
value: { value: {
max: maybe(() => params.createdTo, ""), max: params?.createdTo || "",
min: maybe(() => params.createdFrom, "") min: params?.createdFrom || ""
} }
}, },
customer: { customer: {
active: !!maybe(() => params.customer), active: !!params?.customer,
value: params.customer value: params?.customer
}, },
status: { status: {
active: maybe(() => params.status !== undefined, false), active: params?.status !== undefined,
value: maybe( value: dedupeFilter(
() => params?.status?.map(status =>
dedupeFilter( findValueInEnum(status, OrderStatusFilter)
params.status.map(status => )
findValueInEnum(status, OrderStatusFilter)
)
),
[]
) )
} }
}; };
@ -67,15 +69,14 @@ export function getFilterVariables(
params: OrderListUrlFilters params: OrderListUrlFilters
): OrderFilterInput { ): OrderFilterInput {
return { return {
channels: (params.channel as unknown) as string[],
created: getGteLteVariables({ created: getGteLteVariables({
gte: params.createdFrom, gte: params.createdFrom,
lte: params.createdTo lte: params.createdTo
}), }),
customer: params.customer, customer: params.customer,
search: params.query, search: params.query,
status: maybe(() => status: params?.status?.map(status => findInEnum(status, OrderStatusFilter))
params.status.map(status => findInEnum(status, OrderStatusFilter))
)
}; };
} }
@ -95,10 +96,16 @@ export function getFilterQueryParam(
case OrderFilterKeys.status: case OrderFilterKeys.status:
return getMultipleEnumValueQueryParam( return getMultipleEnumValueQueryParam(
filter, filter,
OrderListUrlFiltersWithMultipleValuesEnum.status, OrderListUrlFiltersWithMultipleValues.status,
OrderStatusFilter OrderStatusFilter
); );
case OrderFilterKeys.channel:
return getMultipleValueQueryParam(
filter,
OrderListUrlFiltersDictWithMultipleValues.channel
);
case OrderFilterKeys.customer: case OrderFilterKeys.customer:
return getSingleValueQueryParam(filter, OrderListUrlFiltersEnum.customer); return getSingleValueQueryParam(filter, OrderListUrlFiltersEnum.customer);
} }
@ -115,5 +122,5 @@ export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
OrderListUrlFilters OrderListUrlFilters
>({ >({
...OrderListUrlFiltersEnum, ...OrderListUrlFiltersEnum,
...OrderListUrlFiltersWithMultipleValuesEnum ...OrderListUrlFiltersWithMultipleValues
}); });

View file

@ -21,6 +21,15 @@ const props: OrderListPageProps = {
...filterPageProps, ...filterPageProps,
...sortPageProps, ...sortPageProps,
filterOpts: { filterOpts: {
channel: {
active: false,
value: [
{
label: "Channel PLN",
value: "channelId"
}
]
},
created: { created: {
active: false, active: false,
value: { value: {

View file

@ -1326,6 +1326,7 @@ export interface OrderFilterInput {
customer?: string | null; customer?: string | null;
created?: DateRangeInput | null; created?: DateRangeInput | null;
search?: string | null; search?: string | null;
channels?: (string | null)[] | null;
} }
export interface OrderFulfillInput { export interface OrderFulfillInput {