Copre giocatori-squadra e la scelta dei messaggi push palloni

Estrae in palloni-core.ts la logica pura di push-messaggio e
promemoria-palloni (finora mista alle chiamate Supabase), così da
poterla testare senza scrivere sul database. Aggiunge test per le
funzioni pure di giocatori-squadra.ts, finora senza copertura.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 09:39:09 +02:00
co-authored by Claude Sonnet 5
parent aa1c49542b
commit 127e7c76e9
5 changed files with 255 additions and 60 deletions
+60 -12
View File
@@ -1,4 +1,4 @@
import { giocatori } from "./crapp-data";
import { formatData, giocatori } from "./crapp-data";
import type { Evento } from "./eventi";
export type Turno = { evento_id: string; giocatore_id: string; aggiornato_da: string | null };
@@ -32,17 +32,15 @@ export function completaTurni(
}
if (assegnato) return;
const scelto = giocatori
.slice()
.sort((a, b) => {
const ca = conteggio.get(a.id) ?? 0;
const cb = conteggio.get(b.id) ?? 0;
if (ca !== cb) return ca - cb;
const ua = ultimo.get(a.id) ?? -1;
const ub = ultimo.get(b.id) ?? -1;
if (ua !== ub) return ua - ub;
return a.nome.localeCompare(b.nome);
})[0];
const scelto = giocatori.slice().sort((a, b) => {
const ca = conteggio.get(a.id) ?? 0;
const cb = conteggio.get(b.id) ?? 0;
if (ca !== cb) return ca - cb;
const ua = ultimo.get(a.id) ?? -1;
const ub = ultimo.get(b.id) ?? -1;
if (ua !== ub) return ua - ub;
return a.nome.localeCompare(b.nome);
})[0];
if (!scelto) return;
risultato[evento.id] = scelto.id;
@@ -83,3 +81,53 @@ export function oggiISO(): string {
const dd = String(d.getDate()).padStart(2, "0");
return `${d.getFullYear()}-${mm}-${dd}`;
}
/** Chi deve ricevere l'avviso push, oggi: chi porta i palloni e chi li riprende. */
export function destinatariPromemoriaPalloni(
turni: Record<string, string>,
eventi: Evento[],
oggi: string,
): string[] {
const destinatari = new Set<string>();
for (const evento of eventiDelGiorno(eventi, oggi)) {
const incaricato = turni[evento.id];
if (incaricato) destinatari.add(incaricato);
const prima = eventoPrecedente(eventi, evento.id);
const precedente = prima ? turni[prima.id] : undefined;
if (precedente) destinatari.add(precedente);
}
return [...destinatari];
}
/** Testo del push per un giocatore: priorità a "riporta oggi", poi "tocca a te", poi generico. */
export function messaggioPalloniOggi(
turni: Record<string, string>,
eventi: Evento[],
oggi: string,
mioId: string,
nome: string,
): { title: string; body: string } {
for (const evento of eventiDelGiorno(eventi, oggi)) {
const prima = eventoPrecedente(eventi, evento.id);
if (prima && turni[prima.id] === mioId) {
return {
title: "Porta i palloni oggi",
body: `${evento.titolo} · ${evento.ora}. I palloni li hai tu dalla volta scorsa.`,
};
}
if (turni[evento.id] === mioId) {
const dopo = eventoSuccessivo(eventi, evento.id);
return {
title: "Tocca a te prendere i palloni",
body: dopo
? `A fine ${evento.titolo} porta a casa i palloni e riportali il ${formatData(dopo.data)}.`
: `A fine ${evento.titolo} porta a casa i palloni.`,
};
}
}
return {
title: "CrAPP · Turno palloni",
body: nome ? `${nome}, controlla il turno palloni nel calendario.` : "Controlla il calendario.",
};
}
+5 -13
View File
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router";
import { completaTurni, eventiPalloni, eventoPrecedente, oggiISO } from "@/lib/palloni-core";
import { completaTurni, destinatariPromemoriaPalloni, oggiISO } from "@/lib/palloni-core";
import { inviaPush } from "@/lib/webpush.server";
import { leggiEventi } from "@/lib/eventi.server";
@@ -18,22 +18,14 @@ export const Route = createFileRoute("/api/public/promemoria-palloni")({
const turni = completaTurni(salvati, eventi);
const oggi = oggiISO();
const destinatari = new Set<string>();
for (const evento of eventiPalloni(eventi)) {
if (evento.data !== oggi) continue;
const incaricato = turni[evento.id];
if (incaricato) destinatari.add(incaricato);
const prima = eventoPrecedente(eventi, evento.id);
const precedente = prima ? turni[prima.id] : undefined;
if (precedente) destinatari.add(precedente);
}
const destinatari = destinatariPromemoriaPalloni(turni, eventi, oggi);
if (destinatari.size === 0) return Response.json({ inviate: 0 });
if (destinatari.length === 0) return Response.json({ inviate: 0 });
const { data: iscrizioni } = await supabaseAdmin
.from("push_subscriptions")
.select("endpoint, giocatore_id")
.in("giocatore_id", [...destinatari]);
.in("giocatore_id", destinatari);
let inviate = 0;
for (const iscrizione of iscrizioni ?? []) {
@@ -56,4 +48,4 @@ export const Route = createFileRoute("/api/public/promemoria-palloni")({
},
},
},
});
});
+6 -34
View File
@@ -1,13 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";
import { formatData, giocatori } from "@/lib/crapp-data";
import {
completaTurni,
eventiPalloni,
eventoPrecedente,
eventoSuccessivo,
oggiISO,
} from "@/lib/palloni-core";
import { giocatori } from "@/lib/crapp-data";
import { completaTurni, messaggioPalloniOggi, oggiISO } from "@/lib/palloni-core";
import { leggiEventi } from "@/lib/eventi.server";
const schema = z.object({ endpoint: z.string().url().max(1000) });
@@ -40,7 +34,8 @@ export const Route = createFileRoute("/api/public/push-messaggio")({
.eq("endpoint", parsed.data.endpoint)
.maybeSingle();
if (!iscrizione) return Response.json({ title: "CrAPP", body: "Controlla il turno palloni." });
if (!iscrizione)
return Response.json({ title: "CrAPP", body: "Controlla il turno palloni." });
const { data: righe } = await supabaseAdmin
.from("turni_palloni")
@@ -54,31 +49,8 @@ export const Route = createFileRoute("/api/public/push-messaggio")({
const mioId = iscrizione.giocatore_id;
const nome = giocatori.find((g) => g.id === mioId)?.nome ?? "";
for (const evento of eventiPalloni(eventi)) {
if (evento.data !== oggi) continue;
const prima = eventoPrecedente(eventi, evento.id);
if (prima && turni[prima.id] === mioId) {
return Response.json({
title: "Porta i palloni oggi",
body: `${evento.titolo} · ${evento.ora}. I palloni li hai tu dalla volta scorsa.`,
});
}
if (turni[evento.id] === mioId) {
const dopo = eventoSuccessivo(eventi, evento.id);
return Response.json({
title: "Tocca a te prendere i palloni",
body: dopo
? `A fine ${evento.titolo} porta a casa i palloni e riportali il ${formatData(dopo.data)}.`
: `A fine ${evento.titolo} porta a casa i palloni.`,
});
}
}
return Response.json({
title: "CrAPP · Turno palloni",
body: nome ? `${nome}, controlla il turno palloni nel calendario.` : "Controlla il calendario.",
});
return Response.json(messaggioPalloniOggi(turni, eventi, oggi, mioId, nome));
},
},
},
});
});