Toglie la proposta automatica dei palloni sugli allenamenti.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,12 @@ qui: sta in [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
## Versione attuale — agosto 2026
|
||||
|
||||
### Palloni: allenamenti senza proposta automatica
|
||||
|
||||
- `completaTurni` non assegna più gli allenamenti: restano «da assegnare» finché non si
|
||||
sceglie a mano. Le partite tengono la rotazione. Migration M10 cancella eventuali turni
|
||||
salvati su allenamenti da oggi in poi.
|
||||
|
||||
### Profilo: etichetta notifiche allineata al comportamento
|
||||
|
||||
- L’interruttore in Impostazioni non è più «Notifiche turno palloni»: iscrive il dispositivo
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ non in questo file.
|
||||
|
||||
| Tabella | Scopo | Note |
|
||||
| -------------------- | --------------------------------------------- | ---- |
|
||||
| `turni_palloni` | Gestione dei turni palloni. | |
|
||||
| `turni_palloni` | Gestione dei turni palloni. | Solo turni **confermati**. Gli allenamenti non ricevono proposta automatica (vedi [palloni.md](modules/palloni.md)); M10 azzera i turni salvati su allenamenti da oggi in poi. |
|
||||
| `push_subscriptions` | Dispositivi registrati per le notifiche Push. | |
|
||||
| `promemoria_push` | Storico dei promemoria inviati. | |
|
||||
|
||||
|
||||
@@ -25,14 +25,16 @@ compaiono.
|
||||
|
||||
## Implementazione
|
||||
|
||||
- `completaTurni()` (`palloni-core.ts`) propone, per ogni evento senza turno già salvato, il
|
||||
candidato con meno turni fatti, poi quello che non lo fa da più tempo, poi per ordine
|
||||
alfabetico — un algoritmo greedy, non un ordine fisso né solo per data.
|
||||
- `completaTurni()` (`palloni-core.ts`) propone, per ogni **partita** o evento extra senza
|
||||
turno già salvato, il candidato con meno turni fatti, poi quello che non lo fa da più
|
||||
tempo, poi per ordine alfabetico — un algoritmo greedy, non un ordine fisso né solo per
|
||||
data. Gli **allenamenti** non ricevono proposta automatica: restano «da assegnare» finché
|
||||
qualcuno non sceglie un incaricato in `TurnoPalloni` (scelta della squadra).
|
||||
- `useAssegnaTurno()` (`palloni.ts`) conferma una proposta o riassegna manualmente, con
|
||||
upsert su `evento_id`.
|
||||
- Il conteggio "quante volte hai portato i palloni" mostrato nel profilo e nei badge è
|
||||
ricalcolato a runtime da `conteggioTurni()` su turni salvati **più proposte non ancora
|
||||
confermate** — non è uno storico in tabella dedicata.
|
||||
confermate** (partite/eventi) — non è uno storico in tabella dedicata.
|
||||
- `TurnoPalloni.tsx` mostra/assegna il turno sulla card di un evento; `PromemoriaPalloni.tsx`
|
||||
è il banner in Home per il giocatore di turno.
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ export function eventiPalloni(eventi: Evento[]): Evento[] {
|
||||
/**
|
||||
* Completa i turni mancanti proponendo, a rotazione, chi ha portato i palloni
|
||||
* meno volte (a parità, chi non lo fa da più tempo).
|
||||
*
|
||||
* Gli **allenamenti** non vengono proposti: restano «da assegnare» finché qualcuno
|
||||
* non conferma un incaricato a mano. Su partite ed eventi extra la rotazione resta.
|
||||
*/
|
||||
export function completaTurni(
|
||||
turni: Record<string, string>,
|
||||
@@ -35,6 +38,8 @@ export function completaTurni(
|
||||
return;
|
||||
}
|
||||
if (assegnato) return;
|
||||
// Allenamenti: niente proposta automatica — li assegna la squadra a mano.
|
||||
if (evento.tipo === "allenamento") return;
|
||||
|
||||
const scelto = rosa.slice().sort((a, b) => {
|
||||
const ca = conteggio.get(a.id) ?? 0;
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ async function fetchTurni(): Promise<Record<string, string>> {
|
||||
return mappa;
|
||||
}
|
||||
|
||||
/** Turni salvati + proposta automatica a rotazione per gli eventi non ancora assegnati. */
|
||||
/** Turni salvati + proposta automatica (solo partite/eventi) per i mancanti. */
|
||||
export function useTurniPalloni() {
|
||||
// Cambia raramente: una lettura per sessione è sufficiente.
|
||||
const query = useQuery({ queryKey: TURNI_KEY, queryFn: fetchTurni, staleTime: 30 * 60_000 });
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
-- M10: gli allenamenti non devono più avere turni palloni pre-assegnati.
|
||||
-- La proposta automatica lato app non li riempie più; qui si azzerano eventuali
|
||||
-- eventuali già salvati su allenamenti da oggi in poi, così restano «da assegnare».
|
||||
|
||||
DELETE FROM public.turni_palloni AS t
|
||||
USING public.eventi_app AS e
|
||||
WHERE t.evento_id = e.id
|
||||
AND e.tipo = 'allenamento'
|
||||
AND e.data >= (CURRENT_DATE AT TIME ZONE 'Europe/Rome')::date;
|
||||
@@ -60,25 +60,26 @@ assert.deepEqual(
|
||||
"il compleanno dello stesso giorno non richiede palloni",
|
||||
);
|
||||
|
||||
// --- completaTurni: assegna i mancanti, rispetta quelli già decisi -----------
|
||||
// --- completaTurni: niente proposta sugli allenamenti; partite sì ------------
|
||||
const turni = completaTurni({ e2: "g5" }, eventi, rosa);
|
||||
assert.equal(turni["e2"], "g5", "il turno già assegnato non viene toccato");
|
||||
assert.equal(Object.keys(turni).length, 3, "tutti gli eventi hanno un incaricato");
|
||||
assert.ok(
|
||||
giocatori.some((g) => g.id === turni["e1"]),
|
||||
"assegna a un giocatore reale",
|
||||
);
|
||||
assert.notEqual(turni["e1"], turni["e3"], "non tocca due volte di fila alla stessa persona");
|
||||
assert.equal(turni["e1"], undefined, "allenamento senza salvataggio resta da assegnare");
|
||||
assert.equal(turni["e3"], undefined, "allenamento futuro resta da assegnare");
|
||||
assert.equal(Object.keys(turni).length, 1, "solo i turni salvati / partite proposte");
|
||||
|
||||
const conAllenamentoSalvato = completaTurni({ e1: "g3", e2: "g5" }, eventi, rosa);
|
||||
assert.equal(conAllenamentoSalvato["e1"], "g3", "un allenamento salvato resta");
|
||||
assert.equal(conAllenamentoSalvato["e2"], "g5");
|
||||
|
||||
const molti = Array.from({ length: giocatori.length + 2 }, (_, i) =>
|
||||
evento(`x${i}`, `2026-10-${String(i + 1).padStart(2, "0")}`),
|
||||
evento(`x${i}`, `2026-10-${String(i + 1).padStart(2, "0")}`, "partita"),
|
||||
);
|
||||
const rotazione = conteggioTurni(completaTurni({}, molti, rosa));
|
||||
const carichi = Object.values(rotazione);
|
||||
assert.equal(
|
||||
Math.max(...carichi) - Math.min(...carichi),
|
||||
1,
|
||||
"su un giro completo il carico resta bilanciato",
|
||||
"su un giro completo di partite il carico resta bilanciato",
|
||||
);
|
||||
assert.equal(
|
||||
Object.keys(rotazione).length,
|
||||
@@ -86,10 +87,20 @@ assert.equal(
|
||||
"nessuno viene saltato prima che tutti abbiano fatto un turno",
|
||||
);
|
||||
|
||||
const soloAllenamenti = Array.from({ length: 5 }, (_, i) =>
|
||||
evento(`a${i}`, `2026-11-${String(i + 1).padStart(2, "0")}`),
|
||||
);
|
||||
assert.deepEqual(
|
||||
completaTurni({}, soloAllenamenti, rosa),
|
||||
{},
|
||||
"solo allenamenti: nessuna proposta automatica",
|
||||
);
|
||||
|
||||
// Un turno salvato per un giocatore non più in rosa non deve rompere il conteggio.
|
||||
const conFantasma = completaTurni({ e1: "gXX" }, eventi, rosa);
|
||||
assert.equal(conFantasma["e1"], "gXX", "il turno storico resta com'è");
|
||||
assert.equal(Object.keys(conFantasma).length, 3);
|
||||
assert.ok(conFantasma["e2"], "la partita riceve comunque una proposta");
|
||||
assert.equal(conFantasma["e3"], undefined, "l'allenamento senza salvataggio resta vuoto");
|
||||
|
||||
// --- conteggioTurni ----------------------------------------------------------
|
||||
assert.deepEqual(conteggioTurni({ a: "g1", b: "g1", c: "g2" }), { g1: 2, g2: 1 });
|
||||
|
||||
Reference in New Issue
Block a user