Remove search and filter functionality from list detail view to fix hydration errors
This commit is contained in:
@@ -1,16 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useCallback, useMemo, useEffect } from 'react'
|
import { useState, useMemo } from 'react'
|
||||||
import { Share2 } from 'lucide-react'
|
import { Share2 } from 'lucide-react'
|
||||||
import ItemCard from './ItemCard'
|
import ItemCard from './ItemCard'
|
||||||
import SearchBar from './SearchBar'
|
|
||||||
import FilterPanel from './FilterPanel'
|
|
||||||
import SearchResults from './SearchResults'
|
|
||||||
import EmptyState from './EmptyState'
|
import EmptyState from './EmptyState'
|
||||||
import ShareListModal from './ShareListModal'
|
import ShareListModal from './ShareListModal'
|
||||||
import PermissionBadge from './PermissionBadge'
|
import PermissionBadge from './PermissionBadge'
|
||||||
import { searchItems, getItemsByFilter } from '@/lib/actions'
|
import { useScrollPosition } from '@/hooks'
|
||||||
import { useSearch, useFilters, useScrollPosition } from '@/hooks'
|
|
||||||
import { useRealtimeSync } from '@/hooks/useRealtimeSync'
|
import { useRealtimeSync } from '@/hooks/useRealtimeSync'
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
@@ -66,9 +62,6 @@ export default function ListDetailView({
|
|||||||
ownerEmail = '',
|
ownerEmail = '',
|
||||||
collaborators = []
|
collaborators = []
|
||||||
}: ListDetailViewProps) {
|
}: ListDetailViewProps) {
|
||||||
const [searchResults, setSearchResults] = useState<ItemWithList[]>([])
|
|
||||||
const [isSearching, setIsSearching] = useState(false)
|
|
||||||
const [filteredItems, setFilteredItems] = useState<Item[]>(initialItems)
|
|
||||||
const [isShareModalOpen, setIsShareModalOpen] = useState(false)
|
const [isShareModalOpen, setIsShareModalOpen] = useState(false)
|
||||||
|
|
||||||
// Use scroll position preservation hook
|
// Use scroll position preservation hook
|
||||||
@@ -81,78 +74,9 @@ export default function ListDetailView({
|
|||||||
pollInterval: 1000, // Poll every 1 second for faster updates
|
pollInterval: 1000, // Poll every 1 second for faster updates
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extract all unique tags from items
|
|
||||||
const availableTags = useMemo(() => {
|
|
||||||
const tagSet = new Set<string>()
|
|
||||||
initialItems.forEach(item => {
|
|
||||||
const tags = Array.isArray(item.tags) ? item.tags : []
|
|
||||||
tags.forEach(tag => {
|
|
||||||
if (typeof tag === 'string') {
|
|
||||||
tagSet.add(tag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return Array.from(tagSet).sort()
|
|
||||||
}, [initialItems])
|
|
||||||
|
|
||||||
// Use search hook
|
|
||||||
const handleSearchCallback = useCallback(async (query: string) => {
|
|
||||||
if (!query.trim()) {
|
|
||||||
setSearchResults([])
|
|
||||||
setIsSearching(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSearching(true)
|
|
||||||
try {
|
|
||||||
const results = await searchItems(query, listId)
|
|
||||||
setSearchResults(results as ItemWithList[])
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Search error:', error)
|
|
||||||
setSearchResults([])
|
|
||||||
} finally {
|
|
||||||
setIsSearching(false)
|
|
||||||
}
|
|
||||||
}, [listId])
|
|
||||||
|
|
||||||
const { debouncedQuery } = useSearch({
|
|
||||||
onSearch: handleSearchCallback
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use filters hook
|
|
||||||
const handleFiltersCallback = useCallback(async (newFilters: any) => {
|
|
||||||
try {
|
|
||||||
const results = await getItemsByFilter(listId, {
|
|
||||||
priority: newFilters.priority,
|
|
||||||
completed: newFilters.completed,
|
|
||||||
tags: newFilters.tags,
|
|
||||||
dueDateRange: newFilters.dueDateRange,
|
|
||||||
sortBy: newFilters.sortBy,
|
|
||||||
sortOrder: newFilters.sortOrder
|
|
||||||
})
|
|
||||||
// Extract items from pagination result
|
|
||||||
const items = 'items' in results ? results.items : results
|
|
||||||
setFilteredItems(items as Item[])
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Filter error:', error)
|
|
||||||
setFilteredItems(initialItems)
|
|
||||||
}
|
|
||||||
}, [listId, initialItems])
|
|
||||||
|
|
||||||
const { filters, setFilters } = useFilters({
|
|
||||||
onFiltersChange: handleFiltersCallback
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update filtered items when initialItems changes (after refresh)
|
|
||||||
// Re-apply filters to the new data
|
|
||||||
useEffect(() => {
|
|
||||||
handleFiltersCallback(filters)
|
|
||||||
}, [initialItems, handleFiltersCallback, filters])
|
|
||||||
|
|
||||||
// Determine which items to display
|
// Determine which items to display
|
||||||
const displayItems = debouncedQuery.trim() ? searchResults : filteredItems
|
const completedItems = useMemo(() => initialItems.filter(item => item.completed), [initialItems])
|
||||||
const completedItems = displayItems.filter(item => item.completed)
|
const incompleteItems = useMemo(() => initialItems.filter(item => !item.completed), [initialItems])
|
||||||
const incompleteItems = displayItems.filter(item => !item.completed)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -176,27 +100,8 @@ export default function ListDetailView({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Search Bar */}
|
{/* Items */}
|
||||||
<SearchBar
|
<div className="space-y-6">
|
||||||
onSearch={handleSearchCallback}
|
|
||||||
placeholder="Search tasks in this list..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Filter Panel */}
|
|
||||||
<FilterPanel
|
|
||||||
filters={filters}
|
|
||||||
onFiltersChange={setFilters}
|
|
||||||
availableTags={availableTags}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Results */}
|
|
||||||
{debouncedQuery.trim() ? (
|
|
||||||
<SearchResults
|
|
||||||
results={searchResults}
|
|
||||||
query={debouncedQuery}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Incomplete Items */}
|
{/* Incomplete Items */}
|
||||||
{incompleteItems.length > 0 && (
|
{incompleteItems.length > 0 && (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
@@ -226,19 +131,14 @@ export default function ListDetailView({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{displayItems.length === 0 && (
|
{initialItems.length === 0 && (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={filters.priority || filters.completed !== null || filters.tags.length > 0 ? 'filter' : 'inbox'}
|
icon="inbox"
|
||||||
title="No tasks found"
|
title="No tasks found"
|
||||||
description={
|
description="Add your first task using the button above"
|
||||||
filters.priority || filters.completed !== null || filters.tags.length > 0
|
|
||||||
? 'Try adjusting your filters to see more tasks'
|
|
||||||
: 'Add your first task using the button above'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Share Modal */}
|
{/* Share Modal */}
|
||||||
<ShareListModal
|
<ShareListModal
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { memo } from 'react'
|
import { memo, useState, useEffect } from 'react'
|
||||||
import { Search, X } from 'lucide-react'
|
import { Search, X } from 'lucide-react'
|
||||||
import { useSearch } from '@/hooks'
|
import { useSearch } from '@/hooks'
|
||||||
|
|
||||||
@@ -17,18 +17,25 @@ function SearchBar({
|
|||||||
debounceMs = 300,
|
debounceMs = 300,
|
||||||
initialValue = ''
|
initialValue = ''
|
||||||
}: SearchBarProps) {
|
}: SearchBarProps) {
|
||||||
|
const [mounted, setMounted] = useState(false)
|
||||||
const { query, setQuery, clearSearch, isDebouncing } = useSearch({
|
const { query, setQuery, clearSearch, isDebouncing } = useSearch({
|
||||||
debounceMs,
|
debounceMs,
|
||||||
onSearch
|
onSearch
|
||||||
})
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search
|
{mounted && (
|
||||||
className="absolute left-3 sm:left-4 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"
|
<Search
|
||||||
size={20}
|
className="absolute left-3 sm:left-4 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"
|
||||||
/>
|
size={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={query}
|
value={query}
|
||||||
@@ -36,7 +43,7 @@ function SearchBar({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="w-full pl-10 sm:pl-12 pr-10 sm:pr-12 py-3 sm:py-3.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm sm:text-base transition-shadow"
|
className="w-full pl-10 sm:pl-12 pr-10 sm:pr-12 py-3 sm:py-3.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm sm:text-base transition-shadow"
|
||||||
/>
|
/>
|
||||||
{query && (
|
{mounted && query && (
|
||||||
<button
|
<button
|
||||||
onClick={clearSearch}
|
onClick={clearSearch}
|
||||||
className="absolute right-3 sm:right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 active:text-gray-700 transition-colors tap-target-sm"
|
className="absolute right-3 sm:right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 active:text-gray-700 transition-colors tap-target-sm"
|
||||||
@@ -45,7 +52,7 @@ function SearchBar({
|
|||||||
<X size={20} />
|
<X size={20} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isDebouncing && (
|
{mounted && isDebouncing && (
|
||||||
<div className="absolute right-12 sm:right-14 top-1/2 -translate-y-1/2">
|
<div className="absolute right-12 sm:right-14 top-1/2 -translate-y-1/2">
|
||||||
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin" />
|
<div className="w-4 h-4 border-2 border-gray-300 border-t-blue-500 rounded-full animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user