c6eb493f25
Aggiunge un'icona nera (nuovo asset serv-nero.png, più visibile del serv.png bianco su sfondo chiaro) accanto al nome della squadra che serviva all'inizio di ogni set nel referto stampabile. Corregge inoltre generaReferto: la stampa partiva subito dopo document.close(), prima che l'icona (risorsa esterna asincrona) finisse di caricare, risultando in un riquadro vuoto. Ora attende l'evento 'load' della finestra popup prima di chiamare print().
231 lines
11 KiB
JavaScript
231 lines
11 KiB
JavaScript
function vincitoreSet(s) {
|
||
if (s.vinc === 'h' || s.vinc === 'g') return s.vinc
|
||
let h = 0, g = 0
|
||
for (const c of s.ris) c === 'h' ? h++ : g++
|
||
if (h > g) return 'h'
|
||
if (g > h) return 'g'
|
||
return null
|
||
}
|
||
|
||
// Costruisce l'HTML del referto (funzione pura, testabile).
|
||
// `now` è iniettabile per rendere deterministica la data nei test.
|
||
export function buildRefertoHtml(state, now = new Date()) {
|
||
const { sp, modalitaPartita } = state
|
||
const { nomi, striscia } = sp
|
||
|
||
const setReali = striscia.filter(s => !s._phantom)
|
||
|
||
const setVinti = { home: 0, guest: 0 }
|
||
for (const s of setReali) {
|
||
const v = vincitoreSet(s)
|
||
if (v === 'h') setVinti.home++
|
||
else if (v === 'g') setVinti.guest++
|
||
}
|
||
|
||
const dataOra = now.toLocaleString('it-IT', {
|
||
day: '2-digit', month: '2-digit', year: 'numeric',
|
||
hour: '2-digit', minute: '2-digit',
|
||
})
|
||
|
||
const giocatoreHtml = (n) => `<div class="giocatore">${n}</div>`
|
||
|
||
const formazioneHtml = (formazioneSet) => {
|
||
if (!formazioneSet) return '<em style="color:#999;font-size:11px">non disponibile</em>'
|
||
return `
|
||
<div class="form-row">
|
||
<div class="form-team">
|
||
<div class="form-team-name">${nomi.home}</div>
|
||
<div class="giocatori">${formazioneSet.home.map(giocatoreHtml).join('')}</div>
|
||
</div>
|
||
<div class="form-team">
|
||
<div class="form-team-name">${nomi.guest}</div>
|
||
<div class="giocatori">${formazioneSet.guest.map(giocatoreHtml).join('')}</div>
|
||
</div>
|
||
</div>`
|
||
}
|
||
|
||
const nomeTeam = (team) => team === 'home' ? nomi.home : nomi.guest
|
||
|
||
const eventiHtml = (s) => {
|
||
const eventi = [
|
||
...(s.timeouts ?? []).map(t => ({ tipo: 'timeout', ...t })),
|
||
...(s.cambi ?? []).map(c => ({ tipo: 'cambio', ...c })),
|
||
].sort((a, b) => (a.punteggio.home + a.punteggio.guest) - (b.punteggio.home + b.punteggio.guest))
|
||
|
||
if (eventi.length === 0) return ''
|
||
|
||
const righe = eventi.map(e => {
|
||
const sul = `sul ${e.punteggio.home}-${e.punteggio.guest}`
|
||
const testo = e.tipo === 'timeout'
|
||
? `Time out <strong>${nomeTeam(e.team)}</strong> ${sul}`
|
||
: `Cambio <strong>${nomeTeam(e.team)}</strong>: entra ${e.in} per ${e.out} ${sul}`
|
||
return `<li class="evento evento-${e.tipo}">${testo}</li>`
|
||
}).join('')
|
||
|
||
return `
|
||
<div class="eventi-set">
|
||
<div class="eventi-label">Cambi e time out</div>
|
||
<ul class="eventi-lista">${righe}</ul>
|
||
</div>`
|
||
}
|
||
|
||
const setsHtml = setReali.map((s, i) => {
|
||
let h = 0, g = 0
|
||
const punti = []
|
||
for (const c of s.ris) {
|
||
c === 'h' ? h++ : g++
|
||
punti.push({ chi: c, h, g })
|
||
}
|
||
|
||
const vinc = vincitoreSet(s)
|
||
const nomeVinc = vinc === 'h' ? nomi.home : vinc === 'g' ? nomi.guest : ''
|
||
const etichettaVinc = nomeVinc ? ` · vinto da <strong>${nomeVinc}</strong>` : ''
|
||
|
||
const servIcon = '<img src="/serv-nero.png" class="serv-icon" alt="Servizio">'
|
||
const servHomeIcon = s.serv === 'h' ? servIcon : ''
|
||
const servGuestIcon = s.serv === 'g' ? servIcon : ''
|
||
|
||
const puntiHtml = punti.map(p =>
|
||
`<span class="punto punto-${p.chi}">${p.h}-${p.g}</span>`
|
||
).join('')
|
||
|
||
return `
|
||
<div class="set-section">
|
||
<div class="set-barra"><span>SET</span><span class="set-numero">${i + 1}</span></div>
|
||
<div class="set-corpo">
|
||
<div class="set-header">
|
||
${servHomeIcon}${nomi.home} <strong>${h}</strong> · <strong>${g}</strong> ${nomi.guest}${servGuestIcon}${etichettaVinc}
|
||
</div>
|
||
<div class="form-inizio">
|
||
<div class="form-inizio-label">Formazione di partenza</div>
|
||
${formazioneHtml(s.formInizio)}
|
||
</div>${eventiHtml(s)}
|
||
<div class="punti-grid">${puntiHtml || '<em style="color:#999;font-size:11px">Nessun punto registrato</em>'}</div>
|
||
</div>
|
||
</div>`
|
||
}).join('')
|
||
|
||
const risultatoSetHtml = setReali.map((s, i) => {
|
||
let h = 0, g = 0
|
||
for (const c of s.ris) c === 'h' ? h++ : g++
|
||
const vinc = vincitoreSet(s)
|
||
return `<tr>
|
||
<td class="ris-set-n">${i + 1}</td>
|
||
<td class="ris-set-p ${vinc === 'h' ? 'ris-vinc' : ''}">${h}</td>
|
||
<td class="ris-set-p ${vinc === 'g' ? 'ris-vinc' : ''}">${g}</td>
|
||
</tr>`
|
||
}).join('')
|
||
|
||
const html = `<!DOCTYPE html>
|
||
<html lang="it">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Referto — ${nomi.home} vs ${nomi.guest}</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
:root { --teal: #23b8dc; --teal-dark: #0f8faf; }
|
||
body { font-family: Arial, Helvetica, sans-serif; color: #111; background: #fff; padding: 20px; font-size: 14px; }
|
||
@media print { body { padding: 8px; } }
|
||
|
||
.foglio { border: 3px solid #111; padding: 14px; }
|
||
|
||
.header { display: flex; align-items: center; justify-content: space-between; gap: 16px; border: 2px solid #111; padding: 10px 16px; margin-bottom: 14px; }
|
||
.titolo { font-size: 15px; font-weight: bold; letter-spacing: 2px; text-transform: uppercase; }
|
||
.meta { font-size: 11px; color: #333; margin-top: 3px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||
|
||
.squadre-row { display: flex; align-items: center; gap: 14px; }
|
||
.badge-ab { display: inline-flex; align-items: center; justify-content: center; height: 22px; padding: 0 8px; border-radius: 11px; border: 2px solid #111; font-weight: bold; font-size: 12px; flex-shrink: 0; }
|
||
.nome-sq { font-size: 18px; font-weight: bold; }
|
||
.vs { font-size: 12px; color: #666; font-weight: bold; }
|
||
|
||
.risultato-riepilogo { display: flex; align-items: center; gap: 10px; }
|
||
.risultato { font-size: 30px; font-weight: bold; letter-spacing: 3px; }
|
||
.modalita-label { font-size: 10px; color: #555; text-transform: uppercase; letter-spacing: 0.5px; }
|
||
|
||
.set-section { display: flex; margin-bottom: 10px; border: 2px solid #111; }
|
||
.set-barra { flex: 0 0 26px; background: var(--teal); color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; border-right: 2px solid #111; }
|
||
.set-barra span:first-child { writing-mode: vertical-rl; transform: rotate(180deg); font-weight: bold; font-size: 11px; letter-spacing: 2px; }
|
||
.set-numero { font-weight: bold; font-size: 15px; }
|
||
.set-corpo { flex: 1; min-width: 0; }
|
||
.set-header { background: #eaf7fb; padding: 6px 12px; font-size: 13px; border-bottom: 1px solid #111; }
|
||
.serv-icon { width: 12px; height: 12px; vertical-align: middle; margin: 0 3px; }
|
||
|
||
.punti-grid { display: flex; flex-wrap: wrap; gap: 0; padding: 0; border-top: 1px solid #111; }
|
||
.punto { display: inline-flex; align-items: center; justify-content: center; min-width: 34px; padding: 3px 4px; font-size: 11px; font-family: 'Courier New', monospace; white-space: nowrap; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; }
|
||
.punto-h { background: #d0e8ff; color: #003a6e; }
|
||
.punto-g { background: #ffddc0; color: #6e2700; }
|
||
|
||
.eventi-set { padding: 6px 12px; border-bottom: 1px solid #111; }
|
||
.eventi-label { font-size: 10px; font-weight: bold; letter-spacing: 0.5px; text-transform: uppercase; color: #0f8faf; margin-bottom: 4px; }
|
||
.eventi-lista { list-style: none; }
|
||
.evento { font-size: 12px; padding: 1px 0; }
|
||
|
||
.form-inizio { padding: 6px 12px; border-bottom: 1px solid #111; background: #fafafa; }
|
||
.form-inizio-label { font-size: 10px; font-weight: bold; letter-spacing: 0.5px; text-transform: uppercase; color: #0f8faf; margin-bottom: 6px; }
|
||
.form-row { display: flex; gap: 40px; }
|
||
.form-team { flex: 1; }
|
||
.form-team-name { font-weight: bold; font-size: 12px; margin-bottom: 5px; }
|
||
.giocatori { display: flex; gap: 5px; flex-wrap: wrap; }
|
||
.giocatore { background: #fff; border-radius: 50%; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 11px; border: 2px solid #111; }
|
||
|
||
.finale { display: flex; gap: 14px; align-items: flex-start; margin-top: 4px; }
|
||
.finale-box { border: 2px solid #111; flex: 0 0 220px; }
|
||
.finale-box-header { background: var(--teal); color: #fff; text-align: center; font-weight: bold; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; padding: 5px; }
|
||
.finale-tabella { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||
.finale-tabella th, .finale-tabella td { border: 1px solid #111; padding: 4px 8px; text-align: center; }
|
||
.finale-tabella th { background: #eaf7fb; font-size: 10px; text-transform: uppercase; }
|
||
.ris-vinc { font-weight: bold; background: #d0e8ff; }
|
||
.finale-vince { padding: 8px; text-align: center; font-size: 14px; font-weight: bold; border-top: 2px solid #111; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="foglio">
|
||
<div class="header">
|
||
<div>
|
||
<div class="titolo">Referto di Gara</div>
|
||
<div class="meta">${dataOra} · Modalità: ${modalitaPartita}</div>
|
||
</div>
|
||
<div class="squadre-row">
|
||
<span class="badge-ab">Casa</span>
|
||
<span class="nome-sq">${nomi.home}</span>
|
||
<span class="vs">vs</span>
|
||
<span class="nome-sq">${nomi.guest}</span>
|
||
<span class="badge-ab">Ospiti</span>
|
||
</div>
|
||
</div>
|
||
|
||
${setsHtml}
|
||
|
||
<div class="finale">
|
||
<div class="finale-box">
|
||
<div class="finale-box-header">Risultato finale</div>
|
||
<table class="finale-tabella">
|
||
<thead><tr><th>Set</th><th>${nomi.home}</th><th>${nomi.guest}</th></tr></thead>
|
||
<tbody>${risultatoSetHtml}</tbody>
|
||
</table>
|
||
<div class="finale-vince">Vince ${setVinti.home >= setVinti.guest ? nomi.home : nomi.guest} ${setVinti.home} – ${setVinti.guest}</div>
|
||
</div>
|
||
<div class="risultato-riepilogo">
|
||
<div class="risultato">${setVinti.home} – ${setVinti.guest}</div>
|
||
<div class="modalita-label">set vinti</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>`
|
||
|
||
return html
|
||
}
|
||
|
||
// Apre il referto in una nuova scheda e avvia la stampa (effetto collaterale,
|
||
// solo browser). La generazione dell'HTML è delegata a buildRefertoHtml.
|
||
export function generaReferto(state) {
|
||
const html = buildRefertoHtml(state)
|
||
const w = window.open('', '_blank')
|
||
w.document.write(html)
|
||
w.document.close()
|
||
// Aspetta il caricamento delle immagini (icona servizio) prima di stampare,
|
||
// altrimenti la stampa parte con risorse ancora in caricamento e appaiono vuote.
|
||
w.addEventListener('load', () => w.print())
|
||
}
|