From bc465f53f51759160916a26957547e85629d1fbe Mon Sep 17 00:00:00 2001 From: Francesco Picone Date: Mon, 19 Oct 2026 15:04:42 +0200 Subject: [PATCH] ++ dopo un'operazione si torna alla pagina di partenza, con i suoi filtri MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gli elenchi tengono ricerca, filtri e ordinamento nell'indirizzo (#[Url]); la pagina c'era già. - I link verso schede e operazioni (data-ritorno) portano ?da= con la pagina di partenza, o il da già ricevuto: si risale all'origine (elenco -> scheda -> Rientra -> elenco filtrato). - Ritorno::valida accetta solo percorsi interni: niente rimandi esterni. - Trait RicordaRitorno: redirect dopo il salvataggio, Annulla e "Torna indietro" usano la pagina di partenza. Senza da, tutto come prima. Co-Authored-By: Claude Opus 5.5 --- HANDOFF.md | 23 +++ app/Livewire/Assegnazioni/Assegna.php | 5 +- app/Livewire/Assegnazioni/Rientra.php | 5 +- app/Livewire/Campagne/CampagnaCreate.php | 5 +- app/Livewire/Campagne/CampagnaEdit.php | 5 +- app/Livewire/Campagne/CampagnaShow.php | 3 + app/Livewire/Concerns/RicordaRitorno.php | 33 +++ .../Proclamatori/ProclamatoreCreate.php | 5 +- .../Proclamatori/ProclamatoreEdit.php | 5 +- .../Proclamatori/ProclamatoreIndex.php | 6 + .../Proclamatori/ProclamatoreShow.php | 3 + app/Livewire/Registro.php | 9 + app/Livewire/Territori/TerritorioCreate.php | 5 +- app/Livewire/Territori/TerritorioEdit.php | 5 +- app/Livewire/Territori/TerritorioIndex.php | 12 ++ app/Livewire/Territori/TerritorioShow.php | 3 + app/Support/Ritorno.php | 49 +++++ resources/js/app.js | 33 +++ .../livewire/assegnazioni/assegna.blade.php | 14 +- .../livewire/assegnazioni/rientra.blade.php | 6 +- .../livewire/campagne/campagna-form.blade.php | 6 +- .../campagne/campagna-index.blade.php | 6 +- .../livewire/campagne/campagna-show.blade.php | 10 +- resources/views/livewire/home.blade.php | 8 +- .../proclamatori/proclamatore-form.blade.php | 6 +- .../proclamatori/proclamatore-index.blade.php | 6 +- .../proclamatori/proclamatore-show.blade.php | 8 +- resources/views/livewire/registro.blade.php | 2 +- .../territori/territorio-form.blade.php | 6 +- .../territori/territorio-index.blade.php | 12 +- .../territori/territorio-show.blade.php | 14 +- tests/Feature/RitornoTest.php | 191 ++++++++++++++++++ 32 files changed, 451 insertions(+), 58 deletions(-) create mode 100644 app/Livewire/Concerns/RicordaRitorno.php create mode 100644 app/Support/Ritorno.php create mode 100644 tests/Feature/RitornoTest.php diff --git a/HANDOFF.md b/HANDOFF.md index aec7cc2..2a9fb17 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -127,6 +127,29 @@ email di prova e di conferma arrivate, link aperto senza login, account sbloccato; riquadro dopo l'assegnazione con WhatsApp e scelta. Configurazione e dati di prova rimossi dopo. +### Si torna da dove si è partiti (19 ottobre 2026) + +Regola dell'utente: dopo un'operazione — confermata **o annullata** — si torna +alla pagina da cui il percorso era partito, con i suoi filtri, ricerca, +ordinamento e pagina. Elenco filtrato → scheda → Rientra → conferma riporta +all'**elenco filtrato**, non alla scheda (scelta esplicita). + +- Gli elenchi (territori, proclamatori, registro) tengono ricerca, filtri e + ordinamento nell'indirizzo con `#[Url]`; la pagina c'era già (paginazione). +- I link verso schede e operazioni hanno `data-ritorno`: `resources/js/app.js` + gli aggiunge `?da=` al momento dell'uso (pointerdown/click/menu + contestuale/focus, prima di wire:navigate). Se la pagina corrente ha già un + `da`, passa quello: si risale sempre all'origine. +- `App\Support\Ritorno::valida()` accetta solo percorsi interni (niente + `https://…`, `//…`, `\`, caratteri di controllo, `/livewire/…`): il + parametro non può diventare un rimando verso l'esterno. +- Le pagine di scheda e operazione usano il trait `RicordaRitorno` + (`$ritorno`, `destinazione()`): redirect dopo il salvataggio, "Annulla" e + "Torna…" (che con un ritorno diventa "Torna indietro"). +- Aperte senza `da` (menu laterale, link salvato) tutto resta com'era. +- Link nuovi verso una scheda o un'operazione: metterci `data-ritorno`; + pagine nuove di operazione: usare `RicordaRitorno`. + ### Invio del link dopo l'assegnazione, nuovo territorio dal designer (19 ottobre 2026) Richieste dell'utente: diff --git a/app/Livewire/Assegnazioni/Assegna.php b/app/Livewire/Assegnazioni/Assegna.php index 0a7c090..bb76aa8 100644 --- a/app/Livewire/Assegnazioni/Assegna.php +++ b/app/Livewire/Assegnazioni/Assegna.php @@ -2,6 +2,7 @@ namespace App\Livewire\Assegnazioni; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use Livewire\Attributes\Computed; use Livewire\Attributes\Locked; @@ -16,6 +17,8 @@ use Illuminate\Support\Facades\Notification; class Assegna extends Component { + use RicordaRitorno; + // ─── Invio del link ───────────────────────────────────────── // // Dopo l'assegnazione non si torna subito alla pagina: si sceglie il @@ -234,7 +237,7 @@ class Assegna extends Component session()->flash('success', $inviato ? "Territorio {$numero} assegnato a {$nome}: link inviato." : "Territorio {$numero} assegnato a {$nome}. Il link è ancora da inviare: lo trovi segnalato nell'elenco."); - return $this->redirect(route('assegnazioni.assegna'), navigate: true); + return $this->redirect($this->destinazione(route('assegnazioni.assegna')), navigate: true); } private function assegnazioneInInvio(): ?Assegnazione diff --git a/app/Livewire/Assegnazioni/Rientra.php b/app/Livewire/Assegnazioni/Rientra.php index 5765075..d59d51d 100644 --- a/app/Livewire/Assegnazioni/Rientra.php +++ b/app/Livewire/Assegnazioni/Rientra.php @@ -3,12 +3,15 @@ namespace App\Livewire\Assegnazioni; use Livewire\Attributes\Locked; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Assegnazione; use App\Models\Campagna; class Rientra extends Component { + use RicordaRitorno; + public Assegnazione $assegnazione; public string $returned_at = ''; public bool $showCampaignPrompt = false; @@ -109,7 +112,7 @@ class Rientra extends Component } session()->flash('success', "Territorio {$territorio->numero} rientrato da {$proclamatore->nome_completo} dopo {$this->assegnazione->giorni} giorni."); - return $this->redirect(route('territori.show', $territorio), navigate: true); + return $this->redirect($this->destinazione(route('territori.show', $territorio)), navigate: true); } public function render() diff --git a/app/Livewire/Campagne/CampagnaCreate.php b/app/Livewire/Campagne/CampagnaCreate.php index 4ebf7a7..1d49bcb 100644 --- a/app/Livewire/Campagne/CampagnaCreate.php +++ b/app/Livewire/Campagne/CampagnaCreate.php @@ -2,11 +2,14 @@ namespace App\Livewire\Campagne; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Campagna; class CampagnaCreate extends Component { + use RicordaRitorno; + public string $descrizione = ''; public string $start_date = ''; public string $end_date = ''; @@ -34,7 +37,7 @@ class CampagnaCreate extends Component ]); session()->flash('success', "Campagna '{$campagna->descrizione}' creata."); - return $this->redirect(route('campagne.index'), navigate: true); + return $this->redirect($this->destinazione(route('campagne.index')), navigate: true); } public function render() diff --git a/app/Livewire/Campagne/CampagnaEdit.php b/app/Livewire/Campagne/CampagnaEdit.php index 99c19c6..97e52a6 100644 --- a/app/Livewire/Campagne/CampagnaEdit.php +++ b/app/Livewire/Campagne/CampagnaEdit.php @@ -2,11 +2,14 @@ namespace App\Livewire\Campagne; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Campagna; class CampagnaEdit extends Component { + use RicordaRitorno; + public Campagna $campagna; public string $descrizione = ''; public string $start_date = ''; @@ -47,7 +50,7 @@ class CampagnaEdit extends Component ]); session()->flash('success', "Campagna aggiornata."); - return $this->redirect(route('campagne.index'), navigate: true); + return $this->redirect($this->destinazione(route('campagne.index')), navigate: true); } public function render() diff --git a/app/Livewire/Campagne/CampagnaShow.php b/app/Livewire/Campagne/CampagnaShow.php index beebae2..a60df4a 100644 --- a/app/Livewire/Campagne/CampagnaShow.php +++ b/app/Livewire/Campagne/CampagnaShow.php @@ -2,6 +2,7 @@ namespace App\Livewire\Campagne; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Campagna; use App\Models\Assegnazione; @@ -9,6 +10,8 @@ use App\Models\Territorio; class CampagnaShow extends Component { + use RicordaRitorno; + public Campagna $campagna; public function mount(Campagna $campagna) diff --git a/app/Livewire/Concerns/RicordaRitorno.php b/app/Livewire/Concerns/RicordaRitorno.php new file mode 100644 index 0000000..b05dd7d --- /dev/null +++ b/app/Livewire/Concerns/RicordaRitorno.php @@ -0,0 +1,33 @@ +ritorno = Ritorno::daRichiesta(); + } + + /** Dove andare dopo la conferma: la pagina di partenza, o quella predefinita. */ + protected function destinazione(string $predefinita): string + { + return $this->ritorno ?? $predefinita; + } +} diff --git a/app/Livewire/Proclamatori/ProclamatoreCreate.php b/app/Livewire/Proclamatori/ProclamatoreCreate.php index b3677a0..d5de8b3 100644 --- a/app/Livewire/Proclamatori/ProclamatoreCreate.php +++ b/app/Livewire/Proclamatori/ProclamatoreCreate.php @@ -2,11 +2,14 @@ namespace App\Livewire\Proclamatori; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Proclamatore; class ProclamatoreCreate extends Component { + use RicordaRitorno; + public string $nome = ''; public string $cognome = ''; public bool $attivo = true; @@ -52,7 +55,7 @@ class ProclamatoreCreate extends Component ->log('created'); session()->flash('success', "Proclamatore {$proclamatore->nome_completo} creato."); - return $this->redirect(route('proclamatori.index'), navigate: true); + return $this->redirect($this->destinazione(route('proclamatori.index')), navigate: true); } public function render() diff --git a/app/Livewire/Proclamatori/ProclamatoreEdit.php b/app/Livewire/Proclamatori/ProclamatoreEdit.php index 85cdc88..c745693 100644 --- a/app/Livewire/Proclamatori/ProclamatoreEdit.php +++ b/app/Livewire/Proclamatori/ProclamatoreEdit.php @@ -2,11 +2,14 @@ namespace App\Livewire\Proclamatori; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Proclamatore; class ProclamatoreEdit extends Component { + use RicordaRitorno; + public Proclamatore $proclamatore; public string $nome = ''; public string $cognome = ''; @@ -59,7 +62,7 @@ class ProclamatoreEdit extends Component ]); session()->flash('success', "Proclamatore {$this->proclamatore->nome_completo} aggiornato."); - return $this->redirect(route('proclamatori.index'), navigate: true); + return $this->redirect($this->destinazione(route('proclamatori.index')), navigate: true); } public function render() diff --git a/app/Livewire/Proclamatori/ProclamatoreIndex.php b/app/Livewire/Proclamatori/ProclamatoreIndex.php index 3ee368d..f5a5831 100644 --- a/app/Livewire/Proclamatori/ProclamatoreIndex.php +++ b/app/Livewire/Proclamatori/ProclamatoreIndex.php @@ -3,6 +3,7 @@ namespace App\Livewire\Proclamatori; use App\Livewire\Concerns\Avvisa; +use Livewire\Attributes\Url; use Livewire\Component; use Livewire\WithPagination; use App\Models\Proclamatore; @@ -12,9 +13,14 @@ class ProclamatoreIndex extends Component use Avvisa; use WithPagination; + // Ricerca, filtro e ordinamento nell'indirizzo: si ritrovano tornando. + #[Url(except: '')] public string $search = ''; + #[Url(except: '')] public string $filtroStato = ''; + #[Url(except: 'cognome')] public string $sortField = 'cognome'; + #[Url(except: 'asc')] public string $sortDirection = 'asc'; protected $queryString = [ diff --git a/app/Livewire/Proclamatori/ProclamatoreShow.php b/app/Livewire/Proclamatori/ProclamatoreShow.php index 96af78a..568d437 100644 --- a/app/Livewire/Proclamatori/ProclamatoreShow.php +++ b/app/Livewire/Proclamatori/ProclamatoreShow.php @@ -2,12 +2,15 @@ namespace App\Livewire\Proclamatori; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Proclamatore; use App\Models\Assegnazione; class ProclamatoreShow extends Component { + use RicordaRitorno; + public Proclamatore $proclamatore; public function mount(Proclamatore $proclamatore) diff --git a/app/Livewire/Registro.php b/app/Livewire/Registro.php index 6b8c98d..da5d4db 100644 --- a/app/Livewire/Registro.php +++ b/app/Livewire/Registro.php @@ -2,6 +2,7 @@ namespace App\Livewire; +use Livewire\Attributes\Url; use Livewire\Component; use Livewire\WithPagination; use App\Models\Assegnazione; @@ -19,12 +20,20 @@ class Registro extends Component { use WithPagination; + // Ricerca, filtri e ordinamento nell'indirizzo: si ritrovano tornando. + #[Url(except: '')] public string $search = ''; + #[Url(except: '')] public string $filtroAnno = ''; + #[Url(except: '')] public string $filtroZona = ''; + #[Url(except: '')] public string $filtroTipologia = ''; + #[Url(except: '')] public string $filtroStato = ''; // aperte, chiuse + #[Url(except: 'territorio_numero')] public string $sortField = 'territorio_numero'; + #[Url(except: 'asc')] public string $sortDirection = 'asc'; // ─── Modal create/edit ────────────────────────────────────── diff --git a/app/Livewire/Territori/TerritorioCreate.php b/app/Livewire/Territori/TerritorioCreate.php index e35c0cd..34ace99 100644 --- a/app/Livewire/Territori/TerritorioCreate.php +++ b/app/Livewire/Territori/TerritorioCreate.php @@ -2,6 +2,7 @@ namespace App\Livewire\Territori; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use Livewire\WithFileUploads; use App\Models\Territorio; @@ -11,6 +12,8 @@ use App\Services\TerritorioThumbnailService; class TerritorioCreate extends Component { + use RicordaRitorno; + use WithFileUploads; public string $numero = ''; @@ -64,7 +67,7 @@ class TerritorioCreate extends Component ->log('created'); session()->flash('success', "Territorio {$territorio->numero} creato."); - return redirect()->route('territori.index'); + return redirect()->to($this->destinazione(route('territori.index'))); } public function render() diff --git a/app/Livewire/Territori/TerritorioEdit.php b/app/Livewire/Territori/TerritorioEdit.php index 6482b70..60b5706 100644 --- a/app/Livewire/Territori/TerritorioEdit.php +++ b/app/Livewire/Territori/TerritorioEdit.php @@ -2,6 +2,7 @@ namespace App\Livewire\Territori; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use Livewire\WithFileUploads; use App\Models\Territorio; @@ -12,6 +13,8 @@ use Illuminate\Support\Facades\Storage; class TerritorioEdit extends Component { + use RicordaRitorno; + use WithFileUploads; public Territorio $territorio; @@ -82,7 +85,7 @@ class TerritorioEdit extends Component $this->territorio->update($data); session()->flash('success', "Territorio {$this->territorio->numero} aggiornato."); - return redirect()->route('territori.show', $this->territorio); + return redirect()->to($this->destinazione(route('territori.show', $this->territorio))); } public function removePdf() diff --git a/app/Livewire/Territori/TerritorioIndex.php b/app/Livewire/Territori/TerritorioIndex.php index bb5307f..0051ee7 100644 --- a/app/Livewire/Territori/TerritorioIndex.php +++ b/app/Livewire/Territori/TerritorioIndex.php @@ -4,6 +4,7 @@ namespace App\Livewire\Territori; use App\Livewire\Concerns\Avvisa; use Illuminate\Pagination\LengthAwarePaginator; +use Livewire\Attributes\Url; use Livewire\Component; use Livewire\WithPagination; use App\Models\Territorio; @@ -15,14 +16,25 @@ class TerritorioIndex extends Component use Avvisa; use WithPagination; + // Ricerca, filtri e ordinamento stanno nell'indirizzo (#[Url]): tornando + // da una scheda o da un'operazione l'elenco si ritrova com'era. + #[Url(except: '')] public string $search = ''; + #[Url(except: '')] public string $filterZona = ''; + #[Url(except: '')] public string $filterTipologia = ''; + #[Url(except: '')] public string $filterStato = ''; + #[Url(except: '')] public string $filterPriorita = ''; + #[Url(except: '')] public string $filterPdf = ''; + #[Url(except: '')] public string $filterContenuti = ''; + #[Url(except: 'numero')] public string $sortField = 'numero'; + #[Url(except: 'asc')] public string $sortDirection = 'asc'; protected $queryString = [ diff --git a/app/Livewire/Territori/TerritorioShow.php b/app/Livewire/Territori/TerritorioShow.php index b0dc808..d34b8ac 100644 --- a/app/Livewire/Territori/TerritorioShow.php +++ b/app/Livewire/Territori/TerritorioShow.php @@ -2,12 +2,15 @@ namespace App\Livewire\Territori; +use App\Livewire\Concerns\RicordaRitorno; use Livewire\Component; use App\Models\Territorio; use App\Models\Assegnazione; class TerritorioShow extends Component { + use RicordaRitorno; + public Territorio $territorio; public function mount(Territorio $territorio) diff --git a/app/Support/Ritorno.php b/app/Support/Ritorno.php new file mode 100644 index 0000000..6f4a9fb --- /dev/null +++ b/app/Support/Ritorno.php @@ -0,0 +1,49 @@ +` + * (lo aggiunge resources/js/app.js ai link con data-ritorno): il percorso + * della pagina di partenza con i suoi filtri, o il `da` che quella pagina + * aveva già ricevuto, così si risale sempre all'origine. Elenco → scheda → + * Rientra → conferma riporta all'elenco filtrato. + * + * Si accettano solo percorsi interni ("/territori?zona=3"): un indirizzo + * completo o "//altro.sito" farebbe del parametro un rimando verso l'esterno. + */ +final class Ritorno +{ + public const PARAM = 'da'; + + /** L'URL a cui tornare, o null se il valore non è un percorso interno valido. */ + public static function valida(mixed $percorso): ?string + { + if (! is_string($percorso) || $percorso === '' || strlen($percorso) > 2000) { + return null; + } + + if (! str_starts_with($percorso, '/') + || str_starts_with($percorso, '//') + || str_contains($percorso, '\\') + || preg_match('/[\x00-\x1F\x7F]/', $percorso)) { + return null; + } + + // Le richieste interne di Livewire non sono pagine. + $path = (string) parse_url($percorso, PHP_URL_PATH); + if ($path === '' || str_starts_with($path, '/livewire')) { + return null; + } + + return url($percorso); + } + + /** Il ritorno indicato dalla richiesta corrente (?da=…), se valido. */ + public static function daRichiesta(): ?string + { + return self::valida(request()->query(self::PARAM)); + } +} diff --git a/resources/js/app.js b/resources/js/app.js index e59d6a0..4519fb5 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,34 @@ import './bootstrap'; + +// ─── Ritorno alla pagina di partenza ──────────────────────────────────────── +// +// I link con data-ritorno (schede e operazioni) portano con sé ?da=: +// la pagina di partenza con i suoi filtri, oppure il `da` che la pagina +// corrente aveva già ricevuto, così si risale sempre all'origine del percorso +// (elenco → scheda → Rientra → torna all'elenco). Il server lo valida e lo usa +// dopo conferma o annulla: App\Support\Ritorno, trait RicordaRitorno. +// +// Si calcola al momento dell'uso e non al caricamento: filtri e pagina degli +// elenchi cambiano l'indirizzo (#[Url] di Livewire) senza ricaricare. + +function ritornoCorrente() { + const qui = new URL(window.location.href); + return qui.searchParams.get('da') || (qui.pathname + qui.search); +} + +function aggiornaLink(link) { + const destinazione = new URL(link.getAttribute('href'), window.location.origin); + if (destinazione.origin !== window.location.origin) return; + destinazione.searchParams.set('da', ritornoCorrente()); + link.setAttribute('href', destinazione.pathname + destinazione.search + destinazione.hash); +} + +// Fase di cattura: si arriva prima di wire:navigate, che legge l'href al +// pointerdown (precaricamento) e al click. contextmenu e focusin coprono +// "apri in una nuova scheda" e la tastiera. +['pointerdown', 'click', 'contextmenu', 'focusin'].forEach((evento) => { + document.addEventListener(evento, (e) => { + const link = e.target instanceof Element ? e.target.closest('a[data-ritorno][href]') : null; + if (link) aggiornaLink(link); + }, true); +}); diff --git a/resources/views/livewire/assegnazioni/assegna.blade.php b/resources/views/livewire/assegnazioni/assegna.blade.php index 6fad26d..31e9b7e 100644 --- a/resources/views/livewire/assegnazioni/assegna.blade.php +++ b/resources/views/livewire/assegnazioni/assegna.blade.php @@ -1,8 +1,8 @@
- + - Torna ai territori + {{ $ritorno ? 'Torna indietro' : 'Torna ai territori' }}

Assegnazioni

Questa funzione consente un'assegnazione arbitraria, indipendente dalle tre schede della Home.

@@ -75,7 +75,7 @@
  • Email non disponibile: il server di posta non è configurato.
  • @endif @if((! $whatsapp || $email === '') && auth()->user()->can('settings.manage') && $invio->proclamatore) -
  • Aggiungi i contatti al proclamatore
  • +
  • Aggiungi i contatti al proclamatore
  • @endif @@ -185,7 +185,11 @@
    Assegna - Annulla + @if($ritorno) + Annulla + @else + Annulla + @endif
    @@ -225,7 +229,7 @@ {{-- Testata riga --}}
    - N° {{ $a->territorio?->numero }} + N° {{ $a->territorio?->numero }} @if($a->territorio?->zona?->nome) {{ $a->territorio?->zona?->nome }} @endif diff --git a/resources/views/livewire/assegnazioni/rientra.blade.php b/resources/views/livewire/assegnazioni/rientra.blade.php index 64690ff..37c16da 100644 --- a/resources/views/livewire/assegnazioni/rientra.blade.php +++ b/resources/views/livewire/assegnazioni/rientra.blade.php @@ -1,8 +1,8 @@
    @@ -62,7 +62,7 @@
    Registra rientro - Annulla + Annulla
    diff --git a/resources/views/livewire/campagne/campagna-form.blade.php b/resources/views/livewire/campagne/campagna-form.blade.php index 4b1d21c..4600cbd 100644 --- a/resources/views/livewire/campagne/campagna-form.blade.php +++ b/resources/views/livewire/campagne/campagna-form.blade.php @@ -1,8 +1,8 @@
    @@ -30,7 +30,7 @@
    {{ $btnLabel }} - Annulla + Annulla
    diff --git a/resources/views/livewire/campagne/campagna-index.blade.php b/resources/views/livewire/campagne/campagna-index.blade.php index a0742f1..98613fd 100644 --- a/resources/views/livewire/campagne/campagna-index.blade.php +++ b/resources/views/livewire/campagne/campagna-index.blade.php @@ -2,7 +2,7 @@

    Campagne

    @can('settings.manage') - + Nuova campagna @@ -47,13 +47,13 @@
    - @can('settings.manage') - diff --git a/resources/views/livewire/campagne/campagna-show.blade.php b/resources/views/livewire/campagne/campagna-show.blade.php index 469028c..2a4aff0 100644 --- a/resources/views/livewire/campagne/campagna-show.blade.php +++ b/resources/views/livewire/campagne/campagna-show.blade.php @@ -1,14 +1,14 @@
    @can('settings.manage') - + Modifica @@ -63,7 +63,7 @@ @forelse($conteggiate as $a) - N° {{ $a->territorio?->numero }} + N° {{ $a->territorio?->numero }} {{ $a->proclamatore?->nome_completo ?? 'N/A' }} {{ $a->assigned_at->format('d/m/Y') }} {{ $a->returned_at?->format('d/m/Y') }} @@ -99,7 +99,7 @@ @forelse($inCorso as $a) - N° {{ $a->territorio?->numero }} + N° {{ $a->territorio?->numero }} {{ $a->proclamatore?->nome_completo ?? 'N/A' }} {{ $a->assigned_at->format('d/m/Y') }} {{ $a->giorni }} diff --git a/resources/views/livewire/home.blade.php b/resources/views/livewire/home.blade.php index 0a3747b..3e7fa2f 100644 --- a/resources/views/livewire/home.blade.php +++ b/resources/views/livewire/home.blade.php @@ -103,7 +103,7 @@
  • - N° {{ $t->numero }} + N° {{ $t->numero }} @if($t->home_is_prioritario) ★ Prioritario @endif @@ -116,7 +116,7 @@

    @can('territori.assign') - Assegna → + Assegna → @endcan
  • @empty @@ -158,7 +158,7 @@ @php($assegnazioneCorrente = $t->assegnazioneCorrente)
  • - N° {{ $t->numero }} + N° {{ $t->numero }}

    {{ $assegnazioneCorrente?->proclamatore?->nome_completo }} — {{ $assegnazioneCorrente?->giorni }} gg @@ -166,7 +166,7 @@

    @can('territori.return') @if($assegnazioneCorrente) - Rientra → + Rientra → @endif @endcan
  • diff --git a/resources/views/livewire/proclamatori/proclamatore-form.blade.php b/resources/views/livewire/proclamatori/proclamatore-form.blade.php index 637992c..4ab4da5 100644 --- a/resources/views/livewire/proclamatori/proclamatore-form.blade.php +++ b/resources/views/livewire/proclamatori/proclamatore-form.blade.php @@ -1,8 +1,8 @@
    @@ -44,7 +44,7 @@
    {{ $btnLabel }} - Annulla + Annulla
    diff --git a/resources/views/livewire/proclamatori/proclamatore-index.blade.php b/resources/views/livewire/proclamatori/proclamatore-index.blade.php index 4baad7e..5b49f58 100644 --- a/resources/views/livewire/proclamatori/proclamatore-index.blade.php +++ b/resources/views/livewire/proclamatori/proclamatore-index.blade.php @@ -6,7 +6,7 @@ link rosso perso in mezzo ai filtri, in un posto diverso rispetto all'altra lista che fa esattamente la stessa cosa. --}}
    - + Nuovo proclamatore @@ -63,13 +63,13 @@
    - @can('settings.manage') - diff --git a/resources/views/livewire/proclamatori/proclamatore-show.blade.php b/resources/views/livewire/proclamatori/proclamatore-show.blade.php index 7d8fdad..2b73ab8 100644 --- a/resources/views/livewire/proclamatori/proclamatore-show.blade.php +++ b/resources/views/livewire/proclamatori/proclamatore-show.blade.php @@ -1,9 +1,9 @@
    - + - Torna alla lista + {{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}

    {{ $proclamatore->nome_completo }}

    @if($proclamatore->telefono || $proclamatore->email) @@ -16,7 +16,7 @@ @endif
    @can('settings.manage') - + Modifica @@ -66,7 +66,7 @@ @foreach($assegnazioni as $assegnazione) - + {{ $assegnazione->territorio?->numero ?? 'N/A' }} diff --git a/resources/views/livewire/registro.blade.php b/resources/views/livewire/registro.blade.php index 319eb49..fc6adf7 100644 --- a/resources/views/livewire/registro.blade.php +++ b/resources/views/livewire/registro.blade.php @@ -82,7 +82,7 @@ class="h-8 w-6 rounded border border-gray-200 object-cover bg-gray-50 flex-none"> @endif
    diff --git a/resources/views/livewire/territori/territorio-form.blade.php b/resources/views/livewire/territori/territorio-form.blade.php index d7d42f4..e0ab639 100644 --- a/resources/views/livewire/territori/territorio-form.blade.php +++ b/resources/views/livewire/territori/territorio-form.blade.php @@ -1,8 +1,8 @@
    @@ -65,7 +65,7 @@ dell'applicazione. Prima cambiava da una pagina all'altra. --}}
    {{ $isEdit ? 'Aggiorna' : 'Crea' }} - Annulla + Annulla
    diff --git a/resources/views/livewire/territori/territorio-index.blade.php b/resources/views/livewire/territori/territorio-index.blade.php index 01121a6..4102663 100644 --- a/resources/views/livewire/territori/territorio-index.blade.php +++ b/resources/views/livewire/territori/territorio-index.blade.php @@ -10,7 +10,7 @@ disegna il confine e lo si crea da lì (PiantineEditor, ?nuovo=1). L'editor è dell'amministratore: chi ha solo territori.manage resta sul form. --}} - + Nuovo territorio @@ -116,7 +116,7 @@ alt="Thumbnail territorio {{ $territorio->numero }}" class="h-8 w-6 rounded border border-gray-200 object-cover bg-gray-50 flex-none shadow-sm"> @endif - {{ $territorio->numero }} + {{ $territorio->numero }}
    {{ $territorio->zona?->nome ?? '-' }} @@ -147,7 +147,7 @@ {{-- Primary actions: Assegna / Rientra --}} @can('territori.assign') @if(!$territorio->assegnazioneCorrente && $territorio->attivo) - + Assegna @@ -158,7 +158,7 @@ {{-- Rientra non cancella niente: era rosso pieno quanto un Elimina. È l'azione principale della riga, alternativa ad Assegna. --}} - + Rientra @@ -168,13 +168,13 @@ {{-- Solo icona: l'aria-label non è ridondante col title, è l'unico nome che un lettore di schermo annuncia in modo affidabile. --}} - @can('territori.manage') - diff --git a/resources/views/livewire/territori/territorio-show.blade.php b/resources/views/livewire/territori/territorio-show.blade.php index e8eb24f..855a83c 100644 --- a/resources/views/livewire/territori/territorio-show.blade.php +++ b/resources/views/livewire/territori/territorio-show.blade.php @@ -1,16 +1,16 @@
    @can('territori.assign') @if(!$territorio->assegnazioneCorrente && $territorio->attivo) - + Assegna @@ -21,14 +21,14 @@ {{-- Rientra era rosso pieno quanto un Elimina. Non cancella niente: è l'azione principale di un territorio assegnato, quindi primary come Assegna, che è la sua alternativa. --}} - + Rientra @endif @endcan @can('territori.manage') - + Modifica @@ -41,7 +41,7 @@
    Il link della piantina è ancora da inviare a {{ $territorio->assegnazioneCorrente->proclamatore?->nome_completo }}. @can('territori.assign') - Invia link + Invia link @endcan
    @endif @@ -109,7 +109,7 @@
    @can('territori.return') - Rientra + Rientra @endcan
    diff --git a/tests/Feature/RitornoTest.php b/tests/Feature/RitornoTest.php new file mode 100644 index 0000000..137be38 --- /dev/null +++ b/tests/Feature/RitornoTest.php @@ -0,0 +1,191 @@ +withoutVite(); + $this->seed(RolesAndPermissionsSeeder::class); + Setting::firstOrCreate([], ['congregazione_nome' => 'Congregazione di prova']); + AnnoTeocratico::create(['label' => '2026-2027', 'start_date' => '2026-09-01', 'end_date' => '2027-08-31']); + + $this->admin = User::create(['name' => 'Admin', 'email' => 'admin@example.test', 'password' => 'password-di-prova']); + $this->admin->assignRole('amministratore'); + } + + // ─── Validazione ──────────────────────────────────────────── + + public function test_un_percorso_interno_diventa_l_url_di_ritorno(): void + { + $this->assertSame(url('/territori?filterZona=3&page=2'), Ritorno::valida('/territori?filterZona=3&page=2')); + $this->assertSame(url('/'), Ritorno::valida('/')); + } + + #[DataProvider('ritorniNonValidi')] + public function test_un_ritorno_esterno_o_malformato_si_ignora(mixed $valore): void + { + $this->assertNull(Ritorno::valida($valore)); + } + + public static function ritorniNonValidi(): array + { + return [ + 'vuoto' => [''], + 'null' => [null], + 'array' => [['/territori']], + 'url completo' => ['https://altro.sito/x'], + 'senza schema' => ['//altro.sito/x'], + 'barra rovesciata' => ['/\\altro.sito'], + 'relativo' => ['territori'], + 'javascript' => ['javascript:alert(1)'], + 'a capo' => ["/territori\r\nX: y"], + 'livewire' => ['/livewire/update'], + ]; + } + + // ─── Dopo la conferma ─────────────────────────────────────── + + private function assegnazioneAperta(): Assegnazione + { + $territorio = Territorio::create(['numero' => '35', 'attivo' => true]); + $proclamatore = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]); + + return Assegnazione::create([ + 'territorio_id' => $territorio->id, + 'proclamatore_id' => $proclamatore->id, + 'anno_teocratico_id' => AnnoTeocratico::sole()->id, + 'assigned_at' => now()->subDays(10), + 'created_by' => $this->admin->id, + ]); + } + + public function test_il_rientro_torna_all_elenco_filtrato_da_cui_si_era_partiti(): void + { + $assegnazione = $this->assegnazioneAperta(); + + Livewire::withQueryParams(['da' => '/territori?filterStato=da_rientrare&page=2']) + ->actingAs($this->admin) + ->test(Rientra::class, ['assegnazione' => $assegnazione]) + ->call('save') + ->assertRedirect(url('/territori?filterStato=da_rientrare&page=2')); + } + + public function test_senza_pagina_di_partenza_il_rientro_va_alla_scheda_come_prima(): void + { + $assegnazione = $this->assegnazioneAperta(); + + Livewire::actingAs($this->admin) + ->test(Rientra::class, ['assegnazione' => $assegnazione]) + ->call('save') + ->assertRedirect(route('territori.show', $assegnazione->territorio_id)); + } + + public function test_un_ritorno_verso_l_esterno_non_viene_seguito(): void + { + $assegnazione = $this->assegnazioneAperta(); + + Livewire::withQueryParams(['da' => '//altro.sito/phishing']) + ->actingAs($this->admin) + ->test(Rientra::class, ['assegnazione' => $assegnazione]) + ->assertSet('ritorno', null) + ->call('save') + ->assertRedirect(route('territori.show', $assegnazione->territorio_id)); + } + + public function test_la_modifica_del_proclamatore_torna_alla_ricerca(): void + { + $mario = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]); + + Livewire::withQueryParams(['da' => '/proclamatori?search=Rossi']) + ->actingAs($this->admin) + ->test(ProclamatoreEdit::class, ['proclamatore' => $mario]) + ->set('nome', 'Mario Luigi') + ->call('save') + ->assertRedirect(url('/proclamatori?search=Rossi')); + } + + // ─── Annulla e "Torna indietro" ───────────────────────────── + + public function test_annulla_e_torna_indietro_portano_alla_pagina_di_partenza(): void + { + $assegnazione = $this->assegnazioneAperta(); + $partenza = '/territori?search=35'; + + $this->actingAs($this->admin) + ->get(route('assegnazioni.rientra', $assegnazione) . '?da=' . urlencode($partenza)) + ->assertOk() + ->assertSee('Torna indietro') + ->assertSee('href="' . e(url($partenza)) . '"', false); + } + + public function test_senza_partenza_le_etichette_restano_quelle_di_prima(): void + { + $assegnazione = $this->assegnazioneAperta(); + + $this->actingAs($this->admin) + ->get(route('assegnazioni.rientra', $assegnazione)) + ->assertOk() + ->assertSee('Torna al territorio') + ->assertDontSee('Torna indietro'); + } + + // ─── Gli elenchi ricordano filtri e ordinamento ───────────── + + public function test_l_elenco_territori_legge_filtri_e_ordinamento_dall_indirizzo(): void + { + Livewire::withQueryParams(['search' => '35', 'filterStato' => 'assegnato', 'sortField' => 'zona', 'sortDirection' => 'desc']) + ->actingAs($this->admin) + ->test(TerritorioIndex::class) + ->assertSet('search', '35') + ->assertSet('filterStato', 'assegnato') + ->assertSet('sortField', 'zona') + ->assertSet('sortDirection', 'desc'); + } + + public function test_l_elenco_proclamatori_legge_la_ricerca_dall_indirizzo(): void + { + Livewire::withQueryParams(['search' => 'Rossi', 'filtroStato' => 'attivi']) + ->actingAs($this->admin) + ->test(ProclamatoreIndex::class) + ->assertSet('search', 'Rossi') + ->assertSet('filtroStato', 'attivi'); + } + + public function test_i_link_verso_schede_e_operazioni_portano_il_ritorno(): void + { + Territorio::create(['numero' => '35', 'attivo' => true]); + + $html = $this->actingAs($this->admin)->get(route('territori.index'))->assertOk()->getContent(); + + // Il link alla scheda è marcato: il JS gli aggiunge ?da= al momento del clic. + $this->assertMatchesRegularExpression('/]*\bdata-ritorno\b)(?=[^>]*href="[^"]*\/territori\/\d+")[^>]*>/s', $html); + } +}