154 lines
5.1 KiB
JavaScript
154 lines
5.1 KiB
JavaScript
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { GlassSurface, Button, Input, SearchBar, StatCard, Modal } from './ui';
|
||
|
|
import AdminCatalogFilters from './AdminCatalogFilters';
|
||
|
|
import AdminCatalogTable from './AdminCatalogTable';
|
||
|
|
import AdminCatalogPagination from './AdminCatalogPagination';
|
||
|
|
|
||
|
|
export default function AdminCatalogView(props) {
|
||
|
|
const {
|
||
|
|
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
|
||
|
|
} = props;
|
||
|
|
|
||
|
|
const [showFilters, setShowFilters] = useState(true);
|
||
|
|
|
||
|
|
const cardCount = cards.length;
|
||
|
|
const totalCount = pagination.total;
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const handleScroll = () => {
|
||
|
|
if (
|
||
|
|
window.innerHeight + document.documentElement.scrollTop >=
|
||
|
|
document.documentElement.offsetHeight - 1000
|
||
|
|
) {
|
||
|
|
handleLoadMore();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
window.addEventListener('scroll', handleScroll);
|
||
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
||
|
|
}, [handleLoadMore, hasMore, loadingMore]);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen" style={{ background: 'var(--bg-primary)' }}>
|
||
|
|
{/* Header */}
|
||
|
|
<div className="px-4 sm:px-6 lg:px-8 pt-6 pb-4">
|
||
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-2xl sm:text-3xl font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||
|
|
Card Catalog
|
||
|
|
</h1>
|
||
|
|
<p className="text-base sm:text-lg" style={{ color: 'var(--text-secondary)' }}>
|
||
|
|
Admin view: {totalCount.toLocaleString()} cards total · showing {cardCount.toLocaleString()}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div className="flex flex-wrap gap-3">
|
||
|
|
<Button
|
||
|
|
variant="secondary"
|
||
|
|
size="sm"
|
||
|
|
onClick={handleRefresh}
|
||
|
|
disabled={loading}
|
||
|
|
leadingIcon={
|
||
|
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||
|
|
</svg>
|
||
|
|
}
|
||
|
|
>
|
||
|
|
Refresh
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
variant="ghost"
|
||
|
|
size="sm"
|
||
|
|
onClick={() => setShowFilters(!showFilters)}
|
||
|
|
leadingIcon={
|
||
|
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||
|
|
</svg>
|
||
|
|
}
|
||
|
|
>
|
||
|
|
{showFilters ? 'Hide Filters' : 'Show Filters'}
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Filters Panel */}
|
||
|
|
{showFilters && (
|
||
|
|
<AdminCatalogFilters
|
||
|
|
searchQuery={searchQuery}
|
||
|
|
selectedGame={selectedGame}
|
||
|
|
selectedRarity={selectedRarity}
|
||
|
|
selectedSet={selectedSet}
|
||
|
|
minPrice={minPrice}
|
||
|
|
maxPrice={maxPrice}
|
||
|
|
verifiedOnly={verifiedOnly}
|
||
|
|
filters={filters}
|
||
|
|
onSearchChange={handleSearchChange}
|
||
|
|
onGameChange={handleGameChange}
|
||
|
|
onRarityChange={handleRarityChange}
|
||
|
|
onSetChange={handleSetChange}
|
||
|
|
onMinPriceChange={handleMinPriceChange}
|
||
|
|
onMaxPriceChange={handleMaxPriceChange}
|
||
|
|
onVerifiedChange={handleVerifiedChange}
|
||
|
|
loading={loading}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Error Banner */}
|
||
|
|
{error && (
|
||
|
|
<GlassSurface tint="low" rim="ember-subtle" className="mt-4 p-4" style={{ background: 'rgba(220, 38, 38, 0.1)' }}>
|
||
|
|
<div className="flex items-center justify-between gap-4">
|
||
|
|
<span style={{ color: '#dc2626' }}>Error: {error}</span>
|
||
|
|
<Button variant="ghost" size="sm" onClick={handleRefresh}>Retry</Button>
|
||
|
|
</div>
|
||
|
|
</GlassSurface>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Table */}
|
||
|
|
<div className="px-4 sm:px-6 lg:px-8 pb-8">
|
||
|
|
<AdminCatalogTable
|
||
|
|
cards={cards}
|
||
|
|
loading={loading || loadingMore}
|
||
|
|
sortColumn={sortColumn}
|
||
|
|
sortDirection={sortDirection}
|
||
|
|
onSort={handleSort}
|
||
|
|
hasMore={hasMore}
|
||
|
|
loadingMore={loadingMore}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Pagination */}
|
||
|
|
<AdminCatalogPagination
|
||
|
|
pagination={pagination}
|
||
|
|
hasMore={hasMore}
|
||
|
|
loadingMore={loadingMore}
|
||
|
|
onLoadMore={handleLoadMore}
|
||
|
|
onLimitChange={handleLimitChange}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|