++ scelta di territori e proclamatori con ricerca
Al posto delle tendine (Assegna, Registro, editor delle piantine) un
campo in cui si scrive e compaiono le proposte più vicine: uguale, inizia
con, contiene, errore di battitura ("forse cercavi"); parole in qualsiasi
ordine, senza badare a maiuscole e accenti. Campo vuoto: la tendina
completa. Fatto anche per il telefono (16px, voci da 44px, scelta al tocco).
- Logica pura in resources/js/scelta.js con test node (tests/js).
- Opzioni dal server: Territorio/Proclamatore::opzioneScelta(); i
territori già assegnati si trovano ("già assegnato a …") ma non si
scelgono. Nomi cifrati ordinati e cercati decifrati, mai in SQL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,111 @@
|
||||
import './bootstrap';
|
||||
import { proponi } from './scelta';
|
||||
|
||||
// ─── Campo di scelta con ricerca (<x-scelta>) ───────────────────────────────
|
||||
//
|
||||
// Al posto della tendina: si scrive e sotto compaiono le proposte più vicine
|
||||
// (logica in ./scelta.js). Campo vuoto o appena aperto: la tendina completa.
|
||||
// Il valore scelto arriva a Livewire con x-modelable + wire:model.
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
// 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 = '') => ({
|
||||
opzioni,
|
||||
vuoto,
|
||||
valore: null,
|
||||
testo: '',
|
||||
aperto: false,
|
||||
evidenziato: 0,
|
||||
|
||||
init() {
|
||||
this.testo = this.etichettaDi(this.valore);
|
||||
// Valore cambiato da Livewire (reset del form, scelta da codice).
|
||||
this.$watch('valore', (v) => { if (!this.aperto) this.testo = this.etichettaDi(v); });
|
||||
},
|
||||
|
||||
scelta() {
|
||||
return this.opzioni.find((o) => String(o.valore) === String(this.valore ?? '')) ?? null;
|
||||
},
|
||||
|
||||
etichettaDi(v) {
|
||||
if (v === null || v === undefined || v === '') {
|
||||
const vuota = this.opzioni.find((o) => o.speciale && o.valore === '');
|
||||
return vuota ? vuota.etichetta : '';
|
||||
}
|
||||
const o = this.opzioni.find((x) => String(x.valore) === String(v));
|
||||
return o ? o.etichetta : '';
|
||||
},
|
||||
|
||||
// Finché nel campo c'è l'etichetta della scelta attuale, si vede la
|
||||
// tendina completa: si cerca solo da quando si comincia a scrivere.
|
||||
get ricerca() {
|
||||
return this.testo === this.etichettaDi(this.valore) ? '' : this.testo;
|
||||
},
|
||||
|
||||
get risultati() {
|
||||
return proponi(this.opzioni, this.ricerca);
|
||||
},
|
||||
|
||||
apri() {
|
||||
if (this.aperto) return;
|
||||
this.aperto = true;
|
||||
const i = this.risultati.findIndex((o) => String(o.valore) === String(this.valore ?? ''));
|
||||
this.evidenziato = Math.max(0, i);
|
||||
this.$nextTick(() => this.mostraEvidenziato());
|
||||
},
|
||||
|
||||
chiudi() {
|
||||
this.aperto = false;
|
||||
this.testo = this.etichettaDi(this.valore);
|
||||
},
|
||||
|
||||
alterna() {
|
||||
if (this.aperto) { this.chiudi(); return; }
|
||||
this.$refs.campo.focus();
|
||||
this.apri();
|
||||
},
|
||||
|
||||
scrivendo() {
|
||||
this.aperto = true;
|
||||
this.evidenziato = 0;
|
||||
},
|
||||
|
||||
sposta(passo) {
|
||||
if (!this.aperto) { this.apri(); return; }
|
||||
const n = this.risultati.length;
|
||||
if (n === 0) return;
|
||||
this.evidenziato = (this.evidenziato + passo + n) % n;
|
||||
this.$nextTick(() => this.mostraEvidenziato());
|
||||
},
|
||||
|
||||
mostraEvidenziato() {
|
||||
this.$refs.elenco?.querySelector('[data-evidenziato="1"]')?.scrollIntoView({ block: 'nearest' });
|
||||
},
|
||||
|
||||
// daTocco: scelta col dito o col mouse. Si toglie il focus, così sul
|
||||
// telefono si chiude la tastiera; con frecce e Invio il focus resta.
|
||||
scegli(opzione, daTocco = false) {
|
||||
if (!opzione || opzione.motivo) return;
|
||||
this.valore = opzione.valore;
|
||||
this.aperto = false;
|
||||
this.testo = opzione.etichetta;
|
||||
if (daTocco) this.$refs.campo.blur();
|
||||
},
|
||||
|
||||
scegliEvidenziato() {
|
||||
if (this.aperto) this.scegli(this.risultati[this.evidenziato]);
|
||||
},
|
||||
|
||||
svuota() {
|
||||
this.valore = this.vuoto;
|
||||
this.testo = '';
|
||||
this.$refs.campo.focus();
|
||||
this.aperto = true;
|
||||
this.evidenziato = 0;
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
// ─── Ritorno alla pagina di partenza ────────────────────────────────────────
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user