Scout: risultati leggibili nella pagina partita
La sezione «Scout» mostra le valutazioni − / + nell'app, non solo nel CSV: percentuale di + e conteggi per fondamentale, selettore Totale / Set 1-5, tabella per giocatore e guida (i) su come leggere i dati. Le azioni (punti, ace, muri, errori) restano in un blocco a parte per le partite scoutate col metodo precedente. Il CSV resta per gli admin. Aggiunge sintesiValutazioni() in scout-store con test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
91ac7ce65c
commit
30fb8e23b6
6 files changed
+348
-64
No files matched your search
@@ -13,6 +13,11 @@ versioni `0.y.z` erano pre-release.
|
|||||||
|
|
||||||
## [Non rilasciato]
|
## [Non rilasciato]
|
||||||
|
|
||||||
|
### Aggiunto
|
||||||
|
|
||||||
|
- Pagina partita, sezione «Scout»: i risultati dello scout si leggono nell'app (percentuale di +
|
||||||
|
e conteggi per fondamentale, per partita o per set, e tabella per giocatore), non solo nel CSV.
|
||||||
|
|
||||||
### Modificato
|
### Modificato
|
||||||
|
|
||||||
- Sondaggio cacche: la classifica completa è visibile a tutti i giocatori, anche a sondaggio
|
- Sondaggio cacche: la classifica completa è visibile a tutti i giocatori, anche a sondaggio
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ pagina partita, sezione "Scout".
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Lettura nell'app
|
||||||
|
|
||||||
|
La sezione «Scout» della pagina partita (`ScoutReport.tsx`, visibile a chi vede la pagina; il CSV
|
||||||
|
resta per i soli admin) mostra, per la partita o per un singolo set (selettore in alto, se i set
|
||||||
|
sono più di uno): per ciascun fondamentale la percentuale di **+**, una barra verde/rossa e i
|
||||||
|
conteggi di squadra; poi la tabella per giocatore con **+ / −** di ogni fondamentale. I totali
|
||||||
|
vengono da `sintesiValutazioni()` (`scout-store.ts`). Le azioni (P/A/M/E) compaiono in un blocco a
|
||||||
|
parte solo se la partita ne ha, cioè quelle scoutate prima di DD-053. Il pulsante (icona i) apre una guida in linea su valutazioni, percentuale, tabella e set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Sondaggio cacche
|
## Sondaggio cacche
|
||||||
|
|
||||||
`SondaggioCacche.tsx` chiede "quante cacche hai fatto prima di questa partita" (0-5+), sempre
|
`SondaggioCacche.tsx` chiede "quante cacche hai fatto prima di questa partita" (0-5+), sempre
|
||||||
@@ -153,11 +164,8 @@ calcolati a runtime).
|
|||||||
|
|
||||||
- Lo stato in corso è raggiungibile solo il giorno della partita: se nessuno preme «Fine partita»
|
- Lo stato in corso è raggiungibile solo il giorno della partita: se nessuno preme «Fine partita»
|
||||||
entro la mezzanotte i dati restano in `scout_live` ma dall'app non si riprendono più.
|
entro la mezzanotte i dati restano in `scout_live` ma dall'app non si riprendono più.
|
||||||
- Le valutazioni dei set chiusi e delle partite finite sono salvate ma non si rivedono nell'app: lo
|
- Lo scout in corso mostra solo il set in corso: le valutazioni dei set chiusi si rivedono solo
|
||||||
scout mostra solo il set in corso e l'unica lettura è il CSV admin. Evoluzione: tabella nella
|
nella pagina partita, dopo «Fine partita».
|
||||||
pagina partita.
|
|
||||||
- Il blocco «Scout» della pagina partita mostra ancora Punti/Ace/Muri/Errori (a zero per le
|
|
||||||
partite di sole valutazioni) e non le valutazioni, che sono solo nel CSV admin.
|
|
||||||
- Scout aperto a tutta la squadra: nessun filtro su chi può registrare le azioni, l'unica
|
- Scout aperto a tutta la squadra: nessun filtro su chi può registrare le azioni, l'unica
|
||||||
garanzia è il lock di sessione (vedi sopra).
|
garanzia è il lock di sessione (vedi sopra).
|
||||||
- Possibile, per quanto improbabile, doppio "successo" applicativo nel prendere il lock:
|
- Possibile, per quanto improbabile, doppio "successo" applicativo nel prendere il lock:
|
||||||
@@ -171,6 +179,5 @@ calcolati a runtime).
|
|||||||
|
|
||||||
## Evoluzioni possibili
|
## Evoluzioni possibili
|
||||||
|
|
||||||
- Mostrare le valutazioni per set e giocatore nella pagina della partita (oggi solo nel CSV admin)
|
- Nello scout, in sola lettura, le valutazioni dei set chiusi.
|
||||||
e, nello scout, in sola lettura quelle dei set chiusi.
|
|
||||||
- Realtime (Supabase Realtime) per aggiornare la sessione condivisa senza refresh manuale.
|
- Realtime (Supabase Realtime) per aggiornare la sessione condivisa senza refresh manuale.
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { BarChart3, Download, Info } from "lucide-react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Card } from "@/components/crapp/ui-bits";
|
||||||
|
import type { Giocatore } from "@/lib/crapp-data";
|
||||||
|
import { csvScoutMatch, scaricaCsv } from "@/lib/scout-export";
|
||||||
|
import {
|
||||||
|
fondamentali,
|
||||||
|
sintesiValutazioni,
|
||||||
|
totaliPerGiocatore,
|
||||||
|
totaliSquadra,
|
||||||
|
type ScoutMatch,
|
||||||
|
} from "@/lib/scout-store";
|
||||||
|
|
||||||
|
const pct = (piu: number, meno: number) =>
|
||||||
|
piu + meno === 0 ? null : Math.round((piu / (piu + meno)) * 100);
|
||||||
|
|
||||||
|
/** Lettura dello scout di una partita: squadra per fondamentale, set per set, dettaglio per giocatore. */
|
||||||
|
export function ScoutReport({
|
||||||
|
scout,
|
||||||
|
rosa,
|
||||||
|
admin,
|
||||||
|
}: {
|
||||||
|
scout: ScoutMatch;
|
||||||
|
rosa: Giocatore[];
|
||||||
|
admin: boolean;
|
||||||
|
}) {
|
||||||
|
// undefined = tutta la partita.
|
||||||
|
const [set, setSet] = useState<number | undefined>(undefined);
|
||||||
|
const [guida, setGuida] = useState(false);
|
||||||
|
const sets = [...new Set(scout.valutazioni.map((v) => v.set))].sort((a, b) => a - b);
|
||||||
|
const { squadra, perGiocatore } = sintesiValutazioni(scout.valutazioni, set);
|
||||||
|
const righe = perGiocatore
|
||||||
|
.map((r) => ({ ...r, g: rosa.find((x) => x.id === r.giocatoreId) }))
|
||||||
|
.filter((r): r is typeof r & { g: Giocatore } => !!r.g)
|
||||||
|
.sort((a, b) => a.g.numero - b.g.numero);
|
||||||
|
|
||||||
|
const azioni = totaliPerGiocatore(scout.azioni);
|
||||||
|
const totaliAzioni = totaliSquadra([scout]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<p className="inline-flex items-center gap-1.5 text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
<BarChart3 className="h-3.5 w-3.5" /> Valutazione scout
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-expanded={guida}
|
||||||
|
aria-label="Come leggere lo scout"
|
||||||
|
onClick={() => setGuida((g) => !g)}
|
||||||
|
className={cn(
|
||||||
|
"premi flex h-8 w-8 items-center justify-center rounded-full transition-colors",
|
||||||
|
guida ? "bg-primary text-primary-foreground" : "bg-secondary text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{guida ? (
|
||||||
|
<dl className="mt-3 space-y-3 rounded-2xl bg-secondary p-4 text-[13px] leading-relaxed">
|
||||||
|
{[
|
||||||
|
[
|
||||||
|
"Cosa misura",
|
||||||
|
<>
|
||||||
|
Durante la partita lo scout assegna a ciascun giocatore una valutazione{" "}
|
||||||
|
<b className="text-success">+</b> (esecuzione positiva) o{" "}
|
||||||
|
<b className="text-destructive">−</b> (esecuzione negativa) su quattro fondamentali:
|
||||||
|
battuta, ricezione, attacco e muro. Sono giudizi qualitativi, non punti: non
|
||||||
|
incidono su risultato, classifiche o badge.
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Indicatori di squadra",
|
||||||
|
<>
|
||||||
|
Ogni scheda riporta, per fondamentale, la percentuale di valutazioni positive (+ ÷
|
||||||
|
(+ + −)), la barra di composizione verde/rossa e il numero di + e −. Un valore alto
|
||||||
|
indica un fondamentale eseguito con continuità.
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Dettaglio per giocatore",
|
||||||
|
<>
|
||||||
|
La tabella elenca, per ogni giocatore, i + e i − di ciascun fondamentale nel formato{" "}
|
||||||
|
<b>+ / −</b>: «3 / 1» indica tre valutazioni positive e una negativa. Compaiono solo
|
||||||
|
i giocatori con almeno una valutazione nel periodo selezionato.
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Periodo",
|
||||||
|
<>
|
||||||
|
«Totale» aggrega l'intera partita; «Set 1», «Set 2»… isolano un singolo set e
|
||||||
|
permettono di osservare l'andamento durante la gara.
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Interpretazione",
|
||||||
|
<>
|
||||||
|
Il campione è ridotto: con pochi tocchi la percentuale è poco significativa (un solo
|
||||||
|
+ su un solo tocco vale 100%). I dati riflettono il giudizio dello scout in servizio
|
||||||
|
e vanno letti come indicazione di squadra, non come voto individuale.
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Partite precedenti",
|
||||||
|
<>
|
||||||
|
Per le partite scoutate con il metodo precedente è mostrato anche il riepilogo delle
|
||||||
|
azioni registrate (punti, ace, muri, errori).
|
||||||
|
</>,
|
||||||
|
],
|
||||||
|
].map(([titolo, testo]) => (
|
||||||
|
<div key={titolo as string}>
|
||||||
|
<dt className="text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
{titolo}
|
||||||
|
</dt>
|
||||||
|
<dd className="mt-0.5">{testo}</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
) : null}
|
||||||
|
{scout.valutazioni.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{sets.length > 1 ? (
|
||||||
|
<div
|
||||||
|
role="tablist"
|
||||||
|
aria-label="Periodo"
|
||||||
|
className="mt-3 grid grid-flow-col auto-cols-fr gap-1 rounded-2xl bg-secondary p-1"
|
||||||
|
>
|
||||||
|
{[undefined, ...sets].map((s) => (
|
||||||
|
<button
|
||||||
|
key={s ?? "tutta"}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={s === set}
|
||||||
|
onClick={() => setSet(s)}
|
||||||
|
className={cn(
|
||||||
|
"min-w-0 truncate rounded-xl px-1 py-2 text-[13px] font-bold uppercase transition-colors",
|
||||||
|
s === set
|
||||||
|
? "bg-primary text-primary-foreground shadow-card"
|
||||||
|
: "text-muted-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{s === undefined ? "Totale" : `Set ${s}`}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="mt-3 grid grid-cols-2 gap-2">
|
||||||
|
{fondamentali.map((f) => {
|
||||||
|
const { piu, meno } = squadra[f.id];
|
||||||
|
const p = pct(piu, meno);
|
||||||
|
return (
|
||||||
|
<div key={f.id} className="rounded-2xl bg-secondary p-3">
|
||||||
|
<div className="flex items-baseline justify-between">
|
||||||
|
<p className="text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
{f.label}
|
||||||
|
</p>
|
||||||
|
<p className="font-display text-xl leading-none">
|
||||||
|
{p === null ? "—" : `${p}%`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex h-1.5 overflow-hidden rounded-full bg-border">
|
||||||
|
<span className="bg-success" style={{ width: `${p ?? 0}%` }} />
|
||||||
|
<span
|
||||||
|
className="bg-destructive"
|
||||||
|
style={{ width: p === null ? 0 : `${100 - p}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 text-[13px] font-semibold tabular-nums">
|
||||||
|
<span className="text-success">+{piu}</span>
|
||||||
|
<span className="mx-1.5 text-muted-foreground">·</span>
|
||||||
|
<span className="text-destructive">−{meno}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-4 text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
Dettaglio giocatori
|
||||||
|
</p>
|
||||||
|
{righe.length === 0 ? (
|
||||||
|
<p className="mt-2 text-[13px] text-muted-foreground">
|
||||||
|
Nessuna valutazione in questo set.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="mt-2 overflow-x-auto rounded-2xl bg-secondary/60">
|
||||||
|
<table className="w-full min-w-[22rem] text-[13px]">
|
||||||
|
<thead>
|
||||||
|
<tr className="text-left text-[13px] uppercase text-muted-foreground">
|
||||||
|
<th className="px-3 py-2 font-bold">Giocatore</th>
|
||||||
|
{fondamentali.map((f) => (
|
||||||
|
<th key={f.id} className="px-2 py-2 text-center font-bold">
|
||||||
|
{f.label.slice(0, 3)}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-border">
|
||||||
|
{righe.map(({ g, conteggi }) => (
|
||||||
|
<tr key={g.id}>
|
||||||
|
<td className="max-w-40 truncate px-3 py-2 font-semibold">
|
||||||
|
<span className="mr-1.5 tabular-nums text-muted-foreground">
|
||||||
|
#{g.numero}
|
||||||
|
</span>
|
||||||
|
{g.nome}
|
||||||
|
</td>
|
||||||
|
{fondamentali.map((f) => (
|
||||||
|
<td key={f.id} className="px-2 py-2 text-center font-semibold tabular-nums">
|
||||||
|
<span className="text-success">{conteggi[f.id].piu}</span>
|
||||||
|
<span className="text-muted-foreground"> / </span>
|
||||||
|
<span className="text-destructive">{conteggi[f.id].meno}</span>
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="mt-2 text-[13px] text-muted-foreground">
|
||||||
|
Valutazioni: <span className="text-success">+</span> positive /{" "}
|
||||||
|
<span className="text-destructive">−</span> negative. La percentuale è la quota di +.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{scout.azioni.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<p className="mt-4 text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
|
Azioni registrate
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 grid grid-cols-4 gap-2 text-center">
|
||||||
|
{[
|
||||||
|
{ l: "Punti", v: totaliAzioni.punti },
|
||||||
|
{ l: "Ace", v: totaliAzioni.ace },
|
||||||
|
{ l: "Muri", v: totaliAzioni.muri },
|
||||||
|
{ l: "Errori", v: totaliAzioni.errori },
|
||||||
|
].map((t) => (
|
||||||
|
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
||||||
|
<p className="font-display text-xl leading-none">{t.v}</p>
|
||||||
|
<p className="mt-1 text-[13px] font-semibold uppercase text-muted-foreground">
|
||||||
|
{t.l}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 space-y-1">
|
||||||
|
{[...azioni.entries()].map(([gid, t]) => {
|
||||||
|
const g = rosa.find((x) => x.id === gid);
|
||||||
|
if (!g) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={gid}
|
||||||
|
className="flex items-center gap-2 rounded-xl bg-secondary/60 px-3 py-2 text-[13px]"
|
||||||
|
>
|
||||||
|
<span className="min-w-0 flex-1 truncate font-semibold">
|
||||||
|
#{g.numero} {g.nome}
|
||||||
|
</span>
|
||||||
|
<span className="tabular-nums text-muted-foreground">
|
||||||
|
{t.punti}P · {t.ace}A · {t.muri}M · {t.errori}E
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{scout.valutazioni.length === 0 && scout.azioni.length === 0 ? (
|
||||||
|
<p className="text-[13px] text-muted-foreground">
|
||||||
|
Nessun dato registrato per questa partita.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{admin ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
scaricaCsv(`scout-${scout.data}-${scout.avversario}.csv`, csvScoutMatch(scout, rosa))
|
||||||
|
}
|
||||||
|
className="premi mt-4 flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-[15px] font-bold uppercase text-accent-foreground shadow-pop"
|
||||||
|
>
|
||||||
|
<Download className="h-4 w-4" /> Esporta CSV
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -96,6 +96,24 @@ export function contaValutazioni(
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Riepilogo delle valutazioni per la lettura nell'app: totali di squadra e per giocatore,
|
||||||
|
* su tutta la partita o su un solo set. Chi non ha valutazioni nel periodo non compare.
|
||||||
|
*/
|
||||||
|
export function sintesiValutazioni(valutazioni: Valutazione[], set?: number) {
|
||||||
|
const filtrate = set === undefined ? valutazioni : valutazioni.filter((v) => v.set === set);
|
||||||
|
const squadra = contaValutazioni([], "", undefined);
|
||||||
|
const perGiocatore = [...new Set(filtrate.map((v) => v.giocatoreId))].map((giocatoreId) => {
|
||||||
|
const c = contaValutazioni(filtrate, giocatoreId);
|
||||||
|
for (const f of fondamentali) {
|
||||||
|
squadra[f.id].piu += c[f.id].piu;
|
||||||
|
squadra[f.id].meno += c[f.id].meno;
|
||||||
|
}
|
||||||
|
return { giocatoreId, conteggi: c };
|
||||||
|
});
|
||||||
|
return { squadra, perGiocatore };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* I + di un giocatore in un set, esclusa la ricezione (che qui non vale come punto messo a
|
* I + di un giocatore in un set, esclusa la ricezione (che qui non vale come punto messo a
|
||||||
* terra): quello che compare nel suo riquadro dello scout.
|
* terra): quello che compare nel suo riquadro dello scout.
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { DettaglioCsiEsteso, LogoSquadra, MetaPartitaCsi } from "@/components/cr
|
|||||||
import { Pagelle } from "@/components/crapp/Pagelle";
|
import { Pagelle } from "@/components/crapp/Pagelle";
|
||||||
import { SondaggioCacche } from "@/components/crapp/SondaggioCacche";
|
import { SondaggioCacche } from "@/components/crapp/SondaggioCacche";
|
||||||
import { ScoutEntry } from "@/components/crapp/ScoutEntry";
|
import { ScoutEntry } from "@/components/crapp/ScoutEntry";
|
||||||
import { haPunteggio, useScoutMatches, totaliPerGiocatore, totaliSquadra } from "@/lib/scout-store";
|
import { haPunteggio, useScoutMatches } from "@/lib/scout-store";
|
||||||
import { csvScoutMatch, scaricaCsv } from "@/lib/scout-export";
|
import { ScoutReport } from "@/components/crapp/ScoutReport";
|
||||||
import { useIsAdmin } from "@/lib/ruoli";
|
import { useIsAdmin } from "@/lib/ruoli";
|
||||||
import { useSonoAllenatore } from "@/lib/user-store";
|
import { useSonoAllenatore } from "@/lib/user-store";
|
||||||
import { VotazioneMvp } from "@/components/crapp/VotazioneMvp";
|
import { VotazioneMvp } from "@/components/crapp/VotazioneMvp";
|
||||||
@@ -90,7 +90,6 @@ function PartitaDetail() {
|
|||||||
: csiMatch
|
: csiMatch
|
||||||
? matchDaPartitaCsi(csiMatch)
|
? matchDaPartitaCsi(csiMatch)
|
||||||
: undefined;
|
: undefined;
|
||||||
const totaliTeam = scout ? totaliSquadra([scout]) : null;
|
|
||||||
const avversario = evento.titolo.includes(" vs ")
|
const avversario = evento.titolo.includes(" vs ")
|
||||||
? (evento.titolo.split(" vs ").find((p) => !p.includes("CRAP")) ?? evento.titolo)
|
? (evento.titolo.split(" vs ").find((p) => !p.includes("CRAP")) ?? evento.titolo)
|
||||||
: evento.titolo;
|
: evento.titolo;
|
||||||
@@ -249,61 +248,9 @@ function PartitaDetail() {
|
|||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{scout && totaliTeam ? (
|
{scout ? (
|
||||||
<Section titolo="Scout">
|
<Section titolo="Scout">
|
||||||
<Card>
|
<ScoutReport scout={scout} rosa={rosa} admin={admin} />
|
||||||
<div className="grid grid-cols-4 gap-2 text-center">
|
|
||||||
{[
|
|
||||||
{ l: "Punti", v: totaliTeam.punti },
|
|
||||||
{ l: "Ace", v: totaliTeam.ace },
|
|
||||||
{ l: "Muri", v: totaliTeam.muri },
|
|
||||||
{ l: "Errori", v: totaliTeam.errori },
|
|
||||||
].map((t) => (
|
|
||||||
<div key={t.l} className="rounded-2xl bg-secondary p-2.5">
|
|
||||||
<p className="font-display text-xl leading-none">{t.v}</p>
|
|
||||||
<p className="mt-1 text-[13px] font-semibold uppercase text-muted-foreground">
|
|
||||||
{t.l}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<p className="mt-4 text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
|
||||||
Dettaglio giocatori (uso interno allenatori)
|
|
||||||
</p>
|
|
||||||
<div className="mt-2 space-y-1">
|
|
||||||
{[...totaliPerGiocatore(scout.azioni).entries()].map(([gid, t]) => {
|
|
||||||
const g = rosa.find((x) => x.id === gid);
|
|
||||||
if (!g) return null;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={gid}
|
|
||||||
className="flex items-center gap-2 rounded-xl bg-secondary/60 px-3 py-2 text-[13px]"
|
|
||||||
>
|
|
||||||
<span className="min-w-0 flex-1 truncate font-semibold">
|
|
||||||
#{g.numero} {g.nome}
|
|
||||||
</span>
|
|
||||||
<span className="tabular-nums text-muted-foreground">
|
|
||||||
{t.punti}P · {t.ace}A · {t.muri}M · {t.errori}E
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
{admin ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
scaricaCsv(
|
|
||||||
`scout-${scout.data}-${scout.avversario}.csv`,
|
|
||||||
csvScoutMatch(scout, rosa),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="premi mt-4 flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-grad py-3 text-[15px] font-bold uppercase text-accent-foreground shadow-pop"
|
|
||||||
>
|
|
||||||
<Download className="h-4 w-4" /> Esporta CSV
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</Card>
|
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
contaPiuSenzaRicezione,
|
contaPiuSenzaRicezione,
|
||||||
haPunteggio,
|
haPunteggio,
|
||||||
parzialiFinali,
|
parzialiFinali,
|
||||||
|
sintesiValutazioni,
|
||||||
contaValutazioni,
|
contaValutazioni,
|
||||||
fondamentali,
|
fondamentali,
|
||||||
giocatoriConScout,
|
giocatoriConScout,
|
||||||
@@ -197,3 +198,19 @@ assert.equal(
|
|||||||
}),
|
}),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- sintesi per la lettura nell'app: squadra e giocatori, partita o singolo set -----
|
||||||
|
const tutta = sintesiValutazioni(val);
|
||||||
|
assert.deepEqual(tutta.squadra.battuta, { piu: 2, meno: 1 });
|
||||||
|
assert.deepEqual(tutta.squadra.attacco, { piu: 1, meno: 0 });
|
||||||
|
assert.deepEqual(tutta.squadra.ricezione, { piu: 0, meno: 1 });
|
||||||
|
assert.deepEqual(tutta.perGiocatore.map((r) => r.giocatoreId).sort(), ["g1", "g2"]);
|
||||||
|
const set2 = sintesiValutazioni(val, 2);
|
||||||
|
assert.deepEqual(
|
||||||
|
set2.perGiocatore.map((r) => r.giocatoreId),
|
||||||
|
["g1"],
|
||||||
|
);
|
||||||
|
assert.deepEqual(set2.squadra.muro, { piu: 1, meno: 0 });
|
||||||
|
assert.deepEqual(set2.squadra.battuta, { piu: 0, meno: 0 });
|
||||||
|
assert.equal(sintesiValutazioni([]).perGiocatore.length, 0);
|
||||||
|
console.log("scout-store sintesi: ok");
|
||||||
Reference in new issue
Block a user