diff --git a/.changeset/silver-items-unite.md b/.changeset/silver-items-unite.md new file mode 100644 index 00000000000..cf2c90d891f --- /dev/null +++ b/.changeset/silver-items-unite.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +List of draft orders now use new filters diff --git a/.featureFlags/draft_orders_filters.md b/.featureFlags/draft_orders_filters.md new file mode 100644 index 00000000000..55b7ef0183c --- /dev/null +++ b/.featureFlags/draft_orders_filters.md @@ -0,0 +1,11 @@ +--- +name: draft_orders_filters +displayName: Draft orders filtering +enabled: true +payload: "default" +visible: true +--- + +![new filters](./images/draft-orders-filters.png) +Experience the new look and enhanced abilities of new fitering mechanism. +Easily combine any criteria you want, and quickly browse their values. \ No newline at end of file diff --git a/.featureFlags/generated.tsx b/.featureFlags/generated.tsx index bb13a6888d6..d8b3972b051 100644 --- a/.featureFlags/generated.tsx +++ b/.featureFlags/generated.tsx @@ -1,15 +1,20 @@ // @ts-nocheck -import L78751 from "./images/discounts-list.png" -import H47779 from "./images/improved_refunds.png" -import D04851 from "./images/page-filters.png" -import X74083 from "./images/vouchers-filters.png" +import V92679 from "./images/discounts-list.png" +import Z34507 from "./images/draft-orders-filters.png" +import R14220 from "./images/improved_refunds.png" +import T12026 from "./images/page-filters.png" +import Q15387 from "./images/vouchers-filters.png" -const discounts_rules = () => (<>

Discount rules

