++ 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

@@ -4,8 +4,8 @@ namespace App\Livewire\Assegnazioni;
use App\Livewire\Concerns\RicordaRitorno;
use Livewire\Component;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
use Livewire\Attributes\On;
use App\Models\Territorio;
use App\Models\Proclamatore;
use App\Models\Assegnazione;
@@ -40,7 +40,6 @@ class Assegna extends Component
public ?int $territorio_id = null;
public ?int $proclamatore_id = null;
public string $assigned_at = '';
public string $linkSearch = '';
// Optional pre-selection from parent context
public ?int $preselectedTerritorioId = null;
@@ -143,7 +142,12 @@ class Assegna extends Component
$this->apriInvio($assegnazione->id);
}
/** Apre l'invio del link: subito dopo l'assegnazione, o da un'assegnazione rimasta in sospeso. */
/**
* Apre l'invio del link: subito dopo l'assegnazione, o da un'assegnazione
* rimasta in sospeso ("Invia link" nell'elenco, che è un componente a sé
* e lo chiede con l'evento apri-invio).
*/
#[On('apri-invio')]
public function apriInvio(int $assegnazioneId): void
{
$this->authorize('territori.assign');
@@ -250,65 +254,27 @@ class Assegna extends Component
return Assegnazione::with(['territorio', 'proclamatore'])->find($this->invioId);
}
public function toggleLinkSent(int $assegnazioneId): void
{
$this->authorize('territori.assign');
$assegnazione = Assegnazione::findOrFail($assegnazioneId);
$assegnazione->registraInvioLink(! $assegnazione->link_sent, $assegnazione->link_canale);
}
#[Computed]
public function selectedThumbnailUrl(): ?string
{
if (!$this->territorio_id) {
return null;
}
$t = Territorio::find($this->territorio_id);
return $t?->miniaturaUrl();
}
public function render()
{
// 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()
// uno si legge a chi è, invece di un "nessun risultato". La miniatura
// serve all'anteprima, che si aggiorna nel browser.
$territori = Territorio::attivi()
->with(['zona', 'tipologia', 'assegnazioneCorrente.proclamatore'])
->get()
->sortBy('numero', SORT_NATURAL)
->sortBy('numero', SORT_NATURAL);
$opzioniTerritori = $territori
->map(fn (Territorio $t) => $t->opzioneScelta(
$t->assegnazioneCorrente ? 'già assegnato a ' . ($t->assegnazioneCorrente->proclamatore?->nome_completo ?? '—') : null
))
) + array_filter(['miniatura' => $t->miniaturaUrl()]))
->values()
->all();
$opzioniProclamatori = Proclamatore::opzioniScelta(Proclamatore::attivi()->get());
// All currently assigned territories with links
$assegnazioniAperte = Assegnazione::aperte()
->with(['territorio.zona', 'proclamatore'])
->get()
->sortBy(fn($a) => (int) $a->territorio?->numero);
// Ricerca rapida nella lista dei link: numero territorio, zona,
// proclamatore in un solo campo. In memoria perché nome/cognome del
// proclamatore sono cifrati e non filtrabili via SQL.
$assegnazioniAperteVisibili = $assegnazioniAperte;
if (trim($this->linkSearch) !== '') {
$term = mb_strtolower(trim($this->linkSearch));
$assegnazioniAperteVisibili = $assegnazioniAperte->filter(function ($a) use ($term) {
$haystack = mb_strtolower(
($a->territorio?->numero ?? '') . ' ' .
($a->territorio?->zona?->nome ?? '') . ' ' .
($a->proclamatore?->nome_completo ?? '')
);
return str_contains($haystack, $term);
})->values();
}
// Il link scade alla soglia rientro (giorni_per_smarrito).
$sogliaRientro = max(1, (int) \App\Models\Setting::getValue('giorni_per_smarrito', 120));
// L'elenco dei territori assegnati sta in ElencoAssegnati.
// L'invio in corso; se nel frattempo l'assegnazione è rientrata o
// sparita non c'è più niente da mandare.
$invio = $this->invioId ? Assegnazione::aperte()->with(['territorio', 'proclamatore'])->find($this->invioId) : null;
@@ -321,9 +287,7 @@ class Assegna extends Component
'postaAttiva' => $invio ? app(ConfigurazionePosta::class)->funzionante() : false,
'opzioniTerritori' => $opzioniTerritori,
'opzioniProclamatori' => $opzioniProclamatori,
'assegnazioniAperte' => $assegnazioniAperte,
'assegnazioniAperteVisibili' => $assegnazioniAperteVisibili,
'sogliaRientro' => $sogliaRientro,
'anteprimaIniziale' => $this->territorio_id ? $territori->firstWhere('id', $this->territorio_id)?->miniaturaUrl() : null,
]);
}
}