diff --git a/README.md b/README.md index 74f0530..3a523a3 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,14 @@ Applicazione web **Progressive Web App (PWA)** per tracciare i punteggi di parti - Tracciamento punti in tempo reale per entrambe le squadre - Conteggio automatico dei set (modalità 2/3 o 3/5) - Indicatore visivo del servizio + - Blocco incremento punti a set concluso - Cronologia punti con striscia visiva - **Formazioni Squadra** - Visualizzazione interattiva dei 6 giocatori in campo - Rotazione automatica regolamentare al cambio palla - Configurazione manuale dei numeri di maglia + - Dialog cambi con uno o due cambi (IN → OUT) e validazioni - Supporto logica pallavolo ufficiale (25 punti + 2 di vantaggio, tie-break a 15 nel set decisivo) - **Controlli Multimodali** @@ -152,6 +154,7 @@ Serve i file dalla cartella `/dist` per testare la build prima del deploy. | `Ctrl + ↑` | Incrementa punti | | `Ctrl + ↓` | Decrementa punti | | `Ctrl + →` | Incrementa set | +| `Ctrl + C` | Apri dialog cambi | ### Controlli Tastiera Squadra Guest @@ -160,6 +163,7 @@ Serve i file dalla cartella `/dist` per testare la build prima del deploy. | `Shift + ↑` | Incrementa punti | | `Shift + ↓` | Decrementa punti | | `Shift + →` | Incrementa set | +| `Shift + C` | Apri dialog cambi | ### Comandi Globali diff --git a/src/components/HomePage/HomePage.html b/src/components/HomePage/HomePage.html index 06e7993..718083e 100644 --- a/src/components/HomePage/HomePage.html +++ b/src/components/HomePage/HomePage.html @@ -68,6 +68,35 @@ Ok + +
Scegli squadra
+ + {{ sp.nomi.home }} + {{ sp.nomi.guest }} + +
+ +
+
{{ sp.nomi[diaCambi.team] }}: CAMBIO
+
+
+ + + +
+
+ + + +
+
+
+ + + CONFERMA + + +
@@ -193,6 +222,9 @@ PUNTEGGIO FORMAZIONI + + CAMBI + STRISCIA diff --git a/src/components/HomePage/HomePage.js b/src/components/HomePage/HomePage.js index e426b20..9c27938 100644 --- a/src/components/HomePage/HomePage.js +++ b/src/components/HomePage/HomePage.js @@ -11,6 +11,15 @@ export default { home: "", guest: "", }, + diaCambi: { + show: false, + team: "home", + guest: { cambi: [{ in: "", out: "" }, { in: "", out: "" }] }, + home: { cambi: [{ in: "", out: "" }, { in: "", out: "" }] }, + }, + diaCambiTeam: { + show: false, + }, visuForm: false, visuButt: true, visuStriscia: true, @@ -42,6 +51,28 @@ export default { computed: { isPunteggioZeroZero() { return this.sp.punt.home === 0 && this.sp.punt.guest === 0; + }, + cambiConfermabili() { + const team = this.diaCambi.team; + const cambi = this.diaCambi[team].cambi || []; + let hasComplete = false; + let allValid = true; + + cambi.forEach((cambio) => { + const teamIn = (cambio.in || "").trim(); + const teamOut = (cambio.out || "").trim(); + + if (!teamIn && !teamOut) { + return; + } + if (!teamIn || !teamOut) { + allValid = false; + return; + } + hasComplete = true; + }); + + return allValid && hasComplete; } }, methods: { @@ -214,6 +245,79 @@ export default { this.disabilitaTastiSpeciali(); this.diaNomi.show = true; }, + resettaCambi(team) { + const teams = team ? [team] : ["home", "guest"]; + teams.forEach((t) => { + this.diaCambi[t].cambi.forEach((cambio) => { + cambio.in = ""; + cambio.out = ""; + }); + }); + }, + apriDialogCambi() { + this.disabilitaTastiSpeciali(); + this.diaCambiTeam.show = true; + }, + apriDialogCambiTeam(team) { + this.disabilitaTastiSpeciali(); + this.diaCambi.team = team; + this.resettaCambi(team); + this.diaCambi.show = true; + }, + selezionaTeamCambi(team) { + this.diaCambiTeam.show = false; + this.apriDialogCambiTeam(team); + }, + chiudiDialogCambi() { + this.diaCambi.show = false; + this.resettaCambi(this.diaCambi.team); + this.abilitaTastiSpeciali(); + }, + confermaCambi() { + if (!this.cambiConfermabili) { + return; + } + + const team = this.diaCambi.team; + const cambi = (this.diaCambi[team].cambi || []) + .map((cambio) => ({ + team, + in: (cambio.in || "").trim(), + out: (cambio.out || "").trim(), + })) + .filter((cambio) => cambio.in || cambio.out); + + const form = this.sp.form[team].map((val) => String(val).trim()); + const formAggiornata = [...form]; + + for (const cambio of cambi) { + if (!/^\d+$/.test(cambio.in) || !/^\d+$/.test(cambio.out)) { + this.$waveui.notify("Inserisci solo numeri nei campi", "warning"); + return; + } + if (cambio.in === cambio.out) { + this.$waveui.notify(`Numero IN e OUT uguali per ${cambio.team}`, "warning"); + return; + } + if (formAggiornata.includes(cambio.in)) { + this.$waveui.notify(`Numero ${cambio.in} già presente in formazione ${cambio.team}`, "warning"); + return; + } + if (!formAggiornata.includes(cambio.out)) { + this.$waveui.notify(`Numero ${cambio.out} non presente in formazione ${cambio.team}`, "warning"); + return; + } + + const idx = formAggiornata.findIndex((val) => String(val).trim() === cambio.out); + if (idx !== -1) { + formAggiornata.splice(idx, 1, cambio.in); + } + } + + this.sp.form[team] = formAggiornata; + + this.chiudiDialogCambi(); + }, disabilitaTastiSpeciali() { window.removeEventListener("keydown", this.funzioneTastiSpeciali); }, @@ -221,32 +325,82 @@ export default { window.addEventListener("keydown", this.funzioneTastiSpeciali); }, funzioneTastiSpeciali(e) { - e.preventDefault(); + if (this.diaNomi.show || this.diaCambi.show || this.diaCambiTeam.show) { + return; + } + + const target = e.target; + const path = typeof e.composedPath === "function" ? e.composedPath() : []; + const elements = [target, ...path].filter(Boolean); + const isTypingField = elements.some((el) => { + if (!el || !el.tagName) { + return false; + } + const tag = String(el.tagName).toLowerCase(); + if (tag === "input" || tag === "textarea") { + return true; + } + if (el.isContentEditable) { + return true; + } + if (el.classList && (el.classList.contains("w-input") || el.classList.contains("w-textarea"))) { + return true; + } + const contentEditable = el.getAttribute && el.getAttribute("contenteditable"); + return contentEditable === "true"; + }); + if (isTypingField) { + return; + } + + let handled = false; if (e.ctrlKey && e.key == "m") { this.diaNomi.show = true + handled = true; } else if (e.ctrlKey && e.key == "b") { this.visuButt = !this.visuButt + handled = true; } else if (e.ctrlKey && e.key == "f") { document.documentElement.requestFullscreen(); + handled = true; } else if (e.ctrlKey && e.key == "s") { this.speak(); + handled = true; } else if (e.ctrlKey && e.key == "z") { this.visuForm = !this.visuForm + handled = true; } else if (e.ctrlKey && e.key == "ArrowUp") { this.incPunt("home") + handled = true; } else if (e.ctrlKey && e.key == "ArrowDown") { this.decPunt("home") + handled = true; } else if (e.ctrlKey && e.key == "ArrowRight") { this.incSet("home") + handled = true; } else if (e.shiftKey && e.key == "ArrowUp") { this.incPunt("guest") + handled = true; } else if (e.shiftKey && e.key == "ArrowDown") { this.decPunt("guest") + handled = true; } else if (e.shiftKey && e.key == "ArrowRight") { this.incSet("guest") + handled = true; } else if (e.ctrlKey && e.key == "ArrowLeft") { this.cambiaPalla() + handled = true; + } else if (e.ctrlKey && (e.key == "c" || e.key == "C")) { + this.apriDialogCambiTeam("home") + handled = true; + } else if (e.shiftKey && (e.key == "c" || e.key == "C")) { + this.apriDialogCambiTeam("guest") + handled = true; } else { return false } + + if (handled) { + e.preventDefault(); + } } } } diff --git a/src/style.css b/src/style.css index b061700..f00b946 100644 --- a/src/style.css +++ b/src/style.css @@ -190,3 +190,75 @@ button:focus-visible { height: 0; margin: 0; } + +.cambi-rows { + display: flex; + flex-direction: column; + gap: 12px; + padding: 8px 0; +} + +.cambi-dialog { + padding: 8px 6px 2px; +} + +.cambi-title { + text-align: center; + font-weight: 800; + letter-spacing: 0.5px; + margin-bottom: 8px; + padding-bottom: 6px; + border-bottom: 1px solid rgba(255, 255, 255, 0.15); +} + +.cambi-row { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; +} + +.cambi-arrow { + font-weight: 700; + font-size: 18px; + line-height: 1; + padding: 6px 8px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + white-space: nowrap; +} + +.cambi-input { + min-width: 48px; + max-width: 64px; +} + +.cambi-input input, +.cambi-input .w-input__input, +.cambi-input .w-input__field { + border: 2px solid rgba(255, 255, 255, 0.35); + border-radius: 8px; + padding: 6px 10px; + color: #000; + text-align: center; + box-sizing: border-box; +} + +.cambi-in input, +.cambi-in .w-input__input, +.cambi-in .w-input__field { + background: rgba(120, 200, 120, 0.4); +} + +.cambi-out input, +.cambi-out .w-input__input, +.cambi-out .w-input__field { + background: rgba(200, 120, 120, 0.4); +} + +.cambi-input input:focus, +.cambi-input .w-input__input:focus, +.cambi-input .w-input__field:focus { + border-color: rgba(255, 255, 255, 0.7); + outline: none; +}