fix: change search from client-side to server-side global search

- Backend: add `search` query param to GET /api/objects, uses LIKE %keyword% across all folders
- Frontend: 300ms debounce before sending search request to backend
- Search is global — finds files in any directory, not just current folder

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
saltbo
2026-04-10 11:35:13 -04:00
co-authored by Claude Opus 4.6
parent 3a3364b9de
commit 741b4afc1e
5 changed files with 26 additions and 14 deletions
+2 -1
View File
@@ -85,11 +85,12 @@ const app = new Hono<Env>()
const parent = c.req.query('path') ?? c.req.query('parent') ?? ''
const status = c.req.query('status') ?? 'active'
const typeFilter = c.req.query('type')
const search = c.req.query('search')
const page = Number(c.req.query('page') ?? '1')
const pageSize = Number(c.req.query('pageSize') ?? '20')
const db = c.get('platform').db
const result = await listMatters(db, orgId, { parent, status, typeFilter, page, pageSize })
const result = await listMatters(db, orgId, { parent, status, typeFilter, search, page, pageSize })
return c.json(result)
})
.post('/', zValidator('json', createMatterSchema), async (c) => {
+4 -1
View File
@@ -68,6 +68,7 @@ interface ListFilters {
page: number
pageSize: number
typeFilter?: string
search?: string
}
export async function listMatters(
@@ -78,7 +79,9 @@ export async function listMatters(
const offset = (filters.page - 1) * filters.pageSize
const conditions = [eq(matters.orgId, orgId), eq(matters.status, filters.status)]
const typeCond = filters.typeFilter ? typeFilterCondition(filters.typeFilter) : undefined
if (typeCond) {
if (filters.search) {
conditions.push(like(matters.name, `%${filters.search}%`))
} else if (typeCond) {
conditions.push(typeCond)
conditions.push(eq(matters.dirtype, DirType.FILE))
} else {
+8 -7
View File
@@ -79,15 +79,16 @@ export function FileManager({ initialPath, filterType }: FileManagerProps) {
const [previewFile, setPreviewFile] = useState<PreviewFile | null>(null)
const [previewOpen, setPreviewOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const [debouncedSearch, setDebouncedSearch] = useState('')
const query = useFilesQuery(currentPath, filterType)
useEffect(() => {
const timer = setTimeout(() => setDebouncedSearch(searchQuery), 300)
return () => clearTimeout(timer)
}, [searchQuery])
const query = useFilesQuery(currentPath, filterType, debouncedSearch || undefined)
const mutations = useFileMutations(currentPath)
const allItems = query.data?.items ?? []
const items = useMemo(
() =>
searchQuery ? allItems.filter((item) => item.name.toLowerCase().includes(searchQuery.toLowerCase())) : allItems,
[allItems, searchQuery],
)
const items = query.data?.items ?? []
// biome-ignore lint/correctness/useExhaustiveDependencies: clear selection and search when path changes
useEffect(() => {
@@ -3,9 +3,9 @@ import { listObjectsByPath } from '@/lib/api'
const FILES_PAGE_SIZE = 500
export function useFilesQuery(path: string, typeFilter?: string) {
export function useFilesQuery(path: string, typeFilter?: string, search?: string) {
return useQuery({
queryKey: ['objects', 'active', 'path', path, typeFilter ?? ''],
queryFn: () => listObjectsByPath(path, 'active', 1, FILES_PAGE_SIZE, typeFilter),
queryKey: ['objects', 'active', 'path', path, typeFilter ?? '', search ?? ''],
queryFn: () => listObjectsByPath(path, 'active', 1, FILES_PAGE_SIZE, { type: typeFilter, search }),
})
}
+9 -2
View File
@@ -21,9 +21,16 @@ export function listObjects(parent: string, status = 'active', page = 1, pageSiz
)
}
export function listObjectsByPath(path: string, status = 'active', page = 1, pageSize = 500, type?: string) {
export function listObjectsByPath(
path: string,
status = 'active',
page = 1,
pageSize = 500,
opts?: { type?: string; search?: string },
) {
const query: Record<string, string> = { path, status, page: String(page), pageSize: String(pageSize) }
if (type) query.type = type
if (opts?.type) query.type = opts.type
if (opts?.search) query.search = opts.search
return unwrap<PaginatedResponse<StorageObject>>(objects.index.$get({ query }))
}