fix collection modal search: use query factory pattern, remove broken debounce
This commit is contained in:
parent
491026399f
commit
a6a0a38d75
2 changed files with 29 additions and 51 deletions
|
|
@ -1,7 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createQuery, createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import { searchAdapter, type SearchResult } from '$lib/api/adapters/search.adapter'
|
|
||||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||||
|
import {
|
||||||
|
searchQueries,
|
||||||
|
type SearchFilters,
|
||||||
|
type SearchPageResult
|
||||||
|
} from '$lib/api/queries/search.queries'
|
||||||
import { useAddCharactersToCollection } from '$lib/api/mutations/collection.mutations'
|
import { useAddCharactersToCollection } from '$lib/api/mutations/collection.mutations'
|
||||||
import Dialog from '$lib/components/ui/Dialog.svelte'
|
import Dialog from '$lib/components/ui/Dialog.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
|
@ -12,6 +16,8 @@
|
||||||
import SelectableCharacterCard from './SelectableCharacterCard.svelte'
|
import SelectableCharacterCard from './SelectableCharacterCard.svelte'
|
||||||
import { IsInViewport } from 'runed'
|
import { IsInViewport } from 'runed'
|
||||||
|
|
||||||
|
type SearchResultItem = SearchPageResult['results'][number]
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open?: boolean
|
open?: boolean
|
||||||
onOpenChange?: (open: boolean) => void
|
onOpenChange?: (open: boolean) => void
|
||||||
|
|
@ -21,8 +27,6 @@
|
||||||
|
|
||||||
// Search state
|
// Search state
|
||||||
let searchQuery = $state('')
|
let searchQuery = $state('')
|
||||||
let debouncedQuery = $state('')
|
|
||||||
let debounceTimer: ReturnType<typeof setTimeout>
|
|
||||||
|
|
||||||
// Filter state
|
// Filter state
|
||||||
let elementFilters = $state<number[]>([])
|
let elementFilters = $state<number[]>([])
|
||||||
|
|
@ -43,51 +47,34 @@
|
||||||
// Get IDs of characters already in collection
|
// Get IDs of characters already in collection
|
||||||
const collectedIdsQuery = createQuery(() => collectionQueries.collectedCharacterIds())
|
const collectedIdsQuery = createQuery(() => collectionQueries.collectedCharacterIds())
|
||||||
|
|
||||||
// Build filters for search
|
// Build filters for search (using SearchFilters type from search.queries)
|
||||||
const searchFilters = $derived({
|
const searchFilters = $derived<SearchFilters>({
|
||||||
element: elementFilters.length > 0 ? elementFilters : undefined,
|
element: elementFilters.length > 0 ? elementFilters : undefined,
|
||||||
rarity: rarityFilters.length > 0 ? rarityFilters : undefined,
|
rarity: rarityFilters.length > 0 ? rarityFilters : undefined,
|
||||||
season: seasonFilters.length > 0 ? seasonFilters : undefined,
|
season: seasonFilters.length > 0 ? seasonFilters : undefined,
|
||||||
characterSeries: seriesFilters.length > 0 ? seriesFilters : undefined,
|
characterSeries: seriesFilters.length > 0 ? seriesFilters : undefined,
|
||||||
// Note: Race, proficiency, and gender filters would need API support
|
// Note: Race and gender filters would need API support
|
||||||
// For now we filter client-side or skip if API doesn't support
|
proficiency: proficiencyFilters.length > 0 ? proficiencyFilters : undefined
|
||||||
proficiency1: proficiencyFilters.length > 0 ? proficiencyFilters : undefined
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Search query with infinite scroll
|
// Search query with infinite scroll using the factory pattern
|
||||||
const searchResults = createInfiniteQuery(() => ({
|
// No debouncing - TanStack Query's staleTime handles caching
|
||||||
queryKey: ['search', 'characters', 'collection', debouncedQuery, searchFilters] as const,
|
const searchResults = createInfiniteQuery(() => {
|
||||||
queryFn: async ({ pageParam }) => {
|
// Capture current reactive values synchronously for dependency tracking
|
||||||
const response = await searchAdapter.searchCharacters({
|
const query = searchQuery
|
||||||
query: debouncedQuery || undefined,
|
const filters = searchFilters
|
||||||
page: pageParam,
|
const excludeIds = collectedIdsQuery.data ?? []
|
||||||
per: 60,
|
const isEnabled = open && !collectedIdsQuery.isLoading
|
||||||
filters: searchFilters,
|
|
||||||
exclude: collectedIdsQuery.data ?? []
|
return searchQueries.characters(query, filters, 'en', excludeIds, isEnabled)
|
||||||
})
|
})
|
||||||
return {
|
|
||||||
results: response.results ?? [],
|
|
||||||
page: response.meta?.page ?? pageParam,
|
|
||||||
totalPages: response.meta?.totalPages ?? 1,
|
|
||||||
total: response.meta?.count ?? 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initialPageParam: 1,
|
|
||||||
getNextPageParam: (lastPage) => {
|
|
||||||
if (lastPage.page < lastPage.totalPages) {
|
|
||||||
return lastPage.page + 1
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
enabled: open && !collectedIdsQuery.isLoading
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Flatten results and deduplicate by ID
|
// Flatten results and deduplicate by ID
|
||||||
// (API may return duplicates across pages)
|
// (API may return duplicates across pages)
|
||||||
const allResults = $derived.by(() => {
|
const allResults = $derived.by(() => {
|
||||||
const pages = searchResults.data?.pages ?? []
|
const pages = searchResults.data?.pages ?? []
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
const results: SearchResult[] = []
|
const results: typeof pages[number]['results'] = []
|
||||||
|
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
for (const result of page.results) {
|
for (const result of page.results) {
|
||||||
|
|
@ -111,16 +98,6 @@
|
||||||
// Add mutation
|
// Add mutation
|
||||||
const addMutation = useAddCharactersToCollection()
|
const addMutation = useAddCharactersToCollection()
|
||||||
|
|
||||||
// Debounce search input
|
|
||||||
$effect(() => {
|
|
||||||
clearTimeout(debounceTimer)
|
|
||||||
debounceTimer = setTimeout(() => {
|
|
||||||
debouncedQuery = searchQuery
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
return () => clearTimeout(debounceTimer)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Infinite scroll
|
// Infinite scroll
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||||
rootMargin: '200px'
|
rootMargin: '200px'
|
||||||
|
|
@ -144,7 +121,6 @@
|
||||||
selectedIds = new Set()
|
selectedIds = new Set()
|
||||||
showOnlySelected = false
|
showOnlySelected = false
|
||||||
searchQuery = ''
|
searchQuery = ''
|
||||||
debouncedQuery = ''
|
|
||||||
elementFilters = []
|
elementFilters = []
|
||||||
rarityFilters = []
|
rarityFilters = []
|
||||||
seasonFilters = []
|
seasonFilters = []
|
||||||
|
|
@ -155,7 +131,7 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function toggleSelection(character: SearchResult) {
|
function toggleSelection(character: SearchResultItem) {
|
||||||
const newSet = new Set(selectedIds)
|
const newSet = new Set(selectedIds)
|
||||||
if (newSet.has(character.id)) {
|
if (newSet.has(character.id)) {
|
||||||
newSet.delete(character.id)
|
newSet.delete(character.id)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getCharacterImage } from '$lib/utils/images'
|
import { getCharacterImage } from '$lib/utils/images'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import type { SearchResult } from '$lib/api/adapters/search.adapter'
|
import type { SearchPageResult } from '$lib/api/queries/search.queries'
|
||||||
|
|
||||||
|
type SearchResultItem = SearchPageResult['results'][number]
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
character: SearchResult
|
character: SearchResultItem
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
onToggle?: (character: SearchResult) => void
|
onToggle?: (character: SearchResultItem) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let { character, selected = false, onToggle }: Props = $props()
|
let { character, selected = false, onToggle }: Props = $props()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue