Add card view and table view toggle to CardDatabaseBrowser - enhanced browsing experience with dual view modes
This commit is contained in:
parent
0c65919b02
commit
d9ed0e911e
1 changed files with 236 additions and 73 deletions
|
|
@ -21,6 +21,7 @@ const CardDatabaseBrowser: React.FC<CardDatabaseBrowserProps> = ({
|
|||
const [selectedCard, setSelectedCard] = useState<Card | null>(null);
|
||||
const [showCardManager, setShowCardManager] = useState(false);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [viewMode, setViewMode] = useState<'cards' | 'table'>('cards');
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
|
@ -294,6 +295,55 @@ const CardDatabaseBrowser: React.FC<CardDatabaseBrowserProps> = ({
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{/* View Toggle */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm font-medium text-surface-700 dark:text-surface-300">View:</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setViewMode('cards');
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
|
||||
viewMode === 'cards'
|
||||
? 'bg-primary-500 text-white'
|
||||
: 'bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-600'
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
|
||||
</svg>
|
||||
Cards
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setViewMode('table');
|
||||
}}
|
||||
className={`px-3 py-1.5 rounded-lg text-sm font-medium transition-colors ${
|
||||
viewMode === 'table'
|
||||
? 'bg-primary-500 text-white'
|
||||
: 'bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-600'
|
||||
}`}
|
||||
>
|
||||
<svg className="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H6a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Table
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{displayCards.length > 0 && (
|
||||
<span className="text-sm text-surface-600 dark:text-surface-400">
|
||||
{displayCards.length} card{displayCards.length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto pb-20">
|
||||
{isLoading ? (
|
||||
|
|
@ -317,85 +367,198 @@ const CardDatabaseBrowser: React.FC<CardDatabaseBrowserProps> = ({
|
|||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{displayCards.map((card) => (
|
||||
<div
|
||||
key={`${card.game}-${card.id}`}
|
||||
className="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-4 transition-all duration-200 hover:shadow-md"
|
||||
>
|
||||
<div className="flex items-start space-x-4">
|
||||
{/* Card Image */}
|
||||
<div className="w-16 h-20 bg-surface-200 dark:bg-surface-700 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
{card.stock_image_url ? (
|
||||
<img
|
||||
src={card.stock_image_url}
|
||||
alt={card.name}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<svg className="w-6 h-6 text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
{/* Card View */}
|
||||
{viewMode === 'cards' && (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{displayCards.map((card) => (
|
||||
<div
|
||||
key={`${card.game}-${card.id}`}
|
||||
className="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 p-4 transition-all duration-200 hover:shadow-md"
|
||||
>
|
||||
<div className="flex items-start space-x-4">
|
||||
{/* Card Image */}
|
||||
<div className="w-16 h-20 bg-surface-200 dark:bg-surface-700 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
{card.stock_image_url ? (
|
||||
<img
|
||||
src={card.stock_image_url}
|
||||
alt={card.name}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
/>
|
||||
) : (
|
||||
<svg className="w-6 h-6 text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Card Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-surface-900 dark:text-white truncate">
|
||||
{card.name}
|
||||
</h3>
|
||||
<p className="text-sm text-surface-600 dark:text-surface-400">
|
||||
{card.set_name} • {card.card_number}
|
||||
</p>
|
||||
|
||||
{/* Badges */}
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getGameBadgeColor(card.game)}`}>
|
||||
{card.game}
|
||||
</span>
|
||||
{card.rarity && (
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getRarityBadgeColor(card.rarity)}`}>
|
||||
{card.rarity}
|
||||
</span>
|
||||
)}
|
||||
{card.current_price && (
|
||||
<span className="px-2 py-1 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 rounded-full text-xs font-medium">
|
||||
${card.current_price}
|
||||
</span>
|
||||
)}
|
||||
{/* Card Info */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-surface-900 dark:text-white truncate">
|
||||
{card.name}
|
||||
</h3>
|
||||
<p className="text-sm text-surface-600 dark:text-surface-400">
|
||||
{card.set_name} • {card.card_number}
|
||||
</p>
|
||||
|
||||
{/* Badges */}
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getGameBadgeColor(card.game)}`}>
|
||||
{card.game}
|
||||
</span>
|
||||
{card.rarity && (
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getRarityBadgeColor(card.rarity)}`}>
|
||||
{card.rarity}
|
||||
</span>
|
||||
)}
|
||||
{card.current_price && (
|
||||
<span className="px-2 py-1 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 rounded-full text-xs font-medium">
|
||||
${card.current_price}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleCardClick(card);
|
||||
}}
|
||||
className="px-3 py-1 bg-primary-500 hover:bg-primary-600 text-white text-xs font-medium rounded-lg transition-colors"
|
||||
>
|
||||
Add to Collection
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddToDatabase(card);
|
||||
}}
|
||||
disabled={addToDatabaseMutation.isPending}
|
||||
className="px-3 py-1 bg-surface-100 dark:bg-surface-700 hover:bg-surface-200 dark:hover:bg-surface-600 text-surface-700 dark:text-surface-300 text-xs font-medium rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{addToDatabaseMutation.isPending ? 'Adding...' : 'Add to DB'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleCardClick(card);
|
||||
}}
|
||||
className="px-3 py-1 bg-primary-500 hover:bg-primary-600 text-white text-xs font-medium rounded-lg transition-colors"
|
||||
>
|
||||
Add to Collection
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddToDatabase(card);
|
||||
}}
|
||||
disabled={addToDatabaseMutation.isPending}
|
||||
className="px-3 py-1 bg-surface-100 dark:bg-surface-700 hover:bg-surface-200 dark:hover:bg-surface-600 text-surface-700 dark:text-surface-300 text-xs font-medium rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{addToDatabaseMutation.isPending ? 'Adding...' : 'Add to DB'}
|
||||
</button>
|
||||
</div>
|
||||
{/* Table View */}
|
||||
{viewMode === 'table' && (
|
||||
<div className="bg-white dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-surface-50 dark:bg-surface-700">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-surface-600 dark:text-surface-400 uppercase tracking-wider">
|
||||
Card
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-surface-600 dark:text-surface-400 uppercase tracking-wider">
|
||||
Set
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-surface-600 dark:text-surface-400 uppercase tracking-wider">
|
||||
Game
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-surface-600 dark:text-surface-400 uppercase tracking-wider">
|
||||
Rarity
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-surface-600 dark:text-surface-400 uppercase tracking-wider">
|
||||
Price
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-surface-600 dark:text-surface-400 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-200 dark:divide-surface-700">
|
||||
{displayCards.map((card) => (
|
||||
<tr key={`${card.game}-${card.id}`} className="hover:bg-surface-50 dark:hover:bg-surface-700 transition-colors">
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-12 bg-surface-200 dark:bg-surface-700 rounded flex items-center justify-center flex-shrink-0">
|
||||
{card.stock_image_url ? (
|
||||
<img
|
||||
src={card.stock_image_url}
|
||||
alt={card.name}
|
||||
className="w-full h-full object-cover rounded"
|
||||
/>
|
||||
) : (
|
||||
<svg className="w-4 h-4 text-surface-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium text-surface-900 dark:text-white truncate">
|
||||
{card.name}
|
||||
</div>
|
||||
<div className="text-xs text-surface-500 dark:text-surface-400">
|
||||
#{card.card_number}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-surface-900 dark:text-white">
|
||||
{card.set_name}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getGameBadgeColor(card.game)}`}>
|
||||
{card.game}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
{card.rarity && (
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getRarityBadgeColor(card.rarity)}`}>
|
||||
{card.rarity}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-surface-900 dark:text-white">
|
||||
{card.current_price ? `$${card.current_price}` : '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleCardClick(card);
|
||||
}}
|
||||
className="px-2 py-1 bg-primary-500 hover:bg-primary-600 text-white text-xs font-medium rounded transition-colors"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleAddToDatabase(card);
|
||||
}}
|
||||
disabled={addToDatabaseMutation.isPending}
|
||||
className="px-2 py-1 bg-surface-100 dark:bg-surface-700 hover:bg-surface-200 dark:hover:bg-surface-600 text-surface-700 dark:text-surface-300 text-xs font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
DB
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue