Add card view and table view toggle to CardDatabaseBrowser - enhanced browsing experience with dual view modes

This commit is contained in:
Randall Stillwell 2025-07-22 20:58:49 -05:00
parent 0c65919b02
commit d9ed0e911e

View file

@ -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>
{/* 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>
)}
{/* 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>