Files
CRAPP/src/lib/rosa.ts
T
davideandClaude Sonnet 5.5 bfb768fb5a Rimuove la serie «Conferme 24h» e i badge collegati
Toglie la serie dal profilo, dalla Home e dalle notifiche di traguardo, insieme
ai badge «Risposta lampo» e «Mai un forfait» e al calcolo di serieConferme.
La colonna risposto_il e il suo trigger restano nel database, inutilizzati.
Test e documentazione allineati.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 13:21:14 +02:00

178 lines
6.7 KiB
TypeScript

import { useMemo } from "react";
import type { Giocatore } from "./crapp-data";
import { inRosa, nomeCompleto, useGiocatoriSquadra } from "./giocatori-squadra";
import { mvpVintiPerGiocatore, useVotiMvp } from "./mvp-voti";
import { mediePagelle, usePagelle } from "./pagelle";
import { statisticheCacche, useCacche } from "./cacche";
import { conteggioTurni, serieConsecutivaPalloni } from "./palloni-core";
import { useTurniPalloni } from "./palloni";
import { useInfortuniERitardi } from "./infortuni";
import { useGiocatoreId } from "./user-store";
import { useEventi } from "./eventi";
import {
contaPartiteGiocate,
contaPresenzeGiocatore,
serieConsecutiva,
totaliEventiGiocatore,
useRispostePresenze,
} from "./presenze";
import { obiettiviOrdinati } from "./obiettivi";
import { useCsi } from "./csi";
import { partiteGiocate } from "./csi-core";
function iniziali(nome: string, cognome: string): string {
return `${nome[0] ?? ""}${cognome[0] ?? ""}`.toUpperCase();
}
/**
* Solo anagrafica (id, nome, ruolo, numero, data di nascita) dei giocatori attivi — es.
* per le liste presenze o lo scout. A differenza di `useRosa` non legge MVP, pagelle,
* cacche, palloni né infortuni: evita di montare quei cinque hook e il relativo `useMemo`
* solo per l'anagrafica. Con `conAllenatori` include anche gli allenatori attivi, per chi
* elenca i membri della squadra e non solo chi gioca (compleanni in Calendario, DD-034).
*/
export function useAnagraficaRosa({
conAllenatori = false,
}: { conAllenatori?: boolean } = {}): Array<
Pick<Giocatore, "id" | "nome" | "ruolo" | "numero" | "nascita">
> {
const { righe: squadra } = useGiocatoriSquadra();
return useMemo(
() =>
squadra
.filter((g) => (conAllenatori ? g.attivo : inRosa(g)))
.map((g) => ({
id: g.id,
nome: nomeCompleto(g),
ruolo: g.ruolo,
numero: g.numero,
nascita: g.nascita ?? "",
})),
[squadra, conAllenatori],
);
}
/**
* Rosa completa con tutte le statistiche personali (presenze, MVP, media voto,
* palloni, infortuni, ritardi, cacche). Legge l'anagrafica da `giocatori_squadra`
* (DD-015): solo i giocatori attivi, gli altri restano nel database ma spariscono
* dagli elenchi correnti. Gli allenatori non ci sono (DD-034): non hanno statistiche. Usa solo cache già in memoria: nessuna query aggiuntiva
* rispetto a quelle che l'app fa comunque.
*/
export function useRosa(): Giocatore[] {
const { righe: squadra } = useGiocatoriSquadra();
const voti = useVotiMvp();
const { voti: pagelle } = usePagelle();
const { righe: cacche } = useCacche();
const { salvati: turniSalvati } = useTurniPalloni();
const { infortuni, ritardi } = useInfortuniERitardi();
const { eventi } = useEventi();
const { presenze: mappaPresenze } = useRispostePresenze();
const votiMvp = voti.data;
return useMemo(() => {
const medie = mediePagelle(pagelle);
const statCacche = statisticheCacche(cacche);
// Solo i turni confermati, non le proposte automatiche di completaTurni(): il badge deve
// premiare chi ha davvero portato i palloni, non chi l'algoritmo di rotazione ha
// scelto per un evento passato senza che nessuno confermasse nulla.
const palloni = conteggioTurni(turniSalvati, eventi);
const mvpVinti = mvpVintiPerGiocatore(votiMvp ?? []);
return squadra.filter(inRosa).map((g) => ({
id: g.id,
nome: nomeCompleto(g),
numero: g.numero,
ruolo: g.ruolo,
nascita: g.nascita ?? "",
iniziali: iniziali(g.nome, g.cognome),
presenze: contaPresenzeGiocatore(g.id, eventi, mappaPresenze),
totaliEventi: totaliEventiGiocatore(g.id, eventi),
partiteGiocate: contaPartiteGiocate(g.id, eventi, mappaPresenze),
streak: serieConsecutiva(g.id, eventi, mappaPresenze),
serieAllenamenti: serieConsecutiva(g.id, eventi, mappaPresenze, "allenamento"),
seriePartite: serieConsecutiva(g.id, eventi, mappaPresenze, "partita"),
mvp: mvpVinti[g.id] ?? 0,
mediaVoto: medie[g.id]?.media ?? 0,
votiPagella: medie[g.id]?.voti ?? 0,
palloni: palloni[g.id] ?? 0,
seriePalloni: serieConsecutivaPalloni(g.id, turniSalvati, eventi),
cacche: statCacche[g.id]?.giornateTop ?? 0,
cacchePartita: statCacche[g.id]?.media ?? 0,
infortuni: infortuni[g.id] ?? 0,
ritardi: ritardi[g.id] ?? 0,
}));
}, [squadra, votiMvp, pagelle, cacche, turniSalvati, infortuni, ritardi, eventi, mappaPresenze]);
}
/** Criteri di ordinamento della classifica interna di Squadra. */
export type CriterioClassifica = "presenze" | "mediaVoto" | "mvp" | "palloni" | "cacchePartita";
/**
* Dettaglio mostrato sotto il nome nella classifica interna, coerente col criterio
* selezionato: mostrare sempre le "presenze consecutive" aveva senso solo per Presenze,
* per gli altri criteri era un dato fuorviante perché scollegato dal valore in classifica.
*/
export function dettaglioClassifica(
g: {
streak: number;
votiPagella: number;
partiteGiocate: number;
cacche: number;
seriePalloni: number;
},
criterio: CriterioClassifica,
): string {
switch (criterio) {
case "mediaVoto":
return `${g.votiPagella} voti pagella`;
case "mvp":
return `${g.partiteGiocate} partite giocate`;
case "cacchePartita":
return `${g.cacche} giornate top`;
case "palloni":
return `${g.seriePalloni} volte consecutive`;
default:
return `${g.streak} presenze consecutive`;
}
}
/**
* Posizione in classifica ("dense rank"): a parità di valore i giocatori condividono la
* stessa posizione e il numero successivo non salta (1, 1, 2 — non 1, 1, 3). `valori` deve
* essere già ordinato in modo decrescente, coerente con l'ordine visualizzato.
*/
export function classificaRank(valori: number[]): number[] {
const rank: number[] = [];
for (let i = 0; i < valori.length; i++) {
if (i > 0 && valori[i] === valori[i - 1]) {
rank.push(rank[i - 1] ?? 1);
} else {
rank.push((rank[i - 1] ?? 0) + 1);
}
}
return rank;
}
/** Il giocatore selezionato sul dispositivo, con le statistiche complete. */
export function useIo(): Giocatore | null {
const id = useGiocatoreId();
const rosa = useRosa();
if (!id) return null;
return rosa.find((g) => g.id === id) ?? null;
}
/** Obiettivi collaborativi calcolati sui dati reali già in cache. */
export function useObiettivi() {
const rosa = useRosa();
const { eventi } = useEventi();
const { presenze } = useRispostePresenze();
const { voti: pagelle } = usePagelle();
const { data: csi } = useCsi();
const vittorie = csi
? partiteGiocate(csi.partite).filter((p) => (p.setNostri ?? 0) > (p.setLoro ?? 0)).length
: 0;
return obiettiviOrdinati(rosa, { eventi, presenze, pagelle, vittorie });
}