Compare commits
12
Commits
252e1528f6
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b93f92965 | ||
|
|
c98e1ef218 | ||
|
|
e6cfb1bf00 | ||
|
|
30fb8e23b6 | ||
|
|
91ac7ce65c | ||
|
|
a7b9affbd6 | ||
|
|
7acdc2ba1e | ||
|
|
c1627a0720 | ||
|
|
63d1f01be6 | ||
|
|
2787672cd5 | ||
|
|
1c63808bfb | ||
|
|
92ce3979a3 |
No files matched your search
+12
-16
@@ -9,9 +9,11 @@ schema in [docs/DATABASE.md](docs/DATABASE.md), le procedure in
|
|||||||
|
|
||||||
## Stato generale
|
## Stato generale
|
||||||
|
|
||||||
- **Versione:** 1.5.0 su `main` (tag `v1.5.0`): campionato CSI sulla stagione 2026/27 (nuovi `project_id`
|
- **Versione:** 1.6.1 su `main`: risultati dello scout leggibili nella pagina
|
||||||
e `team_id`, nessuna Coppa). Su `feature/gara` c'è del lavoro non ancora rilasciato (voto MVP, vedi
|
partita, classifica cacche completa e pagina partita a sezioni a scomparsa, senza migration. La 1.6.0
|
||||||
«Lavoro in corso»). La 1.4.2: ogni evento può avere un link Google Maps del luogo, mostrato come
|
(tag `v1.6.0`, in produzione dal 02/11/2026): voto MVP definitivo, segreto e a
|
||||||
|
tempo (DD-051, DD-052) e Scout live a valutazioni − / + (DD-053). La 1.5.0: campionato CSI sulla stagione 2026/27 (nuovi `project_id` e `team_id`,
|
||||||
|
nessuna Coppa). La 1.4.2: ogni evento può avere un link Google Maps del luogo, mostrato come
|
||||||
«Apri su Maps» nelle schede (`m34`). La 1.4.1: gli eventi con convocati si
|
«Apri su Maps» nelle schede (`m34`). La 1.4.1: gli eventi con convocati si
|
||||||
vedono solo ai convocati (admin e allenatori sempre) e «Rosa e presenze» elenca solo loro. La 1.4.0:
|
vedono solo ai convocati (admin e allenatori sempre) e «Rosa e presenze» elenca solo loro. La 1.4.0:
|
||||||
avviso «Presenza modificata» ad admin e allenatori (DD-050, `m32`-`m33`). La 1.3.1: il Calendario
|
avviso «Presenza modificata» ad admin e allenatori (DD-050, `m32`-`m33`). La 1.3.1: il Calendario
|
||||||
@@ -21,25 +23,19 @@ schema in [docs/DATABASE.md](docs/DATABASE.md), le procedure in
|
|||||||
[docs/CHANGELOG.md](docs/CHANGELOG.md).
|
[docs/CHANGELOG.md](docs/CHANGELOG.md).
|
||||||
- **Backend:** Supabase proprietario (`kfkcldwncxqaixetsjes`). Lovable Cloud e il vecchio
|
- **Backend:** Supabase proprietario (`kfkcldwncxqaixetsjes`). Lovable Cloud e il vecchio
|
||||||
Project Ref `hetycilxgkdmccelwerq` non si usano più.
|
Project Ref `hetycilxgkdmccelwerq` non si usano più.
|
||||||
- **Migration:** 50 file in `supabase/migrations/`, fino a `m39_scout_valutazioni`. In produzione
|
- **Migration:** 50 file in `supabase/migrations/`, fino a `m39_scout_valutazioni`. In produzione sono
|
||||||
sono applicate 45 su 50, fino alla `m34_link_maps_evento` (verificato con
|
applicate tutte e 50 (`npx supabase db push` del 02/11/2026: `m35`-`m39`, verificato con
|
||||||
`npx supabase migration list --linked` il 26/10/2026: le uniche mancanti sono `m35`-`m39`, nessuna
|
`npx supabase migration list --linked`; nessuna migration remota è assente dal repository). Prima
|
||||||
migration remota è assente dal repository). **Da applicare ancora: `m35`-`m39`**
|
del push è stato salvato lo schema di produzione (solo schema, non i dati).
|
||||||
(voto MVP e badge social, DD-051 e DD-052, valutazioni dello scout, DD-053; branch `feature/gara`): `npx supabase db push` è rinviato
|
|
||||||
per scelta, non è stato eseguito. Vanno applicate subito prima del rilascio del codice: con le sole
|
|
||||||
migration il voto MVP dell'app in produzione (1.4.2) smette di funzionare, con il solo codice la
|
|
||||||
vista dei conteggi non esiste.
|
|
||||||
- **Worker delle notifiche** (`mailer/`): in funzione su un Raspberry Pi con Docker dal 29/09/2026
|
- **Worker delle notifiche** (`mailer/`): in funzione su un Raspberry Pi con Docker dal 29/09/2026
|
||||||
([docs/WORKER_EMAIL.md](docs/WORKER_EMAIL.md)). Il codice con l'interruttore «Email» in Profilo
|
([docs/WORKER_EMAIL.md](docs/WORKER_EMAIL.md)). Il codice con l'interruttore «Email» in Profilo
|
||||||
è su `main` dalla 1.3.0.
|
è su `main` dalla 1.3.0.
|
||||||
- **Accesso:** il login Google è l'unica via d'ingresso; i permessi di amministrazione arrivano
|
- **Accesso:** il login Google è l'unica via d'ingresso; i permessi di amministrazione arrivano
|
||||||
solo da `user_roles`. Il collegamento degli account è un processo continuo: ogni giocatore si
|
solo da `user_roles`. Il collegamento degli account è un processo continuo: ogni giocatore si
|
||||||
collega al primo accesso (DD-018).
|
collega al primo accesso (DD-018).
|
||||||
- **Lavoro in corso:** branch `feature/gara` per le funzioni di gara (correzioni e nuove
|
- **Lavoro in corso:** nessuno su branch. Le voci aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto
|
||||||
funzioni; in futuro anche lo scouting). Primo lavoro: il voto MVP definitivo, segreto, a tempo e
|
«Prossimo». Da decidere sullo scout: il pulsante «Annulla» in alto agisce solo sulle azioni, che non
|
||||||
tra presenti, con i pari merito (DD-051, DD-052). Poi lo scout live ridotto alle valutazioni − / +
|
si creano più, quindi è sempre spento.
|
||||||
per set (DD-053), salvate subito su database. Non ancora in `main`, non rilasciato, versione
|
|
||||||
invariata. Le altre voci aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo».
|
|
||||||
|
|
||||||
## Cose da sapere
|
## Cose da sapere
|
||||||
|
|
||||||
|
|||||||
+37
-3
@@ -13,7 +13,36 @@ versioni `0.y.z` erano pre-release.
|
|||||||
|
|
||||||
## [Non rilasciato]
|
## [Non rilasciato]
|
||||||
|
|
||||||
Cinque migration, da `m35` a `m39`, **non ancora applicate in produzione**.
|
## [1.6.1] - 2026-11-06
|
||||||
|
|
||||||
|
Risultati dello scout leggibili nell'app, classifica cacche completa e pagina partita a sezioni
|
||||||
|
a scomparsa. Nessuna migration.
|
||||||
|
|
||||||
|
### Aggiunto
|
||||||
|
|
||||||
|
- Pagina partita, sezione «Scout»: i risultati dello scout si leggono nell'app, non solo nel CSV
|
||||||
|
admin. Per ogni fondamentale: percentuale di +, barra verde/rossa e conteggi di squadra; selettore
|
||||||
|
«Totale» e «Set 1»…«Set 5» e tabella per giocatore con + / −. Il pulsante ⓘ apre una guida 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.
|
||||||
|
|
||||||
|
### Modificato
|
||||||
|
|
||||||
|
- Sondaggio cacche: la classifica completa è visibile a tutti i giocatori (non più solo il podio),
|
||||||
|
anche a sondaggio chiuso, con posizioni a pari merito senza salti (1°, 1°, 2°), barra
|
||||||
|
proporzionale e la propria riga evidenziata.
|
||||||
|
- Pagina partita: formazioni, badge, sondaggio, pagelle, scout e rosa sono sezioni a scomparsa
|
||||||
|
(aperti all'inizio solo rosa e presenze e, nella sua finestra, il sondaggio). Le formazioni
|
||||||
|
indicano la fonte «Livescore CSI»; lo scout mostra il numero di valutazioni. I titoli delle
|
||||||
|
sezioni a scomparsa hanno la stessa dimensione delle altre e le chiuse non lasciano vuoto sotto.
|
||||||
|
- Pagelle di fine partita: non compaiono più i convocati che risultano assenti o infortunati a
|
||||||
|
quella partita (solo filtro di interfaccia).
|
||||||
|
- Pagina partita: la sezione «Report tecnico» si chiama «Scout».
|
||||||
|
|
||||||
|
## [1.6.0] - 2026-11-02
|
||||||
|
|
||||||
|
Voto MVP definitivo e segreto, Scout live a valutazioni. Cinque migration, da `m35` a `m39`,
|
||||||
|
applicate in produzione il 02/11/2026.
|
||||||
|
|
||||||
### Aggiunto
|
### Aggiunto
|
||||||
|
|
||||||
@@ -23,14 +52,16 @@ Cinque migration, da `m35` a `m39`, **non ancora applicate in produzione**.
|
|||||||
|
|
||||||
### Modificato
|
### Modificato
|
||||||
|
|
||||||
- Scout live: tolti i tasti dei punti, il tabellone del punteggio e il blocco avversario / in casa /
|
- Scout live: tolti i tasti dei punti, il tabellone del punteggio, l'elenco «Ultime azioni» e il
|
||||||
trasferta; resta la scelta del giocatore e la valutazione − / + per set
|
blocco avversario / in casa / trasferta; resta la scelta del giocatore e la valutazione − / + per set
|
||||||
([DD-053](decisions/DD-053.md)). «Chiudi set» e «Fine partita» funzionano anche senza punti; le
|
([DD-053](decisions/DD-053.md)). «Chiudi set» e «Fine partita» funzionano anche senza punti; le
|
||||||
partite di sole valutazioni non compaiono come risultato in Home, Classifica e pagina partita.
|
partite di sole valutazioni non compaiono come risultato in Home, Classifica e pagina partita.
|
||||||
- Scout live: il salvataggio è immediato a ogni tocco (prima c'era una pausa di 0,8 secondi) e,
|
- Scout live: il salvataggio è immediato a ogni tocco (prima c'era una pausa di 0,8 secondi) e,
|
||||||
se non riesce, compare un avviso.
|
se non riesce, compare un avviso.
|
||||||
- Scout live: la griglia «Selezione giocatore» si ordina per cognome o per numero; nel riquadro di
|
- Scout live: la griglia «Selezione giocatore» si ordina per cognome o per numero; nel riquadro di
|
||||||
ogni giocatore compaiono i + del set in corso (ricezione esclusa) al posto dei punti.
|
ogni giocatore compaiono i + del set in corso (ricezione esclusa) al posto dei punti.
|
||||||
|
- Scout live: dopo «Fine partita» il messaggio dice solo «Partita salvata» (il voto MVP apre 2 ore
|
||||||
|
dopo l'inizio della partita, non dipende dallo scout).
|
||||||
- Il voto MVP è definitivo ([DD-051](decisions/DD-051.md)): si sceglie un nome, si conferma e non si
|
- Il voto MVP è definitivo ([DD-051](decisions/DD-051.md)): si sceglie un nome, si conferma e non si
|
||||||
può più cambiare. Il database lo impone (solo gli admin possono correggere un voto).
|
può più cambiare. Il database lo impone (solo gli admin possono correggere un voto).
|
||||||
- In caso di parità in testa gli MVP sono più d'uno: ognuno dei pari merito conta un titolo, e in
|
- In caso di parità in testa gli MVP sono più d'uno: ognuno dei pari merito conta un titolo, e in
|
||||||
@@ -41,6 +72,9 @@ Cinque migration, da `m35` a `m39`, **non ancora applicate in produzione**.
|
|||||||
- La votazione MVP si chiude 24 ore dopo l'apertura (cioè 26 ore dopo l'inizio della partita): le
|
- La votazione MVP si chiude 24 ore dopo l'apertura (cioè 26 ore dopo l'inizio della partita): le
|
||||||
partite più vecchie non si votano più.
|
partite più vecchie non si votano più.
|
||||||
|
|
||||||
|
- I test non pretendono più una gara CSI già giocata: a inizio stagione il calendario c'è ma i
|
||||||
|
risultati no (un test si salta finché non c'è una gara giocata).
|
||||||
|
|
||||||
### Sicurezza
|
### Sicurezza
|
||||||
|
|
||||||
- Il database impone anche le regole che prima valevano solo nell'app: il voto MVP è possibile solo per
|
- Il database impone anche le regole che prima valevano solo nell'app: il voto MVP è possibile solo per
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ il campionato. Dove serve il dettaglio c'è il rimando al modulo.
|
|||||||
| **Badge** | Riconoscimenti calcolati a runtime, mai salvati (DD-007): normali (bronzo/argento/oro), segreti, social (votati dai compagni). Vedi [modules/badge.md](modules/badge.md). |
|
| **Badge** | Riconoscimenti calcolati a runtime, mai salvati (DD-007): normali (bronzo/argento/oro), segreti, social (votati dai compagni). Vedi [modules/badge.md](modules/badge.md). |
|
||||||
| **Cacche** | Sondaggio pre-partita (`cacche_partita`) usato per statistiche e per il badge segreto `s-cacche`. |
|
| **Cacche** | Sondaggio pre-partita (`cacche_partita`) usato per statistiche e per il badge segreto `s-cacche`. |
|
||||||
| **Palloni** | Turno a rotazione di chi porta e riporta i palloni a partite e allenamenti. Vedi [modules/palloni.md](modules/palloni.md). |
|
| **Palloni** | Turno a rotazione di chi porta e riporta i palloni a partite e allenamenti. Vedi [modules/palloni.md](modules/palloni.md). |
|
||||||
| **Scout / Scout Live** | Rilevazione in tempo reale delle azioni di una partita, con blocco condiviso («Scout occupato da X»). Solo statistiche di squadra, mai classifiche individuali (DD-008). |
|
| **Scout / Scout Live** | Rilevazione in tempo reale, durante la partita, delle valutazioni − / + di ogni convocato su battuta, ricezione, attacco e muro, per set (DD-053), con blocco condiviso («Scout occupato da X»). Solo statistiche di squadra, mai classifiche individuali (DD-008). |
|
||||||
| **CSI** | Centro Sportivo Italiano, l'ente del campionato. Due usi: il **tesseramento** dei giocatori (manuale, DD-009) e il **collegamento** con classifica e risultati ufficiali ([modules/collegamento-csi.md](modules/collegamento-csi.md)). |
|
| **CSI** | Centro Sportivo Italiano, l'ente del campionato. Due usi: il **tesseramento** dei giocatori (manuale, DD-009) e il **collegamento** con classifica e risultati ufficiali ([modules/collegamento-csi.md](modules/collegamento-csi.md)). |
|
||||||
| **Stagione CSI** | Si ricava dalle date delle gare, non da un'impostazione (DD-032). |
|
| **Stagione CSI** | Si ricava dalle date delle gare, non da un'impostazione (DD-032). |
|
||||||
| **Centro notifiche** | Storico in-app (`notifiche_utente`) sull'avatar del profilo, separato dalla push (DD-030). |
|
| **Centro notifiche** | Storico in-app (`notifiche_utente`) sull'avatar del profilo, separato dalla push (DD-030). |
|
||||||
|
|||||||
@@ -230,7 +230,8 @@ DettaglioCsiEsteso → src/components/crapp/DettaglioCsi.tsx (formazio
|
|||||||
tabella/lista, sia per classifica/partite sia per formazioni/precedenti.
|
tabella/lista, sia per classifica/partite sia per formazioni/precedenti.
|
||||||
- **Fallback**: se il CSI non risponde, l'endpoint `/api/public/csi` restituisce l'ultimo
|
- **Fallback**: se il CSI non risponde, l'endpoint `/api/public/csi` restituisce l'ultimo
|
||||||
dato buono in cache; se non ne ha ancora uno, la classifica resta vuota e i risultati
|
dato buono in cache; se non ne ha ancora uno, la classifica resta vuota e i risultati
|
||||||
ricadono sulle partite dello Scout Live locale (`useScoutMatches()`).
|
ricadono sulle partite dello Scout Live che hanno un punteggio (`useScoutMatches()` filtrato con
|
||||||
|
`haPunteggio()`: quelle di sole valutazioni non sono un risultato).
|
||||||
`/api/public/csi-partita/$id` non ha questo fallback sulla prima chiamata per una gara mai
|
`/api/public/csi-partita/$id` non ha questo fallback sulla prima chiamata per una gara mai
|
||||||
vista (vedi sopra): fallisce con `503`, e la UI (`DettaglioCsiEsteso`) semplicemente non
|
vista (vedi sopra): fallisce con `503`, e la UI (`DettaglioCsiEsteso`) semplicemente non
|
||||||
mostra la sezione formazioni/scontri diretti, senza rompere il resto della pagina.
|
mostra la sezione formazioni/scontri diretti, senza rompere il resto della pagina.
|
||||||
@@ -243,7 +244,7 @@ DettaglioCsiEsteso → src/components/crapp/DettaglioCsi.tsx (formazio
|
|||||||
1. **La classifica si legge da HTML.** Se il portale cambia la struttura della tabella il
|
1. **La classifica si legge da HTML.** Se il portale cambia la struttura della tabella il
|
||||||
parsing restituisce un array vuoto: `/classifica` non si rompe, ma mostra "Classifica non
|
parsing restituisce un array vuoto: `/classifica` non si rompe, ma mostra "Classifica non
|
||||||
ancora disponibile" (o l'ultimo dato buono in cache, se ce n'è uno) e i risultati ricadono
|
ancora disponibile" (o l'ultimo dato buono in cache, se ce n'è uno) e i risultati ricadono
|
||||||
sulle partite dello Scout Live locale, non su dati demo — non esistono più in `crapp-data.ts`.
|
sulle partite dello Scout Live con punteggio, non su dati demo — non esistono più in `crapp-data.ts`.
|
||||||
Il check con `CSI_LIVE=1` serve a scoprire il problema di parsing.
|
Il check con `CSI_LIVE=1` serve a scoprire il problema di parsing.
|
||||||
2. **`project_id` e `team_id` sono legati alla stagione.** Per il 2027/28 serviranno due nuovi
|
2. **`project_id` e `team_id` sono legati alla stagione.** Per il 2027/28 serviranno due nuovi
|
||||||
id (vedi "Sorgente dati" sopra per come ritrovarli): vanno aggiornati a mano in
|
id (vedi "Sorgente dati" sopra per come ritrovarli): vanno aggiornati a mano in
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ UI), `UNIQUE (match_id, votante_id, votato_id)`.
|
|||||||
partita (scout salvato o dato CSI).
|
partita (scout salvato o dato CSI).
|
||||||
- Ogni convocato può votare tutti gli altri convocati, mai se stesso — escluso sia in UI sia
|
- Ogni convocato può votare tutti gli altri convocati, mai se stesso — escluso sia in UI sia
|
||||||
dal vincolo DB.
|
dal vincolo DB.
|
||||||
|
- Nell'elenco da votare non compaiono i convocati che hanno risposto «assente» o «infortunato»
|
||||||
|
alle presenze: solo filtro di interfaccia in `partita.$id.tsx`, il vincolo DB non cambia.
|
||||||
- `useVotaPagella()` fa un upsert su `(match_id, votante_id, votato_id)`: si può votare più
|
- `useVotaPagella()` fa un upsert su `(match_id, votante_id, votato_id)`: si può votare più
|
||||||
volte, l'ultimo voto sovrascrive il precedente.
|
volte, l'ultimo voto sovrascrive il precedente.
|
||||||
- `mediePagelle()` calcola la media aritmetica (arrotondata a un decimale) per giocatore su
|
- `mediePagelle()` calcola la media aritmetica (arrotondata a un decimale) per giocatore su
|
||||||
|
|||||||
+39
-27
@@ -1,6 +1,6 @@
|
|||||||
# Modulo — Scout Live
|
# Modulo — Scout Live
|
||||||
|
|
||||||
**Stato:** implementato (fix M7 per la persistenza condivisa)
|
**Stato:** implementato (fix M7 per la persistenza condivisa; valutazioni − / + dalla 1.6.0, DD-053)
|
||||||
**File principali:** `src/lib/scout-live.ts`, `src/lib/scout-stato.ts`, `src/lib/scout-store.ts`,
|
**File principali:** `src/lib/scout-live.ts`, `src/lib/scout-stato.ts`, `src/lib/scout-store.ts`,
|
||||||
`src/lib/scout-export.ts`, `src/lib/cacche.ts`, `src/components/crapp/ScoutEntry.tsx`,
|
`src/lib/scout-export.ts`, `src/lib/cacche.ts`, `src/components/crapp/ScoutEntry.tsx`,
|
||||||
`src/components/crapp/SondaggioCacche.tsx`, `src/routes/scout.tsx`, `src/routes/partita.$id.tsx`
|
`src/components/crapp/SondaggioCacche.tsx`, `src/routes/scout.tsx`, `src/routes/partita.$id.tsx`
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
Permettere a un solo referente per volta di registrare in tempo reale, durante la partita,
|
Permettere a un solo referente per volta di registrare in tempo reale, durante la partita,
|
||||||
un giudizio − / + sui fondamentali (battuta, ricezione, attacco, muro) di ciascun convocato,
|
un giudizio − / + sui fondamentali (battuta, ricezione, attacco, muro) di ciascun convocato,
|
||||||
per set, con salvataggio condiviso su Supabase (non più solo `localStorage`, fix M7) così che tutta la squadra veda lo stato
|
per set, con salvataggio condiviso su Supabase (non più solo `localStorage`, fix M7) così che chi
|
||||||
aggiornato da qualunque dispositivo.
|
riprende lo scout trovi lo stato aggiornato, da qualunque dispositivo.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -20,10 +20,9 @@ aggiornato da qualunque dispositivo.
|
|||||||
|
|
||||||
- `scout_sessioni` — chi ha il controllo dello Scout Live per una partita (una riga per
|
- `scout_sessioni` — chi ha il controllo dello Scout Live per una partita (una riga per
|
||||||
`evento_id`, quindi un solo detentore).
|
`evento_id`, quindi un solo detentore).
|
||||||
- `scout_live` — stato in corso (azioni non ancora concluse) di una sessione.
|
- `scout_live` — stato in corso (valutazioni, set chiusi, avversario e campo) di una sessione.
|
||||||
- `scout_partite` — archivio delle partite scoutate concluse (risultato, parziali, azioni,
|
- `scout_partite` — archivio delle partite scoutate concluse (risultato, parziali, azioni,
|
||||||
valutazioni − / +),
|
valutazioni − / +), mai più modificato una volta salvato (solo eliminabile per intero).
|
||||||
mai più modificato una volta salvato (solo eliminabile per intero).
|
|
||||||
- `cacche_partita` — sondaggio goliardico pre-partita, un voto per giocatore/evento
|
- `cacche_partita` — sondaggio goliardico pre-partita, un voto per giocatore/evento
|
||||||
(`UNIQUE evento_id, giocatore_id`).
|
(`UNIQUE evento_id, giocatore_id`).
|
||||||
|
|
||||||
@@ -66,14 +65,20 @@ perché occupava spazio 6 giorni su 7.
|
|||||||
|
|
||||||
## Cosa registra
|
## Cosa registra
|
||||||
|
|
||||||
La schermata `/scout` ha due passi: **Selezione giocatore** (griglia dei convocati, ordinata per cognome o per numero con il selettore a destra del titolo, default cognome; in ogni riquadro, sotto il nome, i **+** del giocatore nel set in corso, ricezione esclusa; filtrati sulle
|
La schermata `/scout` ha due passi: **Selezione giocatore** e **Valuta** (vedi sotto), seguiti da
|
||||||
risposte "presente"/"ritardo", con fallback a tutta la rosa se nessuno ha risposto) e **2. Valuta**
|
«Chiudi set» e «Fine partita».
|
||||||
(vedi sotto), seguiti da «Ultime azioni», «Chiudi set» e «Fine partita». Salvataggio automatico su
|
|
||||||
`scout_live` **a ogni cambiamento**, senza attesa: i salvataggi vanno in coda, uno alla volta e in ordine, e ognuno scrive lo stato intero (uno riuscito rimedia a uno fallito). Se un salvataggio non riesce compare un avviso fisso che sparisce al salvataggio successivo riuscito.
|
- **Selezione giocatore:** griglia dei convocati (chi ha risposto "presente"/"ritardo", con fallback
|
||||||
|
a tutta la rosa se nessuno ha risposto), ordinata per cognome o per numero con il selettore a
|
||||||
|
destra del titolo (default cognome). In ogni riquadro, sotto il nome, compaiono i **+** del
|
||||||
|
giocatore nel set in corso, ricezione esclusa (`contaPiuSenzaRicezione()`).
|
||||||
|
- **Salvataggio** su `scout_live` a ogni cambiamento, senza attesa: i salvataggi vanno in coda, uno
|
||||||
|
alla volta e in ordine, e ognuno scrive lo stato intero (uno riuscito rimedia a uno fallito). Se
|
||||||
|
un salvataggio non riesce compare un avviso fisso, che sparisce al salvataggio successivo riuscito.
|
||||||
|
|
||||||
**Punteggio e azioni.** L'interfaccia non registra più il punteggio: sono stati tolti i tasti delle
|
**Punteggio e azioni.** L'interfaccia non registra più il punteggio: sono stati tolti i tasti delle
|
||||||
azioni (punto attacco, ace, muro, errore nostro, punto e errore avversario), il tabellone con punti
|
azioni (punto attacco, ace, muro, errore nostro, punto e errore avversario), il tabellone con punti
|
||||||
e set e il blocco avversario / in casa / trasferta. Avversario e campo restano quelli ricavati dal
|
e set, l'elenco «Ultime azioni» e il blocco avversario / in casa / trasferta. Avversario e campo restano quelli ricavati dal
|
||||||
titolo della partita (o dallo stato già salvato) e non si modificano. I tipi di azione
|
titolo della partita (o dallo stato già salvato) e non si modificano. I tipi di azione
|
||||||
(`AzioneTipo`, `scout-store.ts`: `attacco`, `ace`, `muro`, `errore`, `punto_avv`, `errore_avv`),
|
(`AzioneTipo`, `scout-store.ts`: `attacco`, `ace`, `muro`, `errore`, `punto_avv`, `errore_avv`),
|
||||||
i totali `totaliPerGiocatore()` e il campo `scout_partite.azioni` restano nel codice e nei dati:
|
i totali `totaliPerGiocatore()` e il campo `scout_partite.azioni` restano nel codice e nei dati:
|
||||||
@@ -85,10 +90,10 @@ leggono le partite scoutate prima di questa modifica, ma oggi nessuna schermata
|
|||||||
|
|
||||||
Per ogni convocato lo scout può segnare un **−** o un **+** su battuta, ricezione, attacco e muro
|
Per ogni convocato lo scout può segnare un **−** o un **+** su battuta, ricezione, attacco e muro
|
||||||
([DD-053](../decisions/DD-053.md)). Non sono punti: restano fuori dal punteggio, dai parziali e dai
|
([DD-053](../decisions/DD-053.md)). Non sono punti: restano fuori dal punteggio, dai parziali e dai
|
||||||
totali P/A/M/E. Si seleziona il giocatore (primo passo) e si tocca − o + sul fondamentale (passo 2, il cui titolo mostra
|
totali P/A/M/E. Si seleziona il giocatore e si tocca − o + sul fondamentale: il titolo della sezione
|
||||||
cognome e numero del giocatore e il set in corso);
|
mostra cognome e numero del giocatore e il set in corso, e accanto a ogni tasto compare il conteggio
|
||||||
accanto a ogni tasto compare il conteggio di quel giocatore **nel set in corso**. «Annulla» nella
|
di quel giocatore **nel set in corso**. «Annulla» nella sezione toglie l'ultima valutazione del set in
|
||||||
sezione toglie l'ultima valutazione del set in corso (l'«Annulla» in alto agisce solo sulle azioni, quindi oggi è sempre spento).
|
corso (l'«Annulla» in alto agisce solo sulle azioni, quindi oggi è sempre spento).
|
||||||
|
|
||||||
Ogni tocco è una riga `Valutazione` (`giocatoreId`, `fondamentale`, `segno`, `set`, `ts`) in una
|
Ogni tocco è una riga `Valutazione` (`giocatoreId`, `fondamentale`, `segno`, `set`, `ts`) in una
|
||||||
lista separata da `azioni`; i conteggi per set si calcolano con `contaValutazioni()`
|
lista separata da `azioni`; i conteggi per set si calcolano con `contaValutazioni()`
|
||||||
@@ -104,7 +109,7 @@ Senza punteggio, `chiudiSet()` chiude il set (come 0-0) se ha almeno una valutaz
|
|||||||
parziali se ha dati (`parzialiFinali()`, `scout-store.ts`). Con nessuna valutazione risponde
|
parziali se ha dati (`parzialiFinali()`, `scout-store.ts`). Con nessuna valutazione risponde
|
||||||
«Nessun set da salvare». Una partita senza punti ha parziali 0-0 e **non è un risultato**:
|
«Nessun set da salvare». Una partita senza punti ha parziali 0-0 e **non è un risultato**:
|
||||||
`haPunteggio()` la esclude da Home, Classifica e dalla pagina della partita, dove non sostituisce il
|
`haPunteggio()` la esclude da Home, Classifica e dalla pagina della partita, dove non sostituisce il
|
||||||
referto CSI; resta visibile il «Report tecnico» con il CSV.
|
referto CSI; resta visibile il «Scout» con il CSV.
|
||||||
|
|
||||||
`finePartita()` (`scout.tsx`) compone i parziali finali, inserisce la partita in
|
`finePartita()` (`scout.tsx`) compone i parziali finali, inserisce la partita in
|
||||||
`scout_partite` (INSERT, non upsert), poi cancella la riga da `scout_live` (stato consumato)
|
`scout_partite` (INSERT, non upsert), poi cancella la riga da `scout_live` (stato consumato)
|
||||||
@@ -114,11 +119,21 @@ e rilascia la sessione.
|
|||||||
|
|
||||||
## Export CSV
|
## Export CSV
|
||||||
|
|
||||||
`scout-export.ts` genera un CSV (separatore `;`, BOM UTF-8) con parziali, riepilogo per
|
`scout-export.ts` genera un CSV (separatore `;`, BOM UTF-8) con parziali, riepilogo per giocatore e
|
||||||
giocatore e log cronologico delle azioni. Se ci sono valutazioni, aggiunge una tabella per set e giocatore con i
|
log cronologico delle azioni. Se ci sono valutazioni, aggiunge una tabella per set e giocatore con
|
||||||
|
i conteggi di «più» e «meno» di ogni fondamentale (solo per chi ne ha). Scaricabile dagli admin dalla
|
||||||
|
pagina partita, sezione "Scout".
|
||||||
|
|
||||||
- e i − di ogni fondamentale (solo per chi ne ha). Scaricabile dagli admin dalla pagina partita,
|
---
|
||||||
sezione "Report tecnico".
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -139,7 +154,7 @@ calcolati a runtime).
|
|||||||
|
|
||||||
## Regole rispettate
|
## Regole rispettate
|
||||||
|
|
||||||
- **DD-008 (gamification equa)**: i dati tecnici (punti/ace/muri) restano confinati allo Scout
|
- **DD-008 (gamification equa)**: i dati tecnici (punti/ace/muri e valutazioni) restano confinati allo Scout
|
||||||
Live come statistica di squadra e non entrano nel tipo `Giocatore` usato per badge o
|
Live come statistica di squadra e non entrano nel tipo `Giocatore` usato per badge o
|
||||||
classifiche individuali.
|
classifiche individuali.
|
||||||
|
|
||||||
@@ -149,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 «Report tecnico» 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:
|
||||||
@@ -167,5 +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) e, nello scout, in sola lettura quelle dei set chiusi.
|
- Nello scout, in sola lettura, le valutazioni 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.
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "crapp",
|
"name": "crapp",
|
||||||
"version": "1.5.0",
|
"version": "1.6.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "crapp",
|
"name": "crapp",
|
||||||
"version": "1.5.0",
|
"version": "1.6.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/supabase-js": "^2.111.0",
|
"@supabase/supabase-js": "^2.111.0",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "crapp",
|
"name": "crapp",
|
||||||
"version": "1.5.0",
|
"version": "1.6.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -40,11 +40,15 @@ export function SondaggioCacche({
|
|||||||
const media = mediaPartita(righe, eventoId);
|
const media = mediaPartita(righe, eventoId);
|
||||||
const classifica = [...dellaPartita]
|
const classifica = [...dellaPartita]
|
||||||
.sort((a, b) => b.quantita - a.quantita)
|
.sort((a, b) => b.quantita - a.quantita)
|
||||||
.slice(0, 3)
|
|
||||||
.map((r) => {
|
.map((r) => {
|
||||||
const g = squadra.find((g) => g.id === r.giocatore_id);
|
const g = squadra.find((g) => g.id === r.giocatore_id);
|
||||||
return { ...r, nome: g ? nomeCompleto(g) : "—" };
|
return { ...r, nome: g ? nomeCompleto(g) : "—" };
|
||||||
});
|
})
|
||||||
|
// Pari merito = stessa posizione, senza salti (1°, 1°, 2°).
|
||||||
|
.map((r, _, tutti) => ({
|
||||||
|
...r,
|
||||||
|
pos: new Set(tutti.filter((t) => t.quantita > r.quantita).map((t) => t.quantita)).size + 1,
|
||||||
|
}));
|
||||||
|
|
||||||
async function rispondi(quantita: number) {
|
async function rispondi(quantita: number) {
|
||||||
if (!io) return;
|
if (!io) return;
|
||||||
@@ -89,6 +93,7 @@ export function SondaggioCacche({
|
|||||||
? "Sondaggio chiuso: era aperto fino al fischio d'inizio."
|
? "Sondaggio chiuso: era aperto fino al fischio d'inizio."
|
||||||
: "Apre alle 8:00 del giorno della partita: riceverai una notifica."}
|
: "Apre alle 8:00 del giorno della partita: riceverai una notifica."}
|
||||||
</p>
|
</p>
|
||||||
|
<Classifica righe={classifica} io={io?.id} media={media} />
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -126,19 +131,7 @@ export function SondaggioCacche({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 flex flex-wrap items-center gap-2 text-[13px]">
|
<Classifica righe={classifica} io={io?.id} media={media} />
|
||||||
<span className="rounded-full bg-secondary px-2.5 py-1 font-semibold">
|
|
||||||
Media squadra {media}
|
|
||||||
</span>
|
|
||||||
{classifica.map((r, i) => (
|
|
||||||
<span
|
|
||||||
key={r.giocatore_id}
|
|
||||||
className="rounded-full bg-secondary px-2.5 py-1 font-semibold"
|
|
||||||
>
|
|
||||||
{i === 0 ? "🥇" : i === 1 ? "🥈" : "🥉"} {r.nome} · {r.quantita}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{admin ? (
|
{admin ? (
|
||||||
<button
|
<button
|
||||||
@@ -154,3 +147,61 @@ export function SondaggioCacche({
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MEDAGLIE = ["🥇", "🥈", "🥉"];
|
||||||
|
|
||||||
|
type RigaClassifica = { giocatore_id: string; nome: string; quantita: number; pos: number };
|
||||||
|
|
||||||
|
function Classifica({
|
||||||
|
righe,
|
||||||
|
io,
|
||||||
|
media,
|
||||||
|
}: {
|
||||||
|
righe: RigaClassifica[];
|
||||||
|
io: string | undefined;
|
||||||
|
media: number | string;
|
||||||
|
}) {
|
||||||
|
if (righe.length === 0) return null;
|
||||||
|
const max = Math.max(...righe.map((r) => r.quantita), 1);
|
||||||
|
return (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="mb-2 flex items-center justify-between text-[13px]">
|
||||||
|
<span className="font-bold uppercase tracking-wide text-muted-foreground">Classifica</span>
|
||||||
|
<span className="rounded-full bg-secondary px-2.5 py-1 font-semibold">
|
||||||
|
Media squadra {media}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ol className="divide-y divide-border overflow-hidden rounded-2xl bg-secondary/60">
|
||||||
|
{righe.map((r) => (
|
||||||
|
<li
|
||||||
|
key={r.giocatore_id}
|
||||||
|
className={cn(
|
||||||
|
"relative flex items-center gap-3 px-3 py-2.5",
|
||||||
|
r.giocatore_id === io && "bg-accent/15",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className="absolute inset-y-0 left-0 bg-accent/10"
|
||||||
|
style={{ width: `${(r.quantita / max) * 100}%` }}
|
||||||
|
/>
|
||||||
|
<span className="relative w-7 text-center text-[15px] font-bold tabular-nums text-muted-foreground">
|
||||||
|
{r.pos <= 3 ? MEDAGLIE[r.pos - 1] : `${r.pos}°`}
|
||||||
|
</span>
|
||||||
|
<span className="relative min-w-0 flex-1 truncate text-[15px] font-semibold">
|
||||||
|
{r.nome}
|
||||||
|
{r.giocatore_id === io ? (
|
||||||
|
<span className="ml-1.5 text-[13px] font-bold uppercase text-muted-foreground">
|
||||||
|
tu
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
<span className="relative text-[17px] font-bold tabular-nums">
|
||||||
|
{r.quantita === 5 ? "5+" : r.quantita}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -310,11 +310,14 @@ export function SezioneTendina({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAperta((v) => !v)}
|
onClick={() => setAperta((v) => !v)}
|
||||||
className="mb-3 flex min-h-11 w-full items-center justify-between gap-3 text-left active:scale-[0.99]"
|
className={cn(
|
||||||
|
"flex min-h-11 w-full items-center justify-between gap-3 text-left active:scale-[0.99]",
|
||||||
|
(aperta || anteprima) && "mb-3",
|
||||||
|
)}
|
||||||
aria-expanded={aperta}
|
aria-expanded={aperta}
|
||||||
aria-controls={id}
|
aria-controls={id}
|
||||||
>
|
>
|
||||||
<h2 className="font-display-sm text-lg uppercase">{titolo}</h2>
|
<h2 className="font-display-sm text-[20px] uppercase">{titolo}</h2>
|
||||||
<span className="flex shrink-0 items-center gap-2">
|
<span className="flex shrink-0 items-center gap-2">
|
||||||
{azione}
|
{azione}
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
+41
-68
@@ -1,7 +1,7 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { MapPin, Clock, Users, Trophy, Swords, Download } from "lucide-react";
|
import { MapPin, Clock, Users, Trophy, Swords, Download } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Card, PageHeader, Section } from "@/components/crapp/ui-bits";
|
import { Card, PageHeader, Section, SezioneTendina } from "@/components/crapp/ui-bits";
|
||||||
import { formatData } from "@/lib/crapp-data";
|
import { formatData } from "@/lib/crapp-data";
|
||||||
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
import { convocatiEvento, useEvento } from "@/lib/eventi";
|
||||||
import { useRosa } from "@/lib/rosa";
|
import { useRosa } from "@/lib/rosa";
|
||||||
@@ -10,9 +10,10 @@ import { matchDaPartitaCsi, partiteGiocate } from "@/lib/csi-core";
|
|||||||
import { DettaglioCsiEsteso, LogoSquadra, MetaPartitaCsi } from "@/components/crapp/DettaglioCsi";
|
import { DettaglioCsiEsteso, LogoSquadra, MetaPartitaCsi } from "@/components/crapp/DettaglioCsi";
|
||||||
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 { sondaggioAperto } from "@/lib/cacche";
|
||||||
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 +91,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;
|
||||||
@@ -206,9 +206,16 @@ function PartitaDetail() {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{csiMatch ? (
|
{csiMatch ? (
|
||||||
<Section titolo="Formazioni e scontri diretti">
|
<SezioneTendina
|
||||||
|
titolo="Formazioni e scontri diretti"
|
||||||
|
azione={
|
||||||
|
<span className="text-[13px] font-bold uppercase text-muted-foreground">
|
||||||
|
Livescore CSI
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DettaglioCsiEsteso matchId={csiMatch.id} avversario={avversario} />
|
<DettaglioCsiEsteso matchId={csiMatch.id} avversario={avversario} />
|
||||||
</Section>
|
</SezioneTendina>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Section titolo="MVP eletto dalla squadra">
|
<Section titolo="MVP eletto dalla squadra">
|
||||||
@@ -220,9 +227,9 @@ function PartitaDetail() {
|
|||||||
{match ? (
|
{match ? (
|
||||||
// Badge e cacche non si mostrano all'allenatore (DD-034).
|
// Badge e cacche non si mostrano all'allenatore (DD-034).
|
||||||
sonoAllenatore ? null : (
|
sonoAllenatore ? null : (
|
||||||
<Section titolo="Badge votati dai compagni">
|
<SezioneTendina titolo="Badge votati dai compagni">
|
||||||
<VotoSocial matchId={match.id} />
|
<VotoSocial matchId={match.id} />
|
||||||
</Section>
|
</SezioneTendina>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Section titolo="In programma">
|
<Section titolo="In programma">
|
||||||
@@ -238,83 +245,49 @@ function PartitaDetail() {
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
{sonoAllenatore ? null : (
|
{sonoAllenatore ? null : (
|
||||||
<Section titolo="Sondaggio pre-partita">
|
<SezioneTendina
|
||||||
|
titolo="Sondaggio pre-partita"
|
||||||
|
defaultAperta={sondaggioAperto(evento.data, evento.ora)}
|
||||||
|
>
|
||||||
<SondaggioCacche eventoId={evento.id} dataEvento={evento.data} oraEvento={evento.ora} />
|
<SondaggioCacche eventoId={evento.id} dataEvento={evento.data} oraEvento={evento.ora} />
|
||||||
</Section>
|
</SezioneTendina>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{match ? (
|
{match ? (
|
||||||
<Section titolo="Pagelle di fine partita">
|
<SezioneTendina titolo="Pagelle di fine partita">
|
||||||
<Pagelle matchId={match.id} convocati={convocati} chiuse={evento.pagelleChiuse} />
|
<Pagelle
|
||||||
</Section>
|
matchId={match.id}
|
||||||
|
convocati={convocati.filter(
|
||||||
|
(g) => risposte[g.id] !== "assente" && risposte[g.id] !== "infortunato",
|
||||||
|
)}
|
||||||
|
chiuse={evento.pagelleChiuse}
|
||||||
|
/>
|
||||||
|
</SezioneTendina>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{scout && totaliTeam ? (
|
{scout ? (
|
||||||
<Section titolo="Report tecnico">
|
<SezioneTendina
|
||||||
<Card>
|
titolo="Scout"
|
||||||
<div className="grid grid-cols-4 gap-2 text-center">
|
azione={
|
||||||
{[
|
scout.valutazioni.length > 0 ? (
|
||||||
{ l: "Punti", v: totaliTeam.punti },
|
<span className="text-[13px] font-bold tabular-nums text-muted-foreground">
|
||||||
{ l: "Ace", v: totaliTeam.ace },
|
{scout.valutazioni.length} valutazioni
|
||||||
{ 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>
|
||||||
<span className="tabular-nums text-muted-foreground">
|
) : null
|
||||||
{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
|
<ScoutReport scout={scout} rosa={rosa} admin={admin} />
|
||||||
</button>
|
</SezioneTendina>
|
||||||
) : null}
|
|
||||||
</Card>
|
|
||||||
</Section>
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Section titolo="Rosa e presenze">
|
<SezioneTendina titolo="Rosa e presenze" defaultAperta>
|
||||||
<RosaPresenze
|
<RosaPresenze
|
||||||
eventoId={evento.id}
|
eventoId={evento.id}
|
||||||
data={evento.data}
|
data={evento.data}
|
||||||
tipo={evento.tipo}
|
tipo={evento.tipo}
|
||||||
convocati={evento.convocati}
|
convocati={evento.convocati}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</SezioneTendina>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+1
-39
@@ -341,8 +341,6 @@ function ScoutBoard({
|
|||||||
const contiSelezionato = selezionato
|
const contiSelezionato = selezionato
|
||||||
? contaValutazioni(valutazioni, selezionato, setCorrente)
|
? contaValutazioni(valutazioni, selezionato, setCorrente)
|
||||||
: null;
|
: null;
|
||||||
const ultime = [...azioni].slice(-4).reverse();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className="sticky top-0 z-30 bg-hero px-5 pb-4 pt-6 text-primary-foreground">
|
<header className="sticky top-0 z-30 bg-hero px-5 pb-4 pt-6 text-primary-foreground">
|
||||||
@@ -419,7 +417,7 @@ function ScoutBoard({
|
|||||||
<section className="px-5 pt-4">
|
<section className="px-5 pt-4">
|
||||||
<div className="mb-2 flex items-center justify-between gap-2">
|
<div className="mb-2 flex items-center justify-between gap-2">
|
||||||
<p className="text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
<p className="text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
||||||
2. Valuta{giocatoreSel ? ` ${giocatoreSel.cognome} - ${giocatoreSel.numero}` : ""} · Set{" "}
|
Valuta{giocatoreSel ? ` ${giocatoreSel.cognome} - ${giocatoreSel.numero}` : ""} · Set{" "}
|
||||||
{setCorrente}
|
{setCorrente}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
@@ -469,42 +467,6 @@ function ScoutBoard({
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="px-5 pt-4">
|
|
||||||
<div className="rounded-3xl bg-card p-3 shadow-card">
|
|
||||||
<p className="text-[13px] font-bold uppercase tracking-wide text-muted-foreground">
|
|
||||||
Ultime azioni
|
|
||||||
</p>
|
|
||||||
{ultime.length === 0 ? (
|
|
||||||
<p className="mt-2 text-[13px] text-muted-foreground">Nessuna azione registrata.</p>
|
|
||||||
) : (
|
|
||||||
<ul className="mt-2 space-y-1.5">
|
|
||||||
{ultime.map((a, i) => {
|
|
||||||
const g = rosa.find((x) => x.id === a.giocatoreId);
|
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={a.id}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center justify-between gap-2 px-1 text-[13px]",
|
|
||||||
i === 0 && "anim-riga",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="truncate">{g ? `#${g.numero} ${g.nome}` : "Avversario"}</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"shrink-0 rounded-full px-2 py-0.5 text-[13px] font-bold uppercase",
|
|
||||||
azioniMeta[a.tipo].className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{azioniMeta[a.tipo].short}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="grid grid-cols-2 gap-2 px-5 pb-4 pt-4">
|
<section className="grid grid-cols-2 gap-2 px-5 pb-4 pt-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -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