Skip to content

Commit 192c5c8

Browse files
Teste
1 parent cbde888 commit 192c5c8

2 files changed

Lines changed: 39 additions & 74 deletions

File tree

‎docs/SISTEMA_ADMIN.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,23 @@ O componente `Admin.tsx` verifica se o `userId` do Supabase Auth corresponde ao
2323
Permite criar uma carta de duas formas:
2424

2525
#### Modo Inventário
26-
Cria uma cópia de carta diretamente no inventário do admin via RPC `create_card`. O jogador deve ser escolhido de um template existente; nome, posição, nacionalidade, idade, clube e imagem são herdados desse template, não digitados livremente. Os atributos podem ser ajustados para a cópia.
26+
Cria uma carta nova diretamente no inventário do admin via RPC `create_card`. O jogador, a posição, a nacionalidade, a idade, a imagem e os atributos são preenchidos para essa carta. O clube é escolhido de uma lista de times já usados pelos templates, para manter o valor padronizado no filtro da coleção.
2727

2828
Campos:
29-
- Jogador (template existente)
29+
- Nome do jogador
30+
- URL da imagem
31+
- Nacionalidade
32+
- Idade
33+
- Clube (time pré-definido)
34+
- Posição (GK, Fixo, Ala, Pivo)
3035
- Raridade (Comum, Incomum, Rara, Épica, Lendária, Ícone)
3136
- Overall
3237
- Atributos: Velocidade, Chute, Passe, Drible, Defesa, Físico
3338
- (Se GK) Reflexos, Posicionamento, Defesa de Bola
3439
- Edição (sempre `2025/26`)
3540
- Modo: Calcular OVR automaticamente (usa atributos × raridade) ou Manual
3641

37-
Ao ativar o cálculo automático, a raridade da carta no Inventário — ou a raridade base no modo Pacotes — volta para **Incomum** como padrão. No Inventário, a raridade fica bloqueada enquanto o cálculo automático estiver ativo. O título especial não é escolhido no formulário; nos pacotes, ele é definido pelo sorteio da abertura. A coleção filtra por clube usando o valor salvo na carta, por isso os dados de identificação vêm do template para manter o filtro consistente.
42+
Ao ativar o cálculo automático, a raridade da carta no Inventário — ou a raridade base no modo Pacotes — volta para **Incomum** como padrão. No Inventário, a raridade fica bloqueada enquanto o cálculo automático estiver ativo. O título especial não é escolhido no formulário; nos pacotes, ele é definido pelo sorteio da abertura. A coleção filtra por clube usando o valor salvo na carta, por isso o clube é escolhido da lista padronizada.
3843

3944
#### Modo Pacotes
4045
Cria um template que será sorteado nos pacotes abertos por qualquer jogador via RPC `create_card_template`.

‎src/pages/Admin.tsx‎

Lines changed: 31 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,13 @@ export default function Admin() {
6666
const [editingTemplate, setEditingTemplate] = useState<string | null>(null);
6767
const [templateAttrs, setTemplateAttrs] = useState<Record<string, any>>({});
6868
const [savingTemplate, setSavingTemplate] = useState(false);
69-
const [inventoryTemplateName, setInventoryTemplateName] = useState('');
7069
const [templates, setTemplates] = useState(() => allTemplates());
7170

7271
const [form, setForm] = useState({
7372
name: '',
7473
nationality: 'Brasil',
7574
age: 20,
76-
club: 'Clube',
75+
club: '',
7776
position: 'Ala',
7877
rarity: 'uncommon',
7978
overall: 80,
@@ -112,31 +111,7 @@ export default function Admin() {
112111
const isGK = form.position === 'GK';
113112
const calcMode = form.calcOverall;
114113
const isInventory = form.destination === 'inventory';
115-
const inventoryTemplate = templates.find(t => t.name === inventoryTemplateName);
116-
117-
const selectInventoryTemplate = (name: string) => {
118-
const template = templates.find(t => t.name === name);
119-
setInventoryTemplateName(name);
120-
if (!template) return;
121-
setForm(prev => ({
122-
...prev,
123-
name: template.name,
124-
nationality: template.nationality,
125-
age: template.age,
126-
club: template.club,
127-
position: template.position,
128-
imageUrl: template.image || '',
129-
pace: template.attributes.pace,
130-
shooting: template.attributes.shooting,
131-
passing: template.attributes.passing,
132-
dribbling: template.attributes.dribbling,
133-
defending: template.attributes.defending,
134-
physical: template.attributes.physical,
135-
reflexes: template.gkAttributes?.reflexes ?? 0,
136-
positioning: template.gkAttributes?.positioning ?? 0,
137-
handling: template.gkAttributes?.handling ?? 0,
138-
}));
139-
};
114+
const teams = [...new Set(templates.map(template => template.club).filter(Boolean))].sort((a, b) => a.localeCompare(b));
140115

141116
const rarityToUse = isInventory ? form.rarity : form.baseRarity;
142117
const mod = RARITY_MODIFIER[rarityToUse as Rarity] ?? 1.0;
@@ -288,7 +263,7 @@ export default function Admin() {
288263

289264
const handleCreate = async () => {
290265
if (!supabase || !form.name.trim()) { toast.error('Nome obrigatório.'); return; }
291-
if (isInventory && !inventoryTemplate) { toast.error('Selecione um jogador dos templates do jogo.'); return; }
266+
if (!teams.includes(form.club)) { toast.error('Selecione um time da lista.'); return; }
292267
const fields = [
293268
['Velocidade', form.pace],
294269
['Chute', form.shooting],
@@ -322,12 +297,10 @@ export default function Admin() {
322297
}
323298

324299
if (isInventory) {
325-
const template = inventoryTemplate;
326-
if (!template) { toast.error('Selecione um jogador dos templates do jogo.'); return; }
327300
const ovr = calcMode ? calculatedOverall : form.overall;
328301
const { data: createdCardId, error } = await supabase.rpc('create_card', {
329-
p_name: template.name,
330-
p_position: template.position,
302+
p_name: form.name.trim(),
303+
p_position: form.position,
331304
p_rarity: rarityToUse,
332305
p_overall: ovr,
333306
p_pace: attrsMod.pace,
@@ -340,27 +313,27 @@ export default function Admin() {
340313
p_positioning: isGK ? attrsMod.positioning : null,
341314
p_handling: isGK ? attrsMod.handling : null,
342315
p_special: 'none',
343-
p_image: template.image || null,
344-
p_nationality: template.nationality,
345-
p_age: template.age,
346-
p_club: template.club,
316+
p_image: imageUrl || null,
317+
p_nationality: form.nationality.trim() || 'Brasil',
318+
p_age: Number(form.age),
319+
p_club: form.club,
347320
});
348321
if (error) { toast.error('Erro: ' + error.message); return; }
349322
toast.success('Carta criada no inventário!');
350323
const novaCarta: PlayerCard = {
351324
uid: createdCardId || nanoid(10),
352-
playerId: template.name.toLowerCase().replace(/\s+/g, '-') + '-' + rarityToUse,
353-
name: template.name,
354-
position: template.position as Position,
325+
playerId: form.name.trim().toLowerCase().replace(/\s+/g, '-') + '-' + rarityToUse,
326+
name: form.name.trim(),
327+
position: form.position as Position,
355328
sport: 'futsal',
356-
nationality: template.nationality,
357-
age: template.age,
358-
club: template.club,
329+
nationality: form.nationality.trim() || 'Brasil',
330+
age: Number(form.age),
331+
club: form.club,
359332
rarity: rarityToUse as any,
360333
edition: DEFAULT_EDITION,
361334
special: 'none',
362335
overall: ovr,
363-
image: template.image || undefined,
336+
image: imageUrl || undefined,
364337
pace: attrsMod.pace,
365338
shooting: attrsMod.shooting,
366339
passing: attrsMod.passing,
@@ -409,7 +382,6 @@ export default function Admin() {
409382
setTemplates(allTemplates());
410383
toast.success('Carta adicionada aos pacotes!');
411384
}
412-
if (isInventory) setInventoryTemplateName('');
413385
setForm({ ...form, name: '', imageUrl: '' });
414386
} catch (e: any) {
415387
toast.error('Erro: ' + (e?.message || 'desconhecido'));
@@ -449,28 +421,15 @@ export default function Admin() {
449421
</div>
450422
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
451423
<div>
452-
<label className={labelCls}>{isInventory ? 'Jogador do template' : 'Nome do Jogador'}</label>
453-
{isInventory ? (
454-
<div className="relative">
455-
<select value={inventoryTemplateName} onChange={e => selectInventoryTemplate(e.target.value)} className={`${inputCls} appearance-none pr-10`}>
456-
<option value="">Selecione um jogador</option>
457-
{templates.map(template => <option key={template.name} value={template.name}>{template.name}</option>)}
458-
</select>
459-
<ChevronDown className="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#858A98]" />
460-
</div>
461-
) : (
462-
<input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} className={inputCls} placeholder="Ex: João Marcos" />
463-
)}
424+
<label className={labelCls}>Nome do Jogador</label>
425+
<input value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} className={inputCls} placeholder="Ex: João Marcos" />
426+
</div>
427+
<div>
428+
<label className={labelCls}>URL da Imagem</label>
429+
<input value={form.imageUrl} onChange={e => setForm({ ...form, imageUrl: e.target.value })} className={inputCls} placeholder="https://... ou /assets/imagem.jpg" />
464430
</div>
465-
{!isInventory && (
466-
<div>
467-
<label className={labelCls}>URL da Imagem</label>
468-
<input value={form.imageUrl} onChange={e => setForm({ ...form, imageUrl: e.target.value })} className={inputCls} placeholder="https://... ou /assets/imagem.jpg" />
469-
</div>
470-
)}
471431
</div>
472432

473-
{!isInventory ? (
474433
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
475434
<div>
476435
<label className={labelCls}>Nacionalidade</label>
@@ -482,14 +441,15 @@ export default function Admin() {
482441
</div>
483442
<div>
484443
<label className={labelCls}>Clube</label>
485-
<input value={form.club} onChange={e => setForm({ ...form, club: e.target.value })} className={inputCls} placeholder="Ex: Santos" />
444+
<div className="relative">
445+
<select value={form.club} onChange={e => setForm({ ...form, club: e.target.value })} className={`${inputCls} appearance-none pr-10`}>
446+
<option value="">Selecione um time</option>
447+
{teams.map(team => <option key={team} value={team}>{team}</option>)}
448+
</select>
449+
<ChevronDown className="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#858A98]" />
450+
</div>
486451
</div>
487452
</div>
488-
) : (
489-
<p className="text-xs text-[#858A98]">
490-
Nacionalidade, idade, clube, posição e imagem vêm do template escolhido para manter os dados consistentes com os filtros da coleção.
491-
</p>
492-
)}
493453
</section>
494454

495455
<section className="rounded-2xl border border-[#E9EAF0] p-4 md:p-5 space-y-4">
@@ -501,7 +461,7 @@ export default function Admin() {
501461
<div>
502462
<label className={labelCls}>Posição</label>
503463
<div className="relative">
504-
<select value={form.position} onChange={e => setForm({ ...form, position: e.target.value })} className={`${inputCls} appearance-none pr-10`} disabled={isInventory}>
464+
<select value={form.position} onChange={e => setForm({ ...form, position: e.target.value })} className={`${inputCls} appearance-none pr-10`}>
505465
{POSITIONS.map(p => <option key={p} value={p}>{p}</option>)}
506466
</select>
507467
<ChevronDown className="pointer-events-none absolute right-3 top-1/2 size-4 -translate-y-1/2 text-[#858A98]" />
@@ -630,7 +590,7 @@ export default function Admin() {
630590

631591
<button
632592
onClick={handleCreate}
633-
disabled={creating || !form.name.trim() || (isInventory && !inventoryTemplate)}
593+
disabled={creating || !form.name.trim() || !form.club}
634594
className="w-full py-3 rounded-xl bg-[#E30613] text-white font-bold disabled:opacity-60"
635595
>
636596
{creating ? 'Criando...' : isInventory ? 'Criar no Inventário' : 'Adicionar aos Pacotes'}

0 commit comments

Comments
 (0)