refactor(deck): extract grouped card list panel (Brief 2) #143
2 changed files with 93 additions and 91 deletions
91
components/DeckDetailCardList.js
Normal file
91
components/DeckDetailCardList.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* eslint-disable @next/next/no-img-element -- External card image URLs; next/image migration is out of scope. */
|
||||||
|
import { ManaCost } from './ManaSymbols';
|
||||||
|
|
||||||
|
export default function DeckDetailCardList({ cards, groupedCards }) {
|
||||||
|
const isEmpty = !cards || cards.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="lg:col-span-3">
|
||||||
|
<div className="glass-panel rounded-2xl p-6">
|
||||||
|
{isEmpty ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<div className="text-6xl mb-4">🃏</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||||
|
Empty Deck
|
||||||
|
</h3>
|
||||||
|
<p style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
This deck doesn't have any cards yet
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{Object.entries(groupedCards)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
|
.map(([group, groupCards]) => (
|
||||||
|
<div key={group}>
|
||||||
|
<h3
|
||||||
|
className="text-lg font-semibold mb-3 border-b pb-2"
|
||||||
|
style={{
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
borderColor: 'var(--border)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{group} ({groupCards.reduce((sum, card) => sum + card.quantity, 0)})
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
{groupCards
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
.map((card) => (
|
||||||
|
<div
|
||||||
|
key={`${card.card_id}-${card.id}`}
|
||||||
|
className="flex items-center space-x-3 p-3 rounded-xl transition-colors nav-item-hover"
|
||||||
|
style={{ backgroundColor: 'var(--bg-primary)' }}
|
||||||
|
>
|
||||||
|
{card.image_url && (
|
||||||
|
<img
|
||||||
|
src={card.image_url}
|
||||||
|
alt={card.name}
|
||||||
|
className="w-12 h-16 object-cover rounded"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h4
|
||||||
|
className="font-medium truncate"
|
||||||
|
style={{ color: 'var(--text-primary)' }}
|
||||||
|
>
|
||||||
|
{card.name}
|
||||||
|
</h4>
|
||||||
|
<span
|
||||||
|
className="font-medium ml-2"
|
||||||
|
style={{ color: 'var(--text-primary)' }}
|
||||||
|
>
|
||||||
|
{card.quantity}x
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
|
||||||
|
{card.set_name}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
className="flex items-center space-x-2 text-xs"
|
||||||
|
style={{ color: 'var(--text-secondary)' }}
|
||||||
|
>
|
||||||
|
{card.mana_cost && (
|
||||||
|
<ManaCost cost={card.mana_cost} size="xs" />
|
||||||
|
)}
|
||||||
|
{card.rarity && (
|
||||||
|
<span className="capitalize">{card.rarity}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
/* eslint-disable @next/next/no-img-element -- External or generated image URLs; next/image migration is out of scope. */
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Layout from '../../components/Layout';
|
import Layout from '../../components/Layout';
|
||||||
|
import DeckDetailCardList from '../../components/DeckDetailCardList';
|
||||||
import DeckDetailStatsSidebar from '../../components/DeckDetailStatsSidebar';
|
import DeckDetailStatsSidebar from '../../components/DeckDetailStatsSidebar';
|
||||||
import { ManaCost } from '../../components/ManaSymbols';
|
|
||||||
import { Button } from '../../components/ui';
|
import { Button } from '../../components/ui';
|
||||||
import { computeDeckStats } from '../../lib/deck-builder-stats.js';
|
import { computeDeckStats } from '../../lib/deck-builder-stats.js';
|
||||||
import { groupDeckCards } from '../../lib/deck-detail-grouping.js';
|
import { groupDeckCards } from '../../lib/deck-detail-grouping.js';
|
||||||
|
|
@ -164,95 +163,7 @@ export default function DeckDetail() {
|
||||||
onGroupByChange={setGroupBy}
|
onGroupByChange={setGroupBy}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Card List */}
|
<DeckDetailCardList cards={deck.cards} groupedCards={groupedCards} />
|
||||||
<div className="lg:col-span-3">
|
|
||||||
<div className="glass-panel rounded-2xl p-6">
|
|
||||||
{deck.cards && deck.cards.length === 0 ? (
|
|
||||||
<div className="text-center py-12">
|
|
||||||
<div className="text-6xl mb-4">🃏</div>
|
|
||||||
<h3
|
|
||||||
className="text-xl font-semibold mb-2"
|
|
||||||
style={{ color: 'var(--text-primary)' }}
|
|
||||||
>
|
|
||||||
Empty Deck
|
|
||||||
</h3>
|
|
||||||
<p style={{ color: 'var(--text-secondary)' }}>
|
|
||||||
This deck doesn't have any cards yet
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{Object.entries(groupedCards)
|
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
|
||||||
.map(([group, cards]) => (
|
|
||||||
<div key={group}>
|
|
||||||
<h3
|
|
||||||
className="text-lg font-semibold mb-3 border-b pb-2"
|
|
||||||
style={{
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{group} ({cards.reduce((sum, card) => sum + card.quantity, 0)})
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
||||||
{cards
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
|
||||||
.map((card) => (
|
|
||||||
<div
|
|
||||||
key={`${card.card_id}-${card.id}`}
|
|
||||||
className="flex items-center space-x-3 p-3 rounded-xl transition-colors nav-item-hover"
|
|
||||||
style={{ backgroundColor: 'var(--bg-primary)' }}
|
|
||||||
>
|
|
||||||
{card.image_url && (
|
|
||||||
<img
|
|
||||||
src={card.image_url}
|
|
||||||
alt={card.name}
|
|
||||||
className="w-12 h-16 object-cover rounded"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h4
|
|
||||||
className="font-medium truncate"
|
|
||||||
style={{ color: 'var(--text-primary)' }}
|
|
||||||
>
|
|
||||||
{card.name}
|
|
||||||
</h4>
|
|
||||||
<span
|
|
||||||
className="font-medium ml-2"
|
|
||||||
style={{ color: 'var(--text-primary)' }}
|
|
||||||
>
|
|
||||||
{card.quantity}x
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className="text-sm"
|
|
||||||
style={{ color: 'var(--text-secondary)' }}
|
|
||||||
>
|
|
||||||
{card.set_name}
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
className="flex items-center space-x-2 text-xs"
|
|
||||||
style={{ color: 'var(--text-secondary)' }}
|
|
||||||
>
|
|
||||||
{card.mana_cost && (
|
|
||||||
<ManaCost cost={card.mana_cost} size="xs" />
|
|
||||||
)}
|
|
||||||
{card.rarity && (
|
|
||||||
<span className="capitalize">{card.rarity}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue