++ 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:
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
60
app/Livewire/Assegnazioni/ElencoAssegnati.php
Normal file
60
app/Livewire/Assegnazioni/ElencoAssegnati.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Assegnazioni;
|
||||
|
||||
use App\Models\Assegnazione;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Attributes\Lazy;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* L'elenco dei territori assegnati della pagina Assegna, separato dal form.
|
||||
*
|
||||
* Prima stava nello stesso componente: con ~80 righe la pagina pesava quasi
|
||||
* 400 KB e Livewire li rimandava tutti a ogni interazione (scegliere un
|
||||
* territorio, scrivere nella ricerca, aprire l'invio del link). Ora:
|
||||
* - è un componente a sé, caricato subito dopo la pagina (#[Lazy]): il form
|
||||
* compare subito e le sue azioni non ridisegnano l'elenco;
|
||||
* - la ricerca filtra le righe nel browser (data-cerca), senza richieste;
|
||||
* - le icone sono simboli SVG definiti una volta e richiamati con <use>.
|
||||
*/
|
||||
#[Lazy]
|
||||
class ElencoAssegnati extends Component
|
||||
{
|
||||
public function toggleLinkSent(int $assegnazioneId): void
|
||||
{
|
||||
$this->authorize('territori.assign');
|
||||
$assegnazione = Assegnazione::findOrFail($assegnazioneId);
|
||||
$assegnazione->registraInvioLink(! $assegnazione->link_sent, $assegnazione->link_canale);
|
||||
}
|
||||
|
||||
public function placeholder()
|
||||
{
|
||||
return view('livewire.assegnazioni.elenco-assegnati-attesa');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$assegnazioni = Assegnazione::aperte()
|
||||
->with(['territorio.zona', 'proclamatore'])
|
||||
->get()
|
||||
->sortBy(fn ($a) => (string) $a->territorio?->numero, SORT_NATURAL)
|
||||
->values();
|
||||
|
||||
// Il testo su cui filtra la ricerca nel browser: numero, zona e nome
|
||||
// (decifrato qui: in SQL non si potrebbe cercare), senza accenti.
|
||||
$cerca = $assegnazioni->mapWithKeys(fn ($a) => [$a->id => Str::lower(Str::ascii(implode(' ', array_filter([
|
||||
$a->territorio?->numero,
|
||||
$a->territorio?->zona?->nome,
|
||||
$a->proclamatore?->nome_completo,
|
||||
]))))]);
|
||||
|
||||
return view('livewire.assegnazioni.elenco-assegnati', [
|
||||
'assegnazioni' => $assegnazioni,
|
||||
'cerca' => $cerca,
|
||||
// Il link scade alla soglia rientro (giorni_per_smarrito).
|
||||
'sogliaRientro' => max(1, (int) Setting::getValue('giorni_per_smarrito', 120)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,8 @@ class Assegnazione extends Model
|
||||
public function getGiorniAttribute(): int
|
||||
{
|
||||
$end = $this->returned_at ?? now();
|
||||
return Carbon::parse($this->assigned_at)->diffInDays($end);
|
||||
// Carbon 3 restituisce un float: i giorni interi trascorsi.
|
||||
return (int) Carbon::parse($this->assigned_at)->diffInDays($end);
|
||||
}
|
||||
|
||||
public function getIsApertaAttribute(): bool
|
||||
|
||||
Reference in New Issue
Block a user