From cb180cbbf84156ed55d9b6ce0653ceabc03dba2e Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:22:10 -0400 Subject: [PATCH] feat: search filter in list view --- packages/next/src/utilities/initPage.ts | 5 +- .../next/src/views/List/Default/index.tsx | 9 +- packages/next/src/views/List/Default/types.ts | 3 +- packages/next/src/views/List/index.tsx | 32 +++- packages/payload/src/exports/utilities.ts | 3 +- .../src/utilities/mergeListSearchAndWhere.ts | 74 ++++++++ .../ui/src/elements/ListControls/index.tsx | 6 +- .../src/elements/ListDrawer/DrawerContent.tsx | 2 + .../ui/src/elements/SearchFilter/index.tsx | 46 ++--- .../ui/src/elements/SearchFilter/types.ts | 1 - packages/ui/src/providers/ListInfo/index.tsx | 166 +++++++++++++++++- packages/ui/src/providers/ListInfo/types.ts | 30 +++- tsconfig.json | 2 +- 13 files changed, 322 insertions(+), 57 deletions(-) create mode 100644 packages/payload/src/utilities/mergeListSearchAndWhere.ts diff --git a/packages/next/src/utilities/initPage.ts b/packages/next/src/utilities/initPage.ts index 940000bdda..6cba2bfac0 100644 --- a/packages/next/src/utilities/initPage.ts +++ b/packages/next/src/utilities/initPage.ts @@ -71,13 +71,16 @@ export const initPage = async ({ translations, }) + const queryString = `${qs.stringify(searchParams, { addQueryPrefix: true })}` + const req = await createLocalReq( { fallbackLocale: null, locale: locale.code, req: { i18n, - url: `${payload.config.serverURL}${route}${searchParams ? `${qs.stringify(searchParams, { addQueryPrefix: true })}` : ''}`, + query: qs.parse(queryString, { ignoreQueryPrefix: true }), + url: `${payload.config.serverURL}${route}${searchParams ? queryString : ''}`, } as PayloadRequest, user, }, diff --git a/packages/next/src/views/List/Default/index.tsx b/packages/next/src/views/List/Default/index.tsx index 6a7e827aa2..ca19d8a058 100644 --- a/packages/next/src/views/List/Default/index.tsx +++ b/packages/next/src/views/List/Default/index.tsx @@ -45,9 +45,6 @@ export const DefaultListView: React.FC = () => { data, handlePageChange, handlePerPageChange, - handleSearchChange, - handleSortChange, - handleWhereChange, hasCreatePermission, limit, modifySearchParams, @@ -130,12 +127,8 @@ export const DefaultListView: React.FC = () => { {BeforeListTable} diff --git a/packages/next/src/views/List/Default/types.ts b/packages/next/src/views/List/Default/types.ts index cedec44408..9eef67a096 100644 --- a/packages/next/src/views/List/Default/types.ts +++ b/packages/next/src/views/List/Default/types.ts @@ -3,6 +3,7 @@ import type { SanitizedCollectionConfig } from 'payload/types' export type DefaultListViewProps = { collectionSlug: SanitizedCollectionConfig['slug'] + listSearchableFields: SanitizedCollectionConfig['admin']['listSearchableFields'] } export type ListIndexProps = { @@ -12,5 +13,5 @@ export type ListIndexProps = { export type ListPreferences = { columns: ColumnPreferences limit: number - sort: number + sort: string } diff --git a/packages/next/src/views/List/index.tsx b/packages/next/src/views/List/index.tsx index 66b926c501..036091a367 100644 --- a/packages/next/src/views/List/index.tsx +++ b/packages/next/src/views/List/index.tsx @@ -1,4 +1,4 @@ -import type { AdminViewProps } from 'payload/types' +import type { Where } from 'payload/types' import { HydrateClientUser, @@ -7,7 +7,9 @@ import { TableColumnsProvider, } from '@payloadcms/ui' import { notFound } from 'next/navigation.js' -import { isEntityHidden } from 'payload/utilities' +import { createClientCollectionConfig } from 'packages/payload/src/collections/config/client.js' +import { type AdminViewProps } from 'payload/types' +import { isEntityHidden, isNumber, mergeListSearchAndWhere } from 'payload/utilities' import React, { Fragment } from 'react' import type { DefaultListViewProps, ListPreferences } from './Default/types.js' @@ -25,6 +27,7 @@ export const ListView: React.FC = async ({ initPageResult, searc locale, payload, payload: { config }, + query, user, }, } = initPageResult @@ -43,6 +46,7 @@ export const ListView: React.FC = async ({ initPageResult, searc collection: 'payload-preferences', depth: 0, limit: 1, + user, where: { key: { equals: `${collectionSlug}-list`, @@ -73,31 +77,53 @@ export const ListView: React.FC = async ({ initPageResult, searc CustomListView = CustomList.Component } - const limit = Number(searchParams?.limit) || collectionConfig.admin.pagination.defaultLimit + const page = isNumber(query?.page) ? query.page : 0 + const whereQuery = mergeListSearchAndWhere({ + collectionConfig, + query: { + search: typeof query?.search === 'string' ? query.search : undefined, + where: (query?.where as Where) || undefined, + }, + }) + const limit = isNumber(query?.limit) + ? query.limit + : listPreferences?.limit || collectionConfig.admin.pagination.defaultLimit + const sort = + query?.sort && typeof query.sort === 'string' + ? query.sort + : listPreferences?.sort || undefined const data = await payload.find({ collection: collectionSlug, depth: 0, + draft: true, fallbackLocale: null, limit, locale, overrideAccess: false, + page, + sort, user, + where: whereQuery || {}, }) const viewComponentProps: DefaultListViewProps = { collectionSlug, + listSearchableFields: collectionConfig.admin.listSearchableFields, } return ( { + if ('and' in where) { + where.and.push(queryParams) + } else if ('or' in where) { + where = { + and: [where, queryParams], + } + } else { + where = { + and: [where, queryParams], + } + } + + return where +} + +const getTitleField = (collection: SanitizedCollectionConfig): FieldAffectingData => { + const { + admin: { useAsTitle }, + fields, + } = collection + + const topLevelFields = flattenTopLevelFields(fields) + return topLevelFields.find( + (field) => fieldAffectsData(field) && field.name === useAsTitle, + ) as FieldAffectingData +} + +type Args = { + collectionConfig: SanitizedCollectionConfig + query: { + search?: string + where?: Where + } +} + +export const mergeListSearchAndWhere = ({ collectionConfig, query }: Args): Where => { + const search = query?.search || undefined + let where = QueryString.parse(typeof query?.where === 'string' ? query.where : '') as Where + + if (search) { + let copyOfWhere = { ...(where || {}) } + + const searchAsConditions = ( + collectionConfig.admin.listSearchableFields || [getTitleField(collectionConfig)?.name || 'id'] + ).map((fieldName) => { + return { + [fieldName]: { + like: search, + }, + } + }, []) + + if (searchAsConditions.length > 0) { + const conditionalSearchFields = { + or: [...searchAsConditions], + } + copyOfWhere = hoistQueryParamsToAnd(copyOfWhere, conditionalSearchFields) + } + + where = copyOfWhere + } + + return where +} diff --git a/packages/ui/src/elements/ListControls/index.tsx b/packages/ui/src/elements/ListControls/index.tsx index c4b91fec78..c05b7836b5 100644 --- a/packages/ui/src/elements/ListControls/index.tsx +++ b/packages/ui/src/elements/ListControls/index.tsx @@ -11,6 +11,7 @@ const AnimateHeight = (AnimateHeightImport.default || import type { Props } from './types.js' import { Chevron } from '../../icons/Chevron/index.js' +import { useListInfo } from '../../index.js' import { useSearchParams } from '../../providers/SearchParams/index.js' import { useTranslation } from '../../providers/Translation/index.js' import ColumnSelector from '../ColumnSelector/index.js' @@ -37,15 +38,13 @@ export const ListControls: React.FC = (props) => { enableColumns = true, enableSort = false, fieldMap, - handleSearchChange, - handleSortChange, - handleWhereChange, modifySearchQuery = true, textFieldsToBeSearched, titleField, } = props const { useWindowInfo } = facelessUIImport + const { handleSearchChange, handleWhereChange } = useListInfo() const { searchParams } = useSearchParams() const shouldInitializeWhereOpened = validateWhereQuery(searchParams?.where) @@ -68,7 +67,6 @@ export const ListControls: React.FC = (props) => { fieldName={titleField && fieldAffectsData(titleField) ? titleField.name : undefined} handleChange={handleSearchChange} listSearchableFields={textFieldsToBeSearched} - modifySearchQuery={modifySearchQuery} />
diff --git a/packages/ui/src/elements/ListDrawer/DrawerContent.tsx b/packages/ui/src/elements/ListDrawer/DrawerContent.tsx index 6fb5adba16..ebb1ebeb33 100644 --- a/packages/ui/src/elements/ListDrawer/DrawerContent.tsx +++ b/packages/ui/src/elements/ListDrawer/DrawerContent.tsx @@ -247,6 +247,7 @@ export const ListDrawerContent: React.FC = ({ )}