++ pagina Assegna più leggera; "Vedi tutti" in testata nella Home

Assegna pesava 387 KB e Livewire li rimandava a ogni interazione.
- Elenco dei territori assegnati in un componente lazy a sé
  (ElencoAssegnati): il form compare subito e non lo ridisegna.
- Ricerca nell'elenco e anteprima della miniatura nel browser: nessuna
  richiesta al server mentre si scrive o si sceglie il territorio.
- Icone SVG come simboli richiamati con <use>.
- Assegnazione::giorni: cast a int (Carbon 3 restituisce un float).

Home: "Vedi tutti" diventa un pulsante nella testata dei riquadri.
README e HANDOFF aggiornati.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Francesco Picone
2026-10-19 15:49:08 +02:00
parent fda428f242
commit 81a8f2ba33
12 changed files with 442 additions and 217 deletions

View File

@@ -1,5 +1,5 @@
import './bootstrap';
import { proponi } from './scelta';
import { proponi, normalizza } from './scelta';
// ─── Campo di scelta con ricerca (<x-scelta>) ───────────────────────────────
//
@@ -8,6 +8,21 @@ import { proponi } from './scelta';
// Il valore scelto arriva a Livewire con x-modelable + wire:model.
document.addEventListener('alpine:init', () => {
// Elenco filtrato nel browser: ogni riga ha data-cerca (testo già senza
// accenti, minuscolo); valgono tutte le parole scritte, in qualsiasi
// ordine. Nessuna richiesta al server mentre si scrive.
window.Alpine.data('elencoCercabile', () => ({
q: '',
corrisponde(testo) {
const parole = normalizza(this.q).split(/\s+/).filter(Boolean);
return parole.every((p) => (testo ?? '').includes(p));
},
get nessuno() {
if (normalizza(this.q) === '') return false;
return ![...this.$root.querySelectorAll('[data-cerca]')].some((riga) => this.corrisponde(riga.dataset.cerca));
},
}));
// vuoto: il valore di "nessuna scelta" per la proprietà Livewire collegata
// ('' per le stringhe, null per i ?int: '' su un ?int sarebbe un errore).
window.Alpine.data('sceltaRicerca', (opzioni, vuoto = '') => ({
@@ -90,6 +105,9 @@ document.addEventListener('alpine:init', () => {
this.valore = opzione.valore;
this.aperto = false;
this.testo = opzione.etichetta;
// Per chi vuole reagire alla scelta senza passare dal server
// (l'anteprima della miniatura in Assegna).
this.$dispatch('scelta', { valore: opzione.valore, opzione });
if (daTocco) this.$refs.campo.blur();
},
@@ -100,6 +118,7 @@ document.addEventListener('alpine:init', () => {
svuota() {
this.valore = this.vuoto;
this.testo = '';
this.$dispatch('scelta', { valore: this.vuoto, opzione: null });
this.$refs.campo.focus();
this.aperto = true;
this.evidenziato = 0;