// components/Trades.jsx
const { useMemo: useMemoTr, useState: useStateTr, useEffect: useEffectTr } = React;
// ── Helpers ──────────────────────────────────────────────────────────────────
function getStickerLabel(id) {
const { TEAMS, teamStickers, SPECIAL_STICKERS } = ALBUM_DATA;
const spec = SPECIAL_STICKERS.find(s => s.id === id);
if (spec) return { num: `FWC ${spec.num}`, name: spec.name, team: 'FIFA / Intro', teamId: 'FIFA' };
for (const t of TEAMS) {
const found = (teamStickers[t.id]||[]).find(s => s.id === id);
if (found) return { num: `${t.id} ${found.num}`, name: found.name, team: t.name, teamId: t.id };
}
return { num: '??', name: id, team: '', teamId: '' };
}
// ── Main Component ───────────────────────────────────────────────────────────
function Trades({ stickers, onUpdateSticker }) {
const { TEAMS, teamStickers, SPECIAL_STICKERS } = ALBUM_DATA;
const [tab, setTab] = useStateTr('suggestions'); // suggestions | offer | want | log
// ── Computed analysis ─────────────────────────────────────
const analysis = useMemoTr(() => {
const offer = [], want = [];
SPECIAL_STICKERS.forEach(s => {
const q = stickers[s.id]||0;
if (q >= 2) offer.push({ ...s, qty:q, extras:q-1, section:'FIFA / Intro', teamId:'FIFA' });
if (q === 0) want.push({ ...s, section:'FIFA / Intro', teamId:'FIFA' });
});
TEAMS.forEach(team => {
(teamStickers[team.id]||[]).forEach(s => {
const q = stickers[s.id]||0;
if (q >= 2) offer.push({ ...s, qty:q, extras:q-1, section:team.name, teamId:team.id });
if (q === 0) want.push({ ...s, section:team.name, teamId:team.id });
});
});
// Build suggestions per team
const suggestions = [];
TEAMS.forEach(wantTeam => {
const missing = (teamStickers[wantTeam.id]||[]).filter(s => (stickers[s.id]||0)===0);
if (!missing.length) return;
const offerFrom = TEAMS
.filter(t => t.id !== wantTeam.id)
.map(t => ({ team:t, extras:(teamStickers[t.id]||[]).filter(s=>(stickers[s.id]||0)>=2) }))
.filter(x => x.extras.length > 0)
.slice(0,3);
if (offerFrom.length)
suggestions.push({ want:{team:wantTeam, count:missing.length, examples:missing.slice(0,3)}, offer:offerFrom.map(({team,extras})=>({team,count:extras.length})) });
});
return { offer, want, suggestions: suggestions.slice(0,10) };
}, [stickers]);
const totalOffer = analysis.offer.reduce((a,s) => a+s.extras, 0);
function groupBySection(list) {
const g = {};
list.forEach(s => {
if (!g[s.section]) g[s.section] = { teamId:s.teamId, items:[] };
g[s.section].items.push(s);
});
return g;
}
return (
Intercambios
{/* Summary */}
{totalOffer}
láminas para ofrecer
{analysis.want.length}
láminas que necesitas
{/* Tabs */}
{[['suggestions','💡 Sugerencias'],['offer','🟡 Mis repetidas'],['want','🔵 Me faltan'],['log','📝 Registrar']].map(([v,l]) => (
))}
{/* Suggestions */}
{tab==='suggestions' && (
analysis.suggestions.length === 0
?
:
{analysis.suggestions.map((sug,i) => (
🔵 Necesitas
{sug.want.team.flag}
{sug.want.team.name}
{sug.want.count} lámina{sug.want.count!==1?'s':''}
{sug.want.examples.map(s => `#${String(s.num).padStart(2,'0')} ${s.name}`).join(' · ')}{sug.want.count>3?'…':''}
⇄
🟡 Puedes dar
{sug.offer.map(({team,count}) => (
{team.flag}
{team.name}
×{count}
))}
))}
)}
{/* Offer list */}
{tab==='offer' &&
}
{/* Want list */}
{tab==='want' &&
}
{/* Trade Log */}
{tab==='log' &&
}
);
}
// ── Sticker Group List ────────────────────────────────────────────────────────
function StickerGroupList({ groups, type, emptyMsg }) {
const entries = Object.entries(groups);
if (!entries.length) return ;
return (
{entries.map(([section,{flag,items}]) => (
{flag}
{section}
{items.length} láminas
{items.map(s => (
#{String(s.num||s.id.split('-')[1]).padStart(2,'0')}
{s.name}
{s.extras>0 && ×{s.extras+1}}
))}
))}
);
}
// ── Trade Log ─────────────────────────────────────────────────────────────────
function TradeLog({ stickers, onUpdateSticker }) {
const { TEAMS, teamStickers, SPECIAL_STICKERS } = ALBUM_DATA;
const [giveTeam, setGiveTeam] = useStateTr('');
const [giveNum, setGiveNum] = useStateTr('');
const [recvTeam, setRecvTeam] = useStateTr('');
const [recvNum, setRecvNum] = useStateTr('');
const [partner, setPartner] = useStateTr('');
const [logs, setLogs] = useStateTr(() => {
try { return JSON.parse(localStorage.getItem('mona26_trade_log')||'[]'); } catch { return []; }
});
const [flash, setFlash] = useStateTr('');
function getTeamStickers(teamId) {
if (teamId === 'FIFA') return SPECIAL_STICKERS.map(s=>({...s,type:'special'}));
return teamStickers[teamId] || [];
}
// Solo láminas con repetidas (qty >= 2) para el lado "di"
const giveStickers = giveTeam
? getTeamStickers(giveTeam).filter(s => (stickers[s.id]||0) >= 2)
: [];
// Solo láminas que faltan (qty === 0) para el lado "recibí"
const recvStickers = recvTeam
? getTeamStickers(recvTeam).filter(s => (stickers[s.id]||0) === 0)
: [];
// Solo equipos que tienen al menos una lámina faltante
const teamsWithMissing = [
{ id:'FIFA', name:'FIFA / Intro', flag:'🏆' },
...TEAMS,
].filter(t => {
const sl = t.id === 'FIFA'
? SPECIAL_STICKERS.map(s=>({...s,type:'special'}))
: (teamStickers[t.id]||[]);
return sl.some(s => (stickers[s.id]||0) === 0);
});
// Solo equipos que tienen al menos una repetida
const teamsWithDups = [
{ id:'FIFA', name:'FIFA / Intro', flag:'🏆' },
...TEAMS,
].filter(t => {
const sl = t.id === 'FIFA'
? SPECIAL_STICKERS.map(s=>({...s,type:'special'}))
: (teamStickers[t.id]||[]);
return sl.some(s => (stickers[s.id]||0) >= 2);
});
const giveSticker = giveTeam ? getTeamStickers(giveTeam).find(s => s.id === giveNum) : null;
const recvSticker = recvStickers.find(s => s.id === recvNum);
function canRegister() {
return giveNum && recvNum && giveNum !== recvNum;
}
function registerTrade() {
if (!canRegister()) return;
const giveQty = stickers[giveNum]||0;
const recvQty = stickers[recvNum]||0;
// Update collection
if (giveQty > 0) onUpdateSticker(giveNum, giveQty - 1);
onUpdateSticker(recvNum, recvQty + 1);
// Log
const entry = {
id: Date.now(),
gave: { id: giveNum, label: getStickerLabel(giveNum) },
received: { id: recvNum, label: getStickerLabel(recvNum) },
partner: partner.trim() || null,
date: new Date().toLocaleDateString('es-CO', { day:'numeric', month:'short' }),
};
const newLogs = [entry, ...logs].slice(0, 100);
setLogs(newLogs);
localStorage.setItem('mona26_trade_log', JSON.stringify(newLogs));
setGiveTeam(''); setGiveNum(''); setRecvTeam(''); setRecvNum(''); setPartner('');
setFlash('✅ Intercambio registrado y colección actualizada');
setTimeout(() => setFlash(''), 3000);
}
function deleteLog(id) {
const updated = logs.filter(l => l.id !== id);
setLogs(updated);
localStorage.setItem('mona26_trade_log', JSON.stringify(updated));
}
const sel = { ...tCSS.select };
return (
{/* Form */}
📝 Registrar un intercambio
Al registrar, tu colección se actualiza automáticamente: se quita la lámina que diste y se agrega la que recibiste.
{/* GIVE */}
🟡 Lámina que di
{teamsWithDups.length === 0
?
No tienes láminas repetidas aún.
:
}
{giveTeam && (
)}
{giveSticker && (
#{String(giveSticker.num).padStart(2,'0')}
{giveSticker.name}
Tienes ×{stickers[giveNum]||0}
)}
⇄
{/* RECEIVE */}
🔵 Lámina que recibí
{recvTeam && recvStickers.length === 0 && (
✅ ¡Equipo completo! No te falta ninguna.
)}
{recvTeam && recvStickers.length > 0 && (
)}
{recvSticker && (
#{String(recvSticker.num).padStart(2,'0')}
{recvSticker.name}
Te falta
)}
{/* Partner (optional) */}
setPartner(e.target.value)} />
{flash &&
{flash}
}
{/* Log history */}
{logs.length > 0 && (
📋 Historial de intercambios
{logs.map(entry => (
#{entry.gave.label.num}
{entry.gave.label.flag} {entry.gave.label.name}
⇄
#{entry.received.label.num}
{entry.received.label.flag} {entry.received.label.name}
{entry.partner && (
👤 {entry.partner}
)}
{entry.date}
))}
)}
{logs.length === 0 &&
}
);
}
function EmptyTr({ msg }) {
return (
);
}
const tCSS = {
page: { padding:'28px 24px', maxWidth:900, margin:'0 auto' },
heading: { fontSize:22, fontWeight:700, color:'var(--text)', marginBottom:20 },
summaryRow: { display:'grid', gridTemplateColumns:'1fr 1fr', gap:14, marginBottom:24 },
sumCard: { background:'var(--surface)', border:'1px solid var(--border)', borderRadius:14, padding:20, textAlign:'center' },
sumLabel: { color:'var(--text-muted)', fontSize:13, marginTop:4 },
tabs: { display:'flex', gap:8, marginBottom:24, flexWrap:'wrap' },
tab: { padding:'8px 16px', background:'var(--surface)', border:'1px solid var(--border)', borderRadius:99, color:'var(--text-muted)', fontSize:13, cursor:'pointer', fontWeight:500 },
tabActive: { background:'var(--surface2)', borderColor:'var(--green)', color:'var(--text)' },
sugCard: { background:'var(--surface)', border:'1px solid var(--border)', borderRadius:14, padding:20 },
sugRow: { display:'flex', alignItems:'flex-start', gap:16 },
sugSide: { flex:1 },
sugLabel: { fontSize:11, fontWeight:700, color:'var(--text-dim)', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:8 },
arrow: { fontSize:24, color:'var(--text-dimmer)', alignSelf:'center', flexShrink:0 },
exNames: { color:'var(--text-dim)', fontSize:11, marginTop:4, lineHeight:1.5 },
offerItem: { display:'flex', alignItems:'center', gap:8, marginBottom:6 },
offerBadge: { background:'var(--gold-bg)', color:'var(--gold)', fontSize:11, fontWeight:700, borderRadius:99, padding:'2px 8px', border:'1px solid var(--gold-brd)' },
group: { marginBottom:20 },
groupHeader:{ display:'flex', alignItems:'center', gap:10, marginBottom:10, paddingBottom:8, borderBottom:'1px solid var(--border)' },
chipList: { display:'flex', flexWrap:'wrap', gap:6 },
chip: { display:'flex', alignItems:'center', gap:5, borderRadius:99, padding:'5px 10px', border:'1px solid' },
chipOffer: { background:'var(--gold-bg)', borderColor:'var(--gold-brd)', color:'var(--gold)' },
chipWant: { background:'var(--blue-bg)', borderColor:'var(--blue-brd)', color:'var(--blue)' },
chipExtra: { background:'var(--gold-bg)', borderRadius:99, padding:'1px 5px', fontSize:10, fontWeight:700, color:'var(--gold)' },
// Log
logCard: { background:'var(--surface)', border:'1px solid var(--border)', borderRadius:16, padding:24, marginBottom:28 },
logTitle: { color:'var(--text)', fontSize:16, fontWeight:700, marginBottom:8 },
logHint: { color:'var(--text-dim)', fontSize:13, lineHeight:1.5, marginBottom:20 },
logGrid: { display:'grid', gridTemplateColumns:'1fr auto 1fr', gap:16, alignItems:'start' },
logCol: { display:'flex', flexDirection:'column' },
colHeader: { fontSize:12, fontWeight:700, textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:10 },
logArrow: { fontSize:28, color:'var(--text-dimmer)', alignSelf:'center', paddingTop:24 },
select: { padding:'10px 12px', background:'var(--input-bg)', border:'1px solid var(--border-md)', borderRadius:8, color:'var(--text)', fontSize:13, outline:'none', width:'100%', cursor:'pointer' },
preview: { marginTop:10, display:'flex', alignItems:'center', gap:8, background:'var(--surface2)', border:'1px solid', borderRadius:8, padding:'10px 12px' },
previewNum: { fontWeight:800, fontSize:16, flexShrink:0 },
previewName:{ color:'var(--text)', fontSize:13, flex:1 },
previewQty: { fontWeight:700, fontSize:13, flexShrink:0 },
partnerLabel:{ display:'block', fontSize:12, fontWeight:600, color:'var(--text-muted)', textTransform:'uppercase', letterSpacing:'0.05em', marginBottom:6 },
partnerInput:{ width:'100%', padding:'10px 12px', background:'var(--input-bg)', border:'1px solid var(--border-md)', borderRadius:8, color:'var(--text)', fontSize:13, outline:'none' },
flash: { marginTop:14, padding:'10px 14px', background:'var(--green-bg)', border:'1px solid var(--green-brd)', borderRadius:8, color:'var(--green)', fontSize:13, fontWeight:600 },
registerBtn:{ marginTop:16, padding:'12px 24px', background:'var(--green)', border:'none', borderRadius:10, color:'#fff', fontSize:14, fontWeight:700, cursor:'pointer', transition:'opacity .2s' },
histTitle: { fontSize:15, fontWeight:700, color:'var(--text-muted)', marginBottom:14, textTransform:'uppercase', letterSpacing:'0.06em' },
logEntry: { display:'flex', alignItems:'center', gap:10, background:'var(--surface)', border:'1px solid var(--border)', borderRadius:12, padding:'12px 16px' },
logEntryMain:{ flex:1, display:'flex', alignItems:'center', gap:10, flexWrap:'wrap' },
logChipGave:{ display:'flex', gap:6, alignItems:'center', background:'var(--gold-bg)', border:'1px solid var(--gold-brd)', borderRadius:99, padding:'4px 10px', color:'var(--gold)', fontSize:12 },
logChipRecv:{ display:'flex', gap:6, alignItems:'center', background:'var(--blue-bg)', border:'1px solid var(--blue-brd)', borderRadius:99, padding:'4px 10px', color:'var(--blue)', fontSize:12 },
logPartner: { color:'var(--text-dim)', fontSize:12 },
logDate: { color:'var(--text-dimmer)', fontSize:11, marginLeft:'auto' },
deleteBtn: { background:'none', border:'none', color:'var(--text-dimmer)', cursor:'pointer', fontSize:14, padding:'4px 6px', borderRadius:6, flexShrink:0 },
};
Object.assign(window, { Trades });