+const discounts_rules = () => (<>

Discount rules

Apply the new discounts rules to narrow your promotions audience. Set up conditions and channels that must be fulfilled to apply defined reward.

) -const improved_refunds = () => (<>

Improved refunds

+const draft_orders_filters = () => (<>

new filters +Experience the new look and enhanced abilities of new fitering mechanism. +Easily combine any criteria you want, and quickly browse their values.

+) +const improved_refunds = () => (<>

Improved refunds

Enable the enhanced refund feature to streamline your refund process:

) -const pages_filters = () => (<>

new filters +const pages_filters = () => (<>

new filters Experience the new look and enhanced abilities of new fitering mechanism. Easily combine any criteria you want, and quickly browse their values.

) -const vouchers_filters = () => (<>

new filters +const vouchers_filters = () => (<>

new filters Experience the new look and enhanced abilities of new fitering mechanism. Easily combine any criteria you want, and quickly browse their values.

) @@ -37,6 +42,15 @@ export const AVAILABLE_FLAGS = [{ enabled: true, payload: "default", } +},{ + name: "draft_orders_filters", + displayName: "Draft orders filtering", + component: draft_orders_filters, + visible: true, + content: { + enabled: true, + payload: "default", + } },{ name: "improved_refunds", displayName: "Improved refunds", diff --git a/.featureFlags/images/draft-orders-filters.png b/.featureFlags/images/draft-orders-filters.png new file mode 100644 index 00000000000..05fd7a412e0 Binary files /dev/null and b/.featureFlags/images/draft-orders-filters.png differ diff --git a/src/components/ConditionalFilter/API/DraftOrderFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/DraftOrderFilterAPIProvider.tsx new file mode 100644 index 00000000000..70d14c9d653 --- /dev/null +++ b/src/components/ConditionalFilter/API/DraftOrderFilterAPIProvider.tsx @@ -0,0 +1,15 @@ +import { FilterAPIProvider } from "@dashboard/components/ConditionalFilter/API/FilterAPIProvider"; + +export const useDraftOrderFilterAPIProvider = (): FilterAPIProvider => { + const fetchRightOptions = async () => { + return []; + }; + const fetchLeftOptions = async () => { + return []; + }; + + return { + fetchRightOptions, + fetchLeftOptions, + }; +}; diff --git a/src/components/ConditionalFilter/API/InitialStateResponse.ts b/src/components/ConditionalFilter/API/InitialStateResponse.ts index 6597c779e89..b9fe30f96bb 100644 --- a/src/components/ConditionalFilter/API/InitialStateResponse.ts +++ b/src/components/ConditionalFilter/API/InitialStateResponse.ts @@ -31,6 +31,9 @@ export interface InitialState { giftCard: ItemOption[]; } +const isDateField = (name: string) => + ["created", "updatedAt", "startDate", "endDate"].includes(name); + export class InitialStateResponse implements InitialState { constructor( public category: ItemOption[] = [], @@ -58,6 +61,10 @@ export class InitialStateResponse implements InitialState { return this.attribute[token.name].choices.filter(({ value }) => token.value.includes(value)); } + if (isDateField(token.name)) { + return token.value; + } + if (token.isAttribute()) { const attr = this.attribute[token.name]; diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts index aab7ae06781..8caea170a26 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts @@ -133,7 +133,9 @@ export const toPageFetchingParams = (p: PageFetchingParams, c: UrlToken) => { return p; }; -export const getFetchingPrams = (type: "product" | "order" | "discount" | "voucher" | "page") => { +export const getFetchingPrams = ( + type: "product" | "order" | "discount" | "voucher" | "page" | "draft-order", +) => { switch (type) { case "product": return emptyFetchingParams; diff --git a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts index 6bb03820234..6c97536004a 100644 --- a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts +++ b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts @@ -21,7 +21,7 @@ import { prepareStructure } from "./utils"; export const useUrlValueProvider = ( locationSearch: string, - type: "product" | "order" | "discount" | "voucher" | "page", + type: "product" | "order" | "discount" | "voucher" | "page" | "draft-order", initialState?: | InitialAPIState | InitialOrderAPIState diff --git a/src/components/ConditionalFilter/constants.ts b/src/components/ConditionalFilter/constants.ts index bedf86e11c2..55e68d72ae4 100644 --- a/src/components/ConditionalFilter/constants.ts +++ b/src/components/ConditionalFilter/constants.ts @@ -348,12 +348,28 @@ export const STATIC_PAGE_OPTIONS: LeftOperand[] = [ }, ]; +export const STATIC_DRAFT_ORDER_OPTIONS: LeftOperand[] = [ + { + value: "customer", + label: "Customer", + type: "customer", + slug: "customer", + }, + { + value: "created", + label: "Created", + type: "created", + slug: "created", + }, +]; + export const STATIC_OPTIONS = [ ...STATIC_PRODUCT_OPTIONS, ...STATIC_DISCOUNT_OPTIONS, ...STATIC_ORDER_OPTIONS, ...STATIC_VOUCHER_OPTIONS, ...STATIC_PAGE_OPTIONS, + ...STATIC_DRAFT_ORDER_OPTIONS, ]; export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = { diff --git a/src/components/ConditionalFilter/context/provider.tsx b/src/components/ConditionalFilter/context/provider.tsx index 28f5b64bda9..f3da7948d55 100644 --- a/src/components/ConditionalFilter/context/provider.tsx +++ b/src/components/ConditionalFilter/context/provider.tsx @@ -1,6 +1,7 @@ import React, { FC } from "react"; import { useDiscountFilterAPIProvider } from "../API/DiscountFiltersAPIProvider"; +import { useDraftOrderFilterAPIProvider } from "../API/DraftOrderFilterAPIProvider"; import { useInitialOrderState } from "../API/initialState/orders/useInitialOrderState"; import { useInitialPageState } from "../API/initialState/page/useInitialPageState"; import { useProductInitialAPIState } from "../API/initialState/useInitialAPIState"; @@ -11,6 +12,7 @@ import { useProductFilterAPIProvider } from "../API/ProductFilterAPIProvider"; import { useVoucherAPIProvider } from "../API/VoucherFilterAPIProvider"; import { STATIC_DISCOUNT_OPTIONS, + STATIC_DRAFT_ORDER_OPTIONS, STATIC_ORDER_OPTIONS, STATIC_PAGE_OPTIONS, STATIC_PRODUCT_OPTIONS, @@ -149,3 +151,28 @@ export const ConditionalPageFilterProvider: FC<{ ); }; + +export const ConditionalDraftOrderFilterProvider: FC<{ + locationSearch: string; +}> = ({ children, locationSearch }) => { + const apiProvider = useDraftOrderFilterAPIProvider(); + + const valueProvider = useUrlValueProvider(locationSearch, "draft-order"); + const leftOperandsProvider = useFilterLeftOperandsProvider(STATIC_DRAFT_ORDER_OPTIONS); + const containerState = useContainerState(valueProvider); + const filterWindow = useFilterWindow(); + + return ( + + {children} + + ); +}; diff --git a/src/components/ConditionalFilter/queryVariables.test.ts b/src/components/ConditionalFilter/queryVariables.test.ts index 0aac3632dcb..07a1a44dd3f 100644 --- a/src/components/ConditionalFilter/queryVariables.test.ts +++ b/src/components/ConditionalFilter/queryVariables.test.ts @@ -3,6 +3,7 @@ import { ConditionOptions } from "./FilterElement/ConditionOptions"; import { ConditionSelected } from "./FilterElement/ConditionSelected"; import { ExpressionValue } from "./FilterElement/FilterElement"; import { + creatDraftOrderQueryVariables, createPageQueryVariables, createProductQueryVariables, creatVoucherQueryVariables, @@ -243,6 +244,63 @@ describe("ConditionalFilter / queryVariables / createPageQueryVariables", () => }); }); +describe("ConditionalFilter / queryVariables / creatDraftOrderQueryVariables", () => { + it("should return empty variables for empty filters", () => { + // Arrange + const filters: FilterContainer = []; + const expectedOutput = {}; + // Act + const result = creatDraftOrderQueryVariables(filters); + + // Assert + expect(result).toEqual(expectedOutput); + }); + + it("should create variables with selected filters", () => { + // Arrange + const filters: FilterContainer = [ + new FilterElement( + new ExpressionValue("customer", "Customer", "customer"), + new Condition( + ConditionOptions.fromStaticElementName("customer"), + new ConditionSelected( + { label: "customer1", slug: "customer1", value: "value1" }, + { type: "text", label: "is", value: "input-1" }, + [], + false, + ), + false, + ), + false, + ), + "AND", + new FilterElement( + new ExpressionValue("created", "Created", "created"), + new Condition( + ConditionOptions.fromStaticElementName("customer"), + new ConditionSelected( + ["2025-02-01", "2025-02-05"], + { type: "date.range", label: "between", value: "input-3" }, + [], + false, + ), + false, + ), + false, + ), + ]; + const expectedOutput = { + created: { gte: "2025-02-01", lte: "2025-02-05" }, + customer: "value1", + }; + // Act + const result = creatDraftOrderQueryVariables(filters); + + // Assert + expect(result).toEqual(expectedOutput); + }); +}); + describe("ConditionalFilter / queryVariables / mapStaticQueryPartToLegacyVariables", () => { it("should return queryPart if it is not an object", () => { // Arrange diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 82b2a24bf05..1ce93f7299d 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -5,6 +5,7 @@ import { DateTimeRangeInput, DecimalFilterInput, GlobalIdFilterInput, + OrderDraftFilterInput, PageFilterInput, ProductWhereInput, PromotionWhereInput, @@ -93,6 +94,7 @@ const getRangeQueryPartByType = (value: [string, string], type: string) => { return { valuesRange: { lte: parseFloat(lte), gte: parseFloat(gte) } }; } }; + const getQueryPartByType = (value: string, type: string, what: "lte" | "gte") => { switch (type) { case "datetime": @@ -103,6 +105,7 @@ const getQueryPartByType = (value: string, type: string, what: "lte" | "gte") => return { valuesRange: { [what]: parseFloat(value) } }; } }; + const createAttributeQueryPart = ( attributeSlug: string, selected: ConditionSelected, @@ -284,3 +287,15 @@ export const createPageQueryVariables = (value: FilterContainer): PageFilterInpu return p; }, {} as PageFilterInput); }; + +export const creatDraftOrderQueryVariables = (value: FilterContainer): OrderDraftFilterInput => { + return value.reduce((p, c) => { + if (typeof c === "string" || Array.isArray(c)) return p; + + p[c.value.value as keyof OrderDraftFilterInput] = mapStaticQueryPartToLegacyVariables( + createStaticQueryPart(c.condition.selected), + ); + + return p; + }, {} as OrderDraftFilterInput); +}; diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 535fca25951..eefb29dca64 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -2,6 +2,7 @@ import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton"; import { DashboardCard } from "@dashboard/components/Card"; +import { useFlag } from "@dashboard/featureFlags"; import { OrderDraftListQuery, RefreshLimitsQuery } from "@dashboard/graphql"; import { OrderDraftListUrlSortField } from "@dashboard/orders/urls"; import { FilterPagePropsWithPresets, PageListProps, RelayToFlat, SortPage } from "@dashboard/types"; @@ -54,6 +55,7 @@ const OrderDraftListPage: React.FC = ({ const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); const filterStructure = createFilterStructure(intl, filterOpts); const limitsReached = isLimitReached(limits, "orders"); + const { enabled: isDraftOrdersFilteringEnabled } = useFlag("draft_orders_filters"); return ( <> @@ -83,30 +85,54 @@ const OrderDraftListPage: React.FC = ({ alignItems="stretch" justifyContent="space-between" > - - {selectedOrderDraftIds.length > 0 && ( - - {intl.formatMessage({ - id: "+b/qJ9", - defaultMessage: "Delete draft orders", - })} - - )} - - } - /> + {isDraftOrdersFilteringEnabled ? ( + + {selectedOrderDraftIds.length > 0 && ( + + {intl.formatMessage({ + id: "+b/qJ9", + defaultMessage: "Delete draft orders", + })} + + )} + + } + /> + ) : ( + + {selectedOrderDraftIds.length > 0 && ( + + {intl.formatMessage({ + id: "+b/qJ9", + defaultMessage: "Delete draft orders", + })} + + )} + + } + /> + )} > = ({ location }) => { false, ); - return ; + return ( + + + + ); }; const OrderDetails: React.FC> = ({ location, match }) => { const qs = parseQs(location.search.substr(1)) as any; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index a5df5590dcd..3c715aa153d 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -3,9 +3,12 @@ import { useUser } from "@dashboard/auth"; import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDialog"; import ActionDialog from "@dashboard/components/ActionDialog"; import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext"; +import { useConditionalFilterContext } from "@dashboard/components/ConditionalFilter"; +import { creatDraftOrderQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { useShopLimitsQuery } from "@dashboard/components/Shop/queries"; +import { useFlag } from "@dashboard/featureFlags"; import { useOrderDraftCreateMutation, useOrderDraftListQuery } from "@dashboard/graphql"; import { useFilterPresets } from "@dashboard/hooks/useFilterPresets"; import useListSettings from "@dashboard/hooks/useListSettings"; @@ -48,6 +51,9 @@ export const OrderDraftList: React.FC = ({ params }) => { const notify = useNotifier(); const intl = useIntl(); const { updateListSettings, settings } = useListSettings(ListViews.DRAFT_LIST); + const { enabled: isDraftOrdersFilteringEnabled } = useFlag("draft_orders_filters"); + const { valueProvider } = useConditionalFilterContext(); + const filter = creatDraftOrderQueryVariables(valueProvider.value); usePaginationReset(orderDraftListUrl, params, settings.rowNumber); @@ -119,9 +125,22 @@ export const OrderDraftList: React.FC = ({ params }) => { }), [paginationState, params], ); + + const newFiltersQueryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: { + ...filter, + search: params.query, + }, + sort: getSortQueryVariables(params), + }), + [params, settings.rowNumber, valueProvider.value], + ); + const { data, refetch } = useOrderDraftListQuery({ displayLoader: true, - variables: queryVariables, + variables: isDraftOrdersFilteringEnabled ? newFiltersQueryVariables : queryVariables, }); const orderDrafts = mapEdgesToItems(data?.draftOrders); const paginationValues = usePaginator({