v27.4: Fix blank Collection/Decks pages — API response unwrapping
- collection.ts: unwrap {items} -> cards, map camelCase params (pageSize, sortField, etc)
- decks.ts: unwrap {decks} from listDecks, {cards} from getDeckCards, normalize field names (commander_name->commander, total_cards->card_count, role_tag->category)
- decks.ts: transform getDeckAnalysis from backend shape (counts_by_type, color_pips) to frontend shape (card_count, land_count, type_distribution, etc)
- types: SetInfo.card_count now optional (backend doesn't provide it)
- Installer now bundles pre-built React dist/ as fallback
This commit is contained in:
1 parent
36b3c8c55b
commit
f5dae81193
3 files changed
+77
-8
No files matched your search
@@ -2,9 +2,32 @@ import { get, post, patch, postForm } from './client'
|
||||
import type { CollectionCard, CollectionFilters, SetInfo, ScanResult } from '../types'
|
||||
|
||||
export async function searchCollection(filters: CollectionFilters) {
|
||||
return get<{ cards: CollectionCard[]; total: number; page: number; page_size: number }>(
|
||||
'/api/collection', filters as Record<string, string | number | boolean>
|
||||
// Map frontend snake_case filter names to backend camelCase param names
|
||||
const params: Record<string, string | number | boolean> = {}
|
||||
if (filters.q) params.q = filters.q
|
||||
if (filters.page) params.page = filters.page
|
||||
if (filters.page_size) params.pageSize = filters.page_size
|
||||
if (filters.sort_by) params.sortField = filters.sort_by
|
||||
if (filters.sort_dir) params.sortDir = filters.sort_dir
|
||||
if (filters.color) params.colors = filters.color
|
||||
if (filters.type_line) params.types = filters.type_line
|
||||
if (filters.rarity) params.rarity = filters.rarity
|
||||
if (filters.set_code) params.setCode = filters.set_code
|
||||
if (filters.cmc_min !== undefined) params.cmcMin = filters.cmc_min
|
||||
if (filters.cmc_max !== undefined) params.cmcMax = filters.cmc_max
|
||||
if (filters.price_min !== undefined) params.priceMin = filters.price_min
|
||||
if (filters.price_max !== undefined) params.priceMax = filters.price_max
|
||||
if (filters.owned_only !== undefined) params.owned_only = filters.owned_only
|
||||
|
||||
const res = await get<{ items?: CollectionCard[]; cards?: CollectionCard[]; total: number; page: number; pageSize?: number; page_size?: number }>(
|
||||
'/api/collection', params
|
||||
)
|
||||
return {
|
||||
cards: res.items || res.cards || [],
|
||||
total: res.total || 0,
|
||||
page: res.page || 1,
|
||||
page_size: res.pageSize || res.page_size || 48,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCard(cardId: number) {
|
||||
@@ -16,11 +39,13 @@ export async function updateCard(cardId: number, updates: Partial<CollectionCard
|
||||
}
|
||||
|
||||
export async function getSets() {
|
||||
return get<SetInfo[]>('/api/collection/sets')
|
||||
const res = await get<SetInfo[] | { sets: SetInfo[] }>('/api/collection/sets')
|
||||
return Array.isArray(res) ? res : (res as { sets: SetInfo[] }).sets || []
|
||||
}
|
||||
|
||||
export async function getKeywords() {
|
||||
return get<string[]>('/api/collection/keywords')
|
||||
const res = await get<string[] | { keywords: string[] }>('/api/collection/keywords')
|
||||
return Array.isArray(res) ? res : (res as { keywords: string[] }).keywords || []
|
||||
}
|
||||
|
||||
export async function exportCollection(format: string = 'csv') {
|
||||
|
||||
@@ -2,7 +2,17 @@ import { get, post, put, del, patch } from './client'
|
||||
import type { Deck, DeckCard, DeckAnalysis, EdhRecsResponse, CollectionRecsResponse, PplxStatus } from '../types'
|
||||
|
||||
export async function listDecks() {
|
||||
return get<Deck[]>('/api/decks')
|
||||
const res = await get<{ decks: Record<string, unknown>[] } | Record<string, unknown>[]>('/api/decks')
|
||||
const raw = Array.isArray(res) ? res : (res as { decks: Record<string, unknown>[] }).decks || []
|
||||
return raw.map(d => ({
|
||||
...d,
|
||||
commander: d.commander || d.commander_name || '',
|
||||
card_count: d.card_count ?? d.total_cards ?? 0,
|
||||
total_price: d.total_price ?? 0,
|
||||
color_identity: d.color_identity || [],
|
||||
created_date: d.created_date || d.created_at || '',
|
||||
updated_date: d.updated_date || d.updated_at || '',
|
||||
})) as Deck[]
|
||||
}
|
||||
|
||||
export async function getDeck(deckId: number) {
|
||||
@@ -26,7 +36,13 @@ export async function deleteAllDecks() {
|
||||
}
|
||||
|
||||
export async function getDeckCards(deckId: number) {
|
||||
return get<DeckCard[]>(`/api/decks/${deckId}/cards`)
|
||||
const res = await get<{ cards: Record<string, unknown>[]; total: number } | Record<string, unknown>[]>(`/api/decks/${deckId}/cards`)
|
||||
const raw = Array.isArray(res) ? res : (res as { cards: Record<string, unknown>[] }).cards || []
|
||||
return raw.map(c => ({
|
||||
...c,
|
||||
name: c.name || c.card_name || '',
|
||||
category: c.category || c.role_tag || '',
|
||||
})) as DeckCard[]
|
||||
}
|
||||
|
||||
export async function addCardToDeck(deckId: number, card: { name: string; scryfall_id?: string; quantity?: number; category?: string }) {
|
||||
@@ -42,7 +58,35 @@ export async function updateDeckCard(deckId: number, cardId: number, updates: Pa
|
||||
}
|
||||
|
||||
export async function getDeckAnalysis(deckId: number) {
|
||||
return get<DeckAnalysis>(`/api/decks/${deckId}/analysis`)
|
||||
const raw = await get<Record<string, unknown>>(`/api/decks/${deckId}/analysis`)
|
||||
// Backend returns: counts_by_type, targets, deltas, mana_curve, color_pips, total_cards, roles
|
||||
// Frontend DeckAnalysis expects: card_count, land_count, creature_count, noncreature_count, avg_cmc, mana_curve, color_distribution, type_distribution, total_price, owned_count, missing_count
|
||||
const counts = (raw.counts_by_type || {}) as Record<string, number>
|
||||
const totalCards = (raw.total_cards as number) || 0
|
||||
const landCount = counts['Land'] || 0
|
||||
const creatureCount = counts['Creature'] || 0
|
||||
const manaCurve = (raw.mana_curve || {}) as Record<string | number, number>
|
||||
const colorPips = (raw.color_pips || {}) as Record<string, number>
|
||||
// Compute avg CMC from mana curve
|
||||
let totalMana = 0, nonLandCards = 0
|
||||
for (const [cmc, count] of Object.entries(manaCurve)) {
|
||||
const cmcNum = cmc === '6+' ? 6 : Number(cmc)
|
||||
totalMana += cmcNum * count
|
||||
nonLandCards += count
|
||||
}
|
||||
return {
|
||||
card_count: totalCards,
|
||||
land_count: landCount,
|
||||
creature_count: creatureCount,
|
||||
noncreature_count: totalCards - landCount - creatureCount,
|
||||
avg_cmc: nonLandCards > 0 ? totalMana / nonLandCards : 0,
|
||||
mana_curve: manaCurve as Record<number, number>,
|
||||
color_distribution: colorPips,
|
||||
type_distribution: counts,
|
||||
total_price: 0,
|
||||
owned_count: totalCards,
|
||||
missing_count: 0,
|
||||
} as DeckAnalysis
|
||||
}
|
||||
|
||||
export async function getCollectionRecs(deckId: number) {
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface CollectionStats {
|
||||
export interface SetInfo {
|
||||
code: string
|
||||
name: string
|
||||
card_count: number
|
||||
card_count?: number
|
||||
}
|
||||
|
||||
// ── Scan ─────────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user