fec140df62
Separa punteggio ed etichetta vincitore su un flex con justify-content: space-between per migliorare la leggibilità.
233 lines
11 KiB
JavaScript
233 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 ? `<span class="set-vinc">vinto da <strong>${nomeVinc}</strong></span>` : ''
|
||
|
||
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">
|
||
<span>${servHomeIcon}${nomi.home} <strong>${h}</strong> · <strong>${g}</strong> ${nomi.guest}${servGuestIcon}</span>
|
||
${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; display: flex; justify-content: space-between; align-items: center; gap: 10px; }
|
||
.set-vinc { flex-shrink: 0; }
|
||
.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())
|
||
}
|