`; const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([html],{type:'text/html'})); a.download=`hbtas_${(match.date||'report').replace(/\//g,'_')}.html`;a.click(); }; const handleSave=()=>{onSave({...match,elapsed:timer,period,scoreA,scoreB,events,ended:true,date:match.date||new Date().toLocaleDateString('it-IT')});notify('Salvata ✓');}; const getOdCnt=(a,tk,phase)=>events.filter(e=>e.action===a&&e.team===tk&&(phase?e.odType===phase:true)).length; return(
{confirmModal} {notif&&
✓ {notif}
} {undoState&&(
{undoState.label}
)} {guided&&{ let pl=guided.teamKey==='a'?playersA:playersB; if(guided.evType==='save') pl=pl.filter(p=>p.position==='Portiere'); // Filtra per giocatori in campo se la selezione è attiva (almeno 1 in campo) if(guided.teamKey==='a'&&onField.size>0&&guided.evType!=='save'){ const inFieldFiltered=pl.filter(p=>onField.has(p.id)); if(inFieldFiltered.length>0) pl=inFieldFiltered; } return sortPlayers(pl); })()} timer={fmt(timer)} teamName={guided.teamKey==='a'?team?.name:match.opponent} skipPlayer={guided.teamKey==='b'&&['goal','counter_goal','counter_miss','7m_goal','7m_miss'].includes(guided.evType)} onConfirm={onGuidedConfirm} onCancel={()=>setGuided(null)}/>} {playerPopup&&setPlayerPopup(null)}/>} {/* 2 MIN MODAL */} {twoMinModal&&(
e.target===e.currentTarget&&setTwoMinModal(null)}>
🟥 2 MINUTI — {twoMinModal.phase==='off'?'SUPERIORITÀ (avversaria espulsa)':'INFERIORITÀ (nostra espulsa)'}
{/* Mostra espulsioni attive per questa fase */} {activeExpulsions.filter(x=>x.phase===twoMinModal.phase).length>0&&(
{twoMinModal.phase==='def'?'Giocatrici espulse attive:':'Avversarie espulse attive:'}
{activeExpulsions.filter(x=>x.phase===twoMinModal.phase).map(x=>{ const left=Math.max(0,Math.ceil((x.endTs-nowTs)/1000)); return
{x.playerNum?`#${x.playerNum} ${x.playerName||''}`:twoMinModal.phase==='off'?'Avversaria':'—'} {Math.floor(left/60)}:{String(left%60).padStart(2,'0')}
; })}
)}
{twoMinModal.phase==='def'?'Aggiungi giocatrice espulsa:':'Avvia nuova superiorità numerica'}
{twoMinModal.phase==='def'&&(
{sortPlayers(playersA).map(p=>(
handle2min(twoMinModal.phase,p)} style={{borderColor:onField.has(p.id)?'rgba(255,61,113,.6)':'var(--border)',background:onField.has(p.id)?'rgba(255,61,113,.08)':'var(--bg3)'}}>
{p.number}
{p.name}
{onField.has(p.id)&&
● campo
}
))} {playersA.length===0&&}
)}
{twoMinModal.phase==='off'&&}
)} {/* SUP/INF indicators */} {(isSup||isInf)&&(
{supList.map(x=>{const left=Math.max(0,Math.ceil((x.endTs-nowTs)/1000));return(
⭐ SUP {Math.floor(left/60)}:{String(left%60).padStart(2,'0')}
);})} {infList.map(x=>{const left=Math.max(0,Math.ceil((x.endTs-nowTs)/1000));return(
🟥 {x.playerNum?`#${x.playerNum} `:''}INF {Math.floor(left/60)}:{String(left%60).padStart(2,'0')}
);})}
)} {odFieldModal&&(
e.target===e.currentTarget&&setOdFieldModal(null)}>
{(()=>{const o=[...OD,...OD_DEF_ONLY].find(x=>x.id===odFieldModal.action);const isFallo=odFieldModal.action==='fallo9m';return(<>
{o?.ico} {o?.lbl} — {isFallo?'DIFENSIVA':'OFFENSIVA'}
{odFieldModal.teamKey==='a'?team?.name:match.opponent} · {fmt(timer)}
);})()} { let pl=odFieldModal.teamKey==='a'?playersA:playersB; if(odFieldModal.teamKey==='a'&&onField.size>0){ const f=pl.filter(p=>onField.has(p.id)); if(f.length>0) pl=f; } return pl; })()} odType={odFieldModal.odType} action={odFieldModal.action} onConfirm={onOdFieldConfirm} onCancel={()=>setOdFieldModal(null)}/>
)} {/* ─ HEADER ─ */}
{team?.name} vs {match.opponent}
{fmt(timer)}
{period===1?'1°T':'2°T'}
{scoreA} {scoreB}
{/* ─ BODY ─ */}
{/* ─ SIDEBAR A (azioni offensive/difensive in alto, giocatori con selezione in campo sotto) ─ */}
{team?.name?.slice(0,14)||'Team A'}
{/* OD section — offensive */}
⚔️ Offensiva{isSup?⭐×{supList.length}:''}
{OD.map(o=>(
handleOD(o.id,'off','a')} style={{borderColor:isSup?'rgba(255,214,10,.5)':'var(--border)'}}>
{o.ico}
{getOdCnt(o.id,'a','off')}
{o.lbl}
))}
setTwoMinModal({phase:'off'})}>
🟥
{isSup?`${supList.length}× SUP`:'2 MIN'}
Superiorità
{/* OD section — defensive */}
🛡️ Difensiva{isInf?🟥×{infList.length}:''}
{OD.map(o=>(
handleOD(o.id,'def','a')} style={{borderColor:isInf?'rgba(255,61,113,.3)':'var(--border)'}}>
{o.ico}
{getOdCnt(o.id,'a','def')}
{o.lbl}
))} {OD_DEF_ONLY.map(o=>(
handleOD(o.id,'def','a')} style={{borderColor:'rgba(100,149,237,.5)',gridColumn:'1/-1'}}>
{o.ico}
{getOdCnt(o.id,'a','def')}
{o.lbl}
))}
setTwoMinModal({phase:'def'})}>
🟥
{isInf?`${infList.length}× INF`:'2 MIN'}
Inferiorità
{/* ─ MAIN CENTER ─ */}
{/* Efficiency strip */}
Off.A
{effOffA}%
Par.A
{saveRateA}%
Ctr.A
{ctrEffA}%
{[{v:goalsA,c:'var(--acc)',l:'Gol'+' '+( team?.name?.slice(0,4)||'A')},{v:savesA,c:'var(--green)',l:'Par.A'},{v:ppA,c:'var(--red)',l:'PP.A'},{v:cGolA,c:'var(--yellow)',l:'Ctr'},{v:goalsB,c:'var(--acc2)',l:'Gol '+(match.opponent?.slice(0,4)||'B')},{v:supList.length,c:'var(--yellow)',l:isSup?`SUP×${supList.length}`:'SUP'},{v:infList.length,c:'var(--red)',l:isInf?`INF×${infList.length}`:'INF'}].filter(s=>s.v>0||['Gol'+' '+( team?.name?.slice(0,4)||'A'),'Par.A','PP.A','Gol '+(match.opponent?.slice(0,4)||'B')].includes(s.l)).map(s=>(
{s.v}
{s.l}
))}
{/* Tabs */}
{TABS.map(t=>)}
{/* ─── TAB EVENTI ─── */} {activeTab==='eventi'&&(
{/* SHOOTING + STATS ROW */}
{/* LEFT: Tiri Team A */}
⚽ Tiri — {team?.name}
{lastAction&&lastAction.teamKey==='a'&&( )}
openGuided('goal','a')}>{goalsA}GOAL
openGuided('miss','a')}>{missA}ERRORE
openGuided('save','a')}>🧤{savesA}PARATA
openGuided('counter_goal','a')}>{cGolA}CTROP.GOL
openGuided('counter_miss','a')}> ✗⚡{cMisA}CTROP. ERRORE
{/* 7m row */}
openGuided('7m_goal','a')}>🎯{pen7mGolA}7M GOL
openGuided('7m_miss','a')}>✗🎯{pen7mMisA}7M ERR.
{/* RIGHT: Rich stats */}
📊 Statistiche Live
{/* Efficienza offensiva */}
Eff. Offensiva
{effOffA}%
{/* Portiere */}
% Parate Porto.
{saveRateA}%
{/* Contropiedi */}
Eff. Contropiede
{ctrEffA}%
{/* 7m */}
0?1:.5}}>🎯 Eff. 7 Metri
0?1:.4}}>
0?effColor(eff7mA):'var(--text3)'}}>{pen7mTotA>0?`${eff7mA}%`:'—'}
{/* Superiorità */}
⭐ Sup.{isSup?{supList.length}× {Math.floor(sup2minLeft/60)}:{String(sup2minLeft%60).padStart(2,'0')}:''}
0?1:.4}}>
0?effColor(effSup):'var(--text3)'}}>{supTot>0?`${effSup}%`:'—'}
{/* Inferiorità */}
🟥 Inf.{isInf?{infList.length}× {Math.floor(inf2minLeft/60)}:{String(inf2minLeft%60).padStart(2,'0')}:''}
0?1:.4}}>
0?effColor(effInf):'var(--text3)'}}>{infTot>0?`${effInf}%`:'—'}
{/* Stat pills row */}
{possA}Possessi
{goalsA}Gol
{missA}Errori
{ppA}P.Perse
{ctrTotA}Ctrop.
{/* ZONE EFFICIENCY */} {zoneStatsA().length>0&&(
📍 Efficienza per Zona — {team?.name}
{zoneStatsA().map(z=>(
{z.name}
{z.eff}%
{z.goals}/{z.shots} tiri
Uso {z.usage}%
))}
)} {/* GIOCATORI IN CAMPO — ex sidebar */}
👥 Giocatori — {team?.name}
{onField.size>0&&{onField.size} in campo}
{groupByRole(playersA).map(g=>(
{ROLE_SHORT[g.role]||g.role}
{g.players.map(p=>{ const pid=`#${p.number} ${p.name}`; const pg=events.filter(e=>e.player===pid&&e.type==='goal').length; const ps_=events.filter(e=>e.player===pid&&e.type==='save').length; const isGK=p.position==='Portiere'; const inF=onField.has(p.id); const isExpelled=activeExpulsions.some(x=>x.playerId===p.id&&x.phase==='def'); return(
toggleOnField(p.id)} style={{ minWidth:'38px',minHeight:'44px', borderRadius:'8px', display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center', cursor:'pointer',touchAction:'manipulation',userSelect:'none', transition:'all .15s', background:isExpelled?'rgba(255,61,113,.25)':inF?'rgba(0,230,118,.2)':isGK?'rgba(45,212,191,.06)':'var(--bg3)', border:`2px solid ${isExpelled?'var(--red)':inF?'var(--green)':isGK?'rgba(45,212,191,.4)':'var(--border)'}`, position:'relative', }} >
{p.number}
{isExpelled?'🟥':inF?'●':'○'}
{(pg>0||ps_>0)&&
{isGK?ps_:pg}
}
); })}
))} {playersA.length===0&&
Nessun giocatore
}
)} {activeTab==='stats'&&(
{[ {l:'Eff. Off. '+( team?.name||'A'),v:`${effOffA}%`,c:'var(--acc)',sub:`${goalsA+cGolA+pen7mGolA} gol (${goalsA}+${cGolA}⚡+${pen7mGolA}🎯) / ${offActsA} poss.`}, {l:'Possessi '+(team?.name||'A'),v:possA,c:'var(--text)',sub:`${goalsA} gol + ${missA} err. + ${ppA} pp + ${cGolA+cMisA} ctrop.`}, {l:'% Parate Portiere',v:`${saveRateA}%`,c:'var(--green)',sub:`${savesA} par. / ${shotsOnA} tiri in porta (${goalsB}+${cGolB}⚡+${pen7mGolB}🎯 gol)`}, {l:'Eff. Ctrop.',v:`${ctrEffA}%`,c:'var(--yellow)',sub:`${cGolA} gol / ${ctrTotA} ctrop.`}, {l:'Eff. Superiorità',v:supTot>0?`${effSup}%`:'—',c:'var(--yellow)',sub:supTot>0?`${supGoals} gol / ${supTot} poss. SUP`:'Nessun dato'}, {l:'Eff. Inferiorità',v:infTot>0?`${effInf}%`:'—',c:'var(--red)',sub:infTot>0?`${infGoals} gol / ${infTot} poss. INF`:'Nessun dato'}, {l:'Eff. Off. '+(match.opponent||'Avv.'),v:`${effOffB}%`,c:'var(--acc2)',sub:`${goalsB+cGolB+pen7mGolB} gol (${goalsB}+${cGolB}⚡+${pen7mGolB}🎯) / ${offActsB} poss.`}, {l:'% Par. Port. '+(match.opponent||'Avv.'),v:`${saveRateB}%`,c:'var(--purple)',sub:`${savesB} par. / ${shotsOnB} tiri in porta (${goalsA}+${cGolA}⚡+${pen7mGolA}🎯 gol)`}, {l:'Eff. 7m '+(match.opponent||'Avv.'),v:pen7mTotB>0?`${eff7mB}%`:'—',c:'var(--purple)',sub:pen7mTotB>0?`${pen7mGolB} gol / ${pen7mTotB} rig.`:'Nessun rigore'}, ].map(s=>
{s.l}
4?'20px':'24px'}}>{s.v}
{s.sub}
)}
{/* ── SEZIONE DIFESA AVANZATA ── */}
🛡️ Analisi Difensiva
{/* Possessi avversari */}
{[ {l:'Poss. Avv.',v:totPossB,c:'var(--text)'}, {l:'Annullati',v:possBAnnullati,c:'var(--green)'}, {l:'% Difesa',v:`${pctDifesa}%`,c:effColor(pctDifesa)}, ].map(s=>
{s.l}
{s.v}
)}
{totPossB>0&&(
{[ {l:'Goal subiti',v:events.filter(e=>e.team==='b'&&['goal','counter_goal','7m_goal'].includes(e.type)).length,c:'var(--red)'}, {l:'Tiro sbagliato',v:events.filter(e=>e.team==='b'&&e.type==='miss').length,c:'var(--yellow)'}, {l:'Recupero',v:events.filter(e=>e.team==='a'&&e.type==='od'&&e.odType==='def'&&['area','passi','ppersa','sfondo','rubata','muro'].includes(e.action)).length,c:'var(--teal)'}, ].map(s=>
{s.v}
{s.l}
)}
)} {/* Falli 9m */}
🚫 Falli sui 9 Metri
{[ {l:'Totale Falli',v:falli9m,c:'#6495ed'}, {l:'Poss. con Fallo',v:possConFallo9m,c:'#6495ed'}, {l:'% Poss. con Fallo',v:totPossB>0?`${pct(possConFallo9m,totPossB)}%`:'—',c:'#6495ed'}, ].map(s=>
{s.l}
{s.v}
)}
{possConFallo9m>0&&(
Esiti dei possessi con fallo 9m:
)} {possConFallo9m>0&&(
{[ {l:'Goal subiti',v:possConFallo9m_goal,c:'var(--red)'}, {l:'Tiro sbagliato',v:possConFallo9m_err,c:'var(--yellow)'}, {l:'Recupero',v:possConFallo9m_rec,c:'var(--teal)'}, ].map(s=>
{s.v}
{s.l}
)}
)} {/* Per-player falli 9m */} {falli9m>0&&playersA.length>0&&(()=>{ const byP=sortPlayers(playersA).map(p=>{ const pid=`#${p.number} ${p.name}`; const cnt=events.filter(e=>e.player===pid&&e.action==='fallo9m').length; return{p,cnt}; }).filter(x=>x.cnt>0).sort((a,b)=>b.cnt-a.cnt); const byZ={}; events.filter(e=>e.action==='fallo9m'&&e.fieldZone).forEach(e=>{byZ[e.fieldZone]=(byZ[e.fieldZone]||0)+1;}); const topZ=Object.entries(byZ).sort((a,b)=>b[1]-a[1]); return byP.length>0?(
Falli 9m per giocatrice
{byP.map(({p,cnt})=>(
#{p.number} {p.name}
{cnt}
))} {topZ.length>0&&(
Falli 9m per zona
{topZ.map(([zid,cnt])=>(
{FZN[zid]||zid}
{cnt}
))}
)}
):null; })()}
{/* Contropiedi dopo errore */}
⚡ Contropiedi subiti dopo errore offensivo
Ctr. dopo errore A
{ctrGoalDopErrA}
% su tot. ctr. subiti
0?effColor(100-pct(ctrGoalDopErrA,cGolB)):'var(--text3)'}}>{cGolB>0?`${pct(ctrGoalDopErrA,cGolB)}%`:'—'}
{playersA.length>0&&(
Giocatori {team?.name} — tocca per dettaglio
{sortPlayers(playersA).map(p=>{ const pid=`#${p.number} ${p.name}`;const evp=events.filter(e=>e.player===pid&&e.team==='a'); const g=evp.filter(e=>e.type==='goal').length;const sv=evp.filter(e=>e.type==='save').length;const m=evp.filter(e=>e.type==='miss').length; const pp2=evp.filter(e=>e.type==='od'&&e.odType==='off'&&e.action!=='2min').length; const twoMin=evp.filter(e=>e.action==='2min').length; const cg=evp.filter(e=>e.type==='counter_goal').length; const cm=evp.filter(e=>e.type==='counter_miss').length; const p7g=evp.filter(e=>e.type==='7m_goal').length; const p7m=evp.filter(e=>e.type==='7m_miss').length; const f9m=evp.filter(e=>e.action==='fallo9m').length; const allGoals=g+cg+p7g; const allTiri=g+m+cg+cm+p7g+p7m; const eff2=pct(allGoals,allTiri+pp2||1); const mins=getPlayerMinutes(p.id); const inF=onField.has(p.id); const byZ={};evp.filter(e=>e.fieldZone).forEach(e=>{byZ[e.fieldZone]=(byZ[e.fieldZone]||0)+1;}); const top=Object.entries(byZ).sort((a,b)=>b[1]-a[1])[0]; returnsetPlayerPopup({player:p,team:'a'})} style={{cursor:'pointer',background:inF?'rgba(0,230,118,.03)':''}}> ; })}
#NomeGolTiriEff%Par.PP2minCtr.G🚫9mMin'Zona Pref.
{p.number}{inF&&} {p.name} {allGoals} {allTiri} {eff2}% {sv} {pp2} 0?'var(--red)':'var(--text3)',fontWeight:twoMin>0?700:400}}>{twoMin||0} {cg} 0?'#6495ed':'var(--text3)',fontWeight:f9m>0?700:400}}>{f9m||0} 0?'var(--green)':'var(--text3)',fontFamily:'var(--fh)',fontSize:'12px',fontWeight:700}}>{mins>0?`${mins}'`:'—'} {top?FZN[top[0]]||top[0]:'—'}
)}
)} {/* ─── TAB CAMPO ─── */} {activeTab==='campo'&&(
{(()=>{ // Tiri mostrati nella zona effettivamente cliccata (nessun rimappaggio per periodo) // Le statistiche restano separate per squadra ma ogni squadra può usare tutte le zone const SHOT_TYPES_FIELD=['goal','miss','counter_goal','counter_miss','7m_goal','7m_miss']; // Include anche le parate avversarie: una parata di B su un tiro di A → A ha tirato in quella zona const evAll_A=events.filter(e=>e.fieldZone&&( (e.team==='a'&&SHOT_TYPES_FIELD.includes(e.type))|| (e.team==='b'&&e.type==='save') )); const evAll_B=events.filter(e=>e.fieldZone&&( (e.team==='b'&&SHOT_TYPES_FIELD.includes(e.type))|| (e.team==='a'&&e.type==='save') )); return ; })()}
Efficienza per zona — {fieldView==='a'?team?.name:match.opponent} (tutte le 16 zone, indipendenti dal periodo)
{(()=>{ const byZ={}; const normalize=fieldView==='a'?normalizeZoneA:normalizeZoneB; const oppKey=fieldView==='a'?'b':'a'; const teamEvs=events.filter(e=>e.fieldZone&&( (e.team===fieldView&&['goal','miss','counter_goal','counter_miss','7m_goal','7m_miss'].includes(e.type))|| (e.team===oppKey&&e.type==='save') )); teamEvs.filter(e=>e).forEach(e=>{ const z=normalize(e.fieldZone,e.period); if(!byZ[z])byZ[z]={shots:0,goals:0}; byZ[z].shots++; if(['goal','counter_goal','7m_goal'].includes(e.type))byZ[z].goals++; }); const total=Object.values(byZ).reduce((s,d)=>s+d.shots,0); const ZONE_ORDER=['1','2','3','4','5','6','7','8','R9','R10','R11','R12','R13','R14','R15','R16']; const zoneNum=(zid)=>{const m=FZN[zid];if(!m)return zid;const n=m.match(/^(\d+)/);return n?n[1]:zid;}; const rows=Object.entries(byZ).map(([zid,d])=>({zid,name:FZN[zid]||zid,shots:d.shots,goals:d.goals,eff:pct(d.goals,d.shots),usage:pct(d.shots,total||1)})).sort((a,b)=>{const ia=ZONE_ORDER.indexOf(a.zid);const ib=ZONE_ORDER.indexOf(b.zid);return(ia===-1?99:ia)-(ib===-1?99:ib);}); return rows.length>0 ?
{rows.map(z=>
{zoneNum(z.zid)}{z.name.replace(/^\d+\s*·\s*/,'')}
{z.eff}%
{z.goals}/{z.shots}
Uso {z.usage}%
)}
:
Nessun tiro registrato.
; })()}
)} {/* ─── TAB PORTA ─── */} {activeTab==='porta'&&(
🧤 Parate Portiere A — {team?.name}
e.type==='save'&&e.team==='a'&&GOAL_INNER_ZONES.includes(e.goalZone))} saveColor="green" readOnly/>
{saveRateA}% ({savesA}/{shotsOnA})
{gZoneStatsA().slice(0,6).map(z=>(
{z.name}
{z.cnt} ({z.pct}%)
))}
🧤 Parate Portiere — {match.opponent||'Avversario'}
e.type==='save'&&e.team==='b'&&GOAL_INNER_ZONES.includes(e.goalZone))} saveColor="purple" readOnly/>
{saveRateB}% ({savesB}/{shotsOnB})
{(()=>{ const byZ={};events.filter(e=>e.team==='b'&&e.type==='save'&&e.goalZone&&GOAL_INNER_ZONES.includes(e.goalZone)).forEach(e=>{byZ[e.goalZone]=(byZ[e.goalZone]||0)+1;}); const tot=Object.values(byZ).reduce((a,b)=>a+b,0); return Object.entries(byZ).map(([zid,cnt])=>({zid,name:GZN[zid]||zid,cnt,pct:pct(cnt,tot||1)})).sort((a,b)=>b.cnt-a.cnt).slice(0,6).map(z=>(
{z.name}
{z.cnt} ({z.pct}%)
)); })()}
)} {/* ─── TAB OFF/DEF (full detail for both teams) ─── */} {activeTab==='off/def'&&(
{[{title:'⚔️ OFFENSIVA A',phase:'off',tk:'a',clr:'var(--orange)'},{title:'⚔️ OFFENSIVA B',phase:'off',tk:'b',clr:'var(--acc2)'},{title:'🛡️ DIFENSIVA A',phase:'def',tk:'a',clr:'#94a3b8'},{title:'🛡️ DIFENSIVA B',phase:'def',tk:'b',clr:'#64748b'}].map(s=>(
{s.title}
{s.phase==='off'&&
Le azioni segnate contano come possesso inefficiente
}
{OD.map(o=>{const cnt=getOdCnt(o.id,s.tk,s.phase);return(
0?s.clr:'var(--border)'}} onClick={()=>handleOD(o.id,s.phase,s.tk)}>
{o.ico}
0?s.clr:'var(--text)'}}>{cnt}
{o.lbl}
);})}
))}
)} {/* ─── TAB PERIODI ─── */} {activeTab==='periodi'&&(
{[{p:1,d:p1,lbl:'1° TEMPO'},{p:2,d:p2,lbl:'2° TEMPO'}].map(({p:per,d,lbl})=>(
{lbl} — {team?.name}
{[ {l:'Eff. Offensiva',v:`${d.eff}%`,c:'var(--acc)',sub:`${d.gA} gol / ${d.tot} poss.`}, {l:'Gol Segnati',v:d.gA,c:'var(--acc)',sub:''}, {l:'Gol Subiti',v:d.gB,c:'var(--acc2)',sub:''}, {l:'Errori Tiro',v:d.mA,c:'var(--red)',sub:''}, {l:'Parate Port.',v:d.sA,c:'var(--green)',sub:''}, {l:'Palle Perse',v:d.pp,c:'var(--orange)',sub:''}, ].map(s=>
{s.l}
{s.v}
{s.sub&&
{s.sub}
}
)}
{per===1&&per===2&&
Nessun evento in questo tempo
} {d.tot===0&&d.gA===0&&d.gB===0&&d.sA===0&&
Nessun evento registrato
}
))} {/* Confronto periodi */} {(p1.tot>0||p2.tot>0)&&(
📊 Confronto Tempi
{[ {l:'Eff. Offensiva',v1:p1.eff,v2:p2.eff,suf:'%'}, {l:'Gol Segnati',v1:p1.gA,v2:p2.gA,suf:''}, {l:'Errori Tiro',v1:p1.mA,v2:p2.mA,suf:''}, {l:'Parate Port.',v1:p1.sA,v2:p2.sA,suf:''}, ].map(r=>(
{r.l} {r.v1}{r.suf}
{r.v2}{r.suf}
))}
1° Tempo 2° Tempo
)}
)} {/* ─── TAB TREND (sequenze tiro + grafico possessi) ─── */} {activeTab==='trend'&&(
{/* Grafico andamento efficienza cumulativa */}
📈 Andamento nel Match
{(()=>{ // Tutti gli eventi offensivi in ordine cronologico const offTypes=['goal','miss','counter_goal','counter_miss','7m_goal','7m_miss']; const offEvs=events.filter(e=>e.team==='a'&&(offTypes.includes(e.type)||(e.type==='od'&&e.odType==='off'&&e.action!=='2min'))).sort((a,b)=>(a.seconds||0)-(b.seconds||0)); if(offEvs.length<2)return
Servono almeno 2 azioni offensive per il grafico
; // Calcola efficienza cumulativa dopo ogni azione let cumGoals=0,cumTot=0; const points=offEvs.map((e,i)=>{ const isGoal=['goal','counter_goal','7m_goal'].includes(e.type); if(isGoal)cumGoals++; cumTot++; return{i,t:e.seconds||0,eff:pct(cumGoals,cumTot),isGoal,type:e.type,time:e.time}; }); const W=320,H=72,pad=6; const maxT=points[points.length-1].t||1; const px=t=>pad+Math.round((t/maxT)*(W-pad*2)); const py=v=>H-pad-Math.round((v/100)*(H-pad*2)); // Linea efficienza const pathD=points.map((p,i)=>`${i===0?'M':'L'}${px(p.t)},${py(p.eff)}`).join(' '); // Linea 50% const y50=py(50); return( {/* Griglia */} {[0,25,50,75,100].map(v=>( {v}% ))} {/* Area sotto la linea */} {/* Linea efficienza */} {/* Linea 50% riferimento */} {/* Punti evento */} {points.map((p,i)=>( ))} {/* Label tempo x asse */} {points.filter((_,i)=>i===0||i===Math.floor(points.length/2)||i===points.length-1).map((p,i)=>( {p.time} ))} ); })()}
Gol Errore/Miss P.Persa Linea = Eff. cumulativa · trattino = 50%
{/* Sequenze di tiro */}
🔁 Sequenze Tiro — {team?.name}
Pattern ricorrenti (finestre di 3 azioni offensive consecutive)
{(()=>{ const shotEvs=events.filter(e=>e.team==='a'&&( ['goal','miss','counter_goal','counter_miss','7m_goal','7m_miss'].includes(e.type)|| (e.type==='od'&&e.odType==='off'&&e.action!=='2min') )).sort((a,b)=>(a.seconds||0)-(b.seconds||0)); if(shotEvs.length<3)return
Servono almeno 3 azioni offensive per analizzare le sequenze
; const label=e=>{ if(e.type==='goal')return'⚽G'; if(e.type==='miss')return'✗E'; if(e.type==='counter_goal')return'⚡G'; if(e.type==='counter_miss')return'✗⚡'; if(e.type==='7m_goal')return'🎯G'; if(e.type==='7m_miss')return'✗7m'; return'❌PP'; }; const seqMap={}; for(let i=0;i<=shotEvs.length-3;i++){ const seq=shotEvs.slice(i,i+3).map(label).join('→'); seqMap[seq]=(seqMap[seq]||0)+1; } const seqs=Object.entries(seqMap).sort((a,b)=>b[1]-a[1]).slice(0,8); const maxCnt=seqs[0]?.[1]||1; return(
{seqs.map(([seq,cnt])=>(
{seq}
{cnt}x
))}
); })()}
{/* Sequenze zona campo */}
📍 Sequenze Zona di Tiro
{(()=>{ const shotZ=events.filter(e=>e.team==='a'&&['goal','miss','counter_goal','counter_miss'].includes(e.type)&&e.fieldZone).sort((a,b)=>(a.seconds||0)-(b.seconds||0)); if(shotZ.length<2)return
Servono almeno 2 tiri con zona per analisi
; const seqMap={}; for(let i=0;iv>1).sort((a,b)=>b[1]-a[1]).slice(0,6); if(seqs.length===0)return
Nessuna sequenza ripetuta ancora
; return seqs.map(([seq,cnt])=>{ const [z1,z2]=seq.split('→'); return
{FZN[z1]||z1} → {FZN[z2]||z2}
{cnt}x
; }); })()}
)} {activeTab==='log'&&(
{events.length} eventi
{[...events].reverse().map(e=>{ const isCtr=e.type==='counter_goal'; const isCtrMiss=e.type==='counter_miss'; const cls=e.type==='goal'?'goal':e.type==='save'?'save':e.type==='miss'?'miss':isCtr?'ctr':isCtrMiss?'ctrmiss':e.odType==='off'?'off':'def'; const lbl=e.type==='goal'?'GOAL':e.type==='save'?'PAR':e.type==='miss'?'ERR':isCtr?'⚡ CTR.GOL':isCtrMiss?'✗⚡ CTR.ERR':e.action?.toUpperCase()||'—'; const rowCls=`erow${isCtr?' ctr-row':isCtrMiss?' ctrmiss-row':''}`; const teamLabel=e.team==='a'?(team?.name?.slice(0,6)||'A'):(match.opponent?.slice(0,6)||'B'); return
{e.time} {teamLabel} {lbl} {e.sup2min&&SUP} {e.inf2min&&INF} {e.fieldZone&&📍{FZN[e.fieldZone]||e.fieldZone}} {e.goalZone&&🥅{e.goalZone}} {e.player||'—'}
; })} {events.length===0&&
Nessun evento
}
)} {/* ─── EDIT EVENT MODAL (guided full re-flow) ─── */} {editEvModal&&( {const pl=editEvModal.team==='a'?playersA:playersB;return editEvModal.type==='save'?pl.filter(p=>p.position==='Portiere'):pl;})()} teamName={editEvModal.team==='a'?team?.name:match.opponent} onSave={(changes)=>{ // Aggiorna score se il tipo è cambiato const oldIsGoal=editEvModal.type==='goal'||editEvModal.type==='counter_goal'; const newIsGoal=changes.type==='goal'||changes.type==='counter_goal'; if(oldIsGoal&&!newIsGoal){if(editEvModal.team==='a')setScoreA(s=>Math.max(0,s-1));else setScoreB(s=>Math.max(0,s-1));} else if(!oldIsGoal&&newIsGoal){if(editEvModal.team==='a')setScoreA(s=>s+1);else setScoreB(s=>s+1);} updateEv(editEvModal.id,changes); setEditEvModal(null); }} onDelete={()=>{deleteEv(editEvModal.id);setEditEvModal(null);}} onCancel={()=>setEditEvModal(null)} /> )}
{/* ─ SIDEBAR B (narrow: players + score only) ─ */}
{match.opponent?.slice(0,12)||'Avversario'}
{groupByRole(playersB).map(g=>(
{ROLE_SHORT[g.role]||g.role}
{g.players.map(p=>{ const pid=`#${p.number} ${p.name}`; const pg=events.filter(e=>e.player===pid&&e.type==='goal').length; const ps_=events.filter(e=>e.player===pid&&e.type==='save').length; const isGK=p.position==='Portiere'; return(
setPlayerPopup({player:p,team:'b'})} style={{minWidth:'30px',minHeight:'36px',display:'flex',alignItems:'center',justifyContent:'center',position:'relative',borderColor:isGK?'rgba(45,212,191,.35)':'var(--border)',background:isGK?'rgba(45,212,191,.06)':'var(--bg3)'}}>
{p.number}
{(pg>0||ps_>0)&&
{isGK?ps_:pg}
}
); })}
))}
{/* Quick actions B */}
{match.opponent?.slice(0,10)||'Avv.'}
{[ {l:`GOAL`,c:'goal',t:'goal',i:'⚽'}, {l:`CTR.GOL`,c:'ctr',t:'counter_goal',i:'⚡'}, {l:`ERRORE`,c:'miss',t:'miss',i:'✗'}, {l:`PARATA`,c:'save',t:'save',i:'🧤'}, ].map(b=>( ))}
{/* Score tracker */}
{scoreA}—{scoreB}
1°T: {events.filter(e=>e.period===1&&e.type==='goal'&&e.team==='a').length}—{events.filter(e=>e.period===1&&e.type==='goal'&&e.team==='b').length}
2°T: {events.filter(e=>e.period===2&&e.type==='goal'&&e.team==='a').length}—{events.filter(e=>e.period===2&&e.type==='goal'&&e.team==='b').length}
); } /* ── TEAM VIEW ── */ function TeamView({team:teamProp,allTeams,onBack,onMatchSelect,onTeamUpdate,onDeleteTeam}){ const {ask:confirm,modal:confirmModal}=useConfirm(); const [team,setTeam]=useState(teamProp); useEffect(()=>setTeam(teamProp),[teamProp]); const commit=updated=>{setTeam(updated);onTeamUpdate(updated);}; const [showAddP,setShowAddP]=useState(false); const [addTab,setAddTab]=useState('nuovo'); // 'nuovo' | 'esistente' const [searchQ,setSearchQ]=useState(''); const [searchCat,setSearchCat]=useState(''); const [showCrM,setShowCrM]=useState(false); const [np,setNp]=useState({name:'',number:'',position:''}); const [nm,setNm]=useState({opponent:'',date:new Date().toLocaleDateString('it-IT')}); const [editPlayer,setEditPlayer]=useState(null); const [showEditTeam,setShowEditTeam]=useState(false); const [editTeamData,setEditTeamData]=useState({name:teamProp.name,category:teamProp.category||'',season:teamProp.season||''}); const saveEditTeam=()=>{const u={...team,...editTeamData};commit(u);setShowEditTeam(false);}; const deleteTeam=()=>{confirm(`Eliminare definitivamente la squadra "${team.name}"? Tutti i dati saranno persi.`,()=>onDeleteTeam(team.id),{okLabel:'Elimina Squadra'});}; const addP=()=>{if(!np.name||!np.number)return;commit({...team,players:[...(team.players||[]),{...np,id:uid()}]});setNp({name:'',number:'',position:''});setShowAddP(false);}; const deleteP=(pid)=>{confirm('Eliminare questo giocatore?',()=>commit({...team,players:(team.players||[]).filter(p=>p.id!==pid)}));}; const saveEditP=()=>{if(!editPlayer)return;commit({...team,players:(team.players||[]).map(p=>p.id===editPlayer.id?editPlayer:p)});setEditPlayer(null);}; const deleteMatch=(mid)=>{confirm('Eliminare questa partita? I dati non saranno recuperabili.',()=>commit({...team,matches:(team.matches||[]).filter(m=>m.id!==mid)}),{okLabel:'Elimina Partita'});}; const [editMatch,setEditMatch]=useState(null); const saveEditMatch=()=>{if(!editMatch)return;commit({...team,matches:(team.matches||[]).map(m=>m.id===editMatch.id?editMatch:m)});setEditMatch(null);}; const crM=()=>{if(!nm.opponent)return;const m={id:uid(),opponent:nm.opponent,date:nm.date,scoreA:0,scoreB:0,events:[],ended:false};const u={...team,matches:[...(team.matches||[]),m]};commit(u);setShowCrM(false);onMatchSelect(m,u);}; // Copia giocatore da un'altra squadra (nuovo id, numero personalizzabile) const importPlayer=(srcP)=>{ const alreadyIn=(team.players||[]).some(p=>p.name===srcP.name); const doImport=()=>{commit({...team,players:[...(team.players||[]),{...srcP,id:uid()}]});setShowAddP(false);}; if(alreadyIn){confirm(`"${srcP.name}" è già in rosa. Aggiungere comunque?`,doImport,{okLabel:'Aggiungi',okColor:'var(--acc)'});} else doImport(); }; // Tutti i giocatori di tutte le altre squadre (deduplicati per nome) const allOtherPlayers=React.useMemo(()=>{ const seen=new Set(); const result=[]; (allTeams||[]).forEach(t=>{ if(t.id===team.id)return; (t.players||[]).forEach(p=>{ const key=p.name.toLowerCase().trim(); if(!seen.has(key)){seen.add(key);result.push({...p,_fromTeam:t.name,_fromCat:t.category||''});} }); }); return result.sort((a,b)=>a.name.localeCompare(b.name)); },[allTeams,team.id]); const allCategories=[...new Set(allOtherPlayers.map(p=>p._fromCat).filter(Boolean))]; const filteredPlayers=allOtherPlayers.filter(p=>{ const matchQ=!searchQ||p.name.toLowerCase().includes(searchQ.toLowerCase())||p.number.toString().includes(searchQ); const matchCat=!searchCat||p._fromCat===searchCat; return matchQ&&matchCat; }); const ms=team.matches||[];const ps=team.players||[]; // Stats squadra const avgG=ms.length?(ms.reduce((s,m)=>s+(m.scoreA||0),0)/ms.length).toFixed(1):'—'; const avgS=ms.length?(ms.reduce((s,m)=>s+(m.events||[]).filter(e=>e.type==='save'&&e.team==='a').length,0)/ms.length).toFixed(1):'—'; const avgE=ms.length?(ms.reduce((s,m)=>{const g=(m.events||[]).filter(e=>e.type==='goal'&&e.team==='a').length;const cg=(m.events||[]).filter(e=>e.type==='counter_goal'&&e.team==='a').length;const p7=(m.events||[]).filter(e=>e.type==='7m_goal'&&e.team==='a').length;const cm=(m.events||[]).filter(e=>e.type==='counter_miss'&&e.team==='a').length;const p7m=(m.events||[]).filter(e=>e.type==='7m_miss'&&e.team==='a').length;const pp=(m.events||[]).filter(e=>e.team==='a'&&e.odType==='off').length;const mi=(m.events||[]).filter(e=>e.type==='miss'&&e.team==='a').length;const t=g+cg+p7+mi+cm+p7m+pp;return s+(t>0?pct(g+cg+p7,t):0);},0)/ms.length).toFixed(1)+'%':'—'; // Media efficienza portieri: media di (parate/tiriRicevuti) per partita const avgGkEff=ms.length?(()=>{ const vals=ms.map(m=>{ const evs=m.events||[]; const sv=evs.filter(e=>e.type==='save'&&e.team==='a').length; const gb=evs.filter(e=>e.type==='goal'&&e.team==='b').length; const total=sv+gb; return total>0?pct(sv,total):null; }).filter(v=>v!==null); return vals.length?(vals.reduce((a,b)=>a+b,0)/vals.length).toFixed(1)+'%':'—'; })():'—'; // Statistiche personali per ogni giocatore aggregate su tutte le partite const playerStats=React.useMemo(()=>{ const allEvs=ms.flatMap(m=>m.events||[]); return ps.map(p=>{ const pid=`#${p.number} ${p.name}`; const goals=allEvs.filter(e=>e.player===pid&&e.type==='goal').length; const saves=allEvs.filter(e=>e.player===pid&&e.type==='save').length; const miss=allEvs.filter(e=>e.player===pid&&e.type==='miss').length; const ctrG=allEvs.filter(e=>e.player===pid&&e.type==='counter_goal').length; const ctrM=allEvs.filter(e=>e.player===pid&&e.type==='counter_miss').length; const pen7G=allEvs.filter(e=>e.player===pid&&e.type==='7m_goal').length; const pen7M=allEvs.filter(e=>e.player===pid&&e.type==='7m_miss').length; const pp=allEvs.filter(e=>e.player===pid&&e.odType==='off'&&e.action!=='2min').length; const twoMin=allEvs.filter(e=>e.player===pid&&e.action==='2min').length; const shots=goals+miss+pp+ctrG+ctrM+pen7G+pen7M; const eff=shots>0?pct(goals+ctrG+pen7G,shots):null; // portiere: parate personali + gol subiti su tiri in porta (save auto non hanno player, contiamo quelli con player) const conceded=allEvs.filter(e=>e.player===pid&&e.type==='goal'&&e.team==='b').length; const gkTotal=saves+conceded; const gkEff=gkTotal>0?pct(saves,gkTotal):null; return{...p,goals,saves,miss,pp,twoMin,eff,gkEff,gkTotal}; }); },[ms,ps]); const getTrend=(m,i)=>{if(i===0)return'e';const p=ms[i-1];const ce=pct(m.scoreA||0,(m.scoreA||0)+(m.scoreB||0)||1);const pe=pct(p.scoreA||0,(p.scoreA||0)+(p.scoreB||0)||1);return ce>pe?'u':ce {confirmModal}
H
{team.name}
{team.category||''}{team.season?` · ${team.season}`:''}
← Home
{/* STAT CARDS squadra */}
{[ {l:'Media Gol',v:avgG,c:'var(--acc)'}, {l:'Media Parate',v:avgS,c:'var(--green)'}, {l:'Eff. Portiere',v:avgGkEff,c:'var(--teal)'}, {l:'Media Eff.',v:avgE,c:'var(--yellow)'}, {l:'Partite',v:ms.length||'—',c:'var(--text)'}, ].map(s=>(
{s.l}
{s.v}
))}
{/* TAB BAR */}
{[ {id:'partite',l:`🏐 Partite (${ms.length})`}, {id:'rosa',l:`👥 Rosa (${ps.length})`}, ].map(t=>( ))}
{/* ══ TAB PARTITE ══ */} {tvTab==='partite'&&(
{ms.length===0&&(
🏐
Nessuna partita ancora
Crea la prima partita per iniziare a registrare gli eventi.
)}
{[...ms].sort((a,b)=>{ // Parse Italian date dd/mm/yyyy or fallback to insertion order const parseDate=s=>{if(!s)return 0;const p=s.split('/');if(p.length===3)return new Date(+p[2],+p[1]-1,+p[0]).getTime();return new Date(s).getTime()||0;}; return parseDate(b.date)-parseDate(a.date); }).map((m)=>{ const i=ms.findIndex(x=>x.id===m.id); const tr=getTrend(m,i); const evs=m.events||[]; const sa=evs.filter(e=>e.type==='save'&&e.team==='a').length; const gb=evs.filter(e=>e.type==='goal'&&e.team==='b').length; const gkT=sa+gb; const gkE=gkT>0?pct(sa,gkT):null; const pp=evs.filter(e=>e.team==='a'&&e.odType==='off').length; const mi=evs.filter(e=>e.type==='miss'&&e.team==='a').length; const ea=pct(m.scoreA||0,(m.scoreA||0)+mi+pp||1); const won=(m.scoreA||0)>(m.scoreB||0); const lost=(m.scoreA||0)<(m.scoreB||0); return(
{/* Riga principale cliccabile */}
onMatchSelect(m,team)} style={{cursor:'pointer',padding:'10px 12px',display:'grid',gridTemplateColumns:'80px 1fr auto',gap:'8px',alignItems:'center'}}>
{m.date}
{won?'VINTA':lost?'PERSA':'PARI'}
{m.opponent}
Eff.Off: {ea}% {gkE!==null&&<> · Eff.POR: {gkE}%} {' '}· Parate: {sa}
{m.scoreA||0} {m.scoreB||0}
1°T {evs.filter(e=>e.period===1&&e.type==='goal'&&e.team==='a').length}—{evs.filter(e=>e.period===1&&e.type==='goal'&&e.team==='b').length} {' '}2°T {evs.filter(e=>e.period===2&&e.type==='goal'&&e.team==='a').length}—{evs.filter(e=>e.period===2&&e.type==='goal'&&e.team==='b').length}
{tr==='u'?'▲ Migliorato rispetto alla precedente':tr==='d'?'▼ Peggiorato rispetto alla precedente':'= Stabile rispetto alla precedente'}
); })}
)} {/* ══ TAB ROSA ══ */} {tvTab==='rosa'&&(
{ps.length===0&&(
👥
Rosa vuota
Aggiungi i giocatori per vedere le statistiche personali.
)}
{groupByRole(playerStats).map(g=>(
{ROLE_SHORT[g.role]||g.role} {g.players.length}
{g.players.map(p=>{ const isGK=p.position==='Portiere'; return(
{p.number}
{p.name}
{isGK?( <> {p.saves>0&&{p.saves} par} {p.gkEff!==null&&{p.gkEff}%} {p.twoMin>0&&{p.twoMin} 2m} ):( <> {p.goals>0&&{p.goals} gol} {p.eff!==null&&(p.goals>0||p.miss>0)&&{p.eff}%} {p.twoMin>0&&{p.twoMin} 2m} )}
); })}
))}
)}
{/* ── MODAL AGGIUNGI GIOCATORE (Nuovo / Esistente) ── */} {showAddP&&(
👤 Aggiungi Giocatore
{/* Tab selector */}
{[{id:'nuovo',l:'✚ Nuovo'},{id:'esistente',l:'🔍 Già esistente'}].map(t=>( ))}
{/* TAB NUOVO */} {addTab==='nuovo'&&(
setNp(p=>({...p,number:e.target.value}))}/>
setNp(p=>({...p,name:e.target.value}))}/>
)} {/* TAB ESISTENTE */} {addTab==='esistente'&&(
{allOtherPlayers.length===0 ?
Nessun giocatore disponibile nelle altre squadre.
:(
{/* Filtri */}
setSearchQ(e.target.value)} style={{flex:1}}/> {allCategories.length>0&&( )}
{/* Lista giocatori */}
{filteredPlayers.length===0&&
Nessun risultato.
} {filteredPlayers.map((p,i)=>(
#{p.number}
{p.name}
{p.position||'—'} · {p._fromTeam}{p._fromCat?` · ${p._fromCat}`:''}
))}
Il giocatore verrà copiato nella rosa. Le statistiche rimangono separate per squadra.
) }
)}
)} {showCrM&&
Nuova Partita
setNm(m=>({...m,opponent:e.target.value}))}/>
setNm(m=>({...m,date:e.target.value}))}/>
{ps.length===0&&
Nessun giocatore.
}{ps.map(p=>
#{p.number}{p.name}{p.position}
)}
} {editMatch&&
✏️ Modifica Partita
setEditMatch(m=>({...m,opponent:e.target.value}))}/>
setEditMatch(m=>({...m,date:e.target.value}))}/>
} {editPlayer&&
✏️ Modifica Giocatore (solo in questa squadra)
setEditPlayer(p=>({...p,number:e.target.value}))}/>
setEditPlayer(p=>({...p,name:e.target.value}))}/>
} {showEditTeam&&
✏️ Modifica Squadra
setEditTeamData(d=>({...d,name:e.target.value}))}/>
setEditTeamData(d=>({...d,category:e.target.value}))}/>
setEditTeamData(d=>({...d,season:e.target.value}))}/>
}
); } /* ── HOME ── */ function Home({teams,onTeamSelect,onAddTeam}){ const [showM,setShowM]=useState(false); const [nt,setNt]=useState({name:'',category:'',season:`${new Date().getFullYear()}/${new Date().getFullYear()+1}`}); const crT=()=>{if(!nt.name)return;onAddTeam({id:uid(),...nt,players:[],matches:[]});setShowM(false);setNt({name:'',category:'',season:`${new Date().getFullYear()}/${new Date().getFullYear()+1}`});}; return(
H
HANDBALL TAS
STATISTICS PRO v10
Le Mie Squadre
{teams.map(t=>
onTeamSelect(t)}>
{t.name}
{t.category||'—'}
👥 {(t.players||[]).length}🏐 {(t.matches||[]).length} partite
{t.season||''}
)}
setShowM(true)}>
Aggiungi Squadra
{teams.length===0&&
🤾
Benvenuto in HandballTAS Pro
Gestisci squadre, registra partite con zone precise e analizza le statistiche.
}
{showM&&
Nuova Squadra
setNt(t=>({...t,name:e.target.value}))}/>
setNt(t=>({...t,category:e.target.value}))}/>
setNt(t=>({...t,season:e.target.value}))}/>
}
); } /* ── ROOT ── */ function App(){ const [teams,setTeams]=useState(()=>load().teams||[]); const [view,setView]=useState('home'); const [selTeam,setSelTeam]=useState(null); const [activeMatch,setActiveMatch]=useState(null); useEffect(()=>{persist({teams});},[teams]); const upd=u=>{setTeams(ts=>ts.map(t=>t.id===u.id?u:t));setSelTeam(u);}; const addT=t=>setTeams(ts=>[...ts,t]); const delT=tid=>{setTeams(ts=>ts.filter(t=>t.id!==tid));setView('home');}; const selT=t=>{setSelTeam(t);setView('team');}; const selM=(m,t)=>{setActiveMatch(m);setSelTeam(t);setView('match');}; const saveM=um=>{const ut={...selTeam,matches:(selTeam.matches||[]).map(m=>m.id===um.id?um:m)};upd(ut);setActiveMatch(um);}; if(view==='match'&&activeMatch&&selTeam) returnsetView('team')} onSave={saveM}/>; if(view==='team'&&selTeam) returnsetView('home')} onMatchSelect={selM} onTeamUpdate={upd} onDeleteTeam={delT}/>; returnsetTeams(ts)}/>; } ReactDOM.createRoot(document.getElementById('root')).render();