Squadra e Profilo: ace, attacchi, muri e punti dai + dello scout
Squadra → Stats: punti, ace e muri di squadra sommano i + delle valutazioni scout di tutte le partite. Profilo → Season: al posto di «Turni palloni» compaiono Attacchi, Muri, Ace e Punti (totale) del giocatore. Le partite del metodo precedente contano con le azioni. DD-008 aggiornata: dati solo informativi, fuori da badge e classifiche. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
263621456d
commit
1e4ec9113e
8 files changed
+130
-4
No files matched your search
@@ -15,6 +15,11 @@ versioni `0.y.z` erano pre-release.
|
||||
|
||||
### Modificato
|
||||
|
||||
- Squadra → Stats: «Punti squadra», «Ace squadra» e «Muri squadra» sommano i + dello scout di
|
||||
tutte le partite (ace = + di battuta, muri = + di muro, punti = + di attacco, battuta e muro);
|
||||
le partite del metodo precedente contano con le loro azioni.
|
||||
- Profilo → Season: al posto di «Turni palloni» compaiono Ace, Attacchi, Muri e Punti (totale)
|
||||
del giocatore, somma dei + dello scout di tutte le partite (DD-008: solo informativi).
|
||||
- Layout su schermi stretti (360-402 px, iPhone e Android): niente più pagina tagliata in
|
||||
larghezza. La tabella per giocatore dello scout sta nello schermo (il nome si accorcia), le
|
||||
griglie di riquadri non sfondano con nomi ed etichette lunghe, la pagina non scorre in
|
||||
|
||||
@@ -17,6 +17,9 @@ Le statistiche **personali** in profilo e squadra devono essere **eque per tutti
|
||||
|
||||
- Badge e obiettivi usano presenze, MVP, pagelle, serie, cacche — metriche accessibili a tutti.
|
||||
- Lo scout resta strumento tecnico, non gioco.
|
||||
- Aggiornamento 2026-11-11: in Profilo → Season compaiono, come semplice informazione personale,
|
||||
i totali di ace, attacchi, muri e punti del giocatore ricavati dai + dello scout. Non entrano
|
||||
in badge, obiettivi o classifiche: la regola sulla leva competitiva resta valida.
|
||||
|
||||
**Riesame**
|
||||
Se la squadra chiede esplicitamente classifiche tecniche per ruolo.
|
||||
@@ -285,8 +285,16 @@ Sezione già presente. Contiene.
|
||||
- Voto medio
|
||||
- MVP
|
||||
- Serie
|
||||
- **Ace, Attacchi, Muri e Punti (totale)**: somma, su tutte le partite scoutate, dei soli **+**
|
||||
assegnati a quel giocatore nello scout live (ace = + di battuta, attacchi = + di attacco,
|
||||
muri = + di muro; punti = ace + attacchi + muri, la ricezione non conta). Le partite scoutate
|
||||
col metodo precedente, senza valutazioni, contano con le azioni registrate; se una partita ha
|
||||
entrambe, valgono solo le valutazioni (`totaliTecnici()` in `scout-store.ts`). Il «Turni
|
||||
palloni» non compare più qui: il turno resta nel banner palloni e nel badge dedicato.
|
||||
- Altre statistiche disponibili
|
||||
|
||||
Sono numeri informativi: non alimentano badge, obiettivi né classifiche (DD-008).
|
||||
|
||||
### Badge
|
||||
|
||||
Sezione già presente.
|
||||
|
||||
@@ -82,6 +82,13 @@ successivo non salta — 1, 1, 2, non 1, 1, 3); `dettaglioClassifica()` sceglie
|
||||
sottostatistica mostrare sotto il nome, coerente col criterio selezionato (es. "voti
|
||||
pagella" per il criterio media voto, non sempre "presenze consecutive").
|
||||
|
||||
In cima alla tab, i riquadri «Punti squadra», «Ace squadra» e «Muri squadra» sommano **tutte le
|
||||
partite scoutate** (`totaliSquadraValutazioni()`, che usa `totaliTecnici()`, in `scout-store.ts`), contando solo le valutazioni
|
||||
**+** dello scout live: «Ace» = + di battuta, «Muri» = + di muro, «Punti» = + di attacco, battuta e
|
||||
muro (la ricezione non è un punto messo a terra, come nel riquadro del giocatore). Le partite
|
||||
scoutate col metodo precedente, senza valutazioni, contano con le loro azioni registrate (punti,
|
||||
ace, muri); se una partita ha entrambe, valgono solo le valutazioni, così non si conta due volte.
|
||||
|
||||
Le altre tab di `/squadra` (Rosa, Obiettivi, Badge) sono viste diverse sugli stessi dati di
|
||||
`useRosa()`/`useObiettivi()`/`badges.ts`: non introducono altra logica di dominio, solo
|
||||
presentazione — le rispettive specifiche stanno in `badge.md` e `obiettivi-squadra.md`.
|
||||
|
||||
@@ -291,6 +291,46 @@ export function totaliSquadra(matches: ScoutMatch[]) {
|
||||
return out;
|
||||
}
|
||||
|
||||
export type TotaliTecnici = { punti: number; ace: number; attacchi: number; muri: number };
|
||||
|
||||
/**
|
||||
* Somma, su tutte le partite scoutate, delle sole valutazioni **+** (battuta → ace, attacco →
|
||||
* attacchi, muro → muri; punti = ace + attacchi + muri, non la ricezione). Con `giocatoreId`
|
||||
* conta solo quel giocatore, altrimenti tutta la squadra. Una partita senza valutazioni (metodo
|
||||
* precedente) conta con le sue azioni; se ha entrambe valgono solo le valutazioni, per non
|
||||
* contare due volte.
|
||||
*/
|
||||
export function totaliTecnici(matches: ScoutMatch[], giocatoreId?: string): TotaliTecnici {
|
||||
const out = { punti: 0, ace: 0, attacchi: 0, muri: 0 };
|
||||
for (const m of matches) {
|
||||
if (m.valutazioni.length === 0) {
|
||||
for (const [id, t] of totaliPerGiocatore(m.azioni)) {
|
||||
if (giocatoreId !== undefined && id !== giocatoreId) continue;
|
||||
out.ace += t.ace;
|
||||
out.muri += t.muri;
|
||||
out.attacchi += t.punti - t.ace - t.muri;
|
||||
out.punti += t.punti;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (const v of m.valutazioni) {
|
||||
if (v.segno !== "+" || v.fondamentale === "ricezione") continue;
|
||||
if (giocatoreId !== undefined && v.giocatoreId !== giocatoreId) continue;
|
||||
if (v.fondamentale === "battuta") out.ace += 1;
|
||||
if (v.fondamentale === "attacco") out.attacchi += 1;
|
||||
if (v.fondamentale === "muro") out.muri += 1;
|
||||
out.punti += 1;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Punti, ace e muri di squadra per la tab Stats di `/squadra` (vedi `totaliTecnici`). */
|
||||
export function totaliSquadraValutazioni(matches: ScoutMatch[]) {
|
||||
const { punti, ace, muri } = totaliTecnici(matches);
|
||||
return { punti, ace, muri };
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP ricavati dalle partite scoutate (mappa matchId → nome vincitore).
|
||||
* Le presenze personali restano su `risposte_presenze`, non sullo scout.
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
type GiocatoreSquadra,
|
||||
} from "@/lib/giocatori-squadra";
|
||||
import { esci } from "@/lib/auth";
|
||||
import { totaliTecnici, useScoutMatches } from "@/lib/scout-store";
|
||||
import { useIsAdmin } from "@/lib/ruoli";
|
||||
import { useEmailNotifiche } from "@/lib/preferenze-utente";
|
||||
import { Reveal } from "@/components/motion/Reveal";
|
||||
@@ -80,9 +81,12 @@ function ProfiloGiocatore() {
|
||||
const votiSocial = useVotiSocial();
|
||||
const g = useIo();
|
||||
const ultimoMese = usePresenzeUltimoMese(g?.id);
|
||||
const scoutMatches = useScoutMatches();
|
||||
|
||||
if (!g) return null;
|
||||
|
||||
const tecnici = totaliTecnici(scoutMatches, g.id);
|
||||
|
||||
const percPresenze = g.totaliEventi ? Math.round((g.presenze / g.totaliEventi) * 100) : 0;
|
||||
|
||||
return (
|
||||
@@ -122,9 +126,12 @@ function ProfiloGiocatore() {
|
||||
}
|
||||
label="Presenze di fila"
|
||||
/>
|
||||
<StatTile valore={g.mediaVoto || "—"} label="Media voto" />
|
||||
<StatTile valore={tecnici.attacchi} label="Attacchi" />
|
||||
<StatTile valore={tecnici.muri} label="Muri" />
|
||||
<StatTile valore={tecnici.ace} label="Ace" />
|
||||
<StatTile valore={tecnici.punti} label="Punti" hint="totale" />
|
||||
<StatTile valore={g.mvp} label="MVP" />
|
||||
<StatTile valore={g.palloni} label="Turni palloni" />
|
||||
<StatTile valore={g.mediaVoto || "—"} label="Media voto" />
|
||||
</div>
|
||||
<SerieGriglia g={g} />
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
useGiocatoriSquadra,
|
||||
} from "@/lib/giocatori-squadra";
|
||||
import { useSonoAllenatore } from "@/lib/user-store";
|
||||
import { totaliSquadra, useScoutMatches } from "@/lib/scout-store";
|
||||
import { totaliSquadraValutazioni, useScoutMatches } from "@/lib/scout-store";
|
||||
import { useCsi } from "@/lib/csi";
|
||||
import { partiteGiocate } from "@/lib/csi-core";
|
||||
import { mediaSquadra, usePagelle } from "@/lib/pagelle";
|
||||
@@ -110,7 +110,7 @@ function Squadra() {
|
||||
const [criterio, setCriterio] = useState<Criterio>("presenze");
|
||||
const [filtroAperto, setFiltroAperto] = useState(false);
|
||||
const obiettivi = useObiettivi();
|
||||
const team = totaliSquadra(scoutMatches);
|
||||
const team = totaliSquadraValutazioni(scoutMatches);
|
||||
const mediaPresenze = rosa.length
|
||||
? Math.round(
|
||||
(rosa.reduce((s, g) => s + g.presenze / (g.totaliEventi || 1), 0) / rosa.length) * 100,
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
giocatoriConScout,
|
||||
totaliPerGiocatore,
|
||||
totaliSquadra,
|
||||
totaliSquadraValutazioni,
|
||||
totaliTecnici,
|
||||
type Azione,
|
||||
type Valutazione,
|
||||
type ScoutMatch,
|
||||
@@ -67,6 +69,60 @@ assert.deepEqual(totaliSquadra([match("m1", 3, 0, azioni), match("m2", 3, 1, azi
|
||||
});
|
||||
assert.deepEqual(totaliSquadra([]), { punti: 0, ace: 0, muri: 0, errori: 0 });
|
||||
|
||||
// --- totaliSquadraValutazioni: solo i + dello scout, fallback sulle azioni ------
|
||||
const vl = (fondamentale: Valutazione["fondamentale"], segno: "+" | "-", giocatoreId = "g1") =>
|
||||
({
|
||||
id: Math.random().toString(),
|
||||
giocatoreId,
|
||||
fondamentale,
|
||||
segno,
|
||||
set: 1,
|
||||
ts: 0,
|
||||
}) as Valutazione;
|
||||
const conVal = {
|
||||
...match("v1", 3, 0),
|
||||
valutazioni: [
|
||||
vl("battuta", "+"),
|
||||
vl("battuta", "+", "g2"),
|
||||
vl("battuta", "-"),
|
||||
vl("muro", "+"),
|
||||
vl("muro", "-"),
|
||||
vl("attacco", "+"),
|
||||
vl("attacco", "+", "g2"),
|
||||
vl("attacco", "-"),
|
||||
vl("ricezione", "+"),
|
||||
vl("ricezione", "+"),
|
||||
],
|
||||
};
|
||||
assert.deepEqual(totaliSquadraValutazioni([conVal]), { punti: 5, ace: 2, muri: 1 });
|
||||
assert.deepEqual(
|
||||
totaliSquadraValutazioni([conVal, conVal]),
|
||||
{ punti: 10, ace: 4, muri: 2 },
|
||||
"somma su più partite",
|
||||
);
|
||||
assert.deepEqual(
|
||||
totaliSquadraValutazioni([match("a1", 3, 0, azioni)]),
|
||||
{ punti: 4, ace: 1, muri: 1 },
|
||||
"senza valutazioni: contano le azioni del metodo precedente",
|
||||
);
|
||||
assert.deepEqual(
|
||||
totaliSquadraValutazioni([{ ...conVal, azioni }]),
|
||||
{ punti: 5, ace: 2, muri: 1 },
|
||||
"con entrambe valgono solo le valutazioni",
|
||||
);
|
||||
assert.deepEqual(totaliSquadraValutazioni([]), { punti: 0, ace: 0, muri: 0 });
|
||||
|
||||
// --- totaliTecnici: per giocatore, con attacchi -------------------------------
|
||||
assert.deepEqual(totaliTecnici([conVal], "g1"), { punti: 3, ace: 1, attacchi: 1, muri: 1 });
|
||||
assert.deepEqual(totaliTecnici([conVal], "g2"), { punti: 2, ace: 1, attacchi: 1, muri: 0 });
|
||||
assert.deepEqual(totaliTecnici([conVal], "assente"), { punti: 0, ace: 0, attacchi: 0, muri: 0 });
|
||||
assert.deepEqual(totaliTecnici([conVal, conVal]), { punti: 10, ace: 4, attacchi: 4, muri: 2 });
|
||||
assert.deepEqual(
|
||||
totaliTecnici([match("a1", 3, 0, azioni)], "g1"),
|
||||
{ punti: 3, ace: 1, attacchi: 1, muri: 1 },
|
||||
"metodo precedente: ace, muro e attacco dalle azioni",
|
||||
);
|
||||
|
||||
// --- giocatoriConScout: solo MVP da scout, presenze separate -----------------
|
||||
const base = giocatori.find((g) => g.id === "g1")!;
|
||||
const conScout = giocatoriConScout([match("m1", 3, 0, azioni)], { m1: base.nome });
|
||||
|
||||
Reference in new issue
Block a user