From c070106035695033077de8ea430b3c72a34aa105 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 4 Feb 2025 12:31:35 +0100 Subject: [PATCH 01/12] Collection filters --- .../CollectionListPage/CollectionListPage.tsx | 21 ++--- src/collections/index.tsx | 7 +- .../views/CollectionList/CollectionList.tsx | 56 ++++++------- .../views/CollectionList/filters.test.ts | 19 +---- .../views/CollectionList/filters.ts | 17 +++- .../API/CollectionFilterAPIProvider.tsx | 83 +++++++++++++++++++ .../collections/InitialCollectionState.ts | 29 +++++++ .../API/initialState/collections/types.ts | 0 .../collections/useInitialCollectionsState.ts | 60 ++++++++++++++ .../API/initialState/helpers.ts | 25 +++++- .../API/initialState/types.ts | 1 + .../FilterElement/Condition.ts | 7 +- .../FilterElement/FilterElement.ts | 7 +- .../TokenArray/TokenArray.test.ts | 4 +- .../TokenArray/fetchingParams.ts | 35 ++++++++ .../ValueProvider/TokenArray/index.ts | 38 +++++---- .../ValueProvider/useUrlValueProvider.ts | 19 +++-- src/components/ConditionalFilter/constants.ts | 48 +++++++++++ .../ConditionalFilter/context/provider.tsx | 30 +++++++ .../ConditionalFilter/queryVariables.ts | 36 ++++++++ src/components/ConditionalFilter/types.ts | 8 ++ 21 files changed, 451 insertions(+), 99 deletions(-) create mode 100644 src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx create mode 100644 src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts create mode 100644 src/components/ConditionalFilter/API/initialState/collections/types.ts create mode 100644 src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts create mode 100644 src/components/ConditionalFilter/types.ts diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index 887ffb8dbf2..d82f0e8bd75 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -9,24 +9,23 @@ import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { TopNav } from "@dashboard/components/AppLayout/TopNav"; import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton"; import { DashboardCard } from "@dashboard/components/Card"; -import { getByName } from "@dashboard/components/Filter/utils"; import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect"; import { ListPageLayout } from "@dashboard/components/Layouts"; import { getPrevLocationState } from "@dashboard/hooks/useBackLinkWithState"; import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; -import { FilterPageProps, PageListProps, SortPage } from "@dashboard/types"; +import { PageListProps, SearchPageProps, SortPage, TabPageProps } from "@dashboard/types"; import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useLocation } from "react-router"; import { CollectionListDatagrid } from "../CollectionListDatagrid"; -import { CollectionFilterKeys, CollectionListFilterOpts, createFilterStructure } from "./filters"; export interface CollectionListPageProps extends PageListProps, - Omit, "onTabDelete">, + Omit, + SearchPageProps, SortPage { onTabUpdate: (tabName: string) => void; selectedChannelId: string; @@ -51,21 +50,16 @@ const CollectionListPage: React.FC = ({ onTabUpdate, selectedChannelId, tabs, - filterOpts, - onFilterChange, - onFilterAttributeFocus, hasPresetsChanged, - currencySymbol, selectedCollectionIds, onCollectionsDelete, + ...listProps }) => { const intl = useIntl(); const location = useLocation(); const navigate = useNavigator(); - const filterStructure = createFilterStructure(intl, filterOpts); const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); - const filterDependency = filterStructure.find(getByName("channel")); return ( @@ -117,12 +111,9 @@ const CollectionListPage: React.FC = ({ = ({ { navigate(collectionUrl(id), { state: getPrevLocationState(location), diff --git a/src/collections/index.tsx b/src/collections/index.tsx index 920c02dc2fa..e2b141e551c 100644 --- a/src/collections/index.tsx +++ b/src/collections/index.tsx @@ -1,3 +1,4 @@ +import { ConditionalCollectionFilterProvider } from "@dashboard/components/ConditionalFilter"; import { Route } from "@dashboard/components/Router"; import { sectionNames } from "@dashboard/intl"; import { asSortParams } from "@dashboard/utils/sort"; @@ -24,7 +25,11 @@ const CollectionList: React.FC> = ({ location }) => { const qs = parseQs(location.search.substr(1)) as any; const params: CollectionListUrlQueryParams = asSortParams(qs, CollectionListUrlSortField); - return ; + return ( + + + + ); }; interface CollectionDetailsRouteProps { diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 8298131cf9a..07167594b8f 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -1,6 +1,6 @@ // @ts-strict-ignore 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 { useCollectionBulkDeleteMutation, useCollectionListQuery } from "@dashboard/graphql"; @@ -20,10 +20,10 @@ import { ListViews } from "@dashboard/types"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers"; import createSortHandler from "@dashboard/utils/handlers/sortHandler"; -import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps"; +import { mapEdgesToItems } from "@dashboard/utils/maps"; import { getSortParams } from "@dashboard/utils/sort"; import isEqual from "lodash/isEqual"; -import React, { useCallback, useEffect } from "react"; +import React, { useCallback } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import CollectionListPage from "../../components/CollectionListPage/CollectionListPage"; @@ -32,8 +32,8 @@ import { CollectionListUrlDialog, CollectionListUrlQueryParams, } from "../../urls"; -import { getFilterOpts, getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; -import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort"; +import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; +import { getSortQueryVariables } from "./sort"; interface CollectionListProps { params: CollectionListUrlQueryParams; @@ -47,14 +47,14 @@ export const CollectionList: React.FC = ({ params }) => { usePaginationReset(collectionListUrl, params, settings.rowNumber); - const { channel } = useAppChannel(false); const { clearRowSelection, selectedRowIds, setClearDatagridRowSelectionCallback, setSelectedRowIds, } = useRowSelection(params); - const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ + const { valueProvider } = useConditionalFilterContext(); + const [_, resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: collectionListUrl, getFilterQueryParam, @@ -62,11 +62,7 @@ export const CollectionList: React.FC = ({ params }) => { params, keepActiveTab: true, }); - const { availableChannels } = useAppChannel(false); - const channelOpts = availableChannels - ? mapNodeToChoice(availableChannels, channel => channel.slug) - : null; - const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); + // const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); const { selectedPreset, presets, @@ -87,9 +83,12 @@ export const CollectionList: React.FC = ({ params }) => { const queryVariables = React.useMemo( () => ({ ...paginationState, - filter: getFilterVariables(params), + filter: getFilterVariables({ + params, + filterContainer: valueProvider.value, + }), sort: getSortQueryVariables(params), - channel: selectedChannel?.slug, + // channel: selectedChannel?.slug, }), [params, settings.rowNumber], ); @@ -111,18 +110,17 @@ export const CollectionList: React.FC = ({ params }) => { } }, }); - const filterOpts = getFilterOpts(params, channelOpts); - useEffect(() => { - if (!canBeSorted(params.sort, !!selectedChannel)) { - navigate( - collectionListUrl({ - ...params, - sort: DEFAULT_SORT_KEY, - }), - ); - } - }, [params]); + // useEffect(() => { + // if (!canBeSorted(params.sort, !!selectedChannel)) { + // navigate( + // collectionListUrl({ + // ...params, + // sort: DEFAULT_SORT_KEY, + // }), + // ); + // } + // }, [params]); const [openModal, closeModal] = createDialogActionHandlers< CollectionListUrlDialog, @@ -164,7 +162,7 @@ export const CollectionList: React.FC = ({ params }) => { = ({ params }) => { onSort={handleSort} onUpdateListSettings={updateListSettings} sort={getSortParams(params)} - selectedChannelId={selectedChannel?.id} - filterOpts={filterOpts} - onFilterChange={changeFilters} + selectedChannelId={""} // TODO + // filterOpts={filterOpts} + // onFilterChange={changeFilters} selectedCollectionIds={selectedRowIds} onSelectCollectionIds={handleSetSelectedCollectionIds} hasPresetsChanged={hasPresetsChanged} diff --git a/src/collections/views/CollectionList/filters.test.ts b/src/collections/views/CollectionList/filters.test.ts index 9a781222bdd..259da3002c5 100644 --- a/src/collections/views/CollectionList/filters.test.ts +++ b/src/collections/views/CollectionList/filters.test.ts @@ -1,5 +1,4 @@ import { createFilterStructure } from "@dashboard/collections/components/CollectionListPage"; -import { CollectionListUrlFilters } from "@dashboard/collections/urls"; import { CollectionPublished } from "@dashboard/graphql"; import { FilterOpts } from "@dashboard/types"; import { getFilterQueryParams } from "@dashboard/utils/filters"; @@ -9,24 +8,8 @@ import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; import { config } from "@test/intl"; import { createIntl } from "react-intl"; -import { getFilterQueryParam, getFilterVariables } from "./filters"; +import { getFilterQueryParam } from "./filters"; -describe("Filtering query params", () => { - it("should be empty object if no params given", () => { - const params: CollectionListUrlFilters = {}; - const filterVariables = getFilterVariables(params); - - expect(getExistingKeys(filterVariables)).toHaveLength(0); - }); - it("should not be empty object if params given", () => { - const params: CollectionListUrlFilters = { - status: CollectionPublished.PUBLISHED, - }; - const filterVariables = getFilterVariables(params); - - expect(getExistingKeys(filterVariables)).toHaveLength(1); - }); -}); describe("Filtering URL params", () => { const intl = createIntl(config); const filters = createFilterStructure(intl, { diff --git a/src/collections/views/CollectionList/filters.ts b/src/collections/views/CollectionList/filters.ts index 467d39ed91a..557859cbde5 100644 --- a/src/collections/views/CollectionList/filters.ts +++ b/src/collections/views/CollectionList/filters.ts @@ -3,6 +3,8 @@ import { CollectionFilterKeys, CollectionListFilterOpts, } from "@dashboard/collections/components/CollectionListPage"; +import { FilterContainer } from "@dashboard/components/ConditionalFilter/FilterElement"; +import { createCollectionsQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; import { FilterElement, FilterElementRegular } from "@dashboard/components/Filter"; import { CollectionFilterInput, CollectionPublished } from "@dashboard/graphql"; import { findValueInEnum, maybe } from "@dashboard/misc"; @@ -39,10 +41,19 @@ export function getFilterOpts( }; } -export function getFilterVariables(params: CollectionListUrlFilters): CollectionFilterInput { +export function getFilterVariables({ + filterContainer, + params, +}: { + filterContainer: FilterContainer; + params: CollectionListUrlFilters; +}): CollectionFilterInput { + const { channel, ...vars } = createCollectionsQueryVariables(filterContainer); + return { - published: params.status ? findValueInEnum(params.status, CollectionPublished) : undefined, - search: params.query, + ...vars, + search: params.query, // TODO: change to 'search' + channel: channel, }; } diff --git a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx new file mode 100644 index 00000000000..c684bdfe8d0 --- /dev/null +++ b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx @@ -0,0 +1,83 @@ +import { ApolloClient, useApolloClient } from "@apollo/client"; + +import { RowType } from "../constants"; +import { FilterContainer, FilterElement } from "../FilterElement"; +import { FilterAPIProvider } from "./FilterAPIProvider"; +import { BooleanValuesHandler, ChannelHandler, Handler, NoopValuesHandler } from "./Handler"; + +const isStaticBoolean = (rowType: RowType) => { + return ["published"].includes(rowType); +}; + +const getFilterElement = (value: FilterContainer, index: number): FilterElement => { + const possibleFilterElement = value[index]; + + if (typeof possibleFilterElement !== "string" && !Array.isArray(possibleFilterElement)) { + return possibleFilterElement; + } + + throw new Error("Unknown filter element used to create API handler"); +}; +const createAPIHandler = ( + selectedRow: FilterElement, + client: ApolloClient, + inputValue: string, +): Handler => { + const rowType = selectedRow.rowType(); + + if (rowType && isStaticBoolean(rowType) && rowType !== "attribute") { + return new BooleanValuesHandler([ + { + label: "True", + value: "PUBLISHED", + type: rowType, + slug: "published", + }, + { + label: "False", + value: "HIDDEN", + type: rowType, + slug: "hidden", + }, + ]); + } + + if (rowType === "ids") { + return new NoopValuesHandler([]); + } + + if (rowType === "metadata") { + return new NoopValuesHandler([]); + } + + if (rowType === "channel") { + return new ChannelHandler(client, inputValue); + } + + throw new Error(`Unknown filter element: "${rowType}"`); +}; + +export const useCollectionFilterAPIProvider = (): FilterAPIProvider => { + const client = useApolloClient(); + + const fetchRightOptions = async ( + position: string, + value: FilterContainer, + inputValue: string, + ) => { + const index = parseInt(position, 10); + const filterElement = getFilterElement(value, index); + + const handler = createAPIHandler(filterElement, client, inputValue); + + return handler.fetch(); + }; + const fetchLeftOptions = async () => { + return []; + }; + + return { + fetchRightOptions, + fetchLeftOptions, + }; +}; diff --git a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts new file mode 100644 index 00000000000..f4d49820973 --- /dev/null +++ b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts @@ -0,0 +1,29 @@ +import { ItemOption } from "@dashboard/components/ConditionalFilter/FilterElement/ConditionValue"; +import { UrlToken } from "@dashboard/components/ConditionalFilter/ValueProvider/UrlToken"; + +export interface InitialCollectionState { + channel: ItemOption[]; +} + +export class InitialCollectionStateResponse implements InitialCollectionState { + constructor(public channel: ItemOption[] = []) {} + + static empty() { + return new InitialCollectionStateResponse(); + } + + public filterByUrlToken(token: UrlToken) { + const entry = this.getEntryByName(token.name); + + return entry.filter(item => item.value === token.value); + } + + private getEntryByName(name: string): ItemOption[] { + switch (name) { + case "channel": + return this.channel; + default: + return []; + } + } +} diff --git a/src/components/ConditionalFilter/API/initialState/collections/types.ts b/src/components/ConditionalFilter/API/initialState/collections/types.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts new file mode 100644 index 00000000000..5acf7e6a2ba --- /dev/null +++ b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts @@ -0,0 +1,60 @@ +import { useApolloClient } from "@apollo/client"; +import { CollectionFetchingParams } from "@dashboard/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams"; +import { + _GetLegacyChannelOperandsDocument, + _GetLegacyChannelOperandsQuery, + _GetLegacyChannelOperandsQueryVariables, +} from "@dashboard/graphql"; +import { useState } from "react"; + +import { createInitialCollectionState } from "../helpers"; +import { InitialCollectionAPIResponse } from "../types"; +import { InitialCollectionStateResponse } from "./InitialCollectionState"; + +export interface InitialCollectionAPIState { + data: InitialCollectionStateResponse; + loading: boolean; + fetchQueries: (params: CollectionFetchingParams) => Promise; +} + +export const useInitialCollectionState = (): InitialCollectionAPIState => { + const client = useApolloClient(); + + const [data, setData] = useState( + InitialCollectionStateResponse.empty(), + ); + const [loading, setLoading] = useState(true); + + const queriesToRun: Array> = []; + + const fetchQueries = async ({ channel }: CollectionFetchingParams) => { + if (channel?.length > 0) { + queriesToRun.push( + client.query<_GetLegacyChannelOperandsQuery, _GetLegacyChannelOperandsQueryVariables>({ + query: _GetLegacyChannelOperandsDocument, + }), + ); + } + + if (queriesToRun.length === 0) { + setLoading(false); + + return; + } + + const data = await Promise.all(queriesToRun); + + const initialState = createInitialCollectionState(data, channel); + + const collectionResponse = new InitialCollectionStateResponse(initialState.channel); + + setData(collectionResponse); + setLoading(false); + }; + + return { + data, + loading, + fetchQueries, + }; +}; diff --git a/src/components/ConditionalFilter/API/initialState/helpers.ts b/src/components/ConditionalFilter/API/initialState/helpers.ts index 4457d7e7ff0..610bc9a64a9 100644 --- a/src/components/ConditionalFilter/API/initialState/helpers.ts +++ b/src/components/ConditionalFilter/API/initialState/helpers.ts @@ -11,8 +11,9 @@ import { import { createBooleanOptions } from "../../constants"; import { createOptionsFromAPI } from "../Handler"; import { InitialState } from "../InitialStateResponse"; +import { InitialCollectionState } from "./collections/InitialCollectionState"; import { InitialOrderState } from "./orders/InitialOrderState"; -import { InitialAPIResponse, InitialOrderAPIResponse } from "./types"; +import { InitialAPIResponse, InitialCollectionAPIResponse, InitialOrderAPIResponse } from "./types"; const isChannelQuery = ( query: InitialAPIResponse, @@ -136,3 +137,25 @@ export const createInitialOrderState = (data: InitialOrderAPIResponse[]) => updatedAt: "", }, ); + +export const createInitialCollectionState = ( + data: InitialCollectionAPIResponse[], + channel: string[], +) => + data.reduce( + (acc, query) => { + if (isChannelQuery(query)) { + return { + ...acc, + channel: (query.data?.channels ?? []) + .filter(({ slug }) => channel.includes(slug)) + .map(({ id, name, slug }) => ({ label: name, value: id, slug })), + }; + } + + return acc; + }, + { + channel: [], + }, + ); diff --git a/src/components/ConditionalFilter/API/initialState/types.ts b/src/components/ConditionalFilter/API/initialState/types.ts index faa13239959..6cd48769fde 100644 --- a/src/components/ConditionalFilter/API/initialState/types.ts +++ b/src/components/ConditionalFilter/API/initialState/types.ts @@ -15,3 +15,4 @@ export type InitialAPIResponse = ApolloQueryResult< | _SearchAttributeOperandsQuery >; export type InitialOrderAPIResponse = ApolloQueryResult<_GetChannelOperandsQuery>; +export type InitialCollectionAPIResponse = ApolloQueryResult<_GetChannelOperandsQuery>; diff --git a/src/components/ConditionalFilter/FilterElement/Condition.ts b/src/components/ConditionalFilter/FilterElement/Condition.ts index b1e9abc4f7f..8d98b08637e 100644 --- a/src/components/ConditionalFilter/FilterElement/Condition.ts +++ b/src/components/ConditionalFilter/FilterElement/Condition.ts @@ -1,6 +1,6 @@ -import { InitialOrderStateResponse } from "../API/initialState/orders/InitialOrderState"; import { InitialStateResponse } from "../API/InitialStateResponse"; import { LeftOperand } from "../LeftOperandsProvider"; +import { InitialResponseType } from "../types"; import { UrlToken } from "./../ValueProvider/UrlToken"; import { ConditionOptions, StaticElementName } from "./ConditionOptions"; import { ConditionSelected } from "./ConditionSelected"; @@ -45,10 +45,7 @@ export class Condition { return new Condition(options, ConditionSelected.fromConditionItem(options.first()), false); } - public static fromUrlToken( - token: UrlToken, - response: InitialStateResponse | InitialOrderStateResponse, - ) { + public static fromUrlToken(token: UrlToken, response: InitialResponseType) { if (ConditionOptions.isStaticName(token.name)) { const staticOptions = ConditionOptions.fromStaticElementName(token.name); const selectedOption = staticOptions.findByLabel(token.conditionKind); diff --git a/src/components/ConditionalFilter/FilterElement/FilterElement.ts b/src/components/ConditionalFilter/FilterElement/FilterElement.ts index daa23766fcf..d81c238672a 100644 --- a/src/components/ConditionalFilter/FilterElement/FilterElement.ts +++ b/src/components/ConditionalFilter/FilterElement/FilterElement.ts @@ -1,7 +1,7 @@ -import { InitialOrderStateResponse } from "../API/initialState/orders/InitialOrderState"; import { InitialStateResponse } from "../API/InitialStateResponse"; import { RowType, STATIC_OPTIONS } from "../constants"; import { LeftOperand } from "../LeftOperandsProvider"; +import { InitialResponseType } from "../types"; import { TokenType, UrlEntry, UrlToken } from "./../ValueProvider/UrlToken"; import { Condition } from "./Condition"; import { ConditionItem, ConditionOptions, StaticElementName } from "./ConditionOptions"; @@ -175,10 +175,7 @@ export class FilterElement { return new FilterElement(ExpressionValue.fromSlug(slug), Condition.emptyFromSlug(slug), false); } - public static fromUrlToken( - token: UrlToken, - response: InitialStateResponse | InitialOrderStateResponse, - ) { + public static fromUrlToken(token: UrlToken, response: InitialResponseType) { if (token.isStatic()) { return new FilterElement( ExpressionValue.fromUrlToken(token), diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts index b29af42321f..5b4ee3715c0 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts @@ -15,7 +15,7 @@ describe("ConditionalFilter / ValueProvider / TokenArray", () => { // Arrange const url = new TokenArray(""); // Act - const fetchingParams = url.getFetchingParams(productParams); + const fetchingParams = url.getFetchingParams(productParams, "product"); // Assert expect(fetchingParams).toEqual({ @@ -44,7 +44,7 @@ describe("ConditionalFilter / ValueProvider / TokenArray", () => { }); // Act const url = new TokenArray(params.toString()); - const fetchingParams = url.getFetchingParams(productParams); + const fetchingParams = url.getFetchingParams(productParams, "product"); // Assert expect(fetchingParams).toEqual({ diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts index aa19ebdfd77..0ac8e9a9275 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts @@ -1,5 +1,7 @@ import { TokenType, UrlToken } from "../UrlToken"; +export type FilterProviderType = "product" | "collection" | "order" | "discount"; + export interface FetchingParams { category: string[]; collection: string[]; @@ -18,6 +20,10 @@ export interface OrderFetchingParams { ids: string[]; } +export interface CollectionFetchingParams { + channel: string[]; +} + type FetchingParamsKeys = keyof Omit; type OrderParamsKeys = keyof OrderFetchingParams; @@ -39,6 +45,23 @@ export const emptyOrderFetchingParams: OrderFetchingParams = { ids: [], }; +export const emptyCollectionFetchingParams: CollectionFetchingParams = { + channel: [], +}; + +export type FetchingParamsType = OrderFetchingParams | FetchingParams | CollectionFetchingParams; + +export const getEmptyFetchingParams = (type: FilterProviderType) => { + switch (type) { + case "order": + return emptyOrderFetchingParams; + case "collection": + return emptyCollectionFetchingParams; + default: + return emptyFetchingParams; + } +}; + const unique = (array: Iterable) => Array.from(new Set(array)); const includedInParams = (c: UrlToken) => TokenType.ATTRIBUTE_DROPDOWN === c.type || TokenType.ATTRIBUTE_MULTISELECT === c.type; @@ -88,3 +111,15 @@ export const toOrderFetchingParams = (p: OrderFetchingParams, c: UrlToken) => { return p; }; + +export const toCollectionFetchingParams = (p: CollectionFetchingParams, c: UrlToken) => { + const key = c.name as keyof CollectionFetchingParams; + + if (!p[key]) { + p[key] = []; + } + + p[key] = unique(p[key].concat(c.value)); + + return p; +}; diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts index c55a42bc3a5..5c8eb648a06 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts @@ -1,12 +1,16 @@ import { parse, ParsedQs } from "qs"; -import { InitialOrderStateResponse } from "../../API/initialState/orders/InitialOrderState"; import { InitialStateResponse } from "../../API/InitialStateResponse"; import { FilterContainer, FilterElement } from "../../FilterElement"; +import { InitialResponseType } from "../../types"; import { UrlEntry, UrlToken } from "../UrlToken"; import { + CollectionFetchingParams, FetchingParams, + FetchingParamsType, + FilterProviderType, OrderFetchingParams, + toCollectionFetchingParams, toFetchingParams, toOrderFetchingParams, } from "./fetchingParams"; @@ -43,7 +47,7 @@ const tokenizeUrl = (urlParams: string) => { }; const mapUrlTokensToFilterValues = ( urlTokens: TokenArray, - response: InitialStateResponse | InitialOrderStateResponse, + response: InitialResponseType, ): FilterContainer => urlTokens.map(el => { if (typeof el === "string") { @@ -62,25 +66,31 @@ export class TokenArray extends Array { super(...tokenizeUrl(url)); } - public getFetchingParams(params: OrderFetchingParams | FetchingParams) { - if ("paymentStatus" in params) { - return this.asFlatArray() - .filter(token => token.isLoadable()) - .reduce(toOrderFetchingParams, params); + public getFetchingParams(params: FetchingParamsType, type: FilterProviderType) { + switch (type) { + case "order": + return this.asFlatArray() + .filter(token => token.isLoadable()) + .reduce(toOrderFetchingParams, params as OrderFetchingParams); + case "collection": + return this.asFlatArray() + .filter(token => token.isLoadable()) + .reduce( + toCollectionFetchingParams, + params as CollectionFetchingParams, + ); + default: + return this.asFlatArray() + .filter(token => token.isLoadable()) + .reduce(toFetchingParams, params as FetchingParams); } - - return this.asFlatArray() - .filter(token => token.isLoadable()) - .reduce(toFetchingParams, params); } public asFlatArray() { return flatenate(this); } - public asFilterValuesFromResponse( - response: InitialStateResponse | InitialOrderStateResponse, - ): FilterContainer { + public asFilterValuesFromResponse(response: InitialResponseType): FilterContainer { return this.map(el => { if (typeof el === "string") { return el; diff --git a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts index 16037fcffc0..25769cec389 100644 --- a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts +++ b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts @@ -4,22 +4,24 @@ import { useEffect, useState } from "react"; import useRouter from "use-react-router"; import { InitialAPIState } from "../API"; +import { InitialCollectionAPIState } from "../API/initialState/collections/useInitialCollectionsState"; import { InitialOrderAPIState } from "../API/initialState/orders/useInitialOrderState"; import { FilterContainer, FilterElement } from "../FilterElement"; import { FilterValueProvider } from "../FilterValueProvider"; import { TokenArray } from "./TokenArray"; import { - emptyFetchingParams, - emptyOrderFetchingParams, + CollectionFetchingParams, FetchingParams, + FilterProviderType, + getEmptyFetchingParams, OrderFetchingParams, } from "./TokenArray/fetchingParams"; import { prepareStructure } from "./utils"; export const useUrlValueProvider = ( locationSearch: string, - type: "product" | "order" | "discount", - initialState?: InitialAPIState | InitialOrderAPIState, + type: FilterProviderType, + initialState?: InitialAPIState | InitialOrderAPIState | InitialCollectionAPIState, ): FilterValueProvider => { const router = useRouter(); const params = new URLSearchParams(locationSearch); @@ -37,8 +39,8 @@ export const useUrlValueProvider = ( params.delete("after"); const tokenizedUrl = new TokenArray(params.toString()); - const paramsFromType = type === "product" ? emptyFetchingParams : emptyOrderFetchingParams; - const fetchingParams = tokenizedUrl.getFetchingParams(paramsFromType); + const paramsFromType = getEmptyFetchingParams(type); + const fetchingParams = tokenizedUrl.getFetchingParams(paramsFromType, type); useEffect(() => { if (initialState) { @@ -51,6 +53,11 @@ export const useUrlValueProvider = ( fetchingParams as OrderFetchingParams, ); break; + case "collection": + (initialState as InitialCollectionAPIState).fetchQueries( + fetchingParams as CollectionFetchingParams, + ); + break; } } }, [locationSearch]); diff --git a/src/components/ConditionalFilter/constants.ts b/src/components/ConditionalFilter/constants.ts index b3084e64327..63051544197 100644 --- a/src/components/ConditionalFilter/constants.ts +++ b/src/components/ConditionalFilter/constants.ts @@ -118,6 +118,20 @@ export const STATIC_CONDITIONS = { value: "input-1", }, ], + published: [ + { + type: "select", + label: "is", + value: "input-1", + }, + ], + slugs: [ + { + type: "text", + label: "is", + value: "input-1", + }, + ], }; export const CONSTRAINTS = { @@ -273,10 +287,44 @@ export const STATIC_ORDER_OPTIONS: LeftOperand[] = [ }, ]; +export const STATIC_COLLECTION_OPTIONS: LeftOperand[] = [ + { + value: "published", + label: "Published", + type: "published", + slug: "published", + }, + { + value: "metadata", + label: "Metadata", + type: "metadata", + slug: "metadata", + }, + { + value: "ids", + label: "IDs", + type: "ids", + slug: "ids", + }, + { + value: "channel", + label: "Channel", + type: "channel", + slug: "channel", + }, + { + value: "slugs", + label: "Slug", + type: "slugs", // TODO - its [String!] not String! + slug: "slugs", + }, +]; + export const STATIC_OPTIONS = [ ...STATIC_PRODUCT_OPTIONS, ...STATIC_DISCOUNT_OPTIONS, ...STATIC_ORDER_OPTIONS, + ...STATIC_COLLECTION_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..81f9201bf64 100644 --- a/src/components/ConditionalFilter/context/provider.tsx +++ b/src/components/ConditionalFilter/context/provider.tsx @@ -1,11 +1,14 @@ import React, { FC } from "react"; +import { useCollectionFilterAPIProvider } from "../API/CollectionFilterAPIProvider"; import { useDiscountFilterAPIProvider } from "../API/DiscountFiltersAPIProvider"; +import { useInitialCollectionState } from "../API/initialState/collections/useInitialCollectionsState"; import { useInitialOrderState } from "../API/initialState/orders/useInitialOrderState"; import { useProductInitialAPIState } from "../API/initialState/useInitialAPIState"; import { useOrderFilterAPIProvider } from "../API/OrderFilterAPIProvider"; import { useProductFilterAPIProvider } from "../API/ProductFilterAPIProvider"; import { + STATIC_COLLECTION_OPTIONS, STATIC_DISCOUNT_OPTIONS, STATIC_ORDER_OPTIONS, STATIC_PRODUCT_OPTIONS, @@ -90,3 +93,30 @@ export const ConditionalOrderFilterProvider: FC<{ ); }; + +export const ConditionalCollectionFilterProvider: FC<{ + locationSearch: string; +}> = ({ children, locationSearch }) => { + const apiProvider = useCollectionFilterAPIProvider(); + + const initialState = useInitialCollectionState(); + + const valueProvider = useUrlValueProvider(locationSearch, "collection", initialState); + const leftOperandsProvider = useFilterLeftOperandsProvider(STATIC_COLLECTION_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..5f1cdc1612e 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -1,5 +1,6 @@ import { AttributeInput, + CollectionFilterInput, DateRangeInput, DateTimeFilterInput, DateTimeRangeInput, @@ -57,6 +58,27 @@ const createStaticQueryPart = (selected: ConditionSelected): StaticQueryPart => return value; }; + +const mapStaticQueryPartToLegacyVariables = (queryPart: StaticQueryPart) => { + if (typeof queryPart !== "object") { + return queryPart; + } + + if ("range" in queryPart) { + return queryPart.range; + } + + if ("eq" in queryPart) { + return queryPart.eq; + } + + if ("oneOf" in queryPart) { + return queryPart.oneOf; + } + + return queryPart; +}; + const getRangeQueryPartByType = (value: [string, string], type: string) => { const [gte, lte] = value; @@ -199,3 +221,17 @@ export const createOrderQueryVariables = (value: FilterContainer) => { return p; }, {} as OrderQueryVars); }; + +type CollectionQueryVars = CollectionFilterInput & { channel?: { eq: string } }; + +export const createCollectionsQueryVariables = (value: FilterContainer): CollectionQueryVars => { + return value.reduce((p, c) => { + if (typeof c === "string" || Array.isArray(c)) return p; + + p[c.value.value as keyof CollectionFilterInput] = mapStaticQueryPartToLegacyVariables( + createStaticQueryPart(c.condition.selected), + ); + + return p; + }, {} as CollectionQueryVars); +}; diff --git a/src/components/ConditionalFilter/types.ts b/src/components/ConditionalFilter/types.ts new file mode 100644 index 00000000000..4e84d75e327 --- /dev/null +++ b/src/components/ConditionalFilter/types.ts @@ -0,0 +1,8 @@ +import { InitialCollectionStateResponse } from "./API/initialState/collections/InitialCollectionState"; +import { InitialOrderStateResponse } from "./API/initialState/orders/InitialOrderState"; +import { InitialStateResponse } from "./API/InitialStateResponse"; + +export type InitialResponseType = + | InitialStateResponse + | InitialOrderStateResponse + | InitialCollectionStateResponse; From d7de25931aaa1f371c1005f92235d04e3b21e0ec Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 4 Feb 2025 16:40:09 +0100 Subject: [PATCH 02/12] channel fix --- .../CollectionListPage/CollectionListPage.tsx | 1 - .../views/CollectionList/CollectionList.tsx | 49 +++++++++---------- .../collections/InitialCollectionState.ts | 6 ++- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index d82f0e8bd75..9c37f29ed0d 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -53,7 +53,6 @@ const CollectionListPage: React.FC = ({ hasPresetsChanged, selectedCollectionIds, onCollectionsDelete, - ...listProps }) => { const intl = useIntl(); diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 07167594b8f..851689ce756 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -23,7 +23,7 @@ import createSortHandler from "@dashboard/utils/handlers/sortHandler"; import { mapEdgesToItems } from "@dashboard/utils/maps"; import { getSortParams } from "@dashboard/utils/sort"; import isEqual from "lodash/isEqual"; -import React, { useCallback } from "react"; +import React, { useCallback, useEffect } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import CollectionListPage from "../../components/CollectionListPage/CollectionListPage"; @@ -33,7 +33,7 @@ import { CollectionListUrlQueryParams, } from "../../urls"; import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; -import { getSortQueryVariables } from "./sort"; +import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort"; interface CollectionListProps { params: CollectionListUrlQueryParams; @@ -80,18 +80,20 @@ export const CollectionList: React.FC = ({ params }) => { storageUtils, }); const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ + const queryVariables = React.useMemo(() => { + const { channel, ...variables } = getFilterVariables({ + params, + filterContainer: valueProvider.value, + }); + + return { ...paginationState, - filter: getFilterVariables({ - params, - filterContainer: valueProvider.value, - }), + filter: variables, sort: getSortQueryVariables(params), - // channel: selectedChannel?.slug, - }), - [params, settings.rowNumber], - ); + channel, // Saleor docs say 'channel' in filter is deprecated and should be moved to root + }; + }, [params, settings.rowNumber, valueProvider.value]); + const { data, refetch } = useCollectionListQuery({ displayLoader: true, variables: queryVariables, @@ -111,16 +113,16 @@ export const CollectionList: React.FC = ({ params }) => { }, }); - // useEffect(() => { - // if (!canBeSorted(params.sort, !!selectedChannel)) { - // navigate( - // collectionListUrl({ - // ...params, - // sort: DEFAULT_SORT_KEY, - // }), - // ); - // } - // }, [params]); + useEffect(() => { + if (!canBeSorted(params.sort, !!queryVariables.channel)) { + navigate( + collectionListUrl({ + ...params, + sort: DEFAULT_SORT_KEY, + }), + ); + } + }, [params]); const [openModal, closeModal] = createDialogActionHandlers< CollectionListUrlDialog, @@ -162,7 +164,6 @@ export const CollectionList: React.FC = ({ params }) => { = ({ params }) => { onUpdateListSettings={updateListSettings} sort={getSortParams(params)} selectedChannelId={""} // TODO - // filterOpts={filterOpts} - // onFilterChange={changeFilters} selectedCollectionIds={selectedRowIds} onSelectCollectionIds={handleSetSelectedCollectionIds} hasPresetsChanged={hasPresetsChanged} diff --git a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts index f4d49820973..526ab874731 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts @@ -15,7 +15,11 @@ export class InitialCollectionStateResponse implements InitialCollectionState { public filterByUrlToken(token: UrlToken) { const entry = this.getEntryByName(token.name); - return entry.filter(item => item.value === token.value); + if (!token.isLoadable()) { + return [token.value] as string[]; + } + + return entry.filter(item => item.slug === token.value); } private getEntryByName(name: string): ItemOption[] { From 9c37193e466d403fdbce871d3777e9b8217f2ac1 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Wed, 5 Feb 2025 12:06:05 +0100 Subject: [PATCH 03/12] published labels --- .../views/CollectionList/CollectionList.tsx | 8 +++-- .../API/CollectionFilterAPIProvider.tsx | 30 +++++-------------- .../collections/InitialCollectionState.ts | 18 +++++++++-- .../collections/useInitialCollectionsState.ts | 15 ++++------ .../API/initialState/orders/intl.ts | 20 ++++++++++++- .../API/initialState/orders/messages.ts | 11 +++++++ src/components/ConditionalFilter/constants.ts | 2 +- .../ConditionalFilter/queryVariables.ts | 8 +++++ 8 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 851689ce756..52f5bf48aab 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore 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"; @@ -62,7 +63,7 @@ export const CollectionList: React.FC = ({ params }) => { params, keepActiveTab: true, }); - // const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); + const { availableChannels } = useAppChannel(false); const { selectedPreset, presets, @@ -93,6 +94,9 @@ export const CollectionList: React.FC = ({ params }) => { channel, // Saleor docs say 'channel' in filter is deprecated and should be moved to root }; }, [params, settings.rowNumber, valueProvider.value]); + const selectedChannel = availableChannels.find( + channel => channel.slug === queryVariables.channel, + ); const { data, refetch } = useCollectionListQuery({ displayLoader: true, @@ -182,7 +186,7 @@ export const CollectionList: React.FC = ({ params }) => { onSort={handleSort} onUpdateListSettings={updateListSettings} sort={getSortParams(params)} - selectedChannelId={""} // TODO + selectedChannelId={selectedChannel?.id} selectedCollectionIds={selectedRowIds} onSelectCollectionIds={handleSetSelectedCollectionIds} hasPresetsChanged={hasPresetsChanged} diff --git a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx index c684bdfe8d0..6092e008fa9 100644 --- a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx @@ -1,13 +1,10 @@ import { ApolloClient, useApolloClient } from "@apollo/client"; +import { CollectionPublished } from "@dashboard/graphql/types.generated"; +import { IntlShape, useIntl } from "react-intl"; -import { RowType } from "../constants"; import { FilterContainer, FilterElement } from "../FilterElement"; import { FilterAPIProvider } from "./FilterAPIProvider"; -import { BooleanValuesHandler, ChannelHandler, Handler, NoopValuesHandler } from "./Handler"; - -const isStaticBoolean = (rowType: RowType) => { - return ["published"].includes(rowType); -}; +import { ChannelHandler, EnumValuesHandler, Handler, NoopValuesHandler } from "./Handler"; const getFilterElement = (value: FilterContainer, index: number): FilterElement => { const possibleFilterElement = value[index]; @@ -22,24 +19,12 @@ const createAPIHandler = ( selectedRow: FilterElement, client: ApolloClient, inputValue: string, + intl: IntlShape, ): Handler => { const rowType = selectedRow.rowType(); - if (rowType && isStaticBoolean(rowType) && rowType !== "attribute") { - return new BooleanValuesHandler([ - { - label: "True", - value: "PUBLISHED", - type: rowType, - slug: "published", - }, - { - label: "False", - value: "HIDDEN", - type: rowType, - slug: "hidden", - }, - ]); + if (rowType === "published") { + return new EnumValuesHandler(CollectionPublished, "published", intl); } if (rowType === "ids") { @@ -58,6 +43,7 @@ const createAPIHandler = ( }; export const useCollectionFilterAPIProvider = (): FilterAPIProvider => { + const intl = useIntl(); const client = useApolloClient(); const fetchRightOptions = async ( @@ -68,7 +54,7 @@ export const useCollectionFilterAPIProvider = (): FilterAPIProvider => { const index = parseInt(position, 10); const filterElement = getFilterElement(value, index); - const handler = createAPIHandler(filterElement, client, inputValue); + const handler = createAPIHandler(filterElement, client, inputValue, intl); return handler.fetch(); }; diff --git a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts index 526ab874731..4ca75a13ba5 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts @@ -6,7 +6,13 @@ export interface InitialCollectionState { } export class InitialCollectionStateResponse implements InitialCollectionState { - constructor(public channel: ItemOption[] = []) {} + constructor( + public channel: ItemOption[] = [], + // public published: ItemOption[] = [], + // public ids: ItemOption[] = [], + // public metadata: ItemOption[] = [], + // public slugs: ItemOption[] = [], + ) {} static empty() { return new InitialCollectionStateResponse(); @@ -19,13 +25,21 @@ export class InitialCollectionStateResponse implements InitialCollectionState { return [token.value] as string[]; } - return entry.filter(item => item.slug === token.value); + return entry.filter(({ slug }) => slug === token.value); } private getEntryByName(name: string): ItemOption[] { switch (name) { case "channel": return this.channel; + // case "published": + // return this.published; + // case "ids": + // return this.ids; + // case "metadata": + // return this.metadata; + // case "slugs": + // return this.slugs; default: return []; } diff --git a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts index 5acf7e6a2ba..a9d26a7fcea 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts @@ -1,9 +1,9 @@ import { useApolloClient } from "@apollo/client"; import { CollectionFetchingParams } from "@dashboard/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams"; import { - _GetLegacyChannelOperandsDocument, - _GetLegacyChannelOperandsQuery, - _GetLegacyChannelOperandsQueryVariables, + _GetChannelOperandsDocument, + _GetChannelOperandsQuery, + _GetChannelOperandsQueryVariables, } from "@dashboard/graphql"; import { useState } from "react"; @@ -30,8 +30,8 @@ export const useInitialCollectionState = (): InitialCollectionAPIState => { const fetchQueries = async ({ channel }: CollectionFetchingParams) => { if (channel?.length > 0) { queriesToRun.push( - client.query<_GetLegacyChannelOperandsQuery, _GetLegacyChannelOperandsQueryVariables>({ - query: _GetLegacyChannelOperandsDocument, + client.query<_GetChannelOperandsQuery, _GetChannelOperandsQueryVariables>({ + query: _GetChannelOperandsDocument, }), ); } @@ -43,12 +43,9 @@ export const useInitialCollectionState = (): InitialCollectionAPIState => { } const data = await Promise.all(queriesToRun); - const initialState = createInitialCollectionState(data, channel); - const collectionResponse = new InitialCollectionStateResponse(initialState.channel); - - setData(collectionResponse); + setData(new InitialCollectionStateResponse(initialState.channel)); setLoading(false); }; diff --git a/src/components/ConditionalFilter/API/initialState/orders/intl.ts b/src/components/ConditionalFilter/API/initialState/orders/intl.ts index 7e3d1e2ac82..08940411d9a 100644 --- a/src/components/ConditionalFilter/API/initialState/orders/intl.ts +++ b/src/components/ConditionalFilter/API/initialState/orders/intl.ts @@ -1,5 +1,6 @@ import { LeftOperand } from "@dashboard/components/ConditionalFilter/LeftOperandsProvider"; import { + CollectionPublished, OrderAuthorizeStatusEnum, OrderChargeStatusEnum, OrderStatusFilter, @@ -8,7 +9,11 @@ import { import { transformOrderStatus, transformPaymentStatus } from "@dashboard/misc"; import { IntlShape } from "react-intl"; -import { authorizeStatusMessages, chargeStatusMessages } from "./messages"; +import { + authorizeStatusMessages, + chargeStatusMessages, + collectionFilterMessages, +} from "./messages"; const getPaymentStatusLabel = (status: PaymentChargeStatusEnum, intl: IntlShape) => { const { localized } = transformPaymentStatus(status, intl); @@ -50,6 +55,17 @@ const getChargeStatusLabel = (status: OrderChargeStatusEnum, intl: IntlShape) => } }; +const getPublishedLabel = (status: CollectionPublished, intl: IntlShape) => { + switch (status) { + case CollectionPublished.PUBLISHED: + return intl.formatMessage(collectionFilterMessages.published); + case CollectionPublished.HIDDEN: + return intl.formatMessage(collectionFilterMessages.hidden); + default: + return status; + } +}; + export const getLocalizedLabel = (rowType: LeftOperand["type"], value: string, intl: IntlShape) => { switch (rowType) { case "paymentStatus": @@ -60,6 +76,8 @@ export const getLocalizedLabel = (rowType: LeftOperand["type"], value: string, i return getAuthorizeStatusLabel(value as OrderAuthorizeStatusEnum, intl); case "chargeStatus": return getChargeStatusLabel(value as OrderChargeStatusEnum, intl); + case "published": + return getPublishedLabel(value as CollectionPublished, intl); default: return value; } diff --git a/src/components/ConditionalFilter/API/initialState/orders/messages.ts b/src/components/ConditionalFilter/API/initialState/orders/messages.ts index 64c9cc632b7..f325da03967 100644 --- a/src/components/ConditionalFilter/API/initialState/orders/messages.ts +++ b/src/components/ConditionalFilter/API/initialState/orders/messages.ts @@ -40,3 +40,14 @@ export const chargeStatusMessages = defineMessages({ description: "charge status none", }, }); + +export const collectionFilterMessages = defineMessages({ + published: { + defaultMessage: "Published", + id: "w7tf2z", + }, + hidden: { + defaultMessage: "Hidden", + id: "ThUvIL", + }, +}); diff --git a/src/components/ConditionalFilter/constants.ts b/src/components/ConditionalFilter/constants.ts index 63051544197..bdbeb621494 100644 --- a/src/components/ConditionalFilter/constants.ts +++ b/src/components/ConditionalFilter/constants.ts @@ -136,7 +136,7 @@ export const STATIC_CONDITIONS = { export const CONSTRAINTS = { channel: { - dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished"], + dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished", "published"], removable: false, disabled: ["left", "condition"], }, diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index 5f1cdc1612e..e60bddd64b0 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -228,6 +228,14 @@ export const createCollectionsQueryVariables = (value: FilterContainer): Collect return value.reduce((p, c) => { if (typeof c === "string" || Array.isArray(c)) return p; + // console.log({ c, p }); + + // if (c.value.type === "channel") { + // p[c.value.value as keyof CollectionFilterInput] = c.condition.selected.value.slug; + + // return p; + // } + p[c.value.value as keyof CollectionFilterInput] = mapStaticQueryPartToLegacyVariables( createStaticQueryPart(c.condition.selected), ); From 7ae757610af71da0a1cb7fb2ac4d9bc0bea824f5 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Thu, 6 Feb 2025 10:41:43 +0100 Subject: [PATCH 04/12] ids and slugs --- .../API/CollectionFilterAPIProvider.tsx | 2 +- .../collections/InitialCollectionState.ts | 36 +++++++++++------- .../collections/useInitialCollectionsState.ts | 37 +++++++++++++++---- .../API/initialState/helpers.ts | 4 ++ .../TokenArray/fetchingParams.ts | 14 +++++++ .../ValueProvider/UrlToken.ts | 3 ++ src/components/ConditionalFilter/constants.ts | 10 ++--- .../ConditionalFilter/queryVariables.ts | 12 +++--- 8 files changed, 86 insertions(+), 32 deletions(-) diff --git a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx index 6092e008fa9..edf0ed4cd4a 100644 --- a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx @@ -31,7 +31,7 @@ const createAPIHandler = ( return new NoopValuesHandler([]); } - if (rowType === "metadata") { + if (rowType && ["ids", "slugs", "metadata"].includes(rowType)) { return new NoopValuesHandler([]); } diff --git a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts index 4ca75a13ba5..4f9ddd1cf72 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts @@ -3,15 +3,19 @@ import { UrlToken } from "@dashboard/components/ConditionalFilter/ValueProvider/ export interface InitialCollectionState { channel: ItemOption[]; + published: ItemOption[]; + ids: ItemOption[]; + metadata: ItemOption[]; + slugs: ItemOption[]; } export class InitialCollectionStateResponse implements InitialCollectionState { constructor( public channel: ItemOption[] = [], - // public published: ItemOption[] = [], - // public ids: ItemOption[] = [], - // public metadata: ItemOption[] = [], - // public slugs: ItemOption[] = [], + public published: ItemOption[] = [], + public ids: ItemOption[] = [], + public metadata: ItemOption[] = [], + public slugs: ItemOption[] = [], ) {} static empty() { @@ -25,21 +29,27 @@ export class InitialCollectionStateResponse implements InitialCollectionState { return [token.value] as string[]; } - return entry.filter(({ slug }) => slug === token.value); + return entry.filter(({ slug }) => { + if (Array.isArray(token.value)) { + return token.value.includes(slug); + } + + return slug === token.value; + }); } private getEntryByName(name: string): ItemOption[] { switch (name) { case "channel": return this.channel; - // case "published": - // return this.published; - // case "ids": - // return this.ids; - // case "metadata": - // return this.metadata; - // case "slugs": - // return this.slugs; + case "published": + return this.published; + case "ids": + return this.ids; + case "metadata": + return this.metadata; + case "slugs": + return this.slugs; default: return []; } diff --git a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts index a9d26a7fcea..aadc6bedf23 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts @@ -4,9 +4,12 @@ import { _GetChannelOperandsDocument, _GetChannelOperandsQuery, _GetChannelOperandsQueryVariables, + CollectionPublished, } from "@dashboard/graphql"; import { useState } from "react"; +import { useIntl } from "react-intl"; +import { EnumValuesHandler } from "../../Handler"; import { createInitialCollectionState } from "../helpers"; import { InitialCollectionAPIResponse } from "../types"; import { InitialCollectionStateResponse } from "./InitialCollectionState"; @@ -17,8 +20,17 @@ export interface InitialCollectionAPIState { fetchQueries: (params: CollectionFetchingParams) => Promise; } +const mapToOptions = (data: string[], type: "ids" | "slugs") => + data.map(el => ({ + type, + label: el, + value: el, + slug: el, + })); + export const useInitialCollectionState = (): InitialCollectionAPIState => { const client = useApolloClient(); + const intl = useIntl(); const [data, setData] = useState( InitialCollectionStateResponse.empty(), @@ -27,7 +39,7 @@ export const useInitialCollectionState = (): InitialCollectionAPIState => { const queriesToRun: Array> = []; - const fetchQueries = async ({ channel }: CollectionFetchingParams) => { + const fetchQueries = async ({ channel, ids, slugs }: CollectionFetchingParams) => { if (channel?.length > 0) { queriesToRun.push( client.query<_GetChannelOperandsQuery, _GetChannelOperandsQueryVariables>({ @@ -36,16 +48,25 @@ export const useInitialCollectionState = (): InitialCollectionAPIState => { ); } - if (queriesToRun.length === 0) { - setLoading(false); - - return; - } + const publishedInit = new EnumValuesHandler(CollectionPublished, "published", intl); const data = await Promise.all(queriesToRun); - const initialState = createInitialCollectionState(data, channel); + const initialState = { + ...createInitialCollectionState(data, channel), + published: await publishedInit.fetch(), + slugs: mapToOptions(slugs, "slugs"), + ids: mapToOptions(ids, "ids"), + }; - setData(new InitialCollectionStateResponse(initialState.channel)); + setData( + new InitialCollectionStateResponse( + initialState.channel, + initialState.published, + initialState.ids, + initialState.metadata, + initialState.slugs, + ), + ); setLoading(false); }; diff --git a/src/components/ConditionalFilter/API/initialState/helpers.ts b/src/components/ConditionalFilter/API/initialState/helpers.ts index 610bc9a64a9..c3e6ca5e218 100644 --- a/src/components/ConditionalFilter/API/initialState/helpers.ts +++ b/src/components/ConditionalFilter/API/initialState/helpers.ts @@ -157,5 +157,9 @@ export const createInitialCollectionState = ( }, { channel: [], + published: [], + ids: [], + metadata: [], + slugs: [], }, ); diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts index 0ac8e9a9275..e039c12aed4 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts @@ -22,6 +22,10 @@ export interface OrderFetchingParams { export interface CollectionFetchingParams { channel: string[]; + ids: string[]; + metadata: string[]; + slugs: string[]; + published: string[]; } type FetchingParamsKeys = keyof Omit; @@ -47,6 +51,10 @@ export const emptyOrderFetchingParams: OrderFetchingParams = { export const emptyCollectionFetchingParams: CollectionFetchingParams = { channel: [], + ids: [], + metadata: [], + slugs: [], + published: [], }; export type FetchingParamsType = OrderFetchingParams | FetchingParams | CollectionFetchingParams; @@ -119,6 +127,12 @@ export const toCollectionFetchingParams = (p: CollectionFetchingParams, c: UrlTo p[key] = []; } + if (key === "ids" || key === "slugs") { + p[key] = unique(c.value); + + return p; + } + p[key] = unique(p[key].concat(c.value)); return p; diff --git a/src/components/ConditionalFilter/ValueProvider/UrlToken.ts b/src/components/ConditionalFilter/ValueProvider/UrlToken.ts index 27291100ceb..cc0efe1279d 100644 --- a/src/components/ConditionalFilter/ValueProvider/UrlToken.ts +++ b/src/components/ConditionalFilter/ValueProvider/UrlToken.ts @@ -19,6 +19,8 @@ const ORDER_STATICS = [ "ids", ]; +const COLLECTION_STATICS = ["published", "slugs"]; + const STATIC_TO_LOAD = [ "category", "collection", @@ -30,6 +32,7 @@ const STATIC_TO_LOAD = [ "hasCategory", "giftCard", ...ORDER_STATICS, + ...COLLECTION_STATICS, ]; export const TokenType = { diff --git a/src/components/ConditionalFilter/constants.ts b/src/components/ConditionalFilter/constants.ts index bdbeb621494..fc3cd081843 100644 --- a/src/components/ConditionalFilter/constants.ts +++ b/src/components/ConditionalFilter/constants.ts @@ -127,8 +127,8 @@ export const STATIC_CONDITIONS = { ], slugs: [ { - type: "text", - label: "is", + type: "bulkselect", + label: "in", value: "input-1", }, ], @@ -314,8 +314,8 @@ export const STATIC_COLLECTION_OPTIONS: LeftOperand[] = [ }, { value: "slugs", - label: "Slug", - type: "slugs", // TODO - its [String!] not String! + label: "Slugs", + type: "slugs", slug: "slugs", }, ]; @@ -324,7 +324,7 @@ export const STATIC_OPTIONS = [ ...STATIC_PRODUCT_OPTIONS, ...STATIC_DISCOUNT_OPTIONS, ...STATIC_ORDER_OPTIONS, - ...STATIC_COLLECTION_OPTIONS, // ?? + ...STATIC_COLLECTION_OPTIONS, ]; export const ATTRIBUTE_INPUT_TYPE_CONDITIONS = { diff --git a/src/components/ConditionalFilter/queryVariables.ts b/src/components/ConditionalFilter/queryVariables.ts index e60bddd64b0..a2c3f605ef7 100644 --- a/src/components/ConditionalFilter/queryVariables.ts +++ b/src/components/ConditionalFilter/queryVariables.ts @@ -228,13 +228,15 @@ export const createCollectionsQueryVariables = (value: FilterContainer): Collect return value.reduce((p, c) => { if (typeof c === "string" || Array.isArray(c)) return p; - // console.log({ c, p }); + if (c.value.type === "metadata") { + p.metadata = p.metadata || []; - // if (c.value.type === "channel") { - // p[c.value.value as keyof CollectionFilterInput] = c.condition.selected.value.slug; + const [key, value] = c.condition.selected.value as [string, string]; - // return p; - // } + p.metadata.push({ key, value }); + + return p; + } p[c.value.value as keyof CollectionFilterInput] = mapStaticQueryPartToLegacyVariables( createStaticQueryPart(c.condition.selected), From efcb2fc6e1a6ff72cd276dd9bcc09d301cf22b07 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Thu, 6 Feb 2025 13:05:35 +0100 Subject: [PATCH 05/12] collection filters --- .changeset/late-parents-cross.md | 5 ++ .featureFlags/colleciton-new-filters.md | 11 +++ .featureFlags/generated.tsx | 22 ++++- .../images/collection-list-filtering.jpg | Bin 0 -> 30573 bytes .../CollectionListPage/CollectionListPage.tsx | 80 +++++++++++++----- .../views/CollectionList/CollectionList.tsx | 43 ++++++++-- .../views/CollectionList/filters.ts | 9 +- 7 files changed, 138 insertions(+), 32 deletions(-) create mode 100644 .changeset/late-parents-cross.md create mode 100644 .featureFlags/colleciton-new-filters.md create mode 100644 .featureFlags/images/collection-list-filtering.jpg diff --git a/.changeset/late-parents-cross.md b/.changeset/late-parents-cross.md new file mode 100644 index 00000000000..05caa0bbf80 --- /dev/null +++ b/.changeset/late-parents-cross.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Collection list filters have been ported to expression filtering. This means you can now use new filters in the Collection list. diff --git a/.featureFlags/colleciton-new-filters.md b/.featureFlags/colleciton-new-filters.md new file mode 100644 index 00000000000..9246c226095 --- /dev/null +++ b/.featureFlags/colleciton-new-filters.md @@ -0,0 +1,11 @@ +--- +name: collection_list_new_filters +displayName: Collection list new filtering +enabled: true +payload: "default" +visible: true +--- + +![new filters](./images/collection-list-filtering.jpg) +Experience the new look and enhanced abilities of new filtering 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..607fed7104a 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 O18650 from "./images/collection-list-filtering.jpg" +import A06283 from "./images/discounts-list.png" +import I81106 from "./images/improved_refunds.png" -const discounts_rules = () => (<>

Discount rules

+const collection_list_new_filters = () => (<>

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

+) +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 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.

    @@ -19,6 +24,15 @@ const improved_refunds = () => (<>

    Improved refunds< ) export const AVAILABLE_FLAGS = [{ + name: "collection_list_new_filters", + displayName: "Collection list new filtering", + component: collection_list_new_filters, + visible: true, + content: { + enabled: true, + payload: "default", + } +},{ name: "discounts_rules", displayName: "Discounts rules", component: discounts_rules, diff --git a/.featureFlags/images/collection-list-filtering.jpg b/.featureFlags/images/collection-list-filtering.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cca098d5707671df265bf217d8f0911c9de00883 GIT binary patch literal 30573 zcmeHv2V4|Ows*sjl#Jvw2&hO_P$UjPl87Y9AOa#e=OBY1ARwS1pddlAWF+UD1SARw zl98N7(g4HE*LwHv(4*@_~8Q=r}fB?V>wghm%7?=g12QL7Cq+kIM@PDlD&r+~|O~tNCf&3cB zGWwnoGkYf1!o|fwgpbe8nb-J%y@?sGsl6?qhp_|S6<&TmKn&*LU~Fn_=E7oPW^QRG z&bn38z{+CzK%7ByzTOy`S`E$UA@W!=HPMmv~w}`;IVUN`?ZFfX3nNgmJTkK z_I51aYcw{ocXbhG1zY~nB{PtTf3Ej;#D7ouK|L$W_o#@frH7e~?oCTuGdpLnGI3Uc zYgbMu>6}WsE-dt?BpG`fdnXN$d1m6QS6KN#viSZ)*Z1h386?5-GyLShPY(R#z)ueR zTNmYRl^j{d^MOB|Ou1+QHfx*;qgb5mAMUP1Ae#$8P@b z(+6e`%`GggoLyYq+&w(K0-gj01wRc5jf#F56C3yHb$n`CdPZhe_Pd<-C8cHM6_r)h z%`L5M?H!$69|nhpKaGrzeIB2kn_u|4xU{^oiuks(ySIOUJUseN7ig{iLKb-cn{<(b zbYbJ*KydKC(}jiY{+)1g9NhC)@F=9z@QoeMv0i;lKq>v=ZE@2XHUaf*Dif!Hv()T@ zvlkHGN&7+BA0y2FU!v^Kg#AX>1Rw)o{|FFlYzQs{0>Q<@1p^)d-uFOomf%M?`=>zk zBanO#q<;<=uo5h=4jddDeDEI`;Tb}*e>z|$L8G4=GY$|zus~sgkOMH_q;lIlNKD9a z=H#sBZi3>4sOC?v;|aP2XkSJt(y=FHR;wUy)^cG0Nozh7ek6*s;V}kSxV4P|P69Sy zyXvEA%KpL_;CFdAFu)U~=7wFo_ZXnj0|PWypOuzebyOfC{m0oVDzXj&8HSFV{rKUt z6x)R-Sa}WG%&_e=G&$c{4A4*CjRCOcF~HfWspKh2dzG!TK+|6~75Td+xV*jK9%^;4 zS`G;QUseJ;-9-Mz%E_$UE_yqK2>QDk{)(1A+{tMi<~M`=_%IAW5ElqvCN{$Weh3T@ z2;C0x&_?@>7$HfaNNp*#*Kaj~TyeZ!NYcVjr>Om9Iop#|(!=G3S7{q$@IF~o8(;!& zzA$Ta1kr`hh{0#&QLOidS<5fV{C9fz|8c$)2Cwa6H^7!5nEsRD zZ5S%#9uxxaB9c{l$aJMN)Uu*{gaDbNCJ>RxN>Xu6e%DWXf9kbRU8pVn)^pTw+ubthN@yib;WmOi#?BpAhrRUz5$A3Z4w5go4ln|)iUX06D z5&ev8kQ}h1p5ub^tsjg#8Qdy*8E#hh@f^mGyTcCG9}WWk$P@q za_YFHNO|ELkG9Ov#t53w+mbl2cThmt(P52|i^#2~;A`Zo8QP(xy9F(>Cz&>4dSVys zX9`JF@{K&+?RngGG#o$Fin1t?CGPSLbPHAJOQ;rlT8^lIEY1`aJc7x3Xg`s-p^NOE zlPCeL0+m5#*t$|~g7BhujhZ84>{w5fE*);O)MZ#zoHahJ4d-*^N$)n_i`;>=DH<7k#DPEiz;xySXbmIA z+dP_FH?IkkzxhboLXRi5X{e`t)FnzfTc${s{W6mxq1H7Fz}z;wHmu*v`?YlIDnSxb zkc-SZNK^gk;2CyJZlM^Bh|f)p_>W{ghfuO*A!V=4hIj*G!vypd9mL*WsQME6ygA{Z zMEHdyJ8oB#%@SSwLBw7Sm#8JIm{0KdZT4sVS_txlAM} zZW6jECN!ssz@OMye4n6PH*|^2K>wrKYuy)Sa$4buy1I;HQhWF~^dH08r>#ES``960 zS6vjk{8{l4eS3f2s#`@@LoY|CjmmKa#o7on?vhdMe%o=-wJ--o^E?wrp~m z=Lc9l_iZUW-|ES76lmbqg+_NRCzBOMEs8dfeaW%x*R`&2Cn6)_P4q2lpXTW6rj~-d zTgnH#ZK)PVFu*y%R+{9ASRr~Dz0Y+=d6}2>lsr1MIa#7^i?(o!|7FbN2P?srV}yX^@(cnK;l~Dt>LoE*Pc}Q z>8^|j;q`ED0e0lB`ha|aODMw z7j)p1_*IG);aL|BTvH0y8_23IO1Kll_%^~MJPC^^l0oGCa}Smye=HnTyf#WTN!&kh z(UcyTRT7XAzd`HfuP)`m=T~6R1?#sd*r6Q%+SLI2RmBecF%@gHRWbZ&HJ9DC^7&O_ zmK)ag5XrWnAbAHT2n zSj@v#aKE@{^qT&q@=;!tR7M|zUTM7M*TYwnJQc6)RuQ-C9!@ef_wX+e0X1Iv470q^ z8P?2n2;7mvcUI%cK`g_wp>yAK5({8u_bw%7o}u92G*+W;WxHvRefvJC_81136tsbF z#sdf!uzT#qiYqDWAJF~ zrm|+jRwTHV;S_>h^EWGo_yiNB>CWIOT#OQZ${u;v*CXRu!5JX^nR5(qWcM3UejXJw zf%WfZVbn+!=~|0f#C`=yMxOW(-e-bUE?I#vvfNF~$ zJ;h3ph{-gxqOQ*G%~PKm%L}s?V6kL92e6NI#C)NIM5LIMUY>v+XNE_f3`vrNo!rlj z(c>7qKHtvS3*4>80Fl}#*sB7sYRkEuw-!g|bck&ovhc3Y_doO#3s6N0&xX~k<#sbn zs>p|_xWimzAy~8Liw}M4*E!9Ypc8eYk@@wnr6NoEFo4*^H$kjfCTwITN6 zXI6w~Kia!2Z)IuVn6PXsvT>-JYT}4=RxuP0)7P^g6)%?LtO`A!9&aH@yH?xwwdmYE znSDoEvKHYOX=$I@jWf@6i#+Ei{;H2+n{ez7=X7g5gSfo>oM{YTA82!m z&}a5rU*^U6FGtS8$gO;DCb5ppS4WNSk8BG14|N7ok42YhX1q91Q0Zywq@`8+_z1eb zg8}-kqr#!)$!O1t1q;aQ!Ck)fNprnVC=4KQd{5`W=L%+{n6quq7B)YA*`@s`zJ}f1 z?6n#9PN7<>wERf$;JuNe?Y`>J*6W`4?Im&JY*ErZa2JCIe)Im~G=EilHxCL6P8mCH z6aT&;czvsWnkwrv0`}_t>%r^EJ)EYB3TD{+V*vFH;q2tIHW?MM9cpz#)D_fTWiLaE z*G;PP`>VKVOYV1srv*gsI1bi%|5XDWHonwZV|xrt>ef_B6EYTsS(e2*omJ;+M;;AQ zJ4ipr8}9u0@zv+*?Z~IDy`;JWK57LSgI2NOme9}nxYe?i{8yh3-s(Hw+1En@!IEb` zQuz!T_(`^h^b)@aOFw?xG!k!3H}>d2dw6(i>a&jiVeGc2HB)=CBTtV;PHmLqqC%GO zrOlK1r1RBC_`5Z{xr0{vT6g*?ZeM~JEZBlS^$x~XH(7uGo{qqBegJ-5FlUVojyNI3_P8v3tYg?&dS z0C)p1gMaATBzyh_U0#hx{vJoXclOAA!(lGJ57<}kVa>lW7sSeaAt!4Ne_#pQ`HFvX zY_y$r%%+F|k~v^o29QvN zPnUttHQ0N>o}H&~pi19lzIRO*X;UxlZ%`CFhOZk6VMK?*KJfNWE@^tuxy#@&_EW)MKrIHm*%9GrKngvBas)R95H*mRa7tv-+_(mf^!Y ztXCxI*p25V*hVt{wK>thI+yxe``u=XOwJ%_}iCiGo}oh%R-# z!Q?2(^pZZNbd${>mpLKWXi1N%8Cwd+m1?pdPj(a)xQ+qVUQT}$8O;kTJS6;#0Y)!3 zym2j5_5+8}k820rAPL_5V3^^5&oDYcY0B!nTa3=2Ea&(L&qwPH?i$Lic1hwSyt18~ zYh(%7c?|k~A}oz?>mkecsJ0uzxs~g~vSqF(xRZvmc%%+*74SZIiY31OcxuL|gdjaa zx>@StC#-U+qgQ@%q%5Q1%NU>pT-$ntZ&)_E+e?2lTIJQ3&*OC)U#eZxmtb|}MNtn+ zgQTK^A}(;DZlsW296?`1DThfR?AyAD?~D)=HtT(wdLwwT^vi)(wd+`ErFqrlB2Q~x z!M3fTA8iZQLfHdk%ba%Gq{#H#Xn1L&(;)Gs+1L_B{*^wH_YB=SyK96KPcsr6%r0$^ zeti#U)+5XsnNPBPD3F#p9*HP@?+k^5XoEIyK`wWfSb@o=iVs!sZD!Q(dlD$~BG296 z%o5gQdw4t>{J4yVt4;61X2PPb$N0m_5w@L7s8!6yY#C4G4T~Kj@&Yza34?h@c7HLp zIoQ*BHjf;m&~j$_k$gcOzQ$OX`SqdkGZ>)sV!|Do_oag+??R!xX5mEl$yV2uu-@02 z>J9G&AvNYE1!OA2ZE05$FiW9{l!I7dEK+`S;p7NUs0>+Q+N!`Rrs6tlZLuZ@R9L_?bgXT zlv3TA@_w@0)uamuN z`Qf|8%3?&LS34hQd9bygv{6Z)zx&}$B#93p(sWigGg*Fcu_xAP1(DyjH#x5NSvm6R zRIlB|7q%w0xi9@#QMv6@4p?8IBx3f6yVg)5J0HvZajShJ@={&9%<|AK+wkxOtzyH= zL9!->bh@8fp2fEC_TM6GAgFzWpg3;7g0!E@KVFId`n7DhFHI$bmSyMs3kh2RweTsf zSEh1@EdsaPxn9tI(5}`)!M4L+Z-1^RngCY{pAg$-p!YTQJ?@d)X~&B0&9C9y6qVP=eRv;`%u1CLi@{(=54s4g}-vuMH)cfu84` zK7uB2cUAYfq9X2-H?yUP6ZAWTcZU$v1{p^Aex<_8CmVGBJh8fFg8|H&2%Tzzn3mQY zpEs^ILZu>u(UcM*FbcC00i>TEA#K5hSjC%UcHG@Kc5UacGI|DGQ%5qCgyQ>&@g{+@ zvA`JIcLDdjsfQNhFFnwIN|>9HNE!GdmGh=e<4dAO*BMAj!M940Ee;6l8d`cEzW2d? ziOG88cPccqks~!!lIjox7^Qr_OF}W9{#CWvHfUy6!eV*?5_WX0Zh0B+E0y*~xD*;r zFSfJ`T|%Ve=5Q9iK?k7Ff}VZLu)cXo>g=3)=OfY`=zbmlff`;1U z*WV|le~wN3k{J0*P6+R`yz6tp^|KKUq8Na4>MsRTy&3~NuS-60N;vG$+Y1(m`?Mi2 z{@w1V`YXF*)NtVKNp2>*Zvl3IfVW3(7z&`_=P^J-82XxTs33OmegViH*mDdp-G%|^ zVP!6Thp1pQnh*Kuh@<~3biCReg_gkpa2k{a`qL0>=^OMI*)XrYoF>WfKe$y(#47c< z8xdTu5+fxG(vutMtJJ!QG~Py-kz{2HTAW`9#CTahs8$@)uUC1`F}nS_)5v zEJY{JeKTNP8ZGcGs|6sC$Fg}e|BzRBs;4+B|N8-Hkfq0x`Q4D79ulh)N()@lQ)4Q4 zYD{u>)lw)=4d}orIVc>E^ZPGam9&aCo-u6sG4xj3=l#rt@!w{Acl_kh?bwW7f6nw2 zEk%olib#h?-g$jAbZf3EfajL@vbD|1mjh_$wtj`fpN_T|Rf2xj|FNcFt1ljFYX8JV~5A*!vG7F$WIRKN;35Ti zC2i6a6$=?bmAZ4=qXZ*POW6C9f{^Xpb|~@g7I`<*=+iR34F+PR^~UaZcfV;pl2lrhhExnaShU!%Fs)>;wDi zhr2kuP1Z$2XtI?f7>Y~(5$8^43DOZV(Lx$i`9*HzVwclIP&3xl9fnI0hXq`%u#&QZ z_q=5UdtBO|hFaYvSc{^L?*MhB%f}fRh?OAw%>cH zmE3xeN%U5jN~t}kP>5}+zcAekNt?r{E`cRsc3Z;8c_Tj>`q5`)eKbgI)j`1(lxpm} znn+S0N&ZzOTe8RIB`>}{mA~{dyT(j0?E{z1anp0?P0Q-=q{1w{)o~YpYo)O$4}%Bz z5wb*i0(~q2%HZ@mGHmN;9kzSd^;%+{o7Qt(8FaxU^|j`lXDp3}s+cBxs|nJY?b~NaSPG7I3a9gU!`teF)l{P&lc3o z+ml%ymB)yes*ZyggTb-eAM?yVMSV8DPep5^L1-`B+1Y64*z4il2&KAKV~@9-OwKm2 z8_v+QW$C!E;tnndjUHSlU$5X}-!Bg%vO>PhmN)s3=0MlUHnd9`T7MP+dwOwuB+S|I z+AyCpSxjC*T1mW;(6>zr!YWpM&-hEY2J&oVGH}=8<(M7&ClYkXV@YHZnkCRi5Jp;i z^yJ`$3jfr!0^yVnf4?u-hqg|lf_DAjP4Ug@R`aH_6Ql|O6)Op!+I zzBGvZywNGD;nd-c892`r>#f{*Q73r=GV81Y=Vsq?{XXYX#*y|dZn@T`R+XBJC0ABA zy-KbuL>n>z_rI^#d@R6%bUcP0|T&1H*u>{!iNF@Rb{8RwK! z^}4+fi6jYSRG>bN%XJvyfaU~TNM7E{nc~?_N}T7pKB4r9J(eTil~`0=1_1ErH z3%8WGh{M@A8BH%J0s;P<(((#+_JI|w-aB9M1rass^|nrc{gu?KxQ%b? z5TfguxRYD5vIJql<}og$J+Z_e=7PNtoGs znnZ;3i*xdTX&5Tn+&UA=9!l1gn8us?WarNu69P_&AeyPzZXMI%E5KRzP>%&6j9>j+IF4){*0 z;LH2@t4-gR-mBmnAgo%x?C#B89%=cY=0%;4bgXXp{ekS7q||E;NUo;p!l&9@3Z>qP zcCbPEn@G8>fY%uI7x_e6Bu$h3YB!acMoglpymcgOM2MOIceS6w_4em&z+%G}BU` za<_BHzj7DHPk$jV0RsdaoqVie&_zZuz)3P2Xr9)$j^HV!ITf%+@s)Y5)+^O7LAs{W zAJ=A&dofwh9)vLY*n7{CQd|tqMV9J4<(=x=U~OzV+t|alleRxj^qMGv#>XbV)SRl*qJh-M_N@oW zbhC(ELx!G#uJWsRCT1D)YbBR=jBEVo_B}H#4ddlw>`dL92+!>VuSqZ>c~JPXh3mq+ z^EI(b3At7D1QOjgk*-{~y6o-iRX8v31S+q4$FfIDGG=78hcoZ5jo$F6;S70Ad%-I{8N}%PL0iMP_T2Qd zdKw=#L~Z@Kux6)jl8|)$JNwSaAa9{7Uu>&GqOURqDV%p}c?Ljs!~)&qu9j4n5ydUj)CAS1rTcbD;Vkz;Z46UZu~Q|)4XNF`*l@88l@kbZ}uUG zL(@qewD#i|A#7|8*VokQ5SID_}Kz*(k*18BPcvO-+iceoS$%L*~gat`#f zo;m6eoN9bu2}OSZXPFE$<ucs+W;%N{z;Zrq>4 z!G^#N$9dS2@UXn*jg9G=qLP# ztwsS|Gg?H4Rn`fWc)KbrL~xqu^KKo529KDCQk5l|%|RpcT=A+dWi;}|?Nhy0z<=b=cenyO z!2od>AS`X29Nq^4Ew)e%C_|yY+x^SaJ^xZUj)qqx$2dkigK*G{0cX85>avzNybn}$ zJKL8Yx^-mgM280d-A2K}|KN2}l8XDE^sYfJxhCz8(?-lg=%CQ%>sS3Ab=eP==G=H2 zg!64iHeB8eAnm;SlK(76>hteFR7xo53{U>y47VSFoSv}SfLPOXETiPTBk;D1h519c z(Lbgt+oFUX%HG$#ai5gsMbe`i>5%2rSKrvJ>*Wa2s%|hMBuT#`iE>IN(|K9%TAHzm zk`c`hM@d3ftv;!iF0SrHs&N+1u*pRB#9Kf%;-EY8LQ6ClfHCb<9Q5)ya6EcHkM?

    $kJd3Pw>K)+U-B`p@rpti)t^Y&Zr)nb)b;FhoCR9gX2`3^Mumi zEYzkF)ZTOrHyV&%Psfc?!ye0=BI&<@JavX8scLJ*G^tM!?T~FP(<7}wutVP_Whj-d z*nU7xR7ADz0wh)0Af@9Uz7xhfw_Yu2rjN?La6;Qq2%P zn?f>{f=|8p$5PNlAKk>gKc(z%yhK#@YZTwKb`IRabGxy`oZ>G9l>yD= zX&SBDBCmDf5QaCt_cf1bpQ{-dh1atoA+u#qM6z0;&8)@ zU+t)Gq^f`H7~^0cT?$^`Lr}C&&wWfToa)HcpYpK?`Zlksw85oTQW2nD=E=J|#+tN< z^SE>9_VZ}UXAJAha#DEpXoO0TAdKF_S#3Km#I|=TI#+UkR5gT|?>ZeG6QSIP8UC4> zXZ}(%!(InZP~vkIF3Z;vq^?@h#2)M?7|!v38GqBdYHB9tbau)5GH2sE7C)cdS(T>^ zG*IPjqe#~}r@2n^^JK4OdrNoGu3j_c!?z)HpVrw>G{ARj?scTH)kA zw%fU_5+aYR28SfWT36CDpN*tBR1B3Z?_z)}n@U?Wq}=x`3~bIei)S==8r`2j32rMF zgl8aqiG^%~!r&)ame9m2-?qd?lY71xIs1N-v(3A;^Xxt#%wxa*2&p{BW3s0EY1h-t zd+lX?{G@#$>wE9c&$@QuZ=87=l4Ec_uuXYKM@vSUgM&xJ8JLXnF@ld8V}R7O+&$?& zv;<$nS`h{qP#co;ByBa6X%*>Iy;ojn_HMOeG&T%6oVO*kD-+SgNdb^9pn~3VBVR28CRd zoKrieKz2WbPHxKz(b2LY+KvG}j|QQ4WIyd%vPX5O4^2|6e)&+tuC|z7Eat>3)WL1{ zOggG2M&avjgTF7dIKEGP_?oSf&Ir$*McL~;hN|;IH17QRUDouSY@s)zC?lk)ZDt zKQJi1bD)o8db{csI8v2mEfNu#r(6I6bEK#?ip$;0YebwZ?TR}baf)e^Ci!nZU8f7< zf>hW>;-e_HmjYag1+>bC_n4%2ZYOJ3QA{bFD;->ON+0Fn?To(R(cV}i?Jq~r4%`Ui zi>T*8FnGu)ZAU)I&l>SjF)sL`OuU&svcE4xk2_fw)2b}!HYE6T@M9B`3C@N=HqkBT z>Z}dJj26?`N}G++L?OMA-L7kj8)X}of}OS_bm1i}jLm1pEs45V-;w9os`qrqXzjy^ zeOR_f1IrSj21A>63&}Ub3`416<~h3O7?xcL^Jh)fZz-M4(_te}uw5eJpGVSUSjJXc zAyr!A8-n?E$9QdJ_tNylhV%^$u64_D8cZk?;=Bk6e_}jzi8$zfBV95{{e?aRVaNV> zY$A96vR>g6mpVNLVYkK0n*{}$2ezG4R8$N&k#UiFM--K|=Njp?0_P|Zf;8@N+V9fG zm%kQd?sPYSLc$%fNfap{7+&`|!ed_eQdK(t-$PUv$ahFwtltg}<`t7MO;^r8iC&B) zbW`Ja3aJ_vnwt1R;ut+%y~+Xk=u7K?HVTnoWWGdytv%9OAf|$bJ0nvPHo^gINx~9$ zaBTCsCQqp*MInXF?b!ZS^DdB+av#74u=?-5#0oIQ08mHx#1=YIZw4(eohZ~iuGv`O zzuYwkBbF+kyTZv)Il{xro3iUdqRWs|L?oi^d8)`j!;s5{7G=dJ9+TQoVdF&g2f9=b zD_)p|O45=s3sV_>FTpQ)O`Spvzv-SBBBrA?HyqNg{f@XiO(G}WfPX@UCuW#hW8>Mx zl#FO~FQd%KMbW3jhc!>`bDGtM6=>ht=JR)nBr`;oC~l*Z&GJ{V0(*C_7t6cyo6yyE z#mJN^;4Hj(E-+Y~^Wrc4vyAqUwJPtBqAf!ASO-fjT}Mk>6m1EgM1O#Kw#%5?s{6wH-p?u5($aIybJKfN z>sF2+Vpn6isWytG4fs-GSrvi%95;=Qoj~=oq5mlW^rl0>_(U8!(keS7K|nB%TZ z`DM=Z8R`xo>GQR3G%Y-k+1^n<6a*o9?^9M8A}RmcEXA&}yJr*$ zrzqVZY!it~8VyC`52LixCk@9YblV4Y4}Fzn?5T!#!Ypw#!fm>8~yU|u#TAemT0grEMi3zkN!H^j<-F;LZCd&8v)P{N3w(~!Q z&wuu`q}kuhznJVu*!tcFkrM1;?%*Sg8-f+CLgAxxJ=j^c{ax`&J!K1hMxC1JwJT7nIP>a zWDWo#s`x#=3<#CTYew`QH<9)2QrRm*$k3@92jlFcjxzRk2)eZL$|Qo!?pL30``H;D ztLbp8iFwZ{TWvxiR_OBs1!7!G6=B?|F?Iz!3q8(Kf@&q=rSDU;<8MC0yRSsv!CF_S zuxa8_22VL^r?^F)#b#u5f z5SCu)wRfcNCCc{7P4mwqeM_4zun1+ix{3kF>bWde<}PfXR3qhE_LZ{*^Iqsq_PXc1 z5#@R--NmlIUtKA$PN%4G&ulInrr7VpH*bV(;9&(NvQobkoK;;qWFNnyP+3;_35V5U zAc{KF@_Ka-{e`O>M)At&h2!*mkHyGm>+s-&9^NfSzOT~^wsvgt^G$-rRRnDxAy}W- z&Iff0Gg8h7zCP2uSd~!ny6B9Hc5X=iz0wzSgI+5 zu##KT$IP~u82U8 zMS!pCTM)eyQ9d-DM{~B`CS=hww5~P3u6$`Am^n2M16b-GCD;xEFV+# zo#0$Ty#-z9E7Ap zU08EQjNqXkDuL4_6+tElSZl9hK3k1j#NarNfT3Y$3bl?Y?ZLJ6v;L=>#K}v(U_6kA*;`Trt;&f`I<%g(KMPrcnv}gwPj6@HUE) zqE!l)^g^dsDSPVc1dc9afZ!_MUJS6yg9HJbz&h+uA`5A5Ti=l5#cYR3>`r(8X>;rvp`$WNj#S6;@Tr@I>~A1Q^ua z`EtQsa8v#7nN4di5%gXmXSXlx1X(Ue_`b2)Z6mvlM#bmSYP}VB3$y{U;<6SqcFv|1MLmaeBLy#&q+opVH;%Y#tPlnrPc&)s6zm(&H{HSr2ckybFPSoX|ssj^9uehTaMDAihv$} ztto_1Y*-atgsuwCH(Alpj_zcYzpuZW;5) zvXe)(m*(kEyoHFI5AB{*I63_)nV)B&&e`7r9+aAYb@E)f_wLcL6w0^?S(R_;#hggn z?;|{y%SX~}Ei%@`xP7OH?vizG7_tCo{UV<|qgzh|&JW=NY`x)pf}_y4%ONI z`1+A?{ujcUW6$;3D6X34R5ypx&g-&ck>TIhKU=%aZ|Eb6gu~~;>=~wz=C>;mR8fHe z>QNtQYC|L&&j=e`kz@qV7z+!Ggq|eT>LVEDaF{P3RGwY)*4>>jwAUH5v9VomRbljU zMHR8X@2%Z338k^nqy@ey7N%SCDGmr4Voxb46RK1UrNmp>F^gJ6QCem>v%kNZ0@Ow* zi8AYkut!;|KX3k9o7Ml_&l(scu6N~iAoS-i>?hBzWpCj>MG0qFPVp;i=Cf??wQXd? zb4N=@F4Ahg5+HjPUSn*7nSWt1qsT zYs#IBsp3ms&b$~IiqbfiM41N6bv)fQA|AWxVozZ0r4`LnyJk$J?RYPvrkA@yTWGtF zvY-T7!mYLWO!ICWcazX=WJEHVjfCJmqy)TOV`&KZ*jNxh$RynXs+lt z6f2s>VxXI=e0-NN=l)D`PGLQxkJvCu#&~X0zJ=a!q$(pPBLh{gTKB0sYm)qSry=(K zxSXiQhA*=RUU!>@$7)}T-UDX#O7*B!$Is0b1?l&LDhU_K!ixQ zH_|%?8Le+mW<_(voa}hvOTfXCZoBaJa$njOZg04oR*g~HDHh5NNHF=pwm%kTBLiBJ z=!OLIHMILC?DB19{k`A3Nfx;&*%#gQMMZ2c--yJ+sO28%5Y5f)q-e|3mCh~J30*0s z+Vcx0ADeG9XLGkkId(1j30b6u%D@thkX*s2H<7{c^}FCFXK-y5RaY9aR2@XnukcMI zTT4`i!U0Q|7Jrm&%_!SwTbuMy7)p|S6a#*~cB_vXc&wNC8(QQ8L(pt6!+P;CWbT}O zOC;k88d?a6YS{%Pi!Cz(`_E2h-YbV+e zS&UF>qbE)dtW}yPHq{y^{T4L-VG}*)ZE@R_K>adf>INe(KIHKogmznBXuZhLs3M~9 zSy`fQ8_{i^tvy2UxB^K!Q`@K-URBY+8=YUqR-Mx1lx}vr&g~T+WJVuDePI1Q&a(|q z8ptxD_^2$8ghIsR3sU>ItsU*Ny4k&4^L+1W)cRUU4s{N+a0gvypiHbf1d*)U_$>`M z)>DktNLz0Ij#m2H?nCxeJ|bZnyxxA5V7tU;vmM;+h$1O0L_ATswak^QGp z{{J`Q_)XCAzI9z|a8+i-3;p<=e40mvO!!QY<;zkU>dlGT*KVlpvJkWTBv%^)l3;w1 zC<^QS*o|DIIR=36Vt_-EhO|(w%p_h1EqU?V-0YiuOHeeE&@t$>?ALFBAsO63F1x^6 zGC_|Y{D{!wdojCTi>VY2v#s*O-oTMA(;J`*QOceO1I*ZhcHa2`3_y}_k^yeW;F(QE z5!ZlLqX;^JBW-OD0+t|_fWVtBqm0r#c9an`?RECb;28*65%cRJ)=F1uEXJ;OfH(w_ zR(78;Ep`WjkMf(Uw6sj`EDF5JGK4%C2GeM{Zi3s+Pr+fTPMY?fQr)?&Cb4Dv)$wrS zTECV}DTp>dANZR)><=Miq;%rq#oQ?lJ9`;+zdXA;zshfI7kZA6v16ZcdS^%I1RV`- zqisn*`+j$B4#LA-b~P4_O<$dU1RZmR_q9$R9H}4*l0t=F=`6yE7D3!xNW<1{^1S@_ ziV#04vd&74ct|ZAxh~wWG-rQ&atS$b5-fy5fj(k*I=Gj2WjXD2V1(&0iTxom0gV9^ z(SmSe(5{A^gHNx(+N-@dem4Dm41O~FrB2v>R4H}(9E1OmfB(PoY19=5yOwszE z_2t$UJ~(vI`(Gss@E6sV!T^Lyjmf*`zz>}O96>q8*3VH019v88%%~iJUg`z924pB~ z*3#n~+8=(1XOCum&iq)`69b@l_^{#qDj0wT)XA-Ce^CwAGNpDh2tpg&zI1{EeyfP4 z!8O6{(ps=aDHtjM^rw(Z{;cOGe}2l(&-dcA`uV9h{%`5kABSh%hyPAP`@q42v_AhI DafZoT literal 0 HcmV?d00001 diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index 9c37f29ed0d..caaecf63bc8 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -9,22 +9,32 @@ import { ListFilters } from "@dashboard/components/AppLayout/ListFilters"; import { TopNav } from "@dashboard/components/AppLayout/TopNav"; import { BulkDeleteButton } from "@dashboard/components/BulkDeleteButton"; import { DashboardCard } from "@dashboard/components/Card"; +import { getByName } from "@dashboard/components/Filter/utils"; import { FilterPresetsSelect } from "@dashboard/components/FilterPresetsSelect"; import { ListPageLayout } from "@dashboard/components/Layouts"; +import { useFlag } from "@dashboard/featureFlags"; import { getPrevLocationState } from "@dashboard/hooks/useBackLinkWithState"; import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; -import { PageListProps, SearchPageProps, SortPage, TabPageProps } from "@dashboard/types"; +import { + FilterPageProps, + PageListProps, + SearchPageProps, + SortPage, + TabPageProps, +} from "@dashboard/types"; import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useLocation } from "react-router"; import { CollectionListDatagrid } from "../CollectionListDatagrid"; +import { CollectionFilterKeys, CollectionListFilterOpts, createFilterStructure } from "./filters"; export interface CollectionListPageProps extends PageListProps, Omit, + Omit, "onTabDelete">, SearchPageProps, SortPage { onTabUpdate: (tabName: string) => void; @@ -50,7 +60,11 @@ const CollectionListPage: React.FC = ({ onTabUpdate, selectedChannelId, tabs, + filterOpts, + onFilterChange, + onFilterAttributeFocus, hasPresetsChanged, + currencySymbol, selectedCollectionIds, onCollectionsDelete, ...listProps @@ -59,6 +73,9 @@ const CollectionListPage: React.FC = ({ const location = useLocation(); const navigate = useNavigator(); const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); + const { enabled: isNewCollectionListEnabled } = useFlag("collection_list_new_filters"); + const filterStructure = createFilterStructure(intl, filterOpts); + const filterDependency = filterStructure.find(getByName("channel")); return ( @@ -109,29 +126,52 @@ const CollectionListPage: React.FC = ({ - - {selectedCollectionIds.length > 0 && ( - - - - )} - - } - /> + {isNewCollectionListEnabled ? ( + + {selectedCollectionIds.length > 0 && ( + + + + )} + + } + /> + ) : ( + + {selectedCollectionIds.length > 0 && ( + + + + )} + + } + onFilterChange={onFilterChange} + onFilterAttributeFocus={onFilterAttributeFocus} + filterStructure={filterStructure} + /> + )} { navigate(collectionUrl(id), { state: getPrevLocationState(location), diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 52f5bf48aab..7ccd815e2b8 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -4,6 +4,7 @@ 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 { useFlag } from "@dashboard/featureFlags"; import { useCollectionBulkDeleteMutation, useCollectionListQuery } from "@dashboard/graphql"; import { useFilterPresets } from "@dashboard/hooks/useFilterPresets"; import useListSettings from "@dashboard/hooks/useListSettings"; @@ -21,7 +22,7 @@ import { ListViews } from "@dashboard/types"; import createDialogActionHandlers from "@dashboard/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@dashboard/utils/handlers/filterHandlers"; import createSortHandler from "@dashboard/utils/handlers/sortHandler"; -import { mapEdgesToItems } from "@dashboard/utils/maps"; +import { mapEdgesToItems, mapNodeToChoice } from "@dashboard/utils/maps"; import { getSortParams } from "@dashboard/utils/sort"; import isEqual from "lodash/isEqual"; import React, { useCallback, useEffect } from "react"; @@ -33,7 +34,13 @@ import { CollectionListUrlDialog, CollectionListUrlQueryParams, } from "../../urls"; -import { getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; +import { + getFilterOpts, + getFilterQueryParam, + getFilterVariables, + getFilterVariables_legacy, + storageUtils, +} from "./filters"; import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort"; interface CollectionListProps { @@ -45,6 +52,7 @@ export const CollectionList: React.FC = ({ params }) => { const intl = useIntl(); const notify = useNotifier(); const { updateListSettings, settings } = useListSettings(ListViews.COLLECTION_LIST); + const { enabled: isNewGiftCardsFilterEnabled } = useFlag("collection_list_new_filters"); usePaginationReset(collectionListUrl, params, settings.rowNumber); @@ -55,7 +63,7 @@ export const CollectionList: React.FC = ({ params }) => { setSelectedRowIds, } = useRowSelection(params); const { valueProvider } = useConditionalFilterContext(); - const [_, resetFilters, handleSearchChange] = createFilterHandlers({ + const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: collectionListUrl, getFilterQueryParam, @@ -63,7 +71,11 @@ export const CollectionList: React.FC = ({ params }) => { params, keepActiveTab: true, }); - const { availableChannels } = useAppChannel(false); + const { availableChannels, channel } = useAppChannel(false); + const channelOpts = availableChannels + ? mapNodeToChoice(availableChannels, channel => channel.slug) + : null; + // const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); const { selectedPreset, presets, @@ -81,7 +93,17 @@ export const CollectionList: React.FC = ({ params }) => { storageUtils, }); const paginationState = createPaginationState(settings.rowNumber, params); + const selectedChannel_legacy = availableChannels.find(channel => channel.slug === params.channel); const queryVariables = React.useMemo(() => { + if (!isNewGiftCardsFilterEnabled) { + return { + ...paginationState, + filter: getFilterVariables_legacy(params), + sort: getSortQueryVariables(params), + channel: selectedChannel_legacy?.slug, + }; + } + const { channel, ...variables } = getFilterVariables({ params, filterContainer: valueProvider.value, @@ -93,11 +115,10 @@ export const CollectionList: React.FC = ({ params }) => { sort: getSortQueryVariables(params), channel, // Saleor docs say 'channel' in filter is deprecated and should be moved to root }; - }, [params, settings.rowNumber, valueProvider.value]); + }, [params, settings.rowNumber, valueProvider.value, isNewGiftCardsFilterEnabled]); const selectedChannel = availableChannels.find( channel => channel.slug === queryVariables.channel, ); - const { data, refetch } = useCollectionListQuery({ displayLoader: true, variables: queryVariables, @@ -164,10 +185,16 @@ export const CollectionList: React.FC = ({ params }) => { clearRowSelection(); }, [selectedRowIds]); + const filterOpts = getFilterOpts(params, channelOpts); + const selectedChannelId = isNewGiftCardsFilterEnabled + ? selectedChannel?.id + : selectedChannel_legacy?.id; + return ( = ({ params }) => { onSort={handleSort} onUpdateListSettings={updateListSettings} sort={getSortParams(params)} - selectedChannelId={selectedChannel?.id} + selectedChannelId={selectedChannelId} + filterOpts={filterOpts} + onFilterChange={changeFilters} selectedCollectionIds={selectedRowIds} onSelectCollectionIds={handleSetSelectedCollectionIds} hasPresetsChanged={hasPresetsChanged} diff --git a/src/collections/views/CollectionList/filters.ts b/src/collections/views/CollectionList/filters.ts index 557859cbde5..ad8dc988669 100644 --- a/src/collections/views/CollectionList/filters.ts +++ b/src/collections/views/CollectionList/filters.ts @@ -52,11 +52,18 @@ export function getFilterVariables({ return { ...vars, - search: params.query, // TODO: change to 'search' + search: params.query, channel: channel, }; } +export function getFilterVariables_legacy(params: CollectionListUrlFilters): CollectionFilterInput { + return { + published: params.status ? findValueInEnum(params.status, CollectionPublished) : undefined, + search: params.query, + }; +} + export function getFilterQueryParam( filter: FilterElement, ): CollectionListUrlFilters { From 8697d0d3d8d2446919b89abd08f244b7cac4babc Mon Sep 17 00:00:00 2001 From: Wojciech Date: Thu, 6 Feb 2025 13:11:03 +0100 Subject: [PATCH 06/12] update test --- .../ConditionalFilter/FilterElement/Constraint.test.ts | 2 +- .../TokenArray/__snapshots__/TokenArray.test.ts.snap | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ConditionalFilter/FilterElement/Constraint.test.ts b/src/components/ConditionalFilter/FilterElement/Constraint.test.ts index a44ab24ced4..228a0eb2bcc 100644 --- a/src/components/ConditionalFilter/FilterElement/Constraint.test.ts +++ b/src/components/ConditionalFilter/FilterElement/Constraint.test.ts @@ -21,7 +21,7 @@ describe("ConditionalFilter / FilterElement / Constraint", () => { // Assert expect(constraint).toEqual({ - dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished"], + dependsOn: ["price", "isVisibleInListing", "isAvailable", "isPublished", "published"], disabled: ["left", "condition"], removable: false, }); diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/__snapshots__/TokenArray.test.ts.snap b/src/components/ConditionalFilter/ValueProvider/TokenArray/__snapshots__/TokenArray.test.ts.snap index 75a346f834c..3e1e62796b8 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/__snapshots__/TokenArray.test.ts.snap +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/__snapshots__/TokenArray.test.ts.snap @@ -116,6 +116,7 @@ TokenArray [ "isVisibleInListing", "isAvailable", "isPublished", + "published", ], "disabled": Array [ "left", From 7f8c059644869062d7a774ccbec10e9324e4d882 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Thu, 6 Feb 2025 13:14:57 +0100 Subject: [PATCH 07/12] json messages --- locale/defaultMessages.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 1f9035102e6..002985e821d 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -4922,6 +4922,9 @@ "context": "tax classes card header", "string": "General information" }, + "ThUvIL": { + "string": "Hidden" + }, "TjGYna": { "context": "product inventory, checkbox", "string": "Track Inventory" @@ -9232,6 +9235,9 @@ "w7jT4W": { "string": "No channels selected" }, + "w7tf2z": { + "string": "Published" + }, "w9xgN9": { "context": "see error log label in notification", "string": "See error log" From a88608dec7d6bf521cd3878e28b203c5c75a17d7 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Thu, 6 Feb 2025 18:53:06 +0100 Subject: [PATCH 08/12] remove ids and slugs filters --- .../views/CollectionList/CollectionList.tsx | 1 - .../API/CollectionFilterAPIProvider.tsx | 6 +----- .../collections/InitialCollectionState.ts | 8 -------- .../collections/useInitialCollectionsState.ts | 14 +------------- .../ConditionalFilter/API/initialState/helpers.ts | 2 -- .../ValueProvider/TokenArray/fetchingParams.ts | 6 ------ .../ConditionalFilter/ValueProvider/UrlToken.ts | 2 +- src/components/ConditionalFilter/constants.ts | 12 ------------ 8 files changed, 3 insertions(+), 48 deletions(-) diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 7ccd815e2b8..6e3cad41336 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -75,7 +75,6 @@ export const CollectionList: React.FC = ({ params }) => { const channelOpts = availableChannels ? mapNodeToChoice(availableChannels, channel => channel.slug) : null; - // const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); const { selectedPreset, presets, diff --git a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx index edf0ed4cd4a..d13cf91a471 100644 --- a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx @@ -27,11 +27,7 @@ const createAPIHandler = ( return new EnumValuesHandler(CollectionPublished, "published", intl); } - if (rowType === "ids") { - return new NoopValuesHandler([]); - } - - if (rowType && ["ids", "slugs", "metadata"].includes(rowType)) { + if (rowType === "metadata") { return new NoopValuesHandler([]); } diff --git a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts index 4f9ddd1cf72..bd063dd54aa 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.ts @@ -4,18 +4,14 @@ import { UrlToken } from "@dashboard/components/ConditionalFilter/ValueProvider/ export interface InitialCollectionState { channel: ItemOption[]; published: ItemOption[]; - ids: ItemOption[]; metadata: ItemOption[]; - slugs: ItemOption[]; } export class InitialCollectionStateResponse implements InitialCollectionState { constructor( public channel: ItemOption[] = [], public published: ItemOption[] = [], - public ids: ItemOption[] = [], public metadata: ItemOption[] = [], - public slugs: ItemOption[] = [], ) {} static empty() { @@ -44,12 +40,8 @@ export class InitialCollectionStateResponse implements InitialCollectionState { return this.channel; case "published": return this.published; - case "ids": - return this.ids; case "metadata": return this.metadata; - case "slugs": - return this.slugs; default: return []; } diff --git a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts index aadc6bedf23..44ef37b8f96 100644 --- a/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts +++ b/src/components/ConditionalFilter/API/initialState/collections/useInitialCollectionsState.ts @@ -20,14 +20,6 @@ export interface InitialCollectionAPIState { fetchQueries: (params: CollectionFetchingParams) => Promise; } -const mapToOptions = (data: string[], type: "ids" | "slugs") => - data.map(el => ({ - type, - label: el, - value: el, - slug: el, - })); - export const useInitialCollectionState = (): InitialCollectionAPIState => { const client = useApolloClient(); const intl = useIntl(); @@ -39,7 +31,7 @@ export const useInitialCollectionState = (): InitialCollectionAPIState => { const queriesToRun: Array> = []; - const fetchQueries = async ({ channel, ids, slugs }: CollectionFetchingParams) => { + const fetchQueries = async ({ channel }: CollectionFetchingParams) => { if (channel?.length > 0) { queriesToRun.push( client.query<_GetChannelOperandsQuery, _GetChannelOperandsQueryVariables>({ @@ -54,17 +46,13 @@ export const useInitialCollectionState = (): InitialCollectionAPIState => { const initialState = { ...createInitialCollectionState(data, channel), published: await publishedInit.fetch(), - slugs: mapToOptions(slugs, "slugs"), - ids: mapToOptions(ids, "ids"), }; setData( new InitialCollectionStateResponse( initialState.channel, initialState.published, - initialState.ids, initialState.metadata, - initialState.slugs, ), ); setLoading(false); diff --git a/src/components/ConditionalFilter/API/initialState/helpers.ts b/src/components/ConditionalFilter/API/initialState/helpers.ts index c3e6ca5e218..b8068db334c 100644 --- a/src/components/ConditionalFilter/API/initialState/helpers.ts +++ b/src/components/ConditionalFilter/API/initialState/helpers.ts @@ -158,8 +158,6 @@ export const createInitialCollectionState = ( { channel: [], published: [], - ids: [], metadata: [], - slugs: [], }, ); diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts index e039c12aed4..03f124cd48a 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts @@ -127,12 +127,6 @@ export const toCollectionFetchingParams = (p: CollectionFetchingParams, c: UrlTo p[key] = []; } - if (key === "ids" || key === "slugs") { - p[key] = unique(c.value); - - return p; - } - p[key] = unique(p[key].concat(c.value)); return p; diff --git a/src/components/ConditionalFilter/ValueProvider/UrlToken.ts b/src/components/ConditionalFilter/ValueProvider/UrlToken.ts index cc0efe1279d..9b769ca4de7 100644 --- a/src/components/ConditionalFilter/ValueProvider/UrlToken.ts +++ b/src/components/ConditionalFilter/ValueProvider/UrlToken.ts @@ -19,7 +19,7 @@ const ORDER_STATICS = [ "ids", ]; -const COLLECTION_STATICS = ["published", "slugs"]; +const COLLECTION_STATICS = ["published"]; const STATIC_TO_LOAD = [ "category", diff --git a/src/components/ConditionalFilter/constants.ts b/src/components/ConditionalFilter/constants.ts index fc3cd081843..67b6bd77e2e 100644 --- a/src/components/ConditionalFilter/constants.ts +++ b/src/components/ConditionalFilter/constants.ts @@ -300,24 +300,12 @@ export const STATIC_COLLECTION_OPTIONS: LeftOperand[] = [ type: "metadata", slug: "metadata", }, - { - value: "ids", - label: "IDs", - type: "ids", - slug: "ids", - }, { value: "channel", label: "Channel", type: "channel", slug: "channel", }, - { - value: "slugs", - label: "Slugs", - type: "slugs", - slug: "slugs", - }, ]; export const STATIC_OPTIONS = [ From 38485e71fd275718aa96d43e428ef3ae502a6d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 12 Feb 2025 15:13:08 +0100 Subject: [PATCH 09/12] Refactor --- .../CollectionListPage/CollectionListPage.tsx | 12 +--- .../views/CollectionList/CollectionList.tsx | 60 ++++++---------- .../views/CollectionList/filters.test.ts | 19 ++++- .../views/CollectionList/filters.ts | 20 +----- .../ConditionalFilter/API/Handler.ts | 2 +- src/components/ConditionalFilter/API/index.ts | 1 - .../InitialCollectionState.test.ts | 72 +++++++++++++++++++ .../API/initialState/collections/types.ts | 0 .../API/initialState/helpers.test.ts | 4 +- .../API/initialState/helpers.ts | 27 +++---- .../API/initialState/index.ts | 1 - .../InitalProductStateResponse.test.ts} | 14 ++-- .../product/InitialProductStateResponse.ts} | 14 ++-- .../useProductInitialAPIState.tsx} | 24 ++++--- .../API/initialState/types.ts | 2 +- .../API/{initialState/orders => }/intl.ts | 0 .../API/{initialState/orders => }/messages.ts | 0 .../CollectionFilterAPIProvider.tsx | 6 +- .../CustomerFilterAPIProvider.tsx | 2 +- .../DiscountFiltersAPIProvider.tsx | 0 .../DraftOrderFilterAPIProvider.tsx | 0 .../GiftCardsFilterAPIProvider.tsx | 0 .../OrderFilterAPIProvider.tsx | 6 +- .../{ => providers}/PageFilterAPIProvider.tsx | 2 +- .../ProductFilterAPIProvider.tsx | 8 +-- .../VoucherFilterAPIProvider.ts | 2 +- .../FilterElement/Condition.test.ts | 6 +- .../FilterElement/Condition.ts | 19 +---- .../FilterElement/FilterElement.ts | 21 ++---- .../TokenArray/TokenArray.test.ts | 6 +- .../TokenArray/fetchingParams.test.ts | 31 +++++++- .../TokenArray/fetchingParams.ts | 16 +---- .../ValueProvider/TokenArray/index.ts | 29 ++------ .../TokenArray/ordersFetchingParams.ts | 25 ------- .../ValueProvider/useUrlValueProvider.ts | 14 ++-- .../ConditionalFilter/context/provider.tsx | 20 +++--- src/components/ConditionalFilter/types.ts | 37 +++++++++- 37 files changed, 272 insertions(+), 250 deletions(-) delete mode 100644 src/components/ConditionalFilter/API/index.ts create mode 100644 src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.test.ts delete mode 100644 src/components/ConditionalFilter/API/initialState/collections/types.ts delete mode 100644 src/components/ConditionalFilter/API/initialState/index.ts rename src/components/ConditionalFilter/API/{InitalStateResponse.test.ts => initialState/product/InitalProductStateResponse.test.ts} (83%) rename src/components/ConditionalFilter/API/{InitialStateResponse.ts => initialState/product/InitialProductStateResponse.ts} (86%) rename src/components/ConditionalFilter/API/initialState/{useInitialAPIState.tsx => product/useProductInitialAPIState.tsx} (81%) rename src/components/ConditionalFilter/API/{initialState/orders => }/intl.ts (100%) rename src/components/ConditionalFilter/API/{initialState/orders => }/messages.ts (100%) rename src/components/ConditionalFilter/API/{ => providers}/CollectionFilterAPIProvider.tsx (91%) rename src/components/ConditionalFilter/API/{ => providers}/CustomerFilterAPIProvider.tsx (81%) rename src/components/ConditionalFilter/API/{ => providers}/DiscountFiltersAPIProvider.tsx (100%) rename src/components/ConditionalFilter/API/{ => providers}/DraftOrderFilterAPIProvider.tsx (100%) rename src/components/ConditionalFilter/API/{ => providers}/GiftCardsFilterAPIProvider.tsx (100%) rename src/components/ConditionalFilter/API/{ => providers}/OrderFilterAPIProvider.tsx (95%) rename src/components/ConditionalFilter/API/{ => providers}/PageFilterAPIProvider.tsx (95%) rename src/components/ConditionalFilter/API/{ => providers}/ProductFilterAPIProvider.tsx (93%) rename src/components/ConditionalFilter/API/{ => providers}/VoucherFilterAPIProvider.ts (97%) delete mode 100644 src/components/ConditionalFilter/ValueProvider/TokenArray/ordersFetchingParams.ts diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index caaecf63bc8..56bec897406 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -16,13 +16,7 @@ import { useFlag } from "@dashboard/featureFlags"; import { getPrevLocationState } from "@dashboard/hooks/useBackLinkWithState"; import useNavigator from "@dashboard/hooks/useNavigator"; import { sectionNames } from "@dashboard/intl"; -import { - FilterPageProps, - PageListProps, - SearchPageProps, - SortPage, - TabPageProps, -} from "@dashboard/types"; +import { FilterPageProps, PageListProps, SortPage } from "@dashboard/types"; import { Box, Button, ChevronRightIcon } from "@saleor/macaw-ui-next"; import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; @@ -33,9 +27,7 @@ import { CollectionFilterKeys, CollectionListFilterOpts, createFilterStructure } export interface CollectionListPageProps extends PageListProps, - Omit, Omit, "onTabDelete">, - SearchPageProps, SortPage { onTabUpdate: (tabName: string) => void; selectedChannelId: string; @@ -72,9 +64,9 @@ const CollectionListPage: React.FC = ({ const intl = useIntl(); const location = useLocation(); const navigate = useNavigator(); + const filterStructure = createFilterStructure(intl, filterOpts); const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); const { enabled: isNewCollectionListEnabled } = useFlag("collection_list_new_filters"); - const filterStructure = createFilterStructure(intl, filterOpts); const filterDependency = filterStructure.find(getByName("channel")); return ( diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 6e3cad41336..0ad45f8d8ea 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -2,6 +2,7 @@ import ActionDialog from "@dashboard/components/ActionDialog"; import useAppChannel from "@dashboard/components/AppLayout/AppChannelContext"; import { useConditionalFilterContext } from "@dashboard/components/ConditionalFilter"; +import { createCollectionsQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; import DeleteFilterTabDialog from "@dashboard/components/DeleteFilterTabDialog"; import SaveFilterTabDialog from "@dashboard/components/SaveFilterTabDialog"; import { useFlag } from "@dashboard/featureFlags"; @@ -34,13 +35,7 @@ import { CollectionListUrlDialog, CollectionListUrlQueryParams, } from "../../urls"; -import { - getFilterOpts, - getFilterQueryParam, - getFilterVariables, - getFilterVariables_legacy, - storageUtils, -} from "./filters"; +import { getFilterOpts, getFilterQueryParam, getFilterVariables, storageUtils } from "./filters"; import { canBeSorted, DEFAULT_SORT_KEY, getSortQueryVariables } from "./sort"; interface CollectionListProps { @@ -53,6 +48,8 @@ export const CollectionList: React.FC = ({ params }) => { const notify = useNotifier(); const { updateListSettings, settings } = useListSettings(ListViews.COLLECTION_LIST); const { enabled: isNewGiftCardsFilterEnabled } = useFlag("collection_list_new_filters"); + const { valueProvider } = useConditionalFilterContext(); + const filters = createCollectionsQueryVariables(valueProvider.value); usePaginationReset(collectionListUrl, params, settings.rowNumber); @@ -62,7 +59,6 @@ export const CollectionList: React.FC = ({ params }) => { setClearDatagridRowSelectionCallback, setSelectedRowIds, } = useRowSelection(params); - const { valueProvider } = useConditionalFilterContext(); const [changeFilters, resetFilters, handleSearchChange] = createFilterHandlers({ cleanupFn: clearRowSelection, createUrl: collectionListUrl, @@ -75,6 +71,7 @@ export const CollectionList: React.FC = ({ params }) => { const channelOpts = availableChannels ? mapNodeToChoice(availableChannels, channel => channel.slug) : null; + const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); const { selectedPreset, presets, @@ -92,35 +89,28 @@ export const CollectionList: React.FC = ({ params }) => { storageUtils, }); const paginationState = createPaginationState(settings.rowNumber, params); - const selectedChannel_legacy = availableChannels.find(channel => channel.slug === params.channel); - const queryVariables = React.useMemo(() => { - if (!isNewGiftCardsFilterEnabled) { - return { - ...paginationState, - filter: getFilterVariables_legacy(params), - sort: getSortQueryVariables(params), - channel: selectedChannel_legacy?.slug, - }; - } - - const { channel, ...variables } = getFilterVariables({ - params, - filterContainer: valueProvider.value, - }); + const queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params), + channel: selectedChannel?.slug, + }), + [params, settings.rowNumber], + ); + const newQueryVariables = React.useMemo(() => { + const { channel, ...restFilters } = filters; return { ...paginationState, - filter: variables, + filter: restFilters, sort: getSortQueryVariables(params), - channel, // Saleor docs say 'channel' in filter is deprecated and should be moved to root + channel, }; - }, [params, settings.rowNumber, valueProvider.value, isNewGiftCardsFilterEnabled]); - const selectedChannel = availableChannels.find( - channel => channel.slug === queryVariables.channel, - ); + }, [params, settings.rowNumber, valueProvider.value]); const { data, refetch } = useCollectionListQuery({ displayLoader: true, - variables: queryVariables, + variables: isNewGiftCardsFilterEnabled ? newQueryVariables : queryVariables, }); const collections = mapEdgesToItems(data?.collections); const [collectionBulkDelete, collectionBulkDeleteOpts] = useCollectionBulkDeleteMutation({ @@ -136,9 +126,10 @@ export const CollectionList: React.FC = ({ params }) => { } }, }); + const filterOpts = getFilterOpts(params, channelOpts); useEffect(() => { - if (!canBeSorted(params.sort, !!queryVariables.channel)) { + if (!canBeSorted(params.sort, !!selectedChannel)) { navigate( collectionListUrl({ ...params, @@ -184,11 +175,6 @@ export const CollectionList: React.FC = ({ params }) => { clearRowSelection(); }, [selectedRowIds]); - const filterOpts = getFilterOpts(params, channelOpts); - const selectedChannelId = isNewGiftCardsFilterEnabled - ? selectedChannel?.id - : selectedChannel_legacy?.id; - return ( = ({ params }) => { onSort={handleSort} onUpdateListSettings={updateListSettings} sort={getSortParams(params)} - selectedChannelId={selectedChannelId} + selectedChannelId={selectedChannel?.id} filterOpts={filterOpts} onFilterChange={changeFilters} selectedCollectionIds={selectedRowIds} diff --git a/src/collections/views/CollectionList/filters.test.ts b/src/collections/views/CollectionList/filters.test.ts index 259da3002c5..9a781222bdd 100644 --- a/src/collections/views/CollectionList/filters.test.ts +++ b/src/collections/views/CollectionList/filters.test.ts @@ -1,4 +1,5 @@ import { createFilterStructure } from "@dashboard/collections/components/CollectionListPage"; +import { CollectionListUrlFilters } from "@dashboard/collections/urls"; import { CollectionPublished } from "@dashboard/graphql"; import { FilterOpts } from "@dashboard/types"; import { getFilterQueryParams } from "@dashboard/utils/filters"; @@ -8,8 +9,24 @@ import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; import { config } from "@test/intl"; import { createIntl } from "react-intl"; -import { getFilterQueryParam } from "./filters"; +import { getFilterQueryParam, getFilterVariables } from "./filters"; +describe("Filtering query params", () => { + it("should be empty object if no params given", () => { + const params: CollectionListUrlFilters = {}; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(0); + }); + it("should not be empty object if params given", () => { + const params: CollectionListUrlFilters = { + status: CollectionPublished.PUBLISHED, + }; + const filterVariables = getFilterVariables(params); + + expect(getExistingKeys(filterVariables)).toHaveLength(1); + }); +}); describe("Filtering URL params", () => { const intl = createIntl(config); const filters = createFilterStructure(intl, { diff --git a/src/collections/views/CollectionList/filters.ts b/src/collections/views/CollectionList/filters.ts index ad8dc988669..467d39ed91a 100644 --- a/src/collections/views/CollectionList/filters.ts +++ b/src/collections/views/CollectionList/filters.ts @@ -3,8 +3,6 @@ import { CollectionFilterKeys, CollectionListFilterOpts, } from "@dashboard/collections/components/CollectionListPage"; -import { FilterContainer } from "@dashboard/components/ConditionalFilter/FilterElement"; -import { createCollectionsQueryVariables } from "@dashboard/components/ConditionalFilter/queryVariables"; import { FilterElement, FilterElementRegular } from "@dashboard/components/Filter"; import { CollectionFilterInput, CollectionPublished } from "@dashboard/graphql"; import { findValueInEnum, maybe } from "@dashboard/misc"; @@ -41,23 +39,7 @@ export function getFilterOpts( }; } -export function getFilterVariables({ - filterContainer, - params, -}: { - filterContainer: FilterContainer; - params: CollectionListUrlFilters; -}): CollectionFilterInput { - const { channel, ...vars } = createCollectionsQueryVariables(filterContainer); - - return { - ...vars, - search: params.query, - channel: channel, - }; -} - -export function getFilterVariables_legacy(params: CollectionListUrlFilters): CollectionFilterInput { +export function getFilterVariables(params: CollectionListUrlFilters): CollectionFilterInput { return { published: params.status ? findValueInEnum(params.status, CollectionPublished) : undefined, search: params.query, diff --git a/src/components/ConditionalFilter/API/Handler.ts b/src/components/ConditionalFilter/API/Handler.ts index 4484dcce716..f4cd517120b 100644 --- a/src/components/ConditionalFilter/API/Handler.ts +++ b/src/components/ConditionalFilter/API/Handler.ts @@ -39,7 +39,7 @@ import { IntlShape } from "react-intl"; import { ItemOption } from "../FilterElement/ConditionValue"; import { LeftOperand } from "../LeftOperandsProvider"; -import { getLocalizedLabel } from "./initialState/orders/intl"; +import { getLocalizedLabel } from "./intl"; export interface Handler { fetch: () => Promise; diff --git a/src/components/ConditionalFilter/API/index.ts b/src/components/ConditionalFilter/API/index.ts deleted file mode 100644 index ff44d0671a5..00000000000 --- a/src/components/ConditionalFilter/API/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./initialState"; diff --git a/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.test.ts b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.test.ts new file mode 100644 index 00000000000..a960a0bd633 --- /dev/null +++ b/src/components/ConditionalFilter/API/initialState/collections/InitialCollectionState.test.ts @@ -0,0 +1,72 @@ +import { CollectionPublished } from "@dashboard/graphql"; + +import { UrlEntry, UrlToken } from "../../../ValueProvider/UrlToken"; +import { InitialCollectionStateResponse } from "./InitialCollectionState"; + +describe("ConditionalFilter / API / Page / InitialCollectionStateResponse", () => { + it("should filter by channel", () => { + // Arrange + const initialCollectionState = InitialCollectionStateResponse.empty(); + + initialCollectionState.channel = [ + { + label: "Channel 1", + value: "chan-1", + slug: "chan-1", + }, + { + label: "Channel 2", + value: "chan-2", + slug: "chan-2", + }, + ]; + + const token = UrlToken.fromUrlEntry(new UrlEntry("s0.channel", "chan-1")); + const expectedOutput = [ + { + label: "Channel 1", + value: "chan-1", + slug: "chan-1", + }, + ]; + + // Act + const result = initialCollectionState.filterByUrlToken(token); + + // Assert + expect(result).toEqual(expectedOutput); + }); + + it("should filter by published", () => { + // Arrange + const initialCollectionState = InitialCollectionStateResponse.empty(); + + initialCollectionState.published = [ + { + label: CollectionPublished.PUBLISHED, + value: CollectionPublished.PUBLISHED, + slug: CollectionPublished.PUBLISHED, + }, + { + label: CollectionPublished.HIDDEN, + value: CollectionPublished.HIDDEN, + slug: CollectionPublished.HIDDEN, + }, + ]; + + const token = UrlToken.fromUrlEntry(new UrlEntry("s0.published", "HIDDEN")); + const expectedOutput = [ + { + label: CollectionPublished.HIDDEN, + value: CollectionPublished.HIDDEN, + slug: CollectionPublished.HIDDEN, + }, + ]; + + // Act + const result = initialCollectionState.filterByUrlToken(token); + + // Assert + expect(result).toEqual(expectedOutput); + }); +}); diff --git a/src/components/ConditionalFilter/API/initialState/collections/types.ts b/src/components/ConditionalFilter/API/initialState/collections/types.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/components/ConditionalFilter/API/initialState/helpers.test.ts b/src/components/ConditionalFilter/API/initialState/helpers.test.ts index 59b5498d8ad..0499641e48c 100644 --- a/src/components/ConditionalFilter/API/initialState/helpers.test.ts +++ b/src/components/ConditionalFilter/API/initialState/helpers.test.ts @@ -7,7 +7,7 @@ import { _SearchProductTypesOperandsQuery, } from "@dashboard/graphql"; -import { createInitialStateFromData } from "./helpers"; +import { createInitialProductStateFromData } from "./helpers"; describe("ConditionalFilter / API / createInitialStateFromData", () => { it("should create initial state from queries", () => { @@ -99,7 +99,7 @@ describe("ConditionalFilter / API / createInitialStateFromData", () => { const data = [channelQuery, collectionQuery, categoryQuery, productTypeQuery, attributeQuery]; const channel = ["channel-1"]; // Act - const result = createInitialStateFromData(data, channel); + const result = createInitialProductStateFromData(data, channel); // Assert expect(result).toMatchSnapshot(); diff --git a/src/components/ConditionalFilter/API/initialState/helpers.ts b/src/components/ConditionalFilter/API/initialState/helpers.ts index 57529dcb040..450ff9ca57c 100644 --- a/src/components/ConditionalFilter/API/initialState/helpers.ts +++ b/src/components/ConditionalFilter/API/initialState/helpers.ts @@ -14,38 +14,38 @@ import { import { createBooleanOptions } from "../../constants"; import { createCustomerOptionsFromAPI, createOptionsFromAPI } from "../Handler"; -import { InitialGiftCardsState } from "../initialState/giftCards/InitialGiftCardsState"; -import { InitialPageState } from "../initialState/page/InitialPageState"; -import { InitialVouchersState } from "../initialState/vouchers/InitialVouchersState"; -import { InitialState } from "../InitialStateResponse"; import { InitialCollectionState } from "./collections/InitialCollectionState"; +import { InitialGiftCardsState } from "./giftCards/InitialGiftCardsState"; import { InitialOrderState } from "./orders/InitialOrderState"; +import { InitialPageState } from "./page/InitialPageState"; +import { InitialProductState } from "./product/InitialProductStateResponse"; import { - InitialAPIResponse, InitialCollectionAPIResponse, InitialGiftCardsAPIResponse, InitialOrderAPIResponse, InitialPageAPIResponse, + InitialProductAPIResponse, InitialVoucherAPIResponse, } from "./types"; +import { InitialVouchersState } from "./vouchers/InitialVouchersState"; const isChannelQuery = ( - query: InitialAPIResponse, + query: InitialProductAPIResponse, ): query is ApolloQueryResult<_GetChannelOperandsQuery> => "channels" in query.data; const isChannelsQuery = ( query: InitialOrderAPIResponse, ): query is ApolloQueryResult<_GetLegacyChannelOperandsQuery> => "channels" in query.data; const isCollectionQuery = ( - query: InitialAPIResponse, + query: InitialProductAPIResponse, ): query is ApolloQueryResult<_SearchCollectionsOperandsQuery> => "collections" in query.data; const isCategoryQuery = ( - query: InitialAPIResponse, + query: InitialProductAPIResponse, ): query is ApolloQueryResult<_SearchCategoriesOperandsQuery> => "categories" in query.data; const isProductTypeQuery = ( - query: InitialAPIResponse, + query: InitialProductAPIResponse, ): query is ApolloQueryResult<_SearchProductTypesOperandsQuery> => "productTypes" in query.data; const isAttributeQuery = ( - query: InitialAPIResponse, + query: InitialProductAPIResponse, ): query is ApolloQueryResult<_SearchAttributeOperandsQuery> => "attributes" in query.data; const isPageTypesQuery = ( query: InitialPageAPIResponse, @@ -60,8 +60,11 @@ const isCurrencyQuery = ( query: InitialGiftCardsAPIResponse, ): query is ApolloQueryResult => "shop" in query.data; -export const createInitialStateFromData = (data: InitialAPIResponse[], channel: string[]) => - data.reduce( +export const createInitialProductStateFromData = ( + data: InitialProductAPIResponse[], + channel: string[], +) => + data.reduce( (acc, query) => { if (isChannelQuery(query)) { return { diff --git a/src/components/ConditionalFilter/API/initialState/index.ts b/src/components/ConditionalFilter/API/initialState/index.ts deleted file mode 100644 index 174987ad9f9..00000000000 --- a/src/components/ConditionalFilter/API/initialState/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./useInitialAPIState"; diff --git a/src/components/ConditionalFilter/API/InitalStateResponse.test.ts b/src/components/ConditionalFilter/API/initialState/product/InitalProductStateResponse.test.ts similarity index 83% rename from src/components/ConditionalFilter/API/InitalStateResponse.test.ts rename to src/components/ConditionalFilter/API/initialState/product/InitalProductStateResponse.test.ts index be2439ff2a3..018b762ffbe 100644 --- a/src/components/ConditionalFilter/API/InitalStateResponse.test.ts +++ b/src/components/ConditionalFilter/API/initialState/product/InitalProductStateResponse.test.ts @@ -1,10 +1,10 @@ -import { UrlEntry, UrlToken } from "../ValueProvider/UrlToken"; -import { InitialStateResponse } from "./InitialStateResponse"; +import { UrlEntry, UrlToken } from "../../../ValueProvider/UrlToken"; +import { InitialProductStateResponse } from "./InitialProductStateResponse"; -describe("ConditionalFilter / API / InitialStateResponse", () => { +describe("ConditionalFilter / API / InitialProductStateResponse", () => { it("should filter by dynamic attribute token", () => { // Arrange - const initialState = InitialStateResponse.empty(); + const initialState = InitialProductStateResponse.empty(); initialState.attribute = { "attribute-1": { @@ -29,7 +29,7 @@ describe("ConditionalFilter / API / InitialStateResponse", () => { }); it("should filter by static token type", () => { // Arrange - const initialState = InitialStateResponse.empty(); + const initialState = InitialProductStateResponse.empty(); initialState.category = [{ label: "Category 1", value: "1", slug: "category-1" }]; @@ -43,7 +43,7 @@ describe("ConditionalFilter / API / InitialStateResponse", () => { }); it("should filter by boolean attribute token", () => { // Arrange - const initialState = InitialStateResponse.empty(); + const initialState = InitialProductStateResponse.empty(); initialState.attribute = { "attribute-2": { @@ -73,7 +73,7 @@ describe("ConditionalFilter / API / InitialStateResponse", () => { }); it("should filter by static attribute token", () => { // Arrange - const initialState = InitialStateResponse.empty(); + const initialState = InitialProductStateResponse.empty(); initialState.attribute = { size: { diff --git a/src/components/ConditionalFilter/API/InitialStateResponse.ts b/src/components/ConditionalFilter/API/initialState/product/InitialProductStateResponse.ts similarity index 86% rename from src/components/ConditionalFilter/API/InitialStateResponse.ts rename to src/components/ConditionalFilter/API/initialState/product/InitialProductStateResponse.ts index b9fe30f96bb..758c7845430 100644 --- a/src/components/ConditionalFilter/API/InitialStateResponse.ts +++ b/src/components/ConditionalFilter/API/initialState/product/InitialProductStateResponse.ts @@ -1,9 +1,9 @@ import { AttributeInputTypeEnum } from "@dashboard/graphql"; -import { createBooleanOption } from "../constants"; -import { AttributeInputType } from "../FilterElement/ConditionOptions"; -import { ItemOption } from "../FilterElement/ConditionValue"; -import { UrlToken } from "../ValueProvider/UrlToken"; +import { createBooleanOption } from "../../../constants"; +import { AttributeInputType } from "../../../FilterElement/ConditionOptions"; +import { ItemOption } from "../../../FilterElement/ConditionValue"; +import { UrlToken } from "../../../ValueProvider/UrlToken"; export interface AttributeDTO { choices: Array<{ @@ -18,7 +18,7 @@ export interface AttributeDTO { value: string; } -export interface InitialState { +export interface InitialProductState { category: ItemOption[]; attribute: Record; channel: ItemOption[]; @@ -34,7 +34,7 @@ export interface InitialState { const isDateField = (name: string) => ["created", "updatedAt", "startDate", "endDate"].includes(name); -export class InitialStateResponse implements InitialState { +export class InitialProductStateResponse implements InitialProductState { constructor( public category: ItemOption[] = [], public attribute: Record = {}, @@ -53,7 +53,7 @@ export class InitialStateResponse implements InitialState { } public static empty() { - return new InitialStateResponse(); + return new InitialProductStateResponse(); } public filterByUrlToken(token: UrlToken) { diff --git a/src/components/ConditionalFilter/API/initialState/useInitialAPIState.tsx b/src/components/ConditionalFilter/API/initialState/product/useProductInitialAPIState.tsx similarity index 81% rename from src/components/ConditionalFilter/API/initialState/useInitialAPIState.tsx rename to src/components/ConditionalFilter/API/initialState/product/useProductInitialAPIState.tsx index 44d3c398d34..cc4b4753cb8 100644 --- a/src/components/ConditionalFilter/API/initialState/useInitialAPIState.tsx +++ b/src/components/ConditionalFilter/API/initialState/product/useProductInitialAPIState.tsx @@ -18,22 +18,24 @@ import { } from "@dashboard/graphql"; import { useState } from "react"; -import { FetchingParams } from "../../ValueProvider/TokenArray/fetchingParams"; -import { InitialStateResponse } from "../InitialStateResponse"; -import { createInitialStateFromData } from "./helpers"; -import { InitialAPIResponse } from "./types"; +import { FetchingParams } from "../../../ValueProvider/TokenArray/fetchingParams"; +import { createInitialProductStateFromData } from "../helpers"; +import { InitialProductAPIResponse } from "../types"; +import { InitialProductStateResponse } from "./InitialProductStateResponse"; -export interface InitialAPIState { - data: InitialStateResponse; +export interface InitialProductAPIState { + data: InitialProductStateResponse; loading: boolean; fetchQueries: (params: FetchingParams) => Promise; } -export const useProductInitialAPIState = (): InitialAPIState => { +export const useProductInitialAPIState = (): InitialProductAPIState => { const client = useApolloClient(); - const [data, setData] = useState(InitialStateResponse.empty()); + const [data, setData] = useState( + InitialProductStateResponse.empty(), + ); const [loading, setLoading] = useState(true); - const queriesToRun: Array> = []; + const queriesToRun: Array> = []; const fetchQueries = async ({ category, collection, @@ -99,10 +101,10 @@ export const useProductInitialAPIState = (): InitialAPIState => { } const data = await Promise.all(queriesToRun); - const initialState = createInitialStateFromData(data, channel); + const initialState = createInitialProductStateFromData(data, channel); setData( - new InitialStateResponse( + new InitialProductStateResponse( initialState.category, initialState.attribute, initialState.channel, diff --git a/src/components/ConditionalFilter/API/initialState/types.ts b/src/components/ConditionalFilter/API/initialState/types.ts index 217a26716d1..9b2dd4260d8 100644 --- a/src/components/ConditionalFilter/API/initialState/types.ts +++ b/src/components/ConditionalFilter/API/initialState/types.ts @@ -11,7 +11,7 @@ import { ChannelCurrenciesQuery, } from "@dashboard/graphql"; -export type InitialAPIResponse = ApolloQueryResult< +export type InitialProductAPIResponse = ApolloQueryResult< | _GetChannelOperandsQuery | _SearchCollectionsOperandsQuery | _SearchCategoriesOperandsQuery diff --git a/src/components/ConditionalFilter/API/initialState/orders/intl.ts b/src/components/ConditionalFilter/API/intl.ts similarity index 100% rename from src/components/ConditionalFilter/API/initialState/orders/intl.ts rename to src/components/ConditionalFilter/API/intl.ts diff --git a/src/components/ConditionalFilter/API/initialState/orders/messages.ts b/src/components/ConditionalFilter/API/messages.ts similarity index 100% rename from src/components/ConditionalFilter/API/initialState/orders/messages.ts rename to src/components/ConditionalFilter/API/messages.ts diff --git a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/CollectionFilterAPIProvider.tsx similarity index 91% rename from src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/CollectionFilterAPIProvider.tsx index d13cf91a471..db7ed0afcad 100644 --- a/src/components/ConditionalFilter/API/CollectionFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/providers/CollectionFilterAPIProvider.tsx @@ -2,9 +2,9 @@ import { ApolloClient, useApolloClient } from "@apollo/client"; import { CollectionPublished } from "@dashboard/graphql/types.generated"; import { IntlShape, useIntl } from "react-intl"; -import { FilterContainer, FilterElement } from "../FilterElement"; -import { FilterAPIProvider } from "./FilterAPIProvider"; -import { ChannelHandler, EnumValuesHandler, Handler, NoopValuesHandler } from "./Handler"; +import { FilterContainer, FilterElement } from "../../FilterElement"; +import { FilterAPIProvider } from "../FilterAPIProvider"; +import { ChannelHandler, EnumValuesHandler, Handler, NoopValuesHandler } from "../Handler"; const getFilterElement = (value: FilterContainer, index: number): FilterElement => { const possibleFilterElement = value[index]; diff --git a/src/components/ConditionalFilter/API/CustomerFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/CustomerFilterAPIProvider.tsx similarity index 81% rename from src/components/ConditionalFilter/API/CustomerFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/CustomerFilterAPIProvider.tsx index 5bc80d3c68a..db66287a4c8 100644 --- a/src/components/ConditionalFilter/API/CustomerFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/providers/CustomerFilterAPIProvider.tsx @@ -1,4 +1,4 @@ -import { FilterAPIProvider } from "./FilterAPIProvider"; +import { FilterAPIProvider } from "../FilterAPIProvider"; export const useCustomerAPIProvider = (): FilterAPIProvider => { const fetchRightOptions = async () => { diff --git a/src/components/ConditionalFilter/API/DiscountFiltersAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/DiscountFiltersAPIProvider.tsx similarity index 100% rename from src/components/ConditionalFilter/API/DiscountFiltersAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/DiscountFiltersAPIProvider.tsx diff --git a/src/components/ConditionalFilter/API/DraftOrderFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/DraftOrderFilterAPIProvider.tsx similarity index 100% rename from src/components/ConditionalFilter/API/DraftOrderFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/DraftOrderFilterAPIProvider.tsx diff --git a/src/components/ConditionalFilter/API/GiftCardsFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/GiftCardsFilterAPIProvider.tsx similarity index 100% rename from src/components/ConditionalFilter/API/GiftCardsFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/GiftCardsFilterAPIProvider.tsx diff --git a/src/components/ConditionalFilter/API/OrderFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/OrderFilterAPIProvider.tsx similarity index 95% rename from src/components/ConditionalFilter/API/OrderFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/OrderFilterAPIProvider.tsx index 6547b935a07..102b960bbd6 100644 --- a/src/components/ConditionalFilter/API/OrderFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/providers/OrderFilterAPIProvider.tsx @@ -8,8 +8,8 @@ import { } from "@dashboard/graphql"; import { IntlShape, useIntl } from "react-intl"; -import { RowType } from "../constants"; -import { FilterContainer, FilterElement } from "../FilterElement"; +import { RowType } from "../../constants"; +import { FilterContainer, FilterElement } from "../../FilterElement"; import { BooleanValuesHandler, EnumValuesHandler, @@ -17,7 +17,7 @@ import { LegacyChannelHandler, NoopValuesHandler, TextInputValuesHandler, -} from "./Handler"; +} from "../Handler"; const getFilterElement = (value: FilterContainer, index: number): FilterElement => { const possibleFilterElement = value[index]; diff --git a/src/components/ConditionalFilter/API/PageFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/PageFilterAPIProvider.tsx similarity index 95% rename from src/components/ConditionalFilter/API/PageFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/PageFilterAPIProvider.tsx index d9069c2db3b..25f4432c695 100644 --- a/src/components/ConditionalFilter/API/PageFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/providers/PageFilterAPIProvider.tsx @@ -5,7 +5,7 @@ import { FilterElement, } from "@dashboard/components/ConditionalFilter/FilterElement"; -import { FilterAPIProvider } from "./FilterAPIProvider"; +import { FilterAPIProvider } from "../FilterAPIProvider"; const getFilterElement = (value: FilterContainer, index: number): FilterElement => { const possibleFilterElement = value[index]; diff --git a/src/components/ConditionalFilter/API/ProductFilterAPIProvider.tsx b/src/components/ConditionalFilter/API/providers/ProductFilterAPIProvider.tsx similarity index 93% rename from src/components/ConditionalFilter/API/ProductFilterAPIProvider.tsx rename to src/components/ConditionalFilter/API/providers/ProductFilterAPIProvider.tsx index d55946486f4..0d68087751b 100644 --- a/src/components/ConditionalFilter/API/ProductFilterAPIProvider.tsx +++ b/src/components/ConditionalFilter/API/providers/ProductFilterAPIProvider.tsx @@ -1,9 +1,9 @@ import { ApolloClient, useApolloClient } from "@apollo/client"; import { AttributeInputTypeEnum } from "@dashboard/graphql"; -import { RowType } from "../constants"; -import { FilterContainer, FilterElement } from "../FilterElement"; -import { FilterAPIProvider } from "./FilterAPIProvider"; +import { RowType } from "../../constants"; +import { FilterContainer, FilterElement } from "../../FilterElement"; +import { FilterAPIProvider } from "../FilterAPIProvider"; import { AttributeChoicesHandler, AttributesHandler, @@ -13,7 +13,7 @@ import { CollectionHandler, Handler, ProductTypeHandler, -} from "./Handler"; +} from "../Handler"; const getFilterElement = (value: FilterContainer, index: number): FilterElement => { const possibleFilterElement = value[index]; diff --git a/src/components/ConditionalFilter/API/VoucherFilterAPIProvider.ts b/src/components/ConditionalFilter/API/providers/VoucherFilterAPIProvider.ts similarity index 97% rename from src/components/ConditionalFilter/API/VoucherFilterAPIProvider.ts rename to src/components/ConditionalFilter/API/providers/VoucherFilterAPIProvider.ts index b91fc1c33c6..9d9167720c5 100644 --- a/src/components/ConditionalFilter/API/VoucherFilterAPIProvider.ts +++ b/src/components/ConditionalFilter/API/providers/VoucherFilterAPIProvider.ts @@ -11,7 +11,7 @@ import { import { DiscountStatusEnum, VoucherDiscountType } from "@dashboard/graphql"; import { IntlShape, useIntl } from "react-intl"; -import { FilterAPIProvider } from "./FilterAPIProvider"; +import { FilterAPIProvider } from "../FilterAPIProvider"; const getFilterElement = (value: FilterContainer, index: number): FilterElement => { const possibleFilterElement = value[index]; diff --git a/src/components/ConditionalFilter/FilterElement/Condition.test.ts b/src/components/ConditionalFilter/FilterElement/Condition.test.ts index c282ccaf3a8..2648080bb1e 100644 --- a/src/components/ConditionalFilter/FilterElement/Condition.test.ts +++ b/src/components/ConditionalFilter/FilterElement/Condition.test.ts @@ -1,4 +1,4 @@ -import { InitialStateResponse } from "../API/InitialStateResponse"; +import { InitialProductStateResponse } from "../API/initialState/product/InitialProductStateResponse"; import { STATIC_CONDITIONS } from "../constants"; import { UrlToken } from "../ValueProvider/UrlToken"; import { Condition } from "./Condition"; @@ -24,7 +24,7 @@ describe("ConditionalFilter / FilterElement / Condition", () => { it.each([ { token: new UrlToken("category", ["cat1"], "s", "is"), - response: new InitialStateResponse([ + response: new InitialProductStateResponse([ { label: "Cat1", value: "cat-1-id", @@ -47,7 +47,7 @@ describe("ConditionalFilter / FilterElement / Condition", () => { }, { token: new UrlToken("some-attr1", ["some-attr-1z"], "m", "in"), - response: new InitialStateResponse([], { + response: new InitialProductStateResponse([], { "some-attr1": { choices: [ { diff --git a/src/components/ConditionalFilter/FilterElement/Condition.ts b/src/components/ConditionalFilter/FilterElement/Condition.ts index f254040e6cb..27e7262d4bf 100644 --- a/src/components/ConditionalFilter/FilterElement/Condition.ts +++ b/src/components/ConditionalFilter/FilterElement/Condition.ts @@ -1,8 +1,4 @@ -import { InitialGiftCardsStateResponse } from "../API/initialState/giftCards/InitialGiftCardsState"; -import { InitialOrderStateResponse } from "../API/initialState/orders/InitialOrderState"; -import { InitialPageStateResponse } from "../API/initialState/page/InitialPageState"; -import { InitialVouchersStateResponse } from "../API/initialState/vouchers/InitialVouchersState"; -import { InitialStateResponse } from "../API/InitialStateResponse"; +import { InitialProductStateResponse } from "../API/initialState/product/InitialProductStateResponse"; import { LeftOperand } from "../LeftOperandsProvider"; import { InitialResponseType } from "../types"; import { UrlToken } from "./../ValueProvider/UrlToken"; @@ -49,16 +45,7 @@ export class Condition { return new Condition(options, ConditionSelected.fromConditionItem(options.first()), false); } - public static fromUrlToken( - token: UrlToken, - response: - | InitialResponseType - | InitialStateResponse - | InitialOrderStateResponse - | InitialVouchersStateResponse - | InitialPageStateResponse - | InitialGiftCardsStateResponse, - ) { + public static fromUrlToken(token: UrlToken, response: InitialResponseType) { if (ConditionOptions.isStaticName(token.name)) { const staticOptions = ConditionOptions.fromStaticElementName(token.name); const selectedOption = staticOptions.findByLabel(token.conditionKind); @@ -83,7 +70,7 @@ export class Condition { } if (token.isAttribute()) { - const attribute = (response as InitialStateResponse).attributeByName(token.name); + const attribute = (response as InitialProductStateResponse).attributeByName(token.name); const options = ConditionOptions.fromAttributeType(attribute.inputType); const option = options.find(item => item.label === token.conditionKind)!; const value = response.filterByUrlToken(token); diff --git a/src/components/ConditionalFilter/FilterElement/FilterElement.ts b/src/components/ConditionalFilter/FilterElement/FilterElement.ts index 0644384299a..ea79aa31840 100644 --- a/src/components/ConditionalFilter/FilterElement/FilterElement.ts +++ b/src/components/ConditionalFilter/FilterElement/FilterElement.ts @@ -1,8 +1,4 @@ -import { InitialGiftCardsStateResponse } from "../API/initialState/giftCards/InitialGiftCardsState"; -import { InitialOrderStateResponse } from "../API/initialState/orders/InitialOrderState"; -import { InitialPageStateResponse } from "../API/initialState/page/InitialPageState"; -import { InitialVouchersStateResponse } from "../API/initialState/vouchers/InitialVouchersState"; -import { InitialStateResponse } from "../API/InitialStateResponse"; +import { InitialProductStateResponse } from "../API/initialState/product/InitialProductStateResponse"; import { RowType, STATIC_OPTIONS } from "../constants"; import { LeftOperand } from "../LeftOperandsProvider"; import { InitialResponseType } from "../types"; @@ -50,7 +46,7 @@ export class ExpressionValue { return new ExpressionValue(token.name, option.label, token.name); } - public static forAttribute(attributeName: string, response: InitialStateResponse) { + public static forAttribute(attributeName: string, response: InitialProductStateResponse) { const attribute = response.attributeByName(attributeName); return new ExpressionValue(attributeName, attribute.label, attribute.inputType); @@ -179,16 +175,7 @@ export class FilterElement { return new FilterElement(ExpressionValue.fromSlug(slug), Condition.emptyFromSlug(slug), false); } - public static fromUrlToken( - token: UrlToken, - response: - | InitialResponseType - | InitialStateResponse - | InitialOrderStateResponse - | InitialVouchersStateResponse - | InitialPageStateResponse - | InitialGiftCardsStateResponse, - ) { + public static fromUrlToken(token: UrlToken, response: InitialResponseType) { if (token.isStatic()) { return new FilterElement( ExpressionValue.fromUrlToken(token), @@ -199,7 +186,7 @@ export class FilterElement { if (token.isAttribute()) { return new FilterElement( - ExpressionValue.forAttribute(token.name, response as InitialStateResponse), + ExpressionValue.forAttribute(token.name, response as InitialProductStateResponse), Condition.fromUrlToken(token, response), false, ); diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts index 5b4ee3715c0..63375e4af24 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/TokenArray.test.ts @@ -1,4 +1,4 @@ -import { InitialStateResponse } from "../../API/InitialStateResponse"; +import { InitialProductStateResponse } from "../../API/initialState/product/InitialProductStateResponse"; import { TokenArray } from "."; import { FetchingParams } from "./fetchingParams"; @@ -64,7 +64,7 @@ describe("ConditionalFilter / ValueProvider / TokenArray", () => { "1": "AND", "2[s0.channel]": "channel-pln", }); - const response = new InitialStateResponse( + const response = new InitialProductStateResponse( [ { label: "Cat1", @@ -93,7 +93,7 @@ describe("ConditionalFilter / ValueProvider / TokenArray", () => { const params = new URLSearchParams({ "0[s0.channel]": "channel-pln", }); - const response = new InitialStateResponse( + const response = new InitialProductStateResponse( [ { label: "Cat1", diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.test.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.test.ts index 2547c67e9b4..e83e784d74a 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.test.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.test.ts @@ -1,12 +1,13 @@ import { UrlToken } from "../UrlToken"; import { getEmptyFetchingPrams, + toCollectionFetchingParams, toGiftCardsFetchingParams, toPageFetchingParams, toVouchersFetchingParams, } from "./fetchingParams"; -describe("TokenArray / fetchingParams / getFetchingPrams", () => { +describe("TokenArray / fetchingParams / getEmptyFetchingPrams", () => { it("should return product fetching params", () => { // Arrange const type = "product"; @@ -169,3 +170,31 @@ describe("TokenArray / fetchingParams / toGiftCardsFetchingParams", () => { }); }); }); + +describe("TokenArray / fetchingParams / toCollectionFetchingParams", () => { + it("should return fetching params", () => { + // Arrange + const params = { + channel: [], + metadata: [], + published: [], + }; + + const token = { + conditionKind: "in", + name: "channel", + type: "s", + value: "chan-1", + } as UrlToken; + + // Act + const fetchingParams = toCollectionFetchingParams(params, token); + + // Assert + expect(fetchingParams).toEqual({ + channel: ["chan-1"], + metadata: [], + published: [], + }); + }); +}); diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts index 4a7dd511ae9..b07d1f42ea8 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/fetchingParams.ts @@ -1,16 +1,6 @@ +import { FilterProviderType } from "../../types"; import { TokenType, UrlToken } from "../UrlToken"; -export type FilterProviderType = - | "product" - | "order" - | "discount" - | "customer" - | "voucher" - | "page" - | "draft-order" - | "gift-cards" - | "collection"; - export interface FetchingParams { category: string[]; collection: string[]; @@ -48,9 +38,7 @@ export interface GiftCardsFetchingParams { export interface CollectionFetchingParams { channel: string[]; - ids: string[]; metadata: string[]; - slugs: string[]; published: string[]; } @@ -97,9 +85,7 @@ export const emptyGiftCardsFetchingParams: GiftCardsFetchingParams = { export const emptyCollectionFetchingParams: CollectionFetchingParams = { channel: [], - ids: [], metadata: [], - slugs: [], published: [], }; diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts index d4d6cddf6f2..0363230b796 100644 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts +++ b/src/components/ConditionalFilter/ValueProvider/TokenArray/index.ts @@ -1,18 +1,13 @@ import { parse, ParsedQs } from "qs"; -import { InitialGiftCardsStateResponse } from "../../API/initialState/giftCards/InitialGiftCardsState"; -import { InitialOrderStateResponse } from "../../API/initialState/orders/InitialOrderState"; -import { InitialPageStateResponse } from "../../API/initialState/page/InitialPageState"; -import { InitialVouchersStateResponse } from "../../API/initialState/vouchers/InitialVouchersState"; -import { InitialStateResponse } from "../../API/InitialStateResponse"; +import { InitialProductStateResponse } from "../../API/initialState/product/InitialProductStateResponse"; import { FilterContainer, FilterElement } from "../../FilterElement"; -import { InitialResponseType } from "../../types"; +import { FilterProviderType, InitialResponseType } from "../../types"; import { UrlEntry, UrlToken } from "../UrlToken"; import { CollectionFetchingParams, FetchingParams, FetchingParamsType, - FilterProviderType, GiftCardsFetchingParams, OrderFetchingParams, PageFetchingParams, @@ -57,13 +52,7 @@ const tokenizeUrl = (urlParams: string) => { }; const mapUrlTokensToFilterValues = ( urlTokens: TokenArray, - response: - | InitialResponseType - | InitialStateResponse - | InitialOrderStateResponse - | InitialVouchersStateResponse - | InitialPageStateResponse - | InitialGiftCardsStateResponse, + response: InitialResponseType, ): FilterContainer => urlTokens.map(el => { if (typeof el === "string") { @@ -121,15 +110,7 @@ export class TokenArray extends Array { return flatenate(this); } - public asFilterValuesFromResponse( - response: - | InitialResponseType - | InitialStateResponse - | InitialOrderStateResponse - | InitialVouchersStateResponse - | InitialPageStateResponse - | InitialGiftCardsStateResponse, - ): FilterContainer { + public asFilterValuesFromResponse(response: InitialResponseType): FilterContainer { return this.map(el => { if (typeof el === "string") { return el; @@ -150,6 +131,6 @@ export class TokenArray extends Array { } public asFilterValueFromEmpty(): FilterContainer { - return this.asFilterValuesFromResponse(InitialStateResponse.empty()); + return this.asFilterValuesFromResponse(InitialProductStateResponse.empty()); } } diff --git a/src/components/ConditionalFilter/ValueProvider/TokenArray/ordersFetchingParams.ts b/src/components/ConditionalFilter/ValueProvider/TokenArray/ordersFetchingParams.ts deleted file mode 100644 index e3425d48408..00000000000 --- a/src/components/ConditionalFilter/ValueProvider/TokenArray/ordersFetchingParams.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { UrlToken } from "../UrlToken"; - -export interface OrdersFetchingParams { - paymentStatus: string[]; -} - -type FetchingParamsKeys = keyof Omit; - -export const emptyFetchingParams: OrdersFetchingParams = { - paymentStatus: [], -}; - -const unique = (array: Iterable) => Array.from(new Set(array)); - -export const toFetchingParams = (p: OrdersFetchingParams, c: UrlToken) => { - const key = c.name as FetchingParamsKeys; - - if (!c.isAttribute() && !p[key]) { - p[key] = []; - } - - p[key] = unique(p[key].concat(c.value)); - - return p; -}; diff --git a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts index 1bc04ae7f3a..fe48f3227d2 100644 --- a/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts +++ b/src/components/ConditionalFilter/ValueProvider/useUrlValueProvider.ts @@ -3,19 +3,19 @@ import { stringify } from "qs"; import { useEffect, useState } from "react"; import useRouter from "use-react-router"; -import { InitialAPIState } from "../API"; import { InitialCollectionAPIState } from "../API/initialState/collections/useInitialCollectionsState"; import { InitialGiftCardsAPIState } from "../API/initialState/giftCards/useInitialGiftCardsState"; import { InitialOrderAPIState } from "../API/initialState/orders/useInitialOrderState"; import { InitialPageAPIState } from "../API/initialState/page/useInitialPageState"; +import { InitialProductAPIState } from "../API/initialState/product/useProductInitialAPIState"; import { InitialVoucherAPIState } from "../API/initialState/vouchers/useInitialVouchersState"; import { FilterContainer, FilterElement } from "../FilterElement"; import { FilterValueProvider } from "../FilterValueProvider"; +import { FilterProviderType, InitialAPIState } from "../types"; import { TokenArray } from "./TokenArray"; import { CollectionFetchingParams, FetchingParams, - FilterProviderType, getEmptyFetchingPrams, GiftCardsFetchingParams, OrderFetchingParams, @@ -27,13 +27,7 @@ import { prepareStructure } from "./utils"; export const useUrlValueProvider = ( locationSearch: string, type: FilterProviderType, - initialState?: - | InitialAPIState - | InitialOrderAPIState - | InitialVoucherAPIState - | InitialPageAPIState - | InitialGiftCardsAPIState - | InitialCollectionAPIState, + initialState?: InitialAPIState, ): FilterValueProvider => { const router = useRouter(); const params = new URLSearchParams(locationSearch); @@ -60,7 +54,7 @@ export const useUrlValueProvider = ( if (initialState) { switch (type) { case "product": - (initialState as InitialAPIState).fetchQueries(fetchingParams as FetchingParams); + (initialState as InitialProductAPIState).fetchQueries(fetchingParams as FetchingParams); break; case "order": (initialState as InitialOrderAPIState).fetchQueries( diff --git a/src/components/ConditionalFilter/context/provider.tsx b/src/components/ConditionalFilter/context/provider.tsx index 1ba78d15985..71664358aaf 100644 --- a/src/components/ConditionalFilter/context/provider.tsx +++ b/src/components/ConditionalFilter/context/provider.tsx @@ -1,20 +1,20 @@ import React, { FC } from "react"; -import { useCollectionFilterAPIProvider } from "../API/CollectionFilterAPIProvider"; -import { useCustomerAPIProvider } from "../API/CustomerFilterAPIProvider"; -import { useDiscountFilterAPIProvider } from "../API/DiscountFiltersAPIProvider"; -import { useDraftOrderFilterAPIProvider } from "../API/DraftOrderFilterAPIProvider"; -import { useGiftCardsFiltersAPIProvider } from "../API/GiftCardsFilterAPIProvider"; import { useInitialCollectionState } from "../API/initialState/collections/useInitialCollectionsState"; import { useInitialGiftCardsState } from "../API/initialState/giftCards/useInitialGiftCardsState"; import { useInitialOrderState } from "../API/initialState/orders/useInitialOrderState"; import { useInitialPageState } from "../API/initialState/page/useInitialPageState"; -import { useProductInitialAPIState } from "../API/initialState/useInitialAPIState"; +import { useProductInitialAPIState } from "../API/initialState/product/useProductInitialAPIState"; import { useInitialVouchersState } from "../API/initialState/vouchers/useInitialVouchersState"; -import { useOrderFilterAPIProvider } from "../API/OrderFilterAPIProvider"; -import { usePageAPIProvider } from "../API/PageFilterAPIProvider"; -import { useProductFilterAPIProvider } from "../API/ProductFilterAPIProvider"; -import { useVoucherAPIProvider } from "../API/VoucherFilterAPIProvider"; +import { useCollectionFilterAPIProvider } from "../API/providers/CollectionFilterAPIProvider"; +import { useCustomerAPIProvider } from "../API/providers/CustomerFilterAPIProvider"; +import { useDiscountFilterAPIProvider } from "../API/providers/DiscountFiltersAPIProvider"; +import { useDraftOrderFilterAPIProvider } from "../API/providers/DraftOrderFilterAPIProvider"; +import { useGiftCardsFiltersAPIProvider } from "../API/providers/GiftCardsFilterAPIProvider"; +import { useOrderFilterAPIProvider } from "../API/providers/OrderFilterAPIProvider"; +import { usePageAPIProvider } from "../API/providers/PageFilterAPIProvider"; +import { useProductFilterAPIProvider } from "../API/providers/ProductFilterAPIProvider"; +import { useVoucherAPIProvider } from "../API/providers/VoucherFilterAPIProvider"; import { STATIC_COLLECTION_OPTIONS, STATIC_CUSTOMER_OPTIONS, diff --git a/src/components/ConditionalFilter/types.ts b/src/components/ConditionalFilter/types.ts index 4e84d75e327..762b6cab042 100644 --- a/src/components/ConditionalFilter/types.ts +++ b/src/components/ConditionalFilter/types.ts @@ -1,8 +1,39 @@ import { InitialCollectionStateResponse } from "./API/initialState/collections/InitialCollectionState"; +import { InitialCollectionAPIState } from "./API/initialState/collections/useInitialCollectionsState"; +import { InitialGiftCardsStateResponse } from "./API/initialState/giftCards/InitialGiftCardsState"; +import { InitialGiftCardsAPIState } from "./API/initialState/giftCards/useInitialGiftCardsState"; import { InitialOrderStateResponse } from "./API/initialState/orders/InitialOrderState"; -import { InitialStateResponse } from "./API/InitialStateResponse"; +import { InitialOrderAPIState } from "./API/initialState/orders/useInitialOrderState"; +import { InitialPageStateResponse } from "./API/initialState/page/InitialPageState"; +import { InitialPageAPIState } from "./API/initialState/page/useInitialPageState"; +import { InitialProductStateResponse } from "./API/initialState/product/InitialProductStateResponse"; +import { InitialProductAPIState } from "./API/initialState/product/useProductInitialAPIState"; +import { InitialVouchersStateResponse } from "./API/initialState/vouchers/InitialVouchersState"; +import { InitialVoucherAPIState } from "./API/initialState/vouchers/useInitialVouchersState"; export type InitialResponseType = - | InitialStateResponse + | InitialProductStateResponse | InitialOrderStateResponse - | InitialCollectionStateResponse; + | InitialCollectionStateResponse + | InitialVouchersStateResponse + | InitialPageStateResponse + | InitialGiftCardsStateResponse; + +export type InitialAPIState = + | InitialProductAPIState + | InitialOrderAPIState + | InitialVoucherAPIState + | InitialPageAPIState + | InitialGiftCardsAPIState + | InitialCollectionAPIState; + +export type FilterProviderType = + | "product" + | "order" + | "discount" + | "customer" + | "voucher" + | "page" + | "draft-order" + | "gift-cards" + | "collection"; From 073ef2564d25019e55bcb7129e8b051972c41b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 12 Feb 2025 15:24:10 +0100 Subject: [PATCH 10/12] Update flag --- ...n-new-filters.md => collection_filters.md} | 6 +-- .featureFlags/generated.tsx | 38 +++++++++--------- ...t-filtering.jpg => collection-filters.jpg} | Bin .../CollectionListPage/CollectionListPage.tsx | 2 +- .../views/CollectionList/CollectionList.tsx | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) rename .featureFlags/{colleciton-new-filters.md => collection_filters.md} (60%) rename .featureFlags/images/{collection-list-filtering.jpg => collection-filters.jpg} (100%) diff --git a/.featureFlags/colleciton-new-filters.md b/.featureFlags/collection_filters.md similarity index 60% rename from .featureFlags/colleciton-new-filters.md rename to .featureFlags/collection_filters.md index 9246c226095..039a48e704b 100644 --- a/.featureFlags/colleciton-new-filters.md +++ b/.featureFlags/collection_filters.md @@ -1,11 +1,11 @@ --- -name: collection_list_new_filters -displayName: Collection list new filtering +name: collection_filters +displayName: Collection filtering enabled: true payload: "default" visible: true --- -![new filters](./images/collection-list-filtering.jpg) +![new filters](./images/collection-filters.jpg) Experience the new look and enhanced abilities of new filtering 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 8c243c40940..ffa25c70ac1 100644 --- a/.featureFlags/generated.tsx +++ b/.featureFlags/generated.tsx @@ -1,35 +1,35 @@ // @ts-nocheck -import A57372 from "./images/collection-list-filtering.jpg" -import F96761 from "./images/customers-filters.png" -import D50013 from "./images/discounts-list.png" -import W72891 from "./images/draft-orders-filters.png" -import P04017 from "./images/gift-cards-filters.png" -import I80684 from "./images/improved_refunds.png" -import W65601 from "./images/page-filters.png" -import T54430 from "./images/vouchers-filters.png" +import W24585 from "./images/collection-filters.jpg" +import L16463 from "./images/customers-filters.png" +import L94878 from "./images/discounts-list.png" +import E09437 from "./images/draft-orders-filters.png" +import E78766 from "./images/gift-cards-filters.png" +import X68306 from "./images/improved_refunds.png" +import E85468 from "./images/page-filters.png" +import B37301 from "./images/vouchers-filters.png" -const collection_list_new_filters = () => (<>

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

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

    ) -const customers_filters = () => (<>

    new filters +const customers_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 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 draft_orders_filters = () => (<>

    new filters +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 gift_cards_filters = () => (<>

    new filters +const gift_cards_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

    +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.

      @@ -39,19 +39,19 @@ const improved_refunds = () => (<>

      Improved refunds<

    ) -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.

    ) export const AVAILABLE_FLAGS = [{ - name: "collection_list_new_filters", - displayName: "Collection list new filtering", - component: collection_list_new_filters, + name: "collection_filters", + displayName: "Collection filtering", + component: collection_filters, visible: true, content: { enabled: true, diff --git a/.featureFlags/images/collection-list-filtering.jpg b/.featureFlags/images/collection-filters.jpg similarity index 100% rename from .featureFlags/images/collection-list-filtering.jpg rename to .featureFlags/images/collection-filters.jpg diff --git a/src/collections/components/CollectionListPage/CollectionListPage.tsx b/src/collections/components/CollectionListPage/CollectionListPage.tsx index 56bec897406..0dd2290735e 100644 --- a/src/collections/components/CollectionListPage/CollectionListPage.tsx +++ b/src/collections/components/CollectionListPage/CollectionListPage.tsx @@ -66,7 +66,7 @@ const CollectionListPage: React.FC = ({ const navigate = useNavigator(); const filterStructure = createFilterStructure(intl, filterOpts); const [isFilterPresetOpen, setFilterPresetOpen] = useState(false); - const { enabled: isNewCollectionListEnabled } = useFlag("collection_list_new_filters"); + const { enabled: isNewCollectionListEnabled } = useFlag("collection_filters"); const filterDependency = filterStructure.find(getByName("channel")); return ( diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index 0ad45f8d8ea..f183a8073de 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -47,7 +47,7 @@ export const CollectionList: React.FC = ({ params }) => { const intl = useIntl(); const notify = useNotifier(); const { updateListSettings, settings } = useListSettings(ListViews.COLLECTION_LIST); - const { enabled: isNewGiftCardsFilterEnabled } = useFlag("collection_list_new_filters"); + const { enabled: isNewGiftCardsFilterEnabled } = useFlag("collection_filters"); const { valueProvider } = useConditionalFilterContext(); const filters = createCollectionsQueryVariables(valueProvider.value); From afb52229ca8a9b34f06c9cae918a778a0c8b44e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 12 Feb 2025 16:28:20 +0100 Subject: [PATCH 11/12] Restore collection list --- .../views/CollectionList/CollectionList.tsx | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/collections/views/CollectionList/CollectionList.tsx b/src/collections/views/CollectionList/CollectionList.tsx index f183a8073de..950956f631f 100644 --- a/src/collections/views/CollectionList/CollectionList.tsx +++ b/src/collections/views/CollectionList/CollectionList.tsx @@ -47,9 +47,8 @@ export const CollectionList: React.FC = ({ params }) => { const intl = useIntl(); const notify = useNotifier(); const { updateListSettings, settings } = useListSettings(ListViews.COLLECTION_LIST); - const { enabled: isNewGiftCardsFilterEnabled } = useFlag("collection_filters"); + const { enabled: isNewCollectionFilterEnabled } = useFlag("collection_filters"); const { valueProvider } = useConditionalFilterContext(); - const filters = createCollectionsQueryVariables(valueProvider.value); usePaginationReset(collectionListUrl, params, settings.rowNumber); @@ -71,7 +70,6 @@ export const CollectionList: React.FC = ({ params }) => { const channelOpts = availableChannels ? mapNodeToChoice(availableChannels, channel => channel.slug) : null; - const selectedChannel = availableChannels.find(channel => channel.slug === params.channel); const { selectedPreset, presets, @@ -89,28 +87,32 @@ export const CollectionList: React.FC = ({ params }) => { storageUtils, }); const paginationState = createPaginationState(settings.rowNumber, params); - const queryVariables = React.useMemo( - () => ({ - ...paginationState, - filter: getFilterVariables(params), - sort: getSortQueryVariables(params), - channel: selectedChannel?.slug, - }), - [params, settings.rowNumber], - ); - const newQueryVariables = React.useMemo(() => { - const { channel, ...restFilters } = filters; + const selectedChannel_legacy = availableChannels.find(channel => channel.slug === params.channel); + const queryVariables = React.useMemo(() => { + if (!isNewCollectionFilterEnabled) { + return { + ...paginationState, + filter: getFilterVariables(params), + sort: getSortQueryVariables(params), + channel: selectedChannel_legacy?.slug, + }; + } + + const { channel, ...variables } = createCollectionsQueryVariables(valueProvider.value); return { ...paginationState, - filter: restFilters, + filter: variables, sort: getSortQueryVariables(params), - channel, + channel, // Saleor docs say 'channel' in filter is deprecated and should be moved to root }; - }, [params, settings.rowNumber, valueProvider.value]); + }, [params, settings.rowNumber, valueProvider.value, isNewCollectionFilterEnabled]); + const selectedChannel = availableChannels.find( + channel => channel.slug === queryVariables.channel, + ); const { data, refetch } = useCollectionListQuery({ displayLoader: true, - variables: isNewGiftCardsFilterEnabled ? newQueryVariables : queryVariables, + variables: queryVariables, }); const collections = mapEdgesToItems(data?.collections); const [collectionBulkDelete, collectionBulkDeleteOpts] = useCollectionBulkDeleteMutation({ @@ -126,10 +128,9 @@ export const CollectionList: React.FC = ({ params }) => { } }, }); - const filterOpts = getFilterOpts(params, channelOpts); useEffect(() => { - if (!canBeSorted(params.sort, !!selectedChannel)) { + if (!canBeSorted(params.sort, !!queryVariables.channel)) { navigate( collectionListUrl({ ...params, @@ -175,6 +176,11 @@ export const CollectionList: React.FC = ({ params }) => { clearRowSelection(); }, [selectedRowIds]); + const filterOpts = getFilterOpts(params, channelOpts); + const selectedChannelId = isNewCollectionFilterEnabled + ? selectedChannel?.id + : selectedChannel_legacy?.id; + return ( = ({ params }) => { onSort={handleSort} onUpdateListSettings={updateListSettings} sort={getSortParams(params)} - selectedChannelId={selectedChannel?.id} + selectedChannelId={selectedChannelId} filterOpts={filterOpts} onFilterChange={changeFilters} selectedCollectionIds={selectedRowIds} From a2cbf3eedd1d605456e3ef09fde5e159cc395e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Chy=C5=82a?= Date: Wed, 12 Feb 2025 16:28:51 +0100 Subject: [PATCH 12/12] Refactor statics --- .../ValueProvider/UrlToken.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/components/ConditionalFilter/ValueProvider/UrlToken.ts b/src/components/ConditionalFilter/ValueProvider/UrlToken.ts index 13ef2c83dcf..676fc814bb0 100644 --- a/src/components/ConditionalFilter/ValueProvider/UrlToken.ts +++ b/src/components/ConditionalFilter/ValueProvider/UrlToken.ts @@ -6,6 +6,18 @@ import { slugFromConditionValue } from "../FilterElement/ConditionValue"; export const CONDITIONS = ["is", "equals", "in", "between", "lower", "greater"]; +const PRODUCT_STATICS = [ + "category", + "collection", + "channel", + "productType", + "isAvailable", + "isPublished", + "isVisibleInListing", + "hasCategory", + "giftCard", +]; + const ORDER_STATICS = [ "paymentStatus", "status", @@ -25,18 +37,10 @@ const PAGE_STATIC = ["pageTypes"]; const GIFT_CARDS_STATICS = ["currency", "products", "isActive", "tags", "usedBy"]; -const COLLECTION_STATICS = ["published"]; +const COLLECTION_STATICS = ["channel", "published"]; const STATIC_TO_LOAD = [ - "category", - "collection", - "channel", - "productType", - "isAvailable", - "isPublished", - "isVisibleInListing", - "hasCategory", - "giftCard", + ...PRODUCT_STATICS, ...ORDER_STATICS, ...VOUCHER_STATICS, ...PAGE_STATIC,