++ dopo un'operazione si torna alla pagina di partenza, con i suoi filtri
- 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 <noreply@anthropic.com>
This commit is contained in:
23
HANDOFF.md
23
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
|
sbloccato; riquadro dopo l'assegnazione con WhatsApp e scelta. Configurazione
|
||||||
e dati di prova rimossi dopo.
|
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=<percorso>` 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)
|
### Invio del link dopo l'assegnazione, nuovo territorio dal designer (19 ottobre 2026)
|
||||||
|
|
||||||
Richieste dell'utente:
|
Richieste dell'utente:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Assegnazioni;
|
namespace App\Livewire\Assegnazioni;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\Attributes\Computed;
|
use Livewire\Attributes\Computed;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
@@ -16,6 +17,8 @@ use Illuminate\Support\Facades\Notification;
|
|||||||
|
|
||||||
class Assegna extends Component
|
class Assegna extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
// ─── Invio del link ─────────────────────────────────────────
|
// ─── Invio del link ─────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Dopo l'assegnazione non si torna subito alla pagina: si sceglie il
|
// Dopo l'assegnazione non si torna subito alla pagina: si sceglie il
|
||||||
@@ -234,7 +237,7 @@ class Assegna extends Component
|
|||||||
session()->flash('success', $inviato
|
session()->flash('success', $inviato
|
||||||
? "Territorio {$numero} assegnato a {$nome}: link inviato."
|
? "Territorio {$numero} assegnato a {$nome}: link inviato."
|
||||||
: "Territorio {$numero} assegnato a {$nome}. Il link è ancora da inviare: lo trovi segnalato nell'elenco.");
|
: "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
|
private function assegnazioneInInvio(): ?Assegnazione
|
||||||
|
|||||||
@@ -3,12 +3,15 @@
|
|||||||
namespace App\Livewire\Assegnazioni;
|
namespace App\Livewire\Assegnazioni;
|
||||||
|
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Assegnazione;
|
use App\Models\Assegnazione;
|
||||||
use App\Models\Campagna;
|
use App\Models\Campagna;
|
||||||
|
|
||||||
class Rientra extends Component
|
class Rientra extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public Assegnazione $assegnazione;
|
public Assegnazione $assegnazione;
|
||||||
public string $returned_at = '';
|
public string $returned_at = '';
|
||||||
public bool $showCampaignPrompt = false;
|
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.");
|
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()
|
public function render()
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Campagne;
|
namespace App\Livewire\Campagne;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Campagna;
|
use App\Models\Campagna;
|
||||||
|
|
||||||
class CampagnaCreate extends Component
|
class CampagnaCreate extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public string $descrizione = '';
|
public string $descrizione = '';
|
||||||
public string $start_date = '';
|
public string $start_date = '';
|
||||||
public string $end_date = '';
|
public string $end_date = '';
|
||||||
@@ -34,7 +37,7 @@ class CampagnaCreate extends Component
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
session()->flash('success', "Campagna '{$campagna->descrizione}' creata.");
|
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()
|
public function render()
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Campagne;
|
namespace App\Livewire\Campagne;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Campagna;
|
use App\Models\Campagna;
|
||||||
|
|
||||||
class CampagnaEdit extends Component
|
class CampagnaEdit extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public Campagna $campagna;
|
public Campagna $campagna;
|
||||||
public string $descrizione = '';
|
public string $descrizione = '';
|
||||||
public string $start_date = '';
|
public string $start_date = '';
|
||||||
@@ -47,7 +50,7 @@ class CampagnaEdit extends Component
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
session()->flash('success', "Campagna aggiornata.");
|
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()
|
public function render()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Campagne;
|
namespace App\Livewire\Campagne;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Campagna;
|
use App\Models\Campagna;
|
||||||
use App\Models\Assegnazione;
|
use App\Models\Assegnazione;
|
||||||
@@ -9,6 +10,8 @@ use App\Models\Territorio;
|
|||||||
|
|
||||||
class CampagnaShow extends Component
|
class CampagnaShow extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public Campagna $campagna;
|
public Campagna $campagna;
|
||||||
|
|
||||||
public function mount(Campagna $campagna)
|
public function mount(Campagna $campagna)
|
||||||
|
|||||||
33
app/Livewire/Concerns/RicordaRitorno.php
Normal file
33
app/Livewire/Concerns/RicordaRitorno.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Concerns;
|
||||||
|
|
||||||
|
use App\Support\Ritorno;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per le pagine che si aprono da un'altra (schede e operazioni): ricorda da
|
||||||
|
* dove si è partiti (?da=…, vedi App\Support\Ritorno) e ci torna dopo la
|
||||||
|
* conferma. Annulla e "Torna…" nella vista usano $ritorno allo stesso modo.
|
||||||
|
*
|
||||||
|
* Aperta senza `da` (menu laterale, link salvato) la pagina si comporta come
|
||||||
|
* prima: ogni componente passa la sua destinazione predefinita.
|
||||||
|
*/
|
||||||
|
trait RicordaRitorno
|
||||||
|
{
|
||||||
|
/** URL della pagina di partenza; null se aperta direttamente. */
|
||||||
|
#[Locked]
|
||||||
|
public ?string $ritorno = null;
|
||||||
|
|
||||||
|
/** Hook di Livewire: gira al mount di ogni componente che usa il trait. */
|
||||||
|
public function mountRicordaRitorno(): void
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Proclamatori;
|
namespace App\Livewire\Proclamatori;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Proclamatore;
|
use App\Models\Proclamatore;
|
||||||
|
|
||||||
class ProclamatoreCreate extends Component
|
class ProclamatoreCreate extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public string $nome = '';
|
public string $nome = '';
|
||||||
public string $cognome = '';
|
public string $cognome = '';
|
||||||
public bool $attivo = true;
|
public bool $attivo = true;
|
||||||
@@ -52,7 +55,7 @@ class ProclamatoreCreate extends Component
|
|||||||
->log('created');
|
->log('created');
|
||||||
|
|
||||||
session()->flash('success', "Proclamatore {$proclamatore->nome_completo} creato.");
|
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()
|
public function render()
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Proclamatori;
|
namespace App\Livewire\Proclamatori;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Proclamatore;
|
use App\Models\Proclamatore;
|
||||||
|
|
||||||
class ProclamatoreEdit extends Component
|
class ProclamatoreEdit extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public Proclamatore $proclamatore;
|
public Proclamatore $proclamatore;
|
||||||
public string $nome = '';
|
public string $nome = '';
|
||||||
public string $cognome = '';
|
public string $cognome = '';
|
||||||
@@ -59,7 +62,7 @@ class ProclamatoreEdit extends Component
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
session()->flash('success', "Proclamatore {$this->proclamatore->nome_completo} aggiornato.");
|
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()
|
public function render()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Proclamatori;
|
namespace App\Livewire\Proclamatori;
|
||||||
|
|
||||||
use App\Livewire\Concerns\Avvisa;
|
use App\Livewire\Concerns\Avvisa;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use App\Models\Proclamatore;
|
use App\Models\Proclamatore;
|
||||||
@@ -12,9 +13,14 @@ class ProclamatoreIndex extends Component
|
|||||||
use Avvisa;
|
use Avvisa;
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
|
// Ricerca, filtro e ordinamento nell'indirizzo: si ritrovano tornando.
|
||||||
|
#[Url(except: '')]
|
||||||
public string $search = '';
|
public string $search = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filtroStato = '';
|
public string $filtroStato = '';
|
||||||
|
#[Url(except: 'cognome')]
|
||||||
public string $sortField = 'cognome';
|
public string $sortField = 'cognome';
|
||||||
|
#[Url(except: 'asc')]
|
||||||
public string $sortDirection = 'asc';
|
public string $sortDirection = 'asc';
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Proclamatori;
|
namespace App\Livewire\Proclamatori;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Proclamatore;
|
use App\Models\Proclamatore;
|
||||||
use App\Models\Assegnazione;
|
use App\Models\Assegnazione;
|
||||||
|
|
||||||
class ProclamatoreShow extends Component
|
class ProclamatoreShow extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public Proclamatore $proclamatore;
|
public Proclamatore $proclamatore;
|
||||||
|
|
||||||
public function mount(Proclamatore $proclamatore)
|
public function mount(Proclamatore $proclamatore)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use App\Models\Assegnazione;
|
use App\Models\Assegnazione;
|
||||||
@@ -19,12 +20,20 @@ class Registro extends Component
|
|||||||
{
|
{
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
|
// Ricerca, filtri e ordinamento nell'indirizzo: si ritrovano tornando.
|
||||||
|
#[Url(except: '')]
|
||||||
public string $search = '';
|
public string $search = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filtroAnno = '';
|
public string $filtroAnno = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filtroZona = '';
|
public string $filtroZona = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filtroTipologia = '';
|
public string $filtroTipologia = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filtroStato = ''; // aperte, chiuse
|
public string $filtroStato = ''; // aperte, chiuse
|
||||||
|
#[Url(except: 'territorio_numero')]
|
||||||
public string $sortField = 'territorio_numero';
|
public string $sortField = 'territorio_numero';
|
||||||
|
#[Url(except: 'asc')]
|
||||||
public string $sortDirection = 'asc';
|
public string $sortDirection = 'asc';
|
||||||
|
|
||||||
// ─── Modal create/edit ──────────────────────────────────────
|
// ─── Modal create/edit ──────────────────────────────────────
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Territori;
|
namespace App\Livewire\Territori;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithFileUploads;
|
use Livewire\WithFileUploads;
|
||||||
use App\Models\Territorio;
|
use App\Models\Territorio;
|
||||||
@@ -11,6 +12,8 @@ use App\Services\TerritorioThumbnailService;
|
|||||||
|
|
||||||
class TerritorioCreate extends Component
|
class TerritorioCreate extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
use WithFileUploads;
|
use WithFileUploads;
|
||||||
|
|
||||||
public string $numero = '';
|
public string $numero = '';
|
||||||
@@ -64,7 +67,7 @@ class TerritorioCreate extends Component
|
|||||||
->log('created');
|
->log('created');
|
||||||
|
|
||||||
session()->flash('success', "Territorio {$territorio->numero} creato.");
|
session()->flash('success', "Territorio {$territorio->numero} creato.");
|
||||||
return redirect()->route('territori.index');
|
return redirect()->to($this->destinazione(route('territori.index')));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Territori;
|
namespace App\Livewire\Territori;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithFileUploads;
|
use Livewire\WithFileUploads;
|
||||||
use App\Models\Territorio;
|
use App\Models\Territorio;
|
||||||
@@ -12,6 +13,8 @@ use Illuminate\Support\Facades\Storage;
|
|||||||
|
|
||||||
class TerritorioEdit extends Component
|
class TerritorioEdit extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
use WithFileUploads;
|
use WithFileUploads;
|
||||||
|
|
||||||
public Territorio $territorio;
|
public Territorio $territorio;
|
||||||
@@ -82,7 +85,7 @@ class TerritorioEdit extends Component
|
|||||||
$this->territorio->update($data);
|
$this->territorio->update($data);
|
||||||
|
|
||||||
session()->flash('success', "Territorio {$this->territorio->numero} aggiornato.");
|
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()
|
public function removePdf()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Territori;
|
|||||||
|
|
||||||
use App\Livewire\Concerns\Avvisa;
|
use App\Livewire\Concerns\Avvisa;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
use Livewire\Attributes\Url;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use App\Models\Territorio;
|
use App\Models\Territorio;
|
||||||
@@ -15,14 +16,25 @@ class TerritorioIndex extends Component
|
|||||||
use Avvisa;
|
use Avvisa;
|
||||||
use WithPagination;
|
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 = '';
|
public string $search = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filterZona = '';
|
public string $filterZona = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filterTipologia = '';
|
public string $filterTipologia = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filterStato = '';
|
public string $filterStato = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filterPriorita = '';
|
public string $filterPriorita = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filterPdf = '';
|
public string $filterPdf = '';
|
||||||
|
#[Url(except: '')]
|
||||||
public string $filterContenuti = '';
|
public string $filterContenuti = '';
|
||||||
|
#[Url(except: 'numero')]
|
||||||
public string $sortField = 'numero';
|
public string $sortField = 'numero';
|
||||||
|
#[Url(except: 'asc')]
|
||||||
public string $sortDirection = 'asc';
|
public string $sortDirection = 'asc';
|
||||||
|
|
||||||
protected $queryString = [
|
protected $queryString = [
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Territori;
|
namespace App\Livewire\Territori;
|
||||||
|
|
||||||
|
use App\Livewire\Concerns\RicordaRitorno;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Territorio;
|
use App\Models\Territorio;
|
||||||
use App\Models\Assegnazione;
|
use App\Models\Assegnazione;
|
||||||
|
|
||||||
class TerritorioShow extends Component
|
class TerritorioShow extends Component
|
||||||
{
|
{
|
||||||
|
use RicordaRitorno;
|
||||||
|
|
||||||
public Territorio $territorio;
|
public Territorio $territorio;
|
||||||
|
|
||||||
public function mount(Territorio $territorio)
|
public function mount(Territorio $territorio)
|
||||||
|
|||||||
49
app/Support/Ritorno.php
Normal file
49
app/Support/Ritorno.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La pagina da cui è partito un percorso, per tornarci dopo un'operazione.
|
||||||
|
*
|
||||||
|
* I link che aprono una scheda o un'operazione portano `?da=<percorso>`
|
||||||
|
* (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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,34 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
|
||||||
|
// ─── Ritorno alla pagina di partenza ────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// I link con data-ritorno (schede e operazioni) portano con sé ?da=<percorso>:
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="{{ route('territori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('territori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna ai territori
|
{{ $ritorno ? 'Torna indietro' : 'Torna ai territori' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Assegnazioni</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Assegnazioni</h1>
|
||||||
<p class="text-sm text-gray-500 mt-1">Questa funzione consente un'assegnazione arbitraria, indipendente dalle tre schede della Home.</p>
|
<p class="text-sm text-gray-500 mt-1">Questa funzione consente un'assegnazione arbitraria, indipendente dalle tre schede della Home.</p>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<li>Email non disponibile: il server di posta non è configurato.</li>
|
<li>Email non disponibile: il server di posta non è configurato.</li>
|
||||||
@endif
|
@endif
|
||||||
@if((! $whatsapp || $email === '') && auth()->user()->can('settings.manage') && $invio->proclamatore)
|
@if((! $whatsapp || $email === '') && auth()->user()->can('settings.manage') && $invio->proclamatore)
|
||||||
<li><a href="{{ route('proclamatori.edit', $invio->proclamatore) }}" class="text-indigo-700 underline">Aggiungi i contatti al proclamatore</a></li>
|
<li><a data-ritorno href="{{ route('proclamatori.edit', $invio->proclamatore) }}" class="text-indigo-700 underline">Aggiungi i contatti al proclamatore</a></li>
|
||||||
@endif
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -185,7 +185,11 @@
|
|||||||
|
|
||||||
<div class="flex flex-col-reverse gap-3 pt-4 sm:flex-row sm:items-center">
|
<div class="flex flex-col-reverse gap-3 pt-4 sm:flex-row sm:items-center">
|
||||||
<x-btn variante="primary" type="submit">Assegna</x-btn>
|
<x-btn variante="primary" type="submit">Assegna</x-btn>
|
||||||
<x-btn variante="secondary" x-on:click="formOpen = false">Annulla</x-btn>
|
@if($ritorno)
|
||||||
|
<x-btn variante="secondary" :href="$ritorno">Annulla</x-btn>
|
||||||
|
@else
|
||||||
|
<x-btn variante="secondary" x-on:click="formOpen = false">Annulla</x-btn>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,7 +229,7 @@
|
|||||||
{{-- Testata riga --}}
|
{{-- Testata riga --}}
|
||||||
<div class="flex flex-wrap items-start justify-between gap-x-6 gap-y-2">
|
<div class="flex flex-wrap items-start justify-between gap-x-6 gap-y-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline font-bold text-sm">N° {{ $a->territorio?->numero }}</a>
|
<a data-ritorno href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline font-bold text-sm">N° {{ $a->territorio?->numero }}</a>
|
||||||
@if($a->territorio?->zona?->nome)
|
@if($a->territorio?->zona?->nome)
|
||||||
<span class="text-xs text-gray-400 bg-gray-100 rounded px-1.5 py-0.5">{{ $a->territorio?->zona?->nome }}</span>
|
<span class="text-xs text-gray-400 bg-gray-100 rounded px-1.5 py-0.5">{{ $a->territorio?->zona?->nome }}</span>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="{{ route('territori.show', $assegnazione->territorio) }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('territori.show', $assegnazione->territorio) }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna al territorio
|
{{ $ritorno ? 'Torna indietro' : 'Torna al territorio' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Rientro Territorio</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Rientro Territorio</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-wrap items-center gap-3 pt-4">
|
<div class="flex flex-wrap items-center gap-3 pt-4">
|
||||||
<x-btn variante="primary" type="submit">Registra rientro</x-btn>
|
<x-btn variante="primary" type="submit">Registra rientro</x-btn>
|
||||||
<x-btn variante="secondary" :href="route('territori.show', $assegnazione->territorio)">Annulla</x-btn>
|
<x-btn variante="secondary" :href="$ritorno ?? route('territori.show', $assegnazione->territorio)">Annulla</x-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="{{ route('campagne.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('campagne.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna alla lista
|
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">{{ $titolo }}</h1>
|
<h1 class="text-2xl font-bold text-gray-900">{{ $titolo }}</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-3 pt-4">
|
<div class="flex items-center gap-3 pt-4">
|
||||||
<x-btn variante="primary" type="submit">{{ $btnLabel }}</x-btn>
|
<x-btn variante="primary" type="submit">{{ $btnLabel }}</x-btn>
|
||||||
<x-btn variante="secondary" :href="route('campagne.index')">Annulla</x-btn>
|
<x-btn variante="secondary" :href="$ritorno ?? route('campagne.index')">Annulla</x-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Campagne</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Campagne</h1>
|
||||||
@can('settings.manage')
|
@can('settings.manage')
|
||||||
<x-btn variante="primary" :href="route('campagne.create')">
|
<x-btn data-ritorno variante="primary" :href="route('campagne.create')">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
Nuova campagna
|
Nuova campagna
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -47,13 +47,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 text-sm text-right cell-actions">
|
<td class="px-4 py-3 text-sm text-right cell-actions">
|
||||||
<div class="flex flex-wrap items-center justify-end gap-1">
|
<div class="flex flex-wrap items-center justify-end gap-1">
|
||||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
<x-btn data-ritorno variante="secondary" tono="tenue" dimensione="sm" icona
|
||||||
:href="route('campagne.show', $campagna)"
|
:href="route('campagne.show', $campagna)"
|
||||||
title="Dettaglio" aria-label="Dettaglio della campagna {{ $campagna->descrizione }}">
|
title="Dettaglio" aria-label="Dettaglio della campagna {{ $campagna->descrizione }}">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@can('settings.manage')
|
@can('settings.manage')
|
||||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
<x-btn data-ritorno variante="secondary" tono="tenue" dimensione="sm" icona
|
||||||
:href="route('campagne.edit', $campagna)"
|
:href="route('campagne.edit', $campagna)"
|
||||||
title="Modifica" aria-label="Modifica la campagna {{ $campagna->descrizione }}">
|
title="Modifica" aria-label="Modifica la campagna {{ $campagna->descrizione }}">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ route('campagne.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('campagne.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna alla lista
|
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">{{ $campagna->descrizione }}</h1>
|
<h1 class="text-2xl font-bold text-gray-900">{{ $campagna->descrizione }}</h1>
|
||||||
</div>
|
</div>
|
||||||
@can('settings.manage')
|
@can('settings.manage')
|
||||||
<x-btn variante="secondary" :href="route('campagne.edit', $campagna)">
|
<x-btn data-ritorno variante="secondary" :href="route('campagne.edit', $campagna)">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
Modifica
|
Modifica
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
<tbody class="divide-y divide-gray-100">
|
<tbody class="divide-y divide-gray-100">
|
||||||
@forelse($conteggiate as $a)
|
@forelse($conteggiate as $a)
|
||||||
<tr>
|
<tr>
|
||||||
<td data-label="Territorio" class="px-4 py-2"><a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline">N° {{ $a->territorio?->numero }}</a></td>
|
<td data-label="Territorio" class="px-4 py-2"><a data-ritorno href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline">N° {{ $a->territorio?->numero }}</a></td>
|
||||||
<td data-label="Proclamatore" class="px-4 py-2">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</td>
|
<td data-label="Proclamatore" class="px-4 py-2">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</td>
|
||||||
<td data-label="Assegnato" class="px-4 py-2">{{ $a->assigned_at->format('d/m/Y') }}</td>
|
<td data-label="Assegnato" class="px-4 py-2">{{ $a->assigned_at->format('d/m/Y') }}</td>
|
||||||
<td data-label="Rientrato" class="px-4 py-2">{{ $a->returned_at?->format('d/m/Y') }}</td>
|
<td data-label="Rientrato" class="px-4 py-2">{{ $a->returned_at?->format('d/m/Y') }}</td>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
<tbody class="divide-y divide-gray-100">
|
<tbody class="divide-y divide-gray-100">
|
||||||
@forelse($inCorso as $a)
|
@forelse($inCorso as $a)
|
||||||
<tr>
|
<tr>
|
||||||
<td data-label="Territorio" class="px-4 py-2"><a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline">N° {{ $a->territorio?->numero }}</a></td>
|
<td data-label="Territorio" class="px-4 py-2"><a data-ritorno href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline">N° {{ $a->territorio?->numero }}</a></td>
|
||||||
<td data-label="Proclamatore" class="px-4 py-2">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</td>
|
<td data-label="Proclamatore" class="px-4 py-2">{{ $a->proclamatore?->nome_completo ?? 'N/A' }}</td>
|
||||||
<td data-label="Assegnato" class="px-4 py-2">{{ $a->assigned_at->format('d/m/Y') }}</td>
|
<td data-label="Assegnato" class="px-4 py-2">{{ $a->assigned_at->format('d/m/Y') }}</td>
|
||||||
<td data-label="Giorni" class="px-4 py-2">{{ $a->giorni }}</td>
|
<td data-label="Giorni" class="px-4 py-2">{{ $a->giorni }}</td>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<li class="px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors">
|
<li class="px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<a href="{{ route('territori.show', $t) }}" class="text-sm font-semibold text-gray-900 hover:text-indigo-600 transition-colors">N° {{ $t->numero }}</a>
|
<a data-ritorno href="{{ route('territori.show', $t) }}" class="text-sm font-semibold text-gray-900 hover:text-indigo-600 transition-colors">N° {{ $t->numero }}</a>
|
||||||
@if($t->home_is_prioritario)
|
@if($t->home_is_prioritario)
|
||||||
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide badge-pulse" style="background:#fef3c7;color:#92400e">★ Prioritario</span>
|
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide badge-pulse" style="background:#fef3c7;color:#92400e">★ Prioritario</span>
|
||||||
@endif
|
@endif
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@can('territori.assign')
|
@can('territori.assign')
|
||||||
<x-btn variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.assegna', ['territorioId' => $t->id])">Assegna →</x-btn>
|
<x-btn data-ritorno variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.assegna', ['territorioId' => $t->id])">Assegna →</x-btn>
|
||||||
@endcan
|
@endcan
|
||||||
</li>
|
</li>
|
||||||
@empty
|
@empty
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
@php($assegnazioneCorrente = $t->assegnazioneCorrente)
|
@php($assegnazioneCorrente = $t->assegnazioneCorrente)
|
||||||
<li class="px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors">
|
<li class="px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors">
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ route('territori.show', $t) }}" class="text-sm font-semibold text-gray-900 hover:text-indigo-600 transition-colors">N° {{ $t->numero }}</a>
|
<a data-ritorno href="{{ route('territori.show', $t) }}" class="text-sm font-semibold text-gray-900 hover:text-indigo-600 transition-colors">N° {{ $t->numero }}</a>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
{{ $assegnazioneCorrente?->proclamatore?->nome_completo }}
|
{{ $assegnazioneCorrente?->proclamatore?->nome_completo }}
|
||||||
— <span class="font-medium" style="color:#dc2626">{{ $assegnazioneCorrente?->giorni }} gg</span>
|
— <span class="font-medium" style="color:#dc2626">{{ $assegnazioneCorrente?->giorni }} gg</span>
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@can('territori.return')
|
@can('territori.return')
|
||||||
@if($assegnazioneCorrente)
|
@if($assegnazioneCorrente)
|
||||||
<x-btn variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.rientra', ['assegnazione' => $assegnazioneCorrente->id])">Rientra →</x-btn>
|
<x-btn data-ritorno variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.rientra', ['assegnazione' => $assegnazioneCorrente->id])">Rientra →</x-btn>
|
||||||
@endif
|
@endif
|
||||||
@endcan
|
@endcan
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="{{ route('proclamatori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('proclamatori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna alla lista
|
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">{{ $titolo }}</h1>
|
<h1 class="text-2xl font-bold text-gray-900">{{ $titolo }}</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-3 pt-4">
|
<div class="flex items-center gap-3 pt-4">
|
||||||
<x-btn variante="primary" type="submit">{{ $btnLabel }}</x-btn>
|
<x-btn variante="primary" type="submit">{{ $btnLabel }}</x-btn>
|
||||||
<x-btn variante="secondary" :href="route('proclamatori.index')">Annulla</x-btn>
|
<x-btn variante="secondary" :href="$ritorno ?? route('proclamatori.index')">Annulla</x-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
link rosso perso in mezzo ai filtri, in un posto diverso rispetto
|
link rosso perso in mezzo ai filtri, in un posto diverso rispetto
|
||||||
all'altra lista che fa esattamente la stessa cosa. --}}
|
all'altra lista che fa esattamente la stessa cosa. --}}
|
||||||
<div class="flex flex-wrap items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<x-btn variante="primary" :href="route('proclamatori.create')">
|
<x-btn data-ritorno variante="primary" :href="route('proclamatori.create')">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
Nuovo proclamatore
|
Nuovo proclamatore
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -63,13 +63,13 @@
|
|||||||
<td class="px-4 py-3 text-sm text-right cell-actions">
|
<td class="px-4 py-3 text-sm text-right cell-actions">
|
||||||
<div class="flex flex-wrap items-center justify-end gap-1">
|
<div class="flex flex-wrap items-center justify-end gap-1">
|
||||||
<x-whatsapp :proclamatore="$proclamatore" compatto />
|
<x-whatsapp :proclamatore="$proclamatore" compatto />
|
||||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
<x-btn data-ritorno variante="secondary" tono="tenue" dimensione="sm" icona
|
||||||
:href="route('proclamatori.show', $proclamatore)"
|
:href="route('proclamatori.show', $proclamatore)"
|
||||||
title="Dettaglio" aria-label="Dettaglio del proclamatore">
|
title="Dettaglio" aria-label="Dettaglio del proclamatore">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@can('settings.manage')
|
@can('settings.manage')
|
||||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
<x-btn data-ritorno variante="secondary" tono="tenue" dimensione="sm" icona
|
||||||
:href="route('proclamatori.edit', $proclamatore)"
|
:href="route('proclamatori.edit', $proclamatore)"
|
||||||
title="Modifica" aria-label="Modifica il proclamatore">
|
title="Modifica" aria-label="Modifica il proclamatore">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ route('proclamatori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('proclamatori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna alla lista
|
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">{{ $proclamatore->nome_completo }}</h1>
|
<h1 class="text-2xl font-bold text-gray-900">{{ $proclamatore->nome_completo }}</h1>
|
||||||
@if($proclamatore->telefono || $proclamatore->email)
|
@if($proclamatore->telefono || $proclamatore->email)
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@can('settings.manage')
|
@can('settings.manage')
|
||||||
<x-btn variante="secondary" :href="route('proclamatori.edit', $proclamatore)">
|
<x-btn data-ritorno variante="secondary" :href="route('proclamatori.edit', $proclamatore)">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
Modifica
|
Modifica
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
@foreach($assegnazioni as $assegnazione)
|
@foreach($assegnazioni as $assegnazione)
|
||||||
<tr>
|
<tr>
|
||||||
<td data-label="Territorio" class="px-3 py-2">
|
<td data-label="Territorio" class="px-3 py-2">
|
||||||
<a href="{{ route('territori.show', $assegnazione->territorio_id) }}" class="text-indigo-600 hover:underline">
|
<a data-ritorno href="{{ route('territori.show', $assegnazione->territorio_id) }}" class="text-indigo-600 hover:underline">
|
||||||
{{ $assegnazione->territorio?->numero ?? 'N/A' }}
|
{{ $assegnazione->territorio?->numero ?? 'N/A' }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
class="h-8 w-6 rounded border border-gray-200 object-cover bg-gray-50 flex-none">
|
class="h-8 w-6 rounded border border-gray-200 object-cover bg-gray-50 flex-none">
|
||||||
@endif
|
@endif
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<a href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline font-medium">N° {{ $a->territorio?->numero }}</a>
|
<a data-ritorno href="{{ route('territori.show', $a->territorio_id) }}" class="text-indigo-600 hover:underline font-medium">N° {{ $a->territorio?->numero }}</a>
|
||||||
<span class="text-xs text-gray-400 ml-1">{{ $a->territorio?->zona?->nome }}</span>
|
<span class="text-xs text-gray-400 ml-1">{{ $a->territorio?->zona?->nome }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="{{ route('territori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('territori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna alla lista
|
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">{{ $title }}</h1>
|
<h1 class="text-2xl font-bold text-gray-900">{{ $title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
dell'applicazione. Prima cambiava da una pagina all'altra. --}}
|
dell'applicazione. Prima cambiava da una pagina all'altra. --}}
|
||||||
<div class="flex flex-wrap gap-3 pt-2">
|
<div class="flex flex-wrap gap-3 pt-2">
|
||||||
<x-btn variante="primary" type="submit">{{ $isEdit ? 'Aggiorna' : 'Crea' }}</x-btn>
|
<x-btn variante="primary" type="submit">{{ $isEdit ? 'Aggiorna' : 'Crea' }}</x-btn>
|
||||||
<x-btn variante="secondary" :href="route('territori.index')">Annulla</x-btn>
|
<x-btn variante="secondary" :href="$ritorno ?? route('territori.index')">Annulla</x-btn>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
disegna il confine e lo si crea da lì (PiantineEditor, ?nuovo=1).
|
disegna il confine e lo si crea da lì (PiantineEditor, ?nuovo=1).
|
||||||
L'editor è dell'amministratore: chi ha solo territori.manage
|
L'editor è dell'amministratore: chi ha solo territori.manage
|
||||||
resta sul form. --}}
|
resta sul form. --}}
|
||||||
<x-btn variante="primary" :href="auth()->user()->can('settings.manage') ? route('piantine.editor', ['nuovo' => 1]) : route('territori.create')">
|
<x-btn data-ritorno variante="primary" :href="auth()->user()->can('settings.manage') ? route('piantine.editor', ['nuovo' => 1]) : route('territori.create')">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
Nuovo territorio
|
Nuovo territorio
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
alt="Thumbnail territorio {{ $territorio->numero }}"
|
alt="Thumbnail territorio {{ $territorio->numero }}"
|
||||||
class="h-8 w-6 rounded border border-gray-200 object-cover bg-gray-50 flex-none shadow-sm">
|
class="h-8 w-6 rounded border border-gray-200 object-cover bg-gray-50 flex-none shadow-sm">
|
||||||
@endif
|
@endif
|
||||||
<a href="{{ route('territori.show', $territorio) }}" class="hover:text-indigo-600 transition-colors">{{ $territorio->numero }}</a>
|
<a data-ritorno href="{{ route('territori.show', $territorio) }}" class="hover:text-indigo-600 transition-colors">{{ $territorio->numero }}</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Zona" class="px-4 py-3 text-sm text-gray-600">{{ $territorio->zona?->nome ?? '-' }}</td>
|
<td data-label="Zona" class="px-4 py-3 text-sm text-gray-600">{{ $territorio->zona?->nome ?? '-' }}</td>
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
{{-- Primary actions: Assegna / Rientra --}}
|
{{-- Primary actions: Assegna / Rientra --}}
|
||||||
@can('territori.assign')
|
@can('territori.assign')
|
||||||
@if(!$territorio->assegnazioneCorrente && $territorio->attivo)
|
@if(!$territorio->assegnazioneCorrente && $territorio->attivo)
|
||||||
<x-btn variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.assegna', ['territorioId' => $territorio->id])">
|
<x-btn data-ritorno variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.assegna', ['territorioId' => $territorio->id])">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
Assegna
|
Assegna
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
{{-- Rientra non cancella niente: era rosso pieno
|
{{-- Rientra non cancella niente: era rosso pieno
|
||||||
quanto un Elimina. È l'azione principale
|
quanto un Elimina. È l'azione principale
|
||||||
della riga, alternativa ad Assegna. --}}
|
della riga, alternativa ad Assegna. --}}
|
||||||
<x-btn variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.rientra', ['assegnazione' => $territorio->assegnazioneCorrente->id])">
|
<x-btn data-ritorno variante="primary" tono="tenue" dimensione="sm" :href="route('assegnazioni.rientra', ['assegnazione' => $territorio->assegnazioneCorrente->id])">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
||||||
Rientra
|
Rientra
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -168,13 +168,13 @@
|
|||||||
{{-- Solo icona: l'aria-label non è ridondante col
|
{{-- Solo icona: l'aria-label non è ridondante col
|
||||||
title, è l'unico nome che un lettore di
|
title, è l'unico nome che un lettore di
|
||||||
schermo annuncia in modo affidabile. --}}
|
schermo annuncia in modo affidabile. --}}
|
||||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
<x-btn data-ritorno variante="secondary" tono="tenue" dimensione="sm" icona
|
||||||
:href="route('territori.show', $territorio)"
|
:href="route('territori.show', $territorio)"
|
||||||
title="Dettaglio" aria-label="Dettaglio del territorio {{ $territorio->numero }}">
|
title="Dettaglio" aria-label="Dettaglio del territorio {{ $territorio->numero }}">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@can('territori.manage')
|
@can('territori.manage')
|
||||||
<x-btn variante="secondary" tono="tenue" dimensione="sm" icona
|
<x-btn data-ritorno variante="secondary" tono="tenue" dimensione="sm" icona
|
||||||
:href="route('territori.edit', $territorio)"
|
:href="route('territori.edit', $territorio)"
|
||||||
title="Modifica" aria-label="Modifica il territorio {{ $territorio->numero }}">
|
title="Modifica" aria-label="Modifica il territorio {{ $territorio->numero }}">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ route('territori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
<a href="{{ $ritorno ?? route('territori.index') }}" class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-indigo-600 transition-colors mb-1">
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
||||||
Torna alla lista
|
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||||
</a>
|
</a>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Territorio {{ $territorio->numero }}</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Territorio {{ $territorio->numero }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
@can('territori.assign')
|
@can('territori.assign')
|
||||||
@if(!$territorio->assegnazioneCorrente && $territorio->attivo)
|
@if(!$territorio->assegnazioneCorrente && $territorio->attivo)
|
||||||
<x-btn variante="primary" :href="route('assegnazioni.assegna', ['territorioId' => $territorio->id])">
|
<x-btn data-ritorno variante="primary" :href="route('assegnazioni.assegna', ['territorioId' => $territorio->id])">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
||||||
Assegna
|
Assegna
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -21,14 +21,14 @@
|
|||||||
{{-- Rientra era rosso pieno quanto un Elimina. Non cancella
|
{{-- Rientra era rosso pieno quanto un Elimina. Non cancella
|
||||||
niente: è l'azione principale di un territorio assegnato,
|
niente: è l'azione principale di un territorio assegnato,
|
||||||
quindi primary come Assegna, che è la sua alternativa. --}}
|
quindi primary come Assegna, che è la sua alternativa. --}}
|
||||||
<x-btn variante="primary" :href="route('assegnazioni.rientra', ['assegnazione' => $territorio->assegnazioneCorrente->id])">
|
<x-btn data-ritorno variante="primary" :href="route('assegnazioni.rientra', ['assegnazione' => $territorio->assegnazioneCorrente->id])">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
||||||
Rientra
|
Rientra
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@endif
|
@endif
|
||||||
@endcan
|
@endcan
|
||||||
@can('territori.manage')
|
@can('territori.manage')
|
||||||
<x-btn variante="secondary" :href="route('territori.edit', $territorio)">
|
<x-btn data-ritorno variante="secondary" :href="route('territori.edit', $territorio)">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
|
||||||
Modifica
|
Modifica
|
||||||
</x-btn>
|
</x-btn>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800" role="status">
|
<div class="mb-6 flex flex-wrap items-center justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800" role="status">
|
||||||
<span class="font-semibold">Il link della piantina è ancora da inviare a {{ $territorio->assegnazioneCorrente->proclamatore?->nome_completo }}.</span>
|
<span class="font-semibold">Il link della piantina è ancora da inviare a {{ $territorio->assegnazioneCorrente->proclamatore?->nome_completo }}.</span>
|
||||||
@can('territori.assign')
|
@can('territori.assign')
|
||||||
<x-btn variante="secondary" dimensione="sm" :href="route('assegnazioni.assegna', ['invia' => $territorio->assegnazioneCorrente->id])">Invia link</x-btn>
|
<x-btn data-ritorno variante="secondary" dimensione="sm" :href="route('assegnazioni.assegna', ['invia' => $territorio->assegnazioneCorrente->id])">Invia link</x-btn>
|
||||||
@endcan
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
@can('territori.return')
|
@can('territori.return')
|
||||||
<x-btn variante="primary" :href="route('assegnazioni.rientra', ['assegnazione' => $activeAssignment->id])">Rientra</x-btn>
|
<x-btn data-ritorno variante="primary" :href="route('assegnazioni.rientra', ['assegnazione' => $activeAssignment->id])">Rientra</x-btn>
|
||||||
@endcan
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
191
tests/Feature/RitornoTest.php
Normal file
191
tests/Feature/RitornoTest.php
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Livewire\Assegnazioni\Rientra;
|
||||||
|
use App\Livewire\Proclamatori\ProclamatoreEdit;
|
||||||
|
use App\Livewire\Proclamatori\ProclamatoreIndex;
|
||||||
|
use App\Livewire\Territori\TerritorioIndex;
|
||||||
|
use App\Models\AnnoTeocratico;
|
||||||
|
use App\Models\Assegnazione;
|
||||||
|
use App\Models\Proclamatore;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\Territorio;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Support\Ritorno;
|
||||||
|
use Database\Seeders\RolesAndPermissionsSeeder;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dopo un'operazione (confermata o annullata) si torna alla pagina da cui il
|
||||||
|
* percorso era partito, con i suoi filtri: ?da=… (App\Support\Ritorno).
|
||||||
|
*/
|
||||||
|
class RitornoTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
private User $admin;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->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('/<a\b(?=[^>]*\bdata-ritorno\b)(?=[^>]*href="[^"]*\/territori\/\d+")[^>]*>/s', $html);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user