import { useState, useEffect, useRef, useCallback } from 'react'; export function useAdminCatalog() { const [cards, setCards] = useState([]); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedGame, setSelectedGame] = useState('all'); const [selectedRarity, setSelectedRarity] = useState('all'); const [selectedSet, setSelectedSet] = useState('all'); const [minPrice, setMinPrice] = useState(''); const [maxPrice, setMaxPrice] = useState(''); const [verifiedOnly, setVerifiedOnly] = useState(false); const [sortColumn, setSortColumn] = useState('name'); const [sortDirection, setSortDirection] = useState('asc'); const [pagination, setPagination] = useState({ page: 1, limit: 50, total: 0, pages: 0 }); const [filters, setFilters] = useState({ games: [], rarities: [], sets: [] }); const [hasMore, setHasMore] = useState(true); const [error, setError] = useState(null); const loadingMoreRef = useRef(false); const hasMoreRef = useRef(true); const abortControllerRef = useRef(null); // Batch selection state const [selectedCardIds, setSelectedCardIds] = useState(new Set()); const [selectAllPages, setSelectAllPages] = useState(false); const [batchEditModalOpen, setBatchEditModalOpen] = useState(false); const [batchEditLoading, setBatchEditLoading] = useState(false); const [batchEditError, setBatchEditError] = useState(null); const fetchCards = async (isLoadMore = false) => { if (isLoadMore && (loadingMoreRef.current || !hasMoreRef.current)) return; let signal; try { if (isLoadMore) { setLoadingMore(true); loadingMoreRef.current = true; } else { setLoading(true); setError(null); } abortControllerRef.current = new AbortController(); ({ signal } = abortControllerRef.current); const params = new URLSearchParams({ query: searchQuery, game: selectedGame, rarity: selectedRarity, set: selectedSet, minPrice, maxPrice, page: isLoadMore ? pagination.page + 1 : 1, limit: pagination.limit, sort: sortColumn, dir: sortDirection, verifiedOnly: verifiedOnly.toString() }); const response = await fetch(`/api/admin/cards?${params}`, { signal }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (signal.aborted) return; if (data.success) { const newCards = data.cards; if (isLoadMore) { setCards(prev => [...prev, ...newCards]); } else { setCards(newCards); } setPagination(data.pagination); setFilters(data.filters); setHasMore(data.pagination.hasMore); hasMoreRef.current = data.pagination.hasMore; } else { throw new Error(data.error || 'Failed to fetch cards'); } } catch (err) { if (err.name !== 'AbortError') { console.error('Error fetching admin cards:', err); setError(err.message); } } finally { if (!signal?.aborted) { setLoading(false); setLoadingMore(false); loadingMoreRef.current = false; } } }; const handleSearchChange = (value) => { setSearchQuery(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleGameChange = (value) => { setSelectedGame(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleRarityChange = (value) => { setSelectedRarity(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleSetChange = (value) => { setSelectedSet(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleMinPriceChange = (value) => { setMinPrice(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleMaxPriceChange = (value) => { setMaxPrice(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleVerifiedChange = (value) => { setVerifiedOnly(value); setPagination(prev => ({ ...prev, page: 1 })); setCards([]); }; const handleSort = (column) => { if (sortColumn === column) { setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc'); } else { setSortColumn(column); setSortDirection('asc'); } }; const handleLoadMore = () => { if (!loadingMore && hasMore) { fetchCards(true); } }; const handleRefresh = () => { setPagination(prev => ({ ...prev, page: 1 })); setCards([]); fetchCards(false); }; const handleLimitChange = (limit) => { setPagination(prev => ({ ...prev, limit, page: 1 })); setCards([]); }; // Selection handlers const handleToggleSelect = useCallback((cardId) => { setSelectedCardIds(prev => { const next = new Set(prev); if (next.has(cardId)) { next.delete(cardId); } else { next.add(cardId); } return next; }); }, []); const handleSelectAll = useCallback(() => { if (selectAllPages) { setSelectAllPages(false); setSelectedCardIds(new Set()); } else { // Select all cards in current view const ids = new Set(cards.map(c => c.id)); setSelectedCardIds(ids); setSelectAllPages(false); // Only current page by default } }, [cards, selectAllPages]); const handleClearSelection = useCallback(() => { setSelectedCardIds(new Set()); setSelectAllPages(false); }, []); const isSelected = useCallback((cardId) => selectedCardIds.has(cardId), [selectedCardIds]); const selectedCount = selectedCardIds.size; // Batch edit handlers const openBatchEditModal = useCallback(() => { if (selectedCount > 0) { setBatchEditModalOpen(true); setBatchEditError(null); } }, [selectedCount]); const closeBatchEditModal = useCallback(() => { setBatchEditModalOpen(false); setBatchEditError(null); }, []); const executeBatchEdit = useCallback(async (updates) => { if (selectedCount === 0) return; setBatchEditLoading(true); setBatchEditError(null); try { const response = await fetch('/api/admin/cards/batch', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cardIds: Array.from(selectedCardIds), updates }) }); const data = await response.json(); if (!response.ok) throw new Error(data.error || 'Batch edit failed'); // Refresh cards to reflect changes await fetchCards(false); closeBatchEditModal(); handleClearSelection(); } catch (err) { console.error('Batch edit error:', err); setBatchEditError(err.message); } finally { setBatchEditLoading(false); } }, [selectedCardIds, selectedCount, fetchCards, closeBatchEditModal, handleClearSelection]); useEffect(() => { fetchCards(false); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, []); return { cards, loading, loadingMore, searchQuery, selectedGame, selectedRarity, selectedSet, minPrice, maxPrice, verifiedOnly, sortColumn, sortDirection, pagination, filters, hasMore, error, handleSearchChange, handleGameChange, handleRarityChange, handleSetChange, handleMinPriceChange, handleMaxPriceChange, handleVerifiedChange, handleSort, handleLoadMore, handleRefresh, handleLimitChange, // Selection selectedCardIds, selectAllPages, selectedCount, handleToggleSelect, handleSelectAll, handleClearSelection, isSelected, // Batch edit batchEditModalOpen, batchEditLoading, batchEditError, openBatchEditModal, closeBatchEditModal, executeBatchEdit, setSearchQuery, setSelectedGame, setSelectedRarity, setSelectedSet, setMinPrice, setMaxPrice, setVerifiedOnly, setCards, setPagination, setHasMore, setFilters }; }