From 331bbee3898f5aecfa1fab578833c5c9ce4e4587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 29 Jan 2025 16:59:19 +0100 Subject: [PATCH 01/16] Introduce filers to draft orders --- .../API/DraftOrderFilterAPIProvider.tsx | 15 ++++++ .../ValueProvider/useUrlValueProvider.ts | 2 +- src/components/ConditionalFilter/constants.ts | 16 ++++++ .../ConditionalFilter/context/provider.tsx | 27 ++++++++++ .../ConditionalFilter/queryVariables.ts | 43 +++++++++++++++- .../OrderDraftListPage/OrderDraftListPage.tsx | 22 ++++---- .../components/OrderDraftListPage/filters.ts | 51 ------------------- .../components/OrderDraftListPage/index.ts | 1 - src/orders/index.tsx | 11 +++- .../views/OrderDraftList/OrderDraftList.tsx | 14 ++--- src/orders/views/OrderDraftList/filters.ts | 47 ++++++----------- 11 files changed, 144 insertions(+), 105 deletions(-) create mode 100644 src/components/ConditionalFilter/API/DraftOrderFilterAPIProvider.tsx delete mode 100644 src/orders/components/OrderDraftListPage/filters.ts 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/ValueProvider/useUrlValueProvider.ts b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts index 16037fcffc0..29c07213824 100644 --- a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts +++ b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts @@ -18,7 +18,7 @@ import { prepareStructure } from "./utils"; export const useUrlValueProvider = ( locationSearch: string, - type: "product" | "order" | "discount", + type: "product" | "draft-order" | "order" | "discount", initialState?: InitialAPIState | InitialOrderAPIState, ): FilterValueProvider => { const router = useRouter(); diff --git a/src/components/ConditionalFilter/constants.ts b/src/components/ConditionalFilter/constants.ts index b3084e64327..262317ae7a4 100644 --- a/src/components/ConditionalFilter/constants.ts +++ b/src/components/ConditionalFilter/constants.ts @@ -273,10 +273,26 @@ export const STATIC_ORDER_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_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 eaccbcd0001..b9cc6096baf 100644 --- a/src/components/ConditionalFilter/context/provider.tsx +++ b/src/components/ConditionalFilter/context/provider.tsx @@ -1,3 +1,4 @@ +import { useDraftOrderFilterAPIProvider } from "@dashboard/components/ConditionalFilter/API/DraftOrderFilterAPIProvider"; import React, { FC } from "react"; import { useDiscountFilterAPIProvider } from "../API/DiscountFiltersAPIProvider"; @@ -7,6 +8,7 @@ import { useOrderFilterAPIProvider } from "../API/OrderFilterAPIProvider"; import { useProductFilterAPIProvider } from "../API/ProductFilterAPIProvider"; import { STATIC_DISCOUNT_OPTIONS, + STATIC_DRAFT_ORDER_OPTIONS, STATIC_ORDER_OPTIONS, STATIC_PRODUCT_OPTIONS, } from "../constants"; @@ -90,3 +92,28 @@ export const ConditionalOrderFilterProvider: 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.ts b/src/components/ConditionalFilter/queryVariables.ts index 5cdbf379187..93f1c927edb 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -13,7 +13,7 @@ import { FilterContainer } from "./FilterElement"; import { ConditionSelected } from "./FilterElement/ConditionSelected"; import { isItemOption, isItemOptionArray, isTuple } from "./FilterElement/ConditionValue"; -type StaticQueryPart = string | GlobalIdFilterInput | boolean | DecimalFilterInput; +type StaticQueryPart = string | GlobalIdFilterInput | boolean | DecimalFilterInput | DateRangeInput; const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => { if (!selected.conditionValue) return ""; @@ -199,3 +199,44 @@ export const createOrderQueryVariables = (value: FilterContainer) => { return p; }, {} as OrderQueryVars); }; + +export type DraftOrderQueryVars = { + customer: string; + created: DateRangeInput; +}; + +export const creatDraftOrderQueryVariables = (value: FilterContainer): DraftOrderQueryVars => { + return value.reduce((p, c) => { + if (typeof c === "string" || Array.isArray(c)) return p; + + if (c.isStatic()) { + const label = c.condition.selected.conditionValue?.label; + const value = c.condition.selected.value; + + if (c.value.value === "customer") { + p.customer = value as string; + } + + if (c.value.value === "created") { + if (isTuple(value) && label === "between") { + const [gte, lte] = value; + + p.created = { + gte, + lte, + }; + } + + if (label === "greater") { + p.created = { gte: value as string }; + } + + if (label == "lower") { + p.created = { lte: value as string }; + } + } + } + + return p; + }, {} as DraftOrderQueryVars); +}; diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 535fca25951..b28e5f59a72 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -4,7 +4,13 @@ import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton"; import { DashboardCard } from "@dashboard/components/Card"; import { OrderDraftListQuery, RefreshLimitsQuery } from "@dashboard/graphql"; import { OrderDraftListUrlSortField } from "@dashboard/orders/urls"; -import { FilterPagePropsWithPresets, PageListProps, RelayToFlat, SortPage } from "@dashboard/types"; +import { + FilterPresetsProps, + PageListProps, + RelayToFlat, + SearchPageProps, + SortPage, +} from "@dashboard/types"; import { isLimitReached } from "@dashboard/utils/limits"; import { Box } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; @@ -13,11 +19,11 @@ import { useIntl } from "react-intl"; import { OrderDraftListDatagrid } from "../OrderDraftListDatagrid"; import { OrderDraftListHeader } from "../OrderDraftListHeader/OrderDraftListHeader"; import OrderLimitReached from "../OrderLimitReached"; -import { createFilterStructure, OrderDraftFilterKeys, OrderDraftListFilterOpts } from "./filters"; export interface OrderDraftListPageProps extends PageListProps, - FilterPagePropsWithPresets, + SearchPageProps, + FilterPresetsProps, SortPage { limits: RefreshLimitsQuery["shop"]["limits"]; orders: RelayToFlat; @@ -31,12 +37,10 @@ export interface OrderDraftListPageProps const OrderDraftListPage: React.FC = ({ selectedFilterPreset, disabled, - filterOpts, initialSearch, limits, onAdd, onFilterPresetsAll, - onFilterChange, onSearchChange, onFilterPresetChange, onFilterPresetDelete, @@ -45,14 +49,11 @@ const OrderDraftListPage: React.FC = ({ filterPresets, hasPresetsChanged, onDraftOrdersDelete, - onFilterAttributeFocus, - currencySymbol, selectedOrderDraftIds, ...listProps }) => { const intl = useIntl(); const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); - const filterStructure = createFilterStructure(intl, filterOpts); const limitsReached = isLimitReached(limits, "orders"); return ( @@ -84,12 +85,9 @@ const OrderDraftListPage: React.FC = ({ justifyContent="space-between" > ; - customer: FilterOpts; -} - -const messages = defineMessages({ - created: { - id: "vwMO04", - defaultMessage: "Created", - description: "draft order", - }, - customer: { - id: "iEeIhY", - defaultMessage: "Customer", - description: "draft order", - }, -}); - -export function createFilterStructure( - intl: IntlShape, - opts: OrderDraftListFilterOpts, -): IFilter { - return [ - { - ...createDateField( - OrderDraftFilterKeys.created, - intl.formatMessage(messages.created), - opts.created.value, - ), - active: opts.created.active, - }, - { - ...createTextField( - OrderDraftFilterKeys.customer, - intl.formatMessage(messages.customer), - opts.customer.value, - ), - active: opts.customer.active, - }, - ]; -} diff --git a/src/orders/components/OrderDraftListPage/index.ts b/src/orders/components/OrderDraftListPage/index.ts index 3fc3859a5a6..de5e8daab09 100644 --- a/src/orders/components/OrderDraftListPage/index.ts +++ b/src/orders/components/OrderDraftListPage/index.ts @@ -1,3 +1,2 @@ -export * from "./filters"; export { default } from "./OrderDraftListPage"; export * from "./OrderDraftListPage"; diff --git a/src/orders/index.tsx b/src/orders/index.tsx index 4eb6f85dd31..a5d23bea487 100644 --- a/src/orders/index.tsx +++ b/src/orders/index.tsx @@ -1,4 +1,7 @@ -import { ConditionalOrderFilterProvider } from "@dashboard/components/ConditionalFilter"; +import { + ConditionalDraftOrderFilterProvider, + ConditionalOrderFilterProvider, +} from "@dashboard/components/ConditionalFilter"; import { Route } from "@dashboard/components/Router"; import { sectionNames } from "@dashboard/intl"; import { asSortParams } from "@dashboard/utils/sort"; @@ -71,7 +74,11 @@ const OrderDraftList: React.FC> = ({ 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..f6cd11d3e08 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -3,6 +3,7 @@ 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 DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { useShopLimitsQuery } from "@dashboard/components/Shop/queries"; @@ -35,7 +36,7 @@ import { OrderDraftListUrlQueryParams, orderUrl, } from "../../urls"; -import { getFilterOpts, getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; +import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; import { getSortQueryVariables } from "./sort"; import { useBulkDeletion } from "./useBulkDeletion"; @@ -48,6 +49,7 @@ export const OrderDraftList: React.FC = ({ params }) => { const notify = useNotifier(); const intl = useIntl(); const { updateListSettings, settings } = useListSettings(ListViews.DRAFT_LIST); + const { valueProvider } = useConditionalFilterContext(); usePaginationReset(orderDraftListUrl, params, settings.rowNumber); @@ -82,7 +84,7 @@ export const OrderDraftList: React.FC = ({ params }) => { orders: true, }, }); - const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ + const [_, resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: orderDraftListUrl, getFilterQueryParam, @@ -114,7 +116,10 @@ export const OrderDraftList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, - filter: getFilterVariables(params), + filter: getFilterVariables({ + params, + filterContainer: valueProvider.value, + }), sort: getSortQueryVariables(params), }), [paginationState, params], @@ -152,11 +157,9 @@ export const OrderDraftList: React.FC = ({ params }) => { { @@ -172,7 +175,6 @@ export const OrderDraftList: React.FC = ({ params }) => { onAdd={() => openModal("create-order")} onSort={handleSort} sort={getSortParams(params)} - currencySymbol={channel?.currencyCode} hasPresetsChanged={hasPresetsChanged} onDraftOrdersDelete={() => openModal("remove", { diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index cc76072d6a8..d2a7ad115ed 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -1,16 +1,11 @@ // @ts-strict-ignore +import { FilterContainer } from "@dashboard/components/ConditionalFilter/FilterElement"; +import { creatDraftOrderQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; import { FilterElement } from "@dashboard/components/Filter"; -import { OrderDraftFilterInput } from "@dashboard/graphql"; -import { maybe } from "@dashboard/misc"; -import { - OrderDraftFilterKeys, - OrderDraftListFilterOpts, -} from "@dashboard/orders/components/OrderDraftListPage"; import { createFilterTabUtils, createFilterUtils, - getGteLteVariables, getMinMaxQueryParam, getSingleValueQueryParam, } from "../../../utils/filters"; @@ -22,34 +17,24 @@ import { export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; -export function getFilterOpts(params: OrderDraftListUrlFilters): OrderDraftListFilterOpts { - return { - created: { - active: maybe( - () => [params.createdFrom, params.createdTo].some(field => field !== undefined), - false, - ), - value: { - max: maybe(() => params.createdTo), - min: maybe(() => params.createdFrom), - }, - }, - customer: { - active: !!maybe(() => params.customer), - value: params.customer, - }, - }; -} +export const getFilterVariables = ({ + filterContainer, + params, +}: { + filterContainer: FilterContainer; + params: OrderDraftListUrlFilters; +}) => { + const queryVars = creatDraftOrderQueryVariables(filterContainer); -export function getFilterVariables(params: OrderDraftListUrlFilters): OrderDraftFilterInput { return { - created: getGteLteVariables({ - gte: params.createdFrom, - lte: params.createdTo, - }), - customer: params.customer, + ...queryVars, search: params.query, }; +}; + +export enum OrderDraftFilterKeys { + created = "created", + customer = "customer", } export function getFilterQueryParam( From 08b1e0c2484c807af1168361139e262a37e2d73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 30 Jan 2025 02:03:38 +0100 Subject: [PATCH 02/16] Add method to old filters query params --- .../ConditionalFilter/queryVariables.ts | 87 ++++++++++++------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 93f1c927edb..4a5c9dcb411 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -5,6 +5,7 @@ import { DateTimeRangeInput, DecimalFilterInput, GlobalIdFilterInput, + OrderDraftFilterInput, ProductWhereInput, PromotionWhereInput, } from "@dashboard/graphql"; @@ -13,7 +14,8 @@ import { FilterContainer } from "./FilterElement"; import { ConditionSelected } from "./FilterElement/ConditionSelected"; import { isItemOption, isItemOptionArray, isTuple } from "./FilterElement/ConditionValue"; -type StaticQueryPart = string | GlobalIdFilterInput | boolean | DecimalFilterInput | DateRangeInput; +type StaticQueryPart = string | GlobalIdFilterInput | boolean | DecimalFilterInput; +type StaticQueryPartForOldAPIFilters = string | DateRangeInput | string[] | boolean; const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => { if (!selected.conditionValue) return ""; @@ -57,6 +59,50 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => return value; }; +const createStaticQueryPartForOldAPIFilters = ( + selected: ConditionSelected, +): StaticQueryPartForOldAPIFilters => { + if (!selected.conditionValue) return ""; + + const { label } = selected.conditionValue; + const { value } = selected; + + if (label === "lower") { + return { lte: value }; + } + + if (label === "greater") { + return { gte: value }; + } + + if (isTuple(value) && label === "between") { + const [gte, lte] = value; + + return { lte, gte }; + } + + if (isItemOption(value) && ["true", "false"].includes(value.value)) { + return value.value === "true"; + } + + if (isItemOption(value)) { + return value.value; + } + + if (isItemOptionArray(value)) { + return value.map(x => x.value); + } + + if (typeof value === "string") { + return value; + } + + if (Array.isArray(value)) { + return value; + } + + return value; +}; const getRangeQueryPartByType = (value: [string, string], type: string) => { const [gte, lte] = value; @@ -200,43 +246,18 @@ export const createOrderQueryVariables = (value: FilterContainer) => { }, {} as OrderQueryVars); }; -export type DraftOrderQueryVars = { - customer: string; - created: DateRangeInput; -}; - -export const creatDraftOrderQueryVariables = (value: FilterContainer): DraftOrderQueryVars => { +export const creatDraftOrderQueryVariables = (value: FilterContainer): OrderDraftFilterInput => { return value.reduce((p, c) => { if (typeof c === "string" || Array.isArray(c)) return p; if (c.isStatic()) { - const label = c.condition.selected.conditionValue?.label; - const value = c.condition.selected.value; - - if (c.value.value === "customer") { - p.customer = value as string; - } - - if (c.value.value === "created") { - if (isTuple(value) && label === "between") { - const [gte, lte] = value; - - p.created = { - gte, - lte, - }; - } - - if (label === "greater") { - p.created = { gte: value as string }; - } - - if (label == "lower") { - p.created = { lte: value as string }; - } - } + p[c.value.value as keyof OrderDraftFilterInput] = createStaticQueryPartForOldAPIFilters( + c.condition.selected, + ) as any; + + return p; } return p; - }, {} as DraftOrderQueryVars); + }, {} as OrderDraftFilterInput); }; From b1a1bc53abb6a883b28b650a7fea114539256ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 30 Jan 2025 02:46:33 +0100 Subject: [PATCH 03/16] Remove not used handlers --- .../ConditionalFilter/queryVariables.ts | 3 +- .../views/OrderDraftList/OrderDraftList.tsx | 7 ++-- src/orders/views/OrderDraftList/filters.ts | 31 +---------------- src/utils/handlers/filterHandlers.ts | 34 +++---------------- 4 files changed, 11 insertions(+), 64 deletions(-) diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 4a5c9dcb411..204e10ec8d3 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -65,7 +65,8 @@ const createStaticQueryPartForOldAPIFilters = ( if (!selected.conditionValue) return ""; const { label } = selected.conditionValue; - const { value } = selected; + const { value: selectedValue } = selected; + const value = Array.isArray(selectedValue) ? selectedValue[0] : selectedValue; if (label === "lower") { return { lte: value }; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index f6cd11d3e08..f1ab6b21f33 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -36,7 +36,7 @@ import { OrderDraftListUrlQueryParams, orderUrl, } from "../../urls"; -import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; +import { getFilterVariables, storageUtils } from "./filters"; import { getSortQueryVariables } from "./sort"; import { useBulkDeletion } from "./useBulkDeletion"; @@ -84,10 +84,9 @@ export const OrderDraftList: React.FC = ({ params }) => { orders: true, }, }); - const [_, resetFilters, handleSearchChange] = createFilterHandlers({ + const [resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: orderDraftListUrl, - getFilterQueryParam, navigate, params, keepActiveTab: true, @@ -122,7 +121,7 @@ export const OrderDraftList: React.FC = ({ params }) => { }), sort: getSortQueryVariables(params), }), - [paginationState, params], + [paginationState, params, valueProvider.value], ); const { data, refetch } = useOrderDraftListQuery({ displayLoader: true, diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index d2a7ad115ed..6af0347a446 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -1,14 +1,8 @@ // @ts-strict-ignore import { FilterContainer } from "@dashboard/components/ConditionalFilter/FilterElement"; import { creatDraftOrderQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; -import { FilterElement } from "@dashboard/components/Filter"; -import { - createFilterTabUtils, - createFilterUtils, - getMinMaxQueryParam, - getSingleValueQueryParam, -} from "../../../utils/filters"; +import { createFilterTabUtils, createFilterUtils } from "../../../utils/filters"; import { OrderDraftListUrlFilters, OrderDraftListUrlFiltersEnum, @@ -32,29 +26,6 @@ export const getFilterVariables = ({ }; }; -export enum OrderDraftFilterKeys { - created = "created", - customer = "customer", -} - -export function getFilterQueryParam( - filter: FilterElement, -): OrderDraftListUrlFilters { - const { name } = filter; - - switch (name) { - case OrderDraftFilterKeys.created: - return getMinMaxQueryParam( - filter, - OrderDraftListUrlFiltersEnum.createdFrom, - OrderDraftListUrlFiltersEnum.createdTo, - ); - - case OrderDraftFilterKeys.customer: - return getSingleValueQueryParam(filter, OrderDraftListUrlFiltersEnum.customer); - } -} - export const storageUtils = createFilterTabUtils(ORDER_DRAFT_FILTERS_KEY); export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils< diff --git a/src/utils/handlers/filterHandlers.ts b/src/utils/handlers/filterHandlers.ts index 3a7afc7f82f..b66cf39a356 100644 --- a/src/utils/handlers/filterHandlers.ts +++ b/src/utils/handlers/filterHandlers.ts @@ -1,26 +1,18 @@ -import { IFilter } from "@dashboard/components/Filter"; import { UseNavigatorResult } from "@dashboard/hooks/useNavigator"; import { ActiveTab, Pagination, Search, Sort } from "@dashboard/types"; -import { GetFilterQueryParam, getFilterQueryParams } from "../filters"; - type RequiredParams = ActiveTab & Search & Sort & Pagination & { presestesChanged?: string }; type CreateUrl = (params: RequiredParams) => string; -type CreateFilterHandlers = [ - (filter: IFilter) => void, - () => void, - (query: string) => void, -]; +type CreateFilterHandlers = [() => void, (query: string) => void]; -function createFilterHandlers(opts: { - getFilterQueryParam: GetFilterQueryParam; +function createFilterHandlers(opts: { navigate: UseNavigatorResult; createUrl: CreateUrl; params: RequiredParams; cleanupFn?: () => void; keepActiveTab?: boolean; -}): CreateFilterHandlers { - const { getFilterQueryParam, navigate, createUrl, params, cleanupFn, keepActiveTab } = opts; +}): CreateFilterHandlers { + const { navigate, createUrl, params, cleanupFn, keepActiveTab } = opts; const getActiveTabValue = (removeActiveTab: boolean) => { if (!keepActiveTab || removeActiveTab) { return undefined; @@ -28,23 +20,7 @@ function createFilterHandlers(o return params.activeTab; }; - const changeFilters = (filters: IFilter) => { - if (cleanupFn) { - cleanupFn(); - } - - const filtersQueryParams = getFilterQueryParams(filters, getFilterQueryParam); - navigate( - createUrl({ - ...params, - ...filtersQueryParams, - activeTab: getActiveTabValue( - checkIfParamsEmpty(filtersQueryParams) && !params.query?.length, - ), - }), - ); - }; const resetFilters = () => { if (cleanupFn) { cleanupFn(); @@ -75,7 +51,7 @@ function createFilterHandlers(o ); }; - return [changeFilters, resetFilters, handleSearchChange]; + return [resetFilters, handleSearchChange]; } function checkIfParamsEmpty(params: RequiredParams): boolean { From 4fd4239382c3efc85ec64f2b576345e17cb2466d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Thu, 30 Jan 2025 11:42:59 +0100 Subject: [PATCH 04/16] Fix lint --- locale/defaultMessages.json | 8 --- .../views/OrderDraftList/OrderDraftList.tsx | 5 +- .../views/OrderDraftList/filters.test.ts | 61 +++++-------------- src/orders/views/OrderDraftList/filters.ts | 31 +++++++++- src/utils/handlers/filterHandlers.ts | 34 +++++++++-- 5 files changed, 78 insertions(+), 61 deletions(-) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 1f9035102e6..d38e59d05a6 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -7151,10 +7151,6 @@ "context": "button", "string": "Apply" }, - "iEeIhY": { - "context": "draft order", - "string": "Customer" - }, "iFM716": { "context": "grant refund, refund card subtitle", "string": "How much money do you want to return to the customer for the order?" @@ -9182,10 +9178,6 @@ "vuKrlW": { "string": "Stock" }, - "vwMO04": { - "context": "draft order", - "string": "Created" - }, "vz3yxp": { "string": "Channels permissions" }, diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index f1ab6b21f33..6087972cce7 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -36,7 +36,7 @@ import { OrderDraftListUrlQueryParams, orderUrl, } from "../../urls"; -import { getFilterVariables, storageUtils } from "./filters"; +import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; import { getSortQueryVariables } from "./sort"; import { useBulkDeletion } from "./useBulkDeletion"; @@ -84,9 +84,10 @@ export const OrderDraftList: React.FC = ({ params }) => { orders: true, }, }); - const [resetFilters, handleSearchChange] = createFilterHandlers({ + const [_, resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: orderDraftListUrl, + getFilterQueryParam, navigate, params, keepActiveTab: true, diff --git a/src/orders/views/OrderDraftList/filters.test.ts b/src/orders/views/OrderDraftList/filters.test.ts index 25f07ec5330..bc41fe0241a 100644 --- a/src/orders/views/OrderDraftList/filters.test.ts +++ b/src/orders/views/OrderDraftList/filters.test.ts @@ -1,60 +1,31 @@ -import { date } from "@dashboard/fixtures"; -import { createFilterStructure } from "@dashboard/orders/components/OrderDraftListPage"; +import { FilterElement } from "@dashboard/components/ConditionalFilter/FilterElement"; import { OrderDraftListUrlFilters } from "@dashboard/orders/urls"; -import { getFilterQueryParams } from "@dashboard/utils/filters"; -import { stringifyQs } from "@dashboard/utils/urls"; -import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; -import { config } from "@test/intl"; -import { createIntl } from "react-intl"; +import { getExistingKeys } from "@test/filters"; -import { getFilterQueryParam, getFilterVariables } from "./filters"; +import { getFilterVariables } from "./filters"; describe("Filtering query params", () => { it("should be empty object if no params given", () => { const params: OrderDraftListUrlFilters = {}; - const filterVariables = getFilterVariables(params); + const filterVariables = getFilterVariables({ + params, + filterContainer: [FilterElement.createEmpty()], + }); expect(getExistingKeys(filterVariables)).toHaveLength(0); }); it("should not be empty object if params given", () => { const params: OrderDraftListUrlFilters = { - createdFrom: date.from, - createdTo: date.to, - customer: "admin@example.com", + query: "test", }; - const filterVariables = getFilterVariables(params); + const filterVariables = getFilterVariables({ + params, + filterContainer: [ + FilterElement.createStaticBySlug("created"), + FilterElement.createStaticBySlug("customer"), + ], + }); - expect(getExistingKeys(filterVariables)).toHaveLength(2); - }); -}); -describe("Filtering URL params", () => { - const intl = createIntl(config); - const filters = createFilterStructure(intl, { - created: { - active: false, - value: { - max: date.to, - min: date.from, - }, - }, - customer: { - active: false, - value: "admin@example.com", - }, - }); - - it("should be empty if no active filters", () => { - const filterQueryParams = getFilterQueryParams(filters, getFilterQueryParam); - - expect(getExistingKeys(filterQueryParams)).toHaveLength(0); - }); - it("should not be empty if active filters are present", () => { - const filterQueryParams = getFilterQueryParams( - setFilterOptsStatus(filters, true), - getFilterQueryParam, - ); - - expect(filterQueryParams).toMatchSnapshot(); - expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + expect(getExistingKeys(filterVariables)).toHaveLength(3); }); }); diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index 6af0347a446..d2a7ad115ed 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -1,8 +1,14 @@ // @ts-strict-ignore import { FilterContainer } from "@dashboard/components/ConditionalFilter/FilterElement"; import { creatDraftOrderQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; +import { FilterElement } from "@dashboard/components/Filter"; -import { createFilterTabUtils, createFilterUtils } from "../../../utils/filters"; +import { + createFilterTabUtils, + createFilterUtils, + getMinMaxQueryParam, + getSingleValueQueryParam, +} from "../../../utils/filters"; import { OrderDraftListUrlFilters, OrderDraftListUrlFiltersEnum, @@ -26,6 +32,29 @@ export const getFilterVariables = ({ }; }; +export enum OrderDraftFilterKeys { + created = "created", + customer = "customer", +} + +export function getFilterQueryParam( + filter: FilterElement, +): OrderDraftListUrlFilters { + const { name } = filter; + + switch (name) { + case OrderDraftFilterKeys.created: + return getMinMaxQueryParam( + filter, + OrderDraftListUrlFiltersEnum.createdFrom, + OrderDraftListUrlFiltersEnum.createdTo, + ); + + case OrderDraftFilterKeys.customer: + return getSingleValueQueryParam(filter, OrderDraftListUrlFiltersEnum.customer); + } +} + export const storageUtils = createFilterTabUtils(ORDER_DRAFT_FILTERS_KEY); export const { areFiltersApplied, getActiveFilters, getFiltersCurrentTab } = createFilterUtils< diff --git a/src/utils/handlers/filterHandlers.ts b/src/utils/handlers/filterHandlers.ts index b66cf39a356..3a7afc7f82f 100644 --- a/src/utils/handlers/filterHandlers.ts +++ b/src/utils/handlers/filterHandlers.ts @@ -1,18 +1,26 @@ +import { IFilter } from "@dashboard/components/Filter"; import { UseNavigatorResult } from "@dashboard/hooks/useNavigator"; import { ActiveTab, Pagination, Search, Sort } from "@dashboard/types"; +import { GetFilterQueryParam, getFilterQueryParams } from "../filters"; + type RequiredParams = ActiveTab & Search & Sort & Pagination & { presestesChanged?: string }; type CreateUrl = (params: RequiredParams) => string; -type CreateFilterHandlers = [() => void, (query: string) => void]; +type CreateFilterHandlers = [ + (filter: IFilter) => void, + () => void, + (query: string) => void, +]; -function createFilterHandlers(opts: { +function createFilterHandlers(opts: { + getFilterQueryParam: GetFilterQueryParam; navigate: UseNavigatorResult; createUrl: CreateUrl; params: RequiredParams; cleanupFn?: () => void; keepActiveTab?: boolean; -}): CreateFilterHandlers { - const { navigate, createUrl, params, cleanupFn, keepActiveTab } = opts; +}): CreateFilterHandlers { + const { getFilterQueryParam, navigate, createUrl, params, cleanupFn, keepActiveTab } = opts; const getActiveTabValue = (removeActiveTab: boolean) => { if (!keepActiveTab || removeActiveTab) { return undefined; @@ -20,7 +28,23 @@ function createFilterHandlers(opts: { return params.activeTab; }; + const changeFilters = (filters: IFilter) => { + if (cleanupFn) { + cleanupFn(); + } + + const filtersQueryParams = getFilterQueryParams(filters, getFilterQueryParam); + navigate( + createUrl({ + ...params, + ...filtersQueryParams, + activeTab: getActiveTabValue( + checkIfParamsEmpty(filtersQueryParams) && !params.query?.length, + ), + }), + ); + }; const resetFilters = () => { if (cleanupFn) { cleanupFn(); @@ -51,7 +75,7 @@ function createFilterHandlers(opts: { ); }; - return [resetFilters, handleSearchChange]; + return [changeFilters, resetFilters, handleSearchChange]; } function checkIfParamsEmpty(params: RequiredParams): boolean { From e7a82b94fafdc99c79365bd1d3e187345afc511b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 08:18:08 +0100 Subject: [PATCH 05/16] Fix for data range --- src/components/ConditionalFilter/API/InitialStateResponse.ts | 4 ++++ src/components/ConditionalFilter/queryVariables.ts | 3 ++- src/orders/views/OrderDraftList/OrderDraftList.tsx | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/ConditionalFilter/API/InitialStateResponse.ts b/src/components/ConditionalFilter/API/InitialStateResponse.ts index 6597c779e89..524cba1fe16 100644 --- a/src/components/ConditionalFilter/API/InitialStateResponse.ts +++ b/src/components/ConditionalFilter/API/InitialStateResponse.ts @@ -67,6 +67,10 @@ export class InitialStateResponse implements InitialState { } if (!token.isLoadable()) { + if (Array.isArray(token.value)) { + return token.value; + } + return [token.value] as string[]; } diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 204e10ec8d3..aab0d8b21ad 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -66,7 +66,8 @@ const createStaticQueryPartForOldAPIFilters = ( const { label } = selected.conditionValue; const { value: selectedValue } = selected; - const value = Array.isArray(selectedValue) ? selectedValue[0] : selectedValue; + const value = + Array.isArray(selectedValue) && !isTuple(selectedValue) ? selectedValue[0] : selectedValue; if (label === "lower") { return { lte: value }; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 6087972cce7..4f4bcffdaab 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -122,7 +122,7 @@ export const OrderDraftList: React.FC = ({ params }) => { }), sort: getSortQueryVariables(params), }), - [paginationState, params, valueProvider.value], + [params], ); const { data, refetch } = useOrderDraftListQuery({ displayLoader: true, From 70f8fc6da2dbaded8b1f8a8514b0fffa8f614c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 08:24:01 +0100 Subject: [PATCH 06/16] Add changeset --- .changeset/silver-items-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silver-items-unite.md diff --git a/.changeset/silver-items-unite.md b/.changeset/silver-items-unite.md new file mode 100644 index 00000000000..15098c12bf8 --- /dev/null +++ b/.changeset/silver-items-unite.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +List of draft orders now used new filters From 9ab93e7e03856c662ef1a48c19725f25dda7b6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 08:38:34 +0100 Subject: [PATCH 07/16] Remove snapshot --- .../OrderDraftList/__snapshots__/filters.test.ts.snap | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap diff --git a/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap b/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap deleted file mode 100644 index b48bb140572..00000000000 --- a/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Filtering URL params should not be empty if active filters are present 1`] = ` -Object { - "createdFrom": "2019-12-09", - "createdTo": "2019-12-38", - "customer": "admin@example.com", -} -`; - -exports[`Filtering URL params should not be empty if active filters are present 2`] = `"createdFrom=2019-12-09&createdTo=2019-12-38&customer=admin%40example.com"`; From 32a4514be7f702b3b4fda4421b9fb94315c3ca2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 09:26:50 +0100 Subject: [PATCH 08/16] Add mapper --- .../ConditionalFilter/queryVariables.ts | 60 +++++-------------- 1 file changed, 16 insertions(+), 44 deletions(-) diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index aab0d8b21ad..67765efdca5 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -15,7 +15,6 @@ import { ConditionSelected } from "./FilterElement/ConditionSelected"; import { isItemOption, isItemOptionArray, isTuple } from "./FilterElement/ConditionValue"; type StaticQueryPart = string | GlobalIdFilterInput | boolean | DecimalFilterInput; -type StaticQueryPartForOldAPIFilters = string | DateRangeInput | string[] | boolean; const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => { if (!selected.conditionValue) return ""; @@ -59,52 +58,27 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => return value; }; -const createStaticQueryPartForOldAPIFilters = ( - selected: ConditionSelected, -): StaticQueryPartForOldAPIFilters => { - if (!selected.conditionValue) return ""; - - const { label } = selected.conditionValue; - const { value: selectedValue } = selected; - const value = - Array.isArray(selectedValue) && !isTuple(selectedValue) ? selectedValue[0] : selectedValue; - if (label === "lower") { - return { lte: value }; - } - - if (label === "greater") { - return { gte: value }; +const mapStaticQueryPartToLegacyVariables = (queryPart: StaticQueryPart) => { + if (typeof queryPart !== "object") { + return queryPart; } - if (isTuple(value) && label === "between") { - const [gte, lte] = value; - - return { lte, gte }; + if ("range" in queryPart) { + return queryPart.range; } - if (isItemOption(value) && ["true", "false"].includes(value.value)) { - return value.value === "true"; + if ("eq" in queryPart) { + return queryPart.eq; } - if (isItemOption(value)) { - return value.value; + if ("oneOf" in queryPart) { + return queryPart.oneOf; } - if (isItemOptionArray(value)) { - return value.map(x => x.value); - } - - if (typeof value === "string") { - return value; - } - - if (Array.isArray(value)) { - return value; - } - - return value; + return queryPart; }; + const getRangeQueryPartByType = (value: [string, string], type: string) => { const [gte, lte] = value; @@ -118,6 +92,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": @@ -128,6 +103,7 @@ const getQueryPartByType = (value: string, type: string, what: "lte" | "gte") => return { valuesRange: { [what]: parseFloat(value) } }; } }; + const createAttributeQueryPart = ( attributeSlug: string, selected: ConditionSelected, @@ -252,13 +228,9 @@ export const creatDraftOrderQueryVariables = (value: FilterContainer): OrderDraf return value.reduce((p, c) => { if (typeof c === "string" || Array.isArray(c)) return p; - if (c.isStatic()) { - p[c.value.value as keyof OrderDraftFilterInput] = createStaticQueryPartForOldAPIFilters( - c.condition.selected, - ) as any; - - return p; - } + p[c.value.value as keyof OrderDraftFilterInput] = mapStaticQueryPartToLegacyVariables( + createStaticQueryPart(c.condition.selected), + ); return p; }, {} as OrderDraftFilterInput); From 21cf2e44faba7617dd625634d9da2403f6c3d9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 10:21:24 +0100 Subject: [PATCH 09/16] Improve filters --- .../ConditionalFilter/queryVariables.ts | 5 ++++- .../views/OrderDraftList/OrderDraftList.tsx | 22 ++++++++++--------- src/orders/views/OrderDraftList/filters.ts | 17 -------------- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 67765efdca5..3b1e54da4b2 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -20,7 +20,10 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => if (!selected.conditionValue) return ""; const { label } = selected.conditionValue; - const { value } = selected; + const { value: selectedValue } = selected; + + const value = + Array.isArray(selectedValue) && !isTuple(selectedValue) ? selectedValue[0] : selectedValue; if (label === "lower") { return { range: { lte: value } }; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 4f4bcffdaab..1c17b67062a 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -4,6 +4,7 @@ import ChannelPickerDialog from "@dashboard/channels/components/ChannelPickerDia 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"; @@ -36,7 +37,7 @@ import { OrderDraftListUrlQueryParams, orderUrl, } from "../../urls"; -import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; +import { getFilterQueryParam, storageUtils } from "./filters"; import { getSortQueryVariables } from "./sort"; import { useBulkDeletion } from "./useBulkDeletion"; @@ -50,6 +51,7 @@ export const OrderDraftList: React.FC = ({ params }) => { const intl = useIntl(); const { updateListSettings, settings } = useListSettings(ListViews.DRAFT_LIST); const { valueProvider } = useConditionalFilterContext(); + const where = creatDraftOrderQueryVariables(valueProvider.value); usePaginationReset(orderDraftListUrl, params, settings.rowNumber); @@ -112,18 +114,18 @@ export const OrderDraftList: React.FC = ({ params }) => { getUrl: orderDraftListUrl, storageUtils, }); + const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ + const queryVariables = React.useMemo(() => { + return { ...paginationState, - filter: getFilterVariables({ - params, - filterContainer: valueProvider.value, - }), + filter: { + ...where, + search: params.query, + }, sort: getSortQueryVariables(params), - }), - [params], - ); + }; + }, [params]); const { data, refetch } = useOrderDraftListQuery({ displayLoader: true, variables: queryVariables, diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index d2a7ad115ed..588c248103a 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -1,6 +1,4 @@ // @ts-strict-ignore -import { FilterContainer } from "@dashboard/components/ConditionalFilter/FilterElement"; -import { creatDraftOrderQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; import { FilterElement } from "@dashboard/components/Filter"; import { @@ -17,21 +15,6 @@ import { export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; -export const getFilterVariables = ({ - filterContainer, - params, -}: { - filterContainer: FilterContainer; - params: OrderDraftListUrlFilters; -}) => { - const queryVars = creatDraftOrderQueryVariables(filterContainer); - - return { - ...queryVars, - search: params.query, - }; -}; - export enum OrderDraftFilterKeys { created = "created", customer = "customer", From 8d2ccba35f348e73cfffdf5d3df56a793be197e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 10:36:19 +0100 Subject: [PATCH 10/16] Fix query variables --- .../ConditionalFilter/queryVariables.ts | 4 ++- .../views/OrderDraftList/OrderDraftList.tsx | 7 +++-- .../views/OrderDraftList/filters.test.ts | 31 ------------------- 3 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 src/orders/views/OrderDraftList/filters.test.ts diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 3b1e54da4b2..bd038ae895c 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -23,7 +23,9 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => const { value: selectedValue } = selected; const value = - Array.isArray(selectedValue) && !isTuple(selectedValue) ? selectedValue[0] : selectedValue; + Array.isArray(selectedValue) && !isTuple(selectedValue) && !isItemOptionArray(selectedValue) + ? selectedValue[0] + : selectedValue; if (label === "lower") { return { range: { lte: value } }; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 1c17b67062a..2e5113b79e6 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -51,7 +51,6 @@ export const OrderDraftList: React.FC = ({ params }) => { const intl = useIntl(); const { updateListSettings, settings } = useListSettings(ListViews.DRAFT_LIST); const { valueProvider } = useConditionalFilterContext(); - const where = creatDraftOrderQueryVariables(valueProvider.value); usePaginationReset(orderDraftListUrl, params, settings.rowNumber); @@ -116,16 +115,18 @@ export const OrderDraftList: React.FC = ({ params }) => { }); const paginationState = createPaginationState(settings.rowNumber, params); + const filter = creatDraftOrderQueryVariables(valueProvider.value); + const queryVariables = React.useMemo(() => { return { ...paginationState, filter: { - ...where, + ...filter, search: params.query, }, sort: getSortQueryVariables(params), }; - }, [params]); + }, [params, settings.rowNumber]); const { data, refetch } = useOrderDraftListQuery({ displayLoader: true, variables: queryVariables, diff --git a/src/orders/views/OrderDraftList/filters.test.ts b/src/orders/views/OrderDraftList/filters.test.ts deleted file mode 100644 index bc41fe0241a..00000000000 --- a/src/orders/views/OrderDraftList/filters.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { FilterElement } from "@dashboard/components/ConditionalFilter/FilterElement"; -import { OrderDraftListUrlFilters } from "@dashboard/orders/urls"; -import { getExistingKeys } from "@test/filters"; - -import { getFilterVariables } from "./filters"; - -describe("Filtering query params", () => { - it("should be empty object if no params given", () => { - const params: OrderDraftListUrlFilters = {}; - const filterVariables = getFilterVariables({ - params, - filterContainer: [FilterElement.createEmpty()], - }); - - expect(getExistingKeys(filterVariables)).toHaveLength(0); - }); - it("should not be empty object if params given", () => { - const params: OrderDraftListUrlFilters = { - query: "test", - }; - const filterVariables = getFilterVariables({ - params, - filterContainer: [ - FilterElement.createStaticBySlug("created"), - FilterElement.createStaticBySlug("customer"), - ], - }); - - expect(getExistingKeys(filterVariables)).toHaveLength(3); - }); -}); From b3fa8e03eeabb891749f62dde7670776c6ac4e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Fri, 31 Jan 2025 11:53:16 +0100 Subject: [PATCH 11/16] Update .changeset/silver-items-unite.md Co-authored-by: Wojciech Mista --- .changeset/silver-items-unite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/silver-items-unite.md b/.changeset/silver-items-unite.md index 15098c12bf8..cf2c90d891f 100644 --- a/.changeset/silver-items-unite.md +++ b/.changeset/silver-items-unite.md @@ -2,4 +2,4 @@ "saleor-dashboard": patch --- -List of draft orders now used new filters +List of draft orders now use new filters From 0254c4cd1be3e38adfd1c3960c7ac0245db3aee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 5 Feb 2025 15:41:10 +0100 Subject: [PATCH 12/16] Fix get date value --- .../ConditionalFilter/API/InitialStateResponse.ts | 11 +++++++---- src/components/ConditionalFilter/queryVariables.ts | 7 +------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/ConditionalFilter/API/InitialStateResponse.ts b/src/components/ConditionalFilter/API/InitialStateResponse.ts index 524cba1fe16..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]; @@ -67,10 +74,6 @@ export class InitialStateResponse implements InitialState { } if (!token.isLoadable()) { - if (Array.isArray(token.value)) { - return token.value; - } - return [token.value] as string[]; } diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index bd038ae895c..67765efdca5 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -20,12 +20,7 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => if (!selected.conditionValue) return ""; const { label } = selected.conditionValue; - const { value: selectedValue } = selected; - - const value = - Array.isArray(selectedValue) && !isTuple(selectedValue) && !isItemOptionArray(selectedValue) - ? selectedValue[0] - : selectedValue; + const { value } = selected; if (label === "lower") { return { range: { lte: value } }; From e3be12d3ee59c706be14ad05f701fd85dce4a9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 5 Feb 2025 15:49:36 +0100 Subject: [PATCH 13/16] Generate feature flag --- .featureFlags/draft_orders_filters.md | 11 +++++++++ .featureFlags/generated.tsx | 22 ++++++++++++++---- .featureFlags/images/draft-orders-filters.png | Bin 0 -> 43290 bytes 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 .featureFlags/draft_orders_filters.md create mode 100644 .featureFlags/images/draft-orders-filters.png 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 9dd37debdf9..67d26b8a64f 100644 --- a/.featureFlags/generated.tsx +++ b/.featureFlags/generated.tsx @@ -1,13 +1,18 @@ // @ts-nocheck -import V24493 from "./images/discounts-list.png" -import J49162 from "./images/improved_refunds.png" +import Z20421 from "./images/discounts-list.png" +import B67283 from "./images/draft-orders-filters.png" +import P58973 from "./images/improved_refunds.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:

  • • Choose between automatic calculations based on selected items or enter refund amounts directly for overcharges and custom adjustments.

    @@ -27,6 +32,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 0000000000000000000000000000000000000000..05fd7a412e09c2447ba95ab1bd8ad8100a2b8e04 GIT binary patch literal 43290 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 literal 0 HcmV?d00001 From 2c1eb4cde1a529f153acec64e557695b282bf890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 5 Feb 2025 16:05:18 +0100 Subject: [PATCH 14/16] Display new filters base on a feature flag --- .../OrderDraftListPage/OrderDraftListPage.tsx | 88 ++++++++++++------- .../components/OrderDraftListPage/filters.ts | 51 +++++++++++ .../components/OrderDraftListPage/index.ts | 1 + .../views/OrderDraftList/OrderDraftList.tsx | 32 +++++-- .../__snapshots__/filters.test.ts.snap | 11 +++ .../views/OrderDraftList/filters.test.ts | 60 +++++++++++++ src/orders/views/OrderDraftList/filters.ts | 38 +++++++- 7 files changed, 239 insertions(+), 42 deletions(-) create mode 100644 src/orders/components/OrderDraftListPage/filters.ts create mode 100644 src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap create mode 100644 src/orders/views/OrderDraftList/filters.test.ts diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index b28e5f59a72..eefb29dca64 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -2,15 +2,10 @@ 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 { - FilterPresetsProps, - PageListProps, - RelayToFlat, - SearchPageProps, - SortPage, -} from "@dashboard/types"; +import { FilterPagePropsWithPresets, PageListProps, RelayToFlat, SortPage } from "@dashboard/types"; import { isLimitReached } from "@dashboard/utils/limits"; import { Box } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; @@ -19,11 +14,11 @@ import { useIntl } from "react-intl"; import { OrderDraftListDatagrid } from "../OrderDraftListDatagrid"; import { OrderDraftListHeader } from "../OrderDraftListHeader/OrderDraftListHeader"; import OrderLimitReached from "../OrderLimitReached"; +import { createFilterStructure, OrderDraftFilterKeys, OrderDraftListFilterOpts } from "./filters"; export interface OrderDraftListPageProps extends PageListProps, - SearchPageProps, - FilterPresetsProps, + FilterPagePropsWithPresets, SortPage { limits: RefreshLimitsQuery["shop"]["limits"]; orders: RelayToFlat; @@ -37,10 +32,12 @@ export interface OrderDraftListPageProps const OrderDraftListPage: React.FC = ({ selectedFilterPreset, disabled, + filterOpts, initialSearch, limits, onAdd, onFilterPresetsAll, + onFilterChange, onSearchChange, onFilterPresetChange, onFilterPresetDelete, @@ -49,12 +46,16 @@ const OrderDraftListPage: React.FC = ({ filterPresets, hasPresetsChanged, onDraftOrdersDelete, + onFilterAttributeFocus, + currencySymbol, selectedOrderDraftIds, ...listProps }) => { const intl = useIntl(); const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); + const filterStructure = createFilterStructure(intl, filterOpts); const limitsReached = isLimitReached(limits, "orders"); + const { enabled: isDraftOrdersFilteringEnabled } = useFlag("draft_orders_filters"); return ( <> @@ -84,27 +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", + })} + + )} + + } + /> + )} ; + customer: FilterOpts; +} + +const messages = defineMessages({ + created: { + id: "vwMO04", + defaultMessage: "Created", + description: "draft order", + }, + customer: { + id: "iEeIhY", + defaultMessage: "Customer", + description: "draft order", + }, +}); + +export function createFilterStructure( + intl: IntlShape, + opts: OrderDraftListFilterOpts, +): IFilter { + return [ + { + ...createDateField( + OrderDraftFilterKeys.created, + intl.formatMessage(messages.created), + opts.created.value, + ), + active: opts.created.active, + }, + { + ...createTextField( + OrderDraftFilterKeys.customer, + intl.formatMessage(messages.customer), + opts.customer.value, + ), + active: opts.customer.active, + }, + ]; +} diff --git a/src/orders/components/OrderDraftListPage/index.ts b/src/orders/components/OrderDraftListPage/index.ts index de5e8daab09..3fc3859a5a6 100644 --- a/src/orders/components/OrderDraftListPage/index.ts +++ b/src/orders/components/OrderDraftListPage/index.ts @@ -1,2 +1,3 @@ +export * from "./filters"; export { default } from "./OrderDraftListPage"; export * from "./OrderDraftListPage"; diff --git a/src/orders/views/OrderDraftList/OrderDraftList.tsx b/src/orders/views/OrderDraftList/OrderDraftList.tsx index 2e5113b79e6..3c715aa153d 100644 --- a/src/orders/views/OrderDraftList/OrderDraftList.tsx +++ b/src/orders/views/OrderDraftList/OrderDraftList.tsx @@ -8,6 +8,7 @@ import { creatDraftOrderQueryVariables } from "@dashboard/components/Conditional 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"; @@ -37,7 +38,7 @@ import { OrderDraftListUrlQueryParams, orderUrl, } from "../../urls"; -import { getFilterQueryParam, storageUtils } from "./filters"; +import { getFilterOpts, getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; import { getSortQueryVariables } from "./sort"; import { useBulkDeletion } from "./useBulkDeletion"; @@ -50,7 +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); @@ -85,7 +88,7 @@ export const OrderDraftList: React.FC = ({ params }) => { orders: true, }, }); - const [_, resetFilters, handleSearchChange] = createFilterHandlers({ + const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: orderDraftListUrl, getFilterQueryParam, @@ -113,23 +116,31 @@ export const OrderDraftList: React.FC = ({ params }) => { getUrl: orderDraftListUrl, storageUtils, }); - const paginationState = createPaginationState(settings.rowNumber, params); - const filter = creatDraftOrderQueryVariables(valueProvider.value); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params), + }), + [paginationState, params], + ); - const queryVariables = React.useMemo(() => { - return { + const newFiltersQueryVariables = React.useMemo( + () => ({ ...paginationState, filter: { ...filter, search: params.query, }, sort: getSortQueryVariables(params), - }; - }, [params, settings.rowNumber]); + }), + [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({ @@ -160,9 +171,11 @@ export const OrderDraftList: React.FC = ({ params }) => { { @@ -178,6 +191,7 @@ export const OrderDraftList: React.FC = ({ params }) => { onAdd={() => openModal("create-order")} onSort={handleSort} sort={getSortParams(params)} + currencySymbol={channel?.currencyCode} hasPresetsChanged={hasPresetsChanged} onDraftOrdersDelete={() => openModal("remove", { diff --git a/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap b/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap new file mode 100644 index 00000000000..b48bb140572 --- /dev/null +++ b/src/orders/views/OrderDraftList/__snapshots__/filters.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Filtering URL params should not be empty if active filters are present 1`] = ` +Object { + "createdFrom": "2019-12-09", + "createdTo": "2019-12-38", + "customer": "admin@example.com", +} +`; + +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"createdFrom=2019-12-09&createdTo=2019-12-38&customer=admin%40example.com"`; diff --git a/src/orders/views/OrderDraftList/filters.test.ts b/src/orders/views/OrderDraftList/filters.test.ts new file mode 100644 index 00000000000..25f07ec5330 --- /dev/null +++ b/src/orders/views/OrderDraftList/filters.test.ts @@ -0,0 +1,60 @@ +import { date } from "@dashboard/fixtures"; +import { createFilterStructure } from "@dashboard/orders/components/OrderDraftListPage"; +import { OrderDraftListUrlFilters } from "@dashboard/orders/urls"; +import { getFilterQueryParams } from "@dashboard/utils/filters"; +import { stringifyQs } from "@dashboard/utils/urls"; +import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; +import { config } from "@test/intl"; +import { createIntl } from "react-intl"; + +import { getFilterQueryParam, getFilterVariables } from "./filters"; + +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: OrderDraftListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + it("should not be empty object if params given", () => { + const params: OrderDraftListUrlFilters = { + createdFrom: date.from, + createdTo: date.to, + customer: "admin@example.com", + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(2); + }); +}); +describe("Filtering URL params", () => { + const intl = createIntl(config); + const filters = createFilterStructure(intl, { + created: { + active: false, + value: { + max: date.to, + min: date.from, + }, + }, + customer: { + active: false, + value: "admin@example.com", + }, + }); + + it("should be empty if no active filters", () => { + const filterQueryParams = getFilterQueryParams(filters, getFilterQueryParam); + + expect(getExistingKeys(filterQueryParams)).toHaveLength(0); + }); + it("should not be empty if active filters are present", () => { + const filterQueryParams = getFilterQueryParams( + setFilterOptsStatus(filters, true), + getFilterQueryParam, + ); + + expect(filterQueryParams).toMatchSnapshot(); + expect(stringifyQs(filterQueryParams)).toMatchSnapshot(); + }); +}); diff --git a/src/orders/views/OrderDraftList/filters.ts b/src/orders/views/OrderDraftList/filters.ts index 588c248103a..cc76072d6a8 100644 --- a/src/orders/views/OrderDraftList/filters.ts +++ b/src/orders/views/OrderDraftList/filters.ts @@ -1,9 +1,16 @@ // @ts-strict-ignore import { FilterElement } from "@dashboard/components/Filter"; +import { OrderDraftFilterInput } from "@dashboard/graphql"; +import { maybe } from "@dashboard/misc"; +import { + OrderDraftFilterKeys, + OrderDraftListFilterOpts, +} from "@dashboard/orders/components/OrderDraftListPage"; import { createFilterTabUtils, createFilterUtils, + getGteLteVariables, getMinMaxQueryParam, getSingleValueQueryParam, } from "../../../utils/filters"; @@ -15,9 +22,34 @@ import { export const ORDER_DRAFT_FILTERS_KEY = "orderDraftFilters"; -export enum OrderDraftFilterKeys { - created = "created", - customer = "customer", +export function getFilterOpts(params: OrderDraftListUrlFilters): OrderDraftListFilterOpts { + return { + created: { + active: maybe( + () => [params.createdFrom, params.createdTo].some(field => field !== undefined), + false, + ), + value: { + max: maybe(() => params.createdTo), + min: maybe(() => params.createdFrom), + }, + }, + customer: { + active: !!maybe(() => params.customer), + value: params.customer, + }, + }; +} + +export function getFilterVariables(params: OrderDraftListUrlFilters): OrderDraftFilterInput { + return { + created: getGteLteVariables({ + gte: params.createdFrom, + lte: params.createdTo, + }), + customer: params.customer, + search: params.query, + }; } export function getFilterQueryParam( From a99b8900d8bb3b9e61d90c5f2550fa8d837a13ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 5 Feb 2025 21:09:05 +0100 Subject: [PATCH 15/16] Extract messages --- locale/defaultMessages.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 1972ee4b6a3..7671ec86b1c 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -7154,6 +7154,10 @@ "context": "button", "string": "Apply" }, + "iEeIhY": { + "context": "draft order", + "string": "Customer" + }, "iFM716": { "context": "grant refund, refund card subtitle", "string": "How much money do you want to return to the customer for the order?" @@ -9184,6 +9188,10 @@ "vuKrlW": { "string": "Stock" }, + "vwMO04": { + "context": "draft order", + "string": "Created" + }, "vz3yxp": { "string": "Channels permissions" }, From 54e659c6e3b942210ef5d28b3f2546124bb2a1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Mon, 10 Feb 2025 12:12:10 +0100 Subject: [PATCH 16/16] Add tests --- .../ConditionalFilter/queryVariables.test.ts | 111 +++++++++++++++++- .../ConditionalFilter/queryVariables.ts | 2 +- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/components/ConditionalFilter/queryVariables.test.ts b/src/components/ConditionalFilter/queryVariables.test.ts index 853c298b586..16d02ce1ec6 100644 --- a/src/components/ConditionalFilter/queryVariables.test.ts +++ b/src/components/ConditionalFilter/queryVariables.test.ts @@ -2,7 +2,11 @@ import { Condition, FilterContainer, FilterElement } from "./FilterElement"; import { ConditionOptions } from "./FilterElement/ConditionOptions"; import { ConditionSelected } from "./FilterElement/ConditionSelected"; import { ExpressionValue } from "./FilterElement/FilterElement"; -import { createProductQueryVariables } from "./queryVariables"; +import { + creatDraftOrderQueryVariables, + createProductQueryVariables, + mapStaticQueryPartToLegacyVariables, +} from "./queryVariables"; describe("ConditionalFilter / queryVariables / createProductQueryVariables", () => { it("should return empty variables for empty filters", () => { @@ -75,3 +79,108 @@ describe("ConditionalFilter / queryVariables / createProductQueryVariables", () expect(result).toEqual(expectedOutput); }); }); + +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 + const queryPart = "queryPart"; + const expectedOutput = "queryPart"; + + // Act + const result = mapStaticQueryPartToLegacyVariables(queryPart); + + // Assert + expect(result).toEqual(expectedOutput); + }); + + it("should transform range input to legacy format", () => { + // Arrange + const queryPart = { range: { lte: "value" } }; + const expectedOutput = { lte: "value" }; + + // Act + const result = mapStaticQueryPartToLegacyVariables(queryPart); + + // Assert + expect(result).toEqual(expectedOutput); + }); + + it("should transform eq input to legacy format", () => { + // Arrange + const queryPart = { eq: "value" }; + const expectedOutput = "value"; + // Act + const result = mapStaticQueryPartToLegacyVariables(queryPart); + + // Assert + expect(result).toEqual(expectedOutput); + }); + + it("should transform oneOf input to legacy format", () => { + // Arrange + const queryPart = { oneOf: ["value1", "value2"] }; + const expectedOutput = ["value1", "value2"]; + // Act + const result = mapStaticQueryPartToLegacyVariables(queryPart); + + // Assert + expect(result).toEqual(expectedOutput); + }); +}); diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 67765efdca5..c73211bb6ac 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -59,7 +59,7 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => return value; }; -const mapStaticQueryPartToLegacyVariables = (queryPart: StaticQueryPart) => { +export const mapStaticQueryPartToLegacyVariables = (queryPart: StaticQueryPart) => { if (typeof queryPart !== "object") { return queryPart; }