++ 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:
@@ -40,7 +40,6 @@ class Assegna extends Component
|
||||
public ?int $territorio_id = null;
|
||||
public ?int $proclamatore_id = null;
|
||||
public string $assigned_at = '';
|
||||
public string $territorioSearch = '';
|
||||
public string $linkSearch = '';
|
||||
|
||||
// Optional pre-selection from parent context
|
||||
@@ -270,26 +269,20 @@ class Assegna extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
$territoriQuery = Territorio::where('attivo', true)
|
||||
->whereDoesntHave('assegnazioni', fn($q) => $q->aperte())
|
||||
->with(['zona', 'tipologia']);
|
||||
|
||||
if (trim($this->territorioSearch) !== '') {
|
||||
$term = trim($this->territorioSearch);
|
||||
$territoriQuery->where(function ($q) use ($term) {
|
||||
$q->where('numero', 'like', "%{$term}%")
|
||||
->orWhereHas('zona', fn($z) => $z->where('nome', 'like', "%{$term}%"))
|
||||
->orWhereHas('tipologia', fn($t) => $t->where('nome', 'like', "%{$term}%"));
|
||||
});
|
||||
}
|
||||
|
||||
$territoriDisponibili = $territoriQuery
|
||||
->orderBy('numero')
|
||||
->get();
|
||||
|
||||
$proclamatoriAttivi = Proclamatore::attivi()
|
||||
// Le opzioni dei campi di scelta con ricerca (<x-scelta>). Ci sono anche
|
||||
// i territori già assegnati, che non si possono scegliere: cercandone
|
||||
// uno si legge a chi è, invece di un "nessun risultato".
|
||||
$opzioniTerritori = Territorio::attivi()
|
||||
->with(['zona', 'tipologia', 'assegnazioneCorrente.proclamatore'])
|
||||
->get()
|
||||
->sortBy(fn($p) => mb_strtolower($p->cognome . ' ' . $p->nome));
|
||||
->sortBy('numero', SORT_NATURAL)
|
||||
->map(fn (Territorio $t) => $t->opzioneScelta(
|
||||
$t->assegnazioneCorrente ? 'già assegnato a ' . ($t->assegnazioneCorrente->proclamatore?->nome_completo ?? '—') : null
|
||||
))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$opzioniProclamatori = Proclamatore::opzioniScelta(Proclamatore::attivi()->get());
|
||||
|
||||
// All currently assigned territories with links
|
||||
$assegnazioniAperte = Assegnazione::aperte()
|
||||
@@ -326,8 +319,8 @@ class Assegna extends Component
|
||||
return view('livewire.assegnazioni.assegna', [
|
||||
'invio' => $invio,
|
||||
'postaAttiva' => $invio ? app(ConfigurazionePosta::class)->funzionante() : false,
|
||||
'territoriDisponibili' => $territoriDisponibili,
|
||||
'proclamatoriAttivi' => $proclamatoriAttivi,
|
||||
'opzioniTerritori' => $opzioniTerritori,
|
||||
'opzioniProclamatori' => $opzioniProclamatori,
|
||||
'assegnazioniAperte' => $assegnazioniAperte,
|
||||
'assegnazioniAperteVisibili' => $assegnazioniAperteVisibili,
|
||||
'sogliaRientro' => $sogliaRientro,
|
||||
|
||||
@@ -577,18 +577,33 @@ class PiantineEditor extends Component
|
||||
->pluck('territorio_id');
|
||||
|
||||
$territori = Territorio::attivi()
|
||||
->with(['zona:id,nome', 'tipologia:id,nome'])
|
||||
->whereNotIn('id', $occupati)
|
||||
->orderByRaw('LENGTH(numero), numero')
|
||||
->get(['id', 'numero', 'attivo', 'deleted_at']);
|
||||
->get(['id', 'numero', 'zona_id', 'tipologia_id', 'attivo', 'deleted_at']);
|
||||
|
||||
if ($mappa?->territorio && ! $territori->contains('id', $mappa->territorio_id)) {
|
||||
$territori->prepend($mappa->territorio);
|
||||
}
|
||||
|
||||
// Il campo di scelta con ricerca (<x-scelta>): due voci fisse in cima,
|
||||
// poi i territori, che qui restano sceglibili anche se cestinati o
|
||||
// disattivati (è il collegamento che già c'era).
|
||||
$opzioniTerritori = [
|
||||
['valore' => '', 'etichetta' => '— nessun territorio —', 'speciale' => true],
|
||||
['valore' => 'nuovo', 'etichetta' => '+ Crea un territorio nuovo', 'speciale' => true],
|
||||
...$territori->map(function (Territorio $t) {
|
||||
$opzione = $t->opzioneScelta(nota: $t->trashed() ? 'nel cestino' : (! $t->attivo ? 'disattivato' : null));
|
||||
$opzione['etichetta'] = str_replace('N° ', 'Territorio ', $opzione['etichetta']);
|
||||
|
||||
return $opzione;
|
||||
})->all(),
|
||||
];
|
||||
|
||||
return view('livewire.piantine.piantine-editor', [
|
||||
'mappa' => $mappa,
|
||||
'areaCaricata' => app(PiantineArea::class)->config()['kml_nome'] !== null,
|
||||
'territori' => $territori,
|
||||
'opzioniTerritori' => $opzioniTerritori,
|
||||
'zone' => Zona::orderBy('nome')->get(['id', 'nome']),
|
||||
'tipologie' => Tipologia::orderBy('nome')->get(['id', 'nome']),
|
||||
'title' => $mappa ? $mappa->etichetta() . ' — Editor delle piantine' : 'Editor delle piantine',
|
||||
|
||||
@@ -348,8 +348,14 @@ class Registro extends Component
|
||||
'anni' => AnnoTeocratico::orderByDesc('start_date')->get(),
|
||||
'zone' => Zona::attive()->orderBy('nome')->get(),
|
||||
'tipologie' => Tipologia::orderBy('nome')->get(),
|
||||
'territori' => Territorio::attivi()->orderBy('numero')->get(),
|
||||
'proclamatori' => Proclamatore::attivi()->orderBy('cognome')->orderBy('nome')->get(),
|
||||
// Campi di scelta con ricerca della finestra di modifica. Ci sono
|
||||
// anche i disattivati, non sceglibili: modificando una vecchia
|
||||
// assegnazione il campo mostra comunque il nome giusto.
|
||||
'opzioniTerritori' => Territorio::with(['zona', 'tipologia'])->get()
|
||||
->sortBy('numero', SORT_NATURAL)
|
||||
->map(fn (Territorio $t) => $t->opzioneScelta($t->attivo ? null : 'territorio disattivato'))
|
||||
->values()->all(),
|
||||
'opzioniProclamatori' => Proclamatore::opzioniScelta(Proclamatore::all()),
|
||||
'campagne' => Campagna::orderByDesc('created_at')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,34 @@ class Proclamatore extends Model
|
||||
return 'https://wa.me/' . $numero . ($messaggio !== null && $messaggio !== '' ? '?text=' . rawurlencode($messaggio) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* L'opzione per il campo di scelta con ricerca (<x-scelta>): si cerca per
|
||||
* cognome e nome. Nome e cognome sono cifrati nel database: la ricerca
|
||||
* avviene sui valori già decifrati, nella pagina, mai in SQL.
|
||||
*/
|
||||
public function opzioneScelta(?string $motivo = null): array
|
||||
{
|
||||
return array_filter([
|
||||
'valore' => (string) $this->id,
|
||||
'etichetta' => $this->nome_completo,
|
||||
'chiavi' => [(string) $this->cognome, (string) $this->nome],
|
||||
'motivo' => $motivo,
|
||||
], fn ($v) => $v !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Le opzioni di un elenco di proclamatori, in ordine di cognome e nome.
|
||||
* L'ordine si fa qui: in SQL ordinerebbe i testi cifrati.
|
||||
*/
|
||||
public static function opzioniScelta(iterable $proclamatori): array
|
||||
{
|
||||
return collect($proclamatori)
|
||||
->sortBy(fn (self $p) => mb_strtolower($p->cognome . ' ' . $p->nome))
|
||||
->map(fn (self $p) => $p->opzioneScelta($p->attivo ? null : 'non attivo'))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function assegnazioni()
|
||||
{
|
||||
return $this->hasMany(Assegnazione::class, 'proclamatore_id');
|
||||
|
||||
@@ -145,6 +145,24 @@ class Territorio extends Model
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* L'opzione per il campo di scelta con ricerca (<x-scelta>): si cerca per
|
||||
* numero, zona e tipologia. Con $motivo compare cercando ma non si può
|
||||
* scegliere ("già assegnato a …").
|
||||
*/
|
||||
public function opzioneScelta(?string $motivo = null, ?string $nota = null): array
|
||||
{
|
||||
$dettaglio = trim(($this->zona?->nome ?? '') . ($this->tipologia?->nome ? " ({$this->tipologia->nome})" : ''));
|
||||
|
||||
return array_filter([
|
||||
'valore' => (string) $this->id,
|
||||
'etichetta' => "N° {$this->numero}" . ($nota ? " ({$nota})" : ''),
|
||||
'dettaglio' => $dettaglio !== '' ? $dettaglio : null,
|
||||
'chiavi' => [(string) $this->numero, (string) $this->zona?->nome, (string) $this->tipologia?->nome],
|
||||
'motivo' => $motivo,
|
||||
], fn ($v) => $v !== null);
|
||||
}
|
||||
|
||||
// ─── Scopes ─────────────────────────────────────────────────
|
||||
|
||||
public function scopeAttivi($query)
|
||||
|
||||
Reference in New Issue
Block a user