expo-popcore-app/hooks/use-works-search.ts

157 lines
3.7 KiB
TypeScript

/**
* TDD Phase 2: GREEN - Minimal code to pass tests
*
* This Hook implements works search functionality:
* - Supports keyword search
* - Supports category filter
* - Supports pagination
* - Only executes query when keyword is provided
*/
import { root } from '@repo/core'
import { TemplateGenerationController } from '@repo/sdk'
import { useCallback, useEffect, useState } from 'react'
import { type ApiError } from '@/lib/types'
import { handleError } from './use-error'
// Type definitions
export type WorksCategory = '全部' | '萌宠' | '写真' | '合拍'
export interface UseWorksSearchParams {
keyword: string
category?: WorksCategory
page?: number
limit?: number
}
export interface WorksSearchResult {
id: string
createdAt: Date
template: {
id: string
name: string
}
status: string
duration: number
}
export interface WorksSearchResponse {
data: WorksSearchResult[]
total: number
page: number
limit: number
totalPages: number
}
/**
* Hook for searching works with keyword and category filtering
*
* @param params - Search parameters including keyword, category, page, and limit
* @returns Object with data, loading state, error, and refetch function
*
* @example
* ```tsx
* const { data, works, isLoading, error } = useWorksSearch({
* keyword: '测试',
* category: '萌宠',
* page: 1,
* limit: 20
* })
* ```
*/
export function useWorksSearch(params: UseWorksSearchParams) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<ApiError | null>(null)
const [data, setData] = useState<WorksSearchResponse | undefined>(undefined)
const { keyword, category, page = 1, limit = 20 } = params
// Auto-execute search when params change
useEffect(() => {
const execute = async () => {
// Only execute query when keyword has content
const trimmedKeyword = keyword?.trim() || ''
if (!trimmedKeyword) {
setData(undefined)
setError(null)
return
}
setLoading(true)
setError(null)
// Build API params - only include category if it's not "全部"
const apiParams = {
keyword: trimmedKeyword,
...(category && category !== '全部' && { category }),
page,
limit,
}
const templateGeneration = root.get(TemplateGenerationController)
const { data: result, error: err } = await handleError(async () =>
await templateGeneration.list(apiParams)
)
if (err) {
setError(err)
setLoading(false)
return
}
setData(result as WorksSearchResponse)
setLoading(false)
}
execute()
}, [keyword, category, page, limit])
const refetch = useCallback(() => {
const execute = async () => {
const trimmedKeyword = keyword?.trim() || ''
if (!trimmedKeyword) {
setData(undefined)
setError(null)
return { data: undefined, error: null }
}
setLoading(true)
setError(null)
const apiParams = {
keyword: trimmedKeyword,
...(category && category !== '全部' && { category }),
page,
limit,
}
const templateGeneration = root.get(TemplateGenerationController)
const { data: result, error: err } = await handleError(async () =>
await templateGeneration.list(apiParams)
)
if (err) {
setError(err)
setLoading(false)
return { data: undefined, error: err }
}
setData(result as WorksSearchResponse)
setLoading(false)
return { data: result as WorksSearchResponse, error: null }
}
return execute()
}, [keyword, category, page, limit])
return {
data,
works: data?.data || [],
isLoading: loading,
error,
refetch,
}
}