diff --git a/HANDOFF.md b/HANDOFF.md
index cbf58c6..23667c2 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -235,6 +235,19 @@ Decisioni successive dell'utente (20 ottobre 2026):
(erano `home_giorni_giacenza` e `home_is_prioritario`, anche nel PDF).
- "Il mio gruppo" mostra sotto il titolo nome e cognome del sorvegliante.
+### Storico assegnazioni in linea (20 ottobre 2026)
+
+Richiesta dell'utente (screenshot della scheda territorio): le colonne dello
+storico cambiavano posizione da un anno all'altro, perché ogni anno era una
+tabella a sé dimensionata sul suo contenuto. Ora, nella scheda del territorio
+e in quella del proclamatore, è **una tabella sola** con un'intestazione,
+colonne a larghezza fissa (`table-fixed`, `w-[..%]`), e l'anno come riga di
+separazione `tr.tm-riga-gruppo` (in `app.css`, su telefono resta un titolo e
+non diventa una scheda). Ordine esplicito: anni dal più recente
+(`sortKeysDesc`), dentro l'anno per data di assegnazione. Nella scheda del
+proclamatore la colonna Campagna ora dice "Sì — …" / "No" come in quella del
+territorio, e c'è il badge "via gruppo …". Test: `StoricoAssegnazioniTest`.
+
### Rientro dall'elenco dei territori assegnati (20 ottobre 2026)
Richiesta dell'utente ("un tasto che va alla registrazione", dallo screenshot
diff --git a/README.md b/README.md
index 62ced75..9905590 100644
--- a/README.md
+++ b/README.md
@@ -698,7 +698,9 @@ docker compose --profile dev up -d --build
il form)
- Filtri per zona, tipologia e stato, conservati nell'indirizzo della pagina
- Badge di stato colorati e indicatore di priorità (manuale o automatica)
-- Dettaglio con visualizzatore PDF e storico assegnazioni per anno teocratico
+- Dettaglio con visualizzatore PDF e storico assegnazioni per anno teocratico:
+ una tabella sola con le colonne in linea, l'anno come riga di separazione,
+ dal più recente (lo stesso nella scheda del proclamatore)
- Link PDF pubblico condivisibile, a scadenza configurabile
- Soft delete con cestino (ripristino o eliminazione definitiva)
- Import massivo di PDF da cartella o archivio ZIP, elaborato in background
diff --git a/app/Livewire/Proclamatori/ProclamatoreShow.php b/app/Livewire/Proclamatori/ProclamatoreShow.php
index b5550b0..57c5d98 100644
--- a/app/Livewire/Proclamatori/ProclamatoreShow.php
+++ b/app/Livewire/Proclamatori/ProclamatoreShow.php
@@ -24,8 +24,11 @@ class ProclamatoreShow extends Component
$assegnazioni = Assegnazione::where('proclamatore_id', $this->proclamatore->id)
->with(['territorio', 'annoTeocratico', 'campagna'])
->orderByDesc('assigned_at')
+ ->orderByDesc('id')
->get()
- ->groupBy(fn($a) => $a->annoTeocratico->label);
+ ->groupBy(fn($a) => $a->annoTeocratico->label)
+ // Dal più recente: gli anni ("2026-2027") si ordinano come testo.
+ ->sortKeysDesc();
$stats = [
'totale_assegnazioni' => Assegnazione::where('proclamatore_id', $this->proclamatore->id)->count(),
diff --git a/app/Livewire/Territori/TerritorioShow.php b/app/Livewire/Territori/TerritorioShow.php
index d34b8ac..53192da 100644
--- a/app/Livewire/Territori/TerritorioShow.php
+++ b/app/Livewire/Territori/TerritorioShow.php
@@ -23,8 +23,11 @@ class TerritorioShow extends Component
$assegnazioni = Assegnazione::where('territorio_id', $this->territorio->id)
->with(['proclamatore', 'annoTeocratico', 'campagna', 'creatoDa', 'rientratoDa'])
->orderByDesc('assigned_at')
+ ->orderByDesc('id')
->get()
- ->groupBy(fn($a) => $a->annoTeocratico->label);
+ ->groupBy(fn($a) => $a->annoTeocratico->label)
+ // Dal più recente: gli anni ("2026-2027") si ordinano come testo.
+ ->sortKeysDesc();
return view('livewire.territori.territorio-show', [
'activeAssignment' => $this->territorio->assegnazioneCorrente,
diff --git a/resources/css/app.css b/resources/css/app.css
index a33494e..1a7ccb6 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -1652,6 +1652,18 @@ html { scroll-behavior: smooth; }
padding-top: 10px;
border-top: 1px solid var(--tm-bordo) !important;
}
+ /* Riga di separazione di un gruppo (l'anno teocratico negli storici):
+ resta un titolo, non diventa una scheda. */
+ table.table-cards tr.tm-riga-gruppo {
+ border: none;
+ background: transparent;
+ padding: 0;
+ margin: 16px 0 6px;
+ }
+ table.table-cards tr.tm-riga-gruppo th {
+ display: block;
+ padding: 0 2px;
+ }
/* Cells spanning all columns (empty state, inline edit form) stay full
width and block, so their inner layout isn't squeezed by the flex row. */
table.table-cards td[colspan] {
diff --git a/resources/views/livewire/proclamatori/proclamatore-show.blade.php b/resources/views/livewire/proclamatori/proclamatore-show.blade.php
index ed05027..6505c10 100644
--- a/resources/views/livewire/proclamatori/proclamatore-show.blade.php
+++ b/resources/views/livewire/proclamatori/proclamatore-show.blade.php
@@ -101,52 +101,56 @@
Storico Assegnazioni
- @forelse($assegnazioniPerAnno as $annoLabel => $assegnazioni)
-
-
Anno Teocratico {{ $annoLabel }}
-
-
-
-
- | Territorio |
- Assegnato |
- Rientrato |
- Giorni |
- Campagna |
-
-
-
- @foreach($assegnazioni as $assegnazione)
-
- |
-
- {{ $assegnazione->territorio?->numero ?? 'N/A' }}
-
- |
- {{ $assegnazione->assigned_at->format('d/m/Y') }} |
-
- @if($assegnazione->returned_at)
- {{ $assegnazione->returned_at->format('d/m/Y') }}
- @else
- In corso
- @endif
- |
- {{ $assegnazione->giorni }} |
-
- @if($assegnazione->counted_in_campaign)
- {{ $assegnazione->campagna?->descrizione }}
- @else
- -
- @endif
- |
-
- @endforeach
-
-
-
-
- @empty
+ {{-- Come nella scheda del territorio: una tabella sola, colonne fisse,
+ l'anno come riga di separazione (tm-riga-gruppo), dal più recente. --}}
+ @if($assegnazioniPerAnno->isEmpty())
Nessuna assegnazione registrata.
- @endforelse
+ @else
+
+
+
+
+ | Territorio |
+ Assegnato |
+ Rientrato |
+ Giorni |
+ Campagna |
+
+
+ @foreach($assegnazioniPerAnno as $annoLabel => $assegnazioni)
+
+
+ | Anno Teocratico {{ $annoLabel }} |
+
+ @foreach($assegnazioni as $assegnazione)
+
+ |
+ N° {{ $assegnazione->territorio?->numero ?? 'N/A' }}@if($assegnazione->gruppo_nome)via gruppo {{ $assegnazione->gruppo_nome }}@endif
+ |
+ {{ $assegnazione->assigned_at->format('d/m/Y') }} |
+
+ @if($assegnazione->returned_at)
+ {{ $assegnazione->returned_at->format('d/m/Y') }}
+ @else
+ In corso
+ @endif
+ |
+ {{ $assegnazione->giorni }} |
+
+ @if($assegnazione->counted_in_campaign)
+ Sì — {{ $assegnazione->campagna?->descrizione }}
+ @elseif($assegnazione->counted_in_campaign === false)
+ No
+ @else
+ -
+ @endif
+ |
+
+ @endforeach
+
+ @endforeach
+
+
+ @endif
diff --git a/resources/views/livewire/territori/territorio-show.blade.php b/resources/views/livewire/territori/territorio-show.blade.php
index 5af6811..a7a02b2 100644
--- a/resources/views/livewire/territori/territorio-show.blade.php
+++ b/resources/views/livewire/territori/territorio-show.blade.php
@@ -161,50 +161,56 @@
Storico Assegnazioni
- @forelse($assegnazioniPerAnno as $annoLabel => $assegnazioni)
-
-
Anno Teocratico {{ $annoLabel }}
-
-
-
-
- | Proclamatore |
- Assegnato |
- Rientrato |
- Giorni |
- Campagna |
-
-
-
- @foreach($assegnazioni as $assegnazione)
-
- | {{ $assegnazione->proclamatore?->nome_completo ?? 'N/A' }}@if($assegnazione->gruppo_nome)via gruppo {{ $assegnazione->gruppo_nome }}@endif |
- {{ $assegnazione->assigned_at->format('d/m/Y') }} |
-
- @if($assegnazione->returned_at)
- {{ $assegnazione->returned_at->format('d/m/Y') }}
- @else
- In corso
- @endif
- |
- {{ $assegnazione->giorni }} |
-
- @if($assegnazione->counted_in_campaign)
- Sì — {{ $assegnazione->campagna?->descrizione }}
- @elseif($assegnazione->counted_in_campaign === false)
- No
- @else
- -
- @endif
- |
-
- @endforeach
-
-
-
-
- @empty
+ {{-- Una tabella sola, colonne a larghezza fissa: con una tabella per
+ anno ciascuna si dimensionava sul suo contenuto e le colonne non
+ erano in linea. L'anno è una riga di separazione (tm-riga-gruppo).
+ Ordine: dal più recente, per anno e per data di assegnazione. --}}
+ @if($assegnazioniPerAnno->isEmpty())
Nessuna assegnazione registrata.
- @endforelse
+ @else
+
+
+
+
+ | Proclamatore |
+ Assegnato |
+ Rientrato |
+ Giorni |
+ Campagna |
+
+
+ @foreach($assegnazioniPerAnno as $annoLabel => $assegnazioni)
+
+
+ | Anno Teocratico {{ $annoLabel }} |
+
+ @foreach($assegnazioni as $assegnazione)
+
+ | {{ $assegnazione->proclamatore?->nome_completo ?? 'N/A' }}@if($assegnazione->gruppo_nome)via gruppo {{ $assegnazione->gruppo_nome }}@endif |
+ {{ $assegnazione->assigned_at->format('d/m/Y') }} |
+
+ @if($assegnazione->returned_at)
+ {{ $assegnazione->returned_at->format('d/m/Y') }}
+ @else
+ In corso
+ @endif
+ |
+ {{ $assegnazione->giorni }} |
+
+ @if($assegnazione->counted_in_campaign)
+ Sì — {{ $assegnazione->campagna?->descrizione }}
+ @elseif($assegnazione->counted_in_campaign === false)
+ No
+ @else
+ -
+ @endif
+ |
+
+ @endforeach
+
+ @endforeach
+
+
+ @endif
diff --git a/tests/Feature/StoricoAssegnazioniTest.php b/tests/Feature/StoricoAssegnazioniTest.php
new file mode 100644
index 0000000..42145a4
--- /dev/null
+++ b/tests/Feature/StoricoAssegnazioniTest.php
@@ -0,0 +1,74 @@
+withoutVite();
+ $this->seed(RolesAndPermissionsSeeder::class);
+ Setting::firstOrCreate([], ['congregazione_nome' => 'Congregazione di prova']);
+
+ $this->admin = User::create(['name' => 'Admin', 'email' => 'admin@example.test', 'password' => 'password-di-prova']);
+ $this->admin->assignRole('amministratore');
+
+ $this->territorio = Territorio::create(['numero' => '7', 'attivo' => true]);
+ $this->proclamatore = Proclamatore::create(['nome' => 'Mario', 'cognome' => 'Rossi', 'attivo' => true]);
+
+ $vecchio = AnnoTeocratico::create(['label' => '2024-2025', 'start_date' => '2024-09-01', 'end_date' => '2025-08-31']);
+ $recente = AnnoTeocratico::create(['label' => '2025-2026', 'start_date' => '2025-09-01', 'end_date' => '2026-08-31']);
+
+ // Create in disordine: l'ordine lo deve dare la pagina.
+ foreach ([[$vecchio, '2024-10-01', '2024-11-01'], [$recente, '2025-10-01', '2025-11-15'], [$vecchio, '2025-03-01', '2025-04-01']] as [$anno, $dal, $al]) {
+ Assegnazione::create([
+ 'territorio_id' => $this->territorio->id, 'proclamatore_id' => $this->proclamatore->id,
+ 'anno_teocratico_id' => $anno->id, 'assigned_at' => $dal, 'returned_at' => $al, 'created_by' => $this->admin->id,
+ ]);
+ }
+ }
+
+ public function test_nella_scheda_del_territorio_una_tabella_sola_dal_piu_recente(): void
+ {
+ $html = $this->actingAs($this->admin)->get(route('territori.show', $this->territorio))->assertOk()
+ ->assertSeeInOrder(['Anno Teocratico 2025-2026', '01/10/2025', 'Anno Teocratico 2024-2025', '01/03/2025', '01/10/2024'])
+ ->getContent();
+
+ $this->assertSame(1, substr_count($html, '>Proclamatore'), "Un'intestazione sola: colonne in linea");
+ $this->assertSame(2, substr_count($html, 'tm-riga-gruppo'));
+ }
+
+ public function test_nella_scheda_del_proclamatore_una_tabella_sola_dal_piu_recente(): void
+ {
+ $html = $this->actingAs($this->admin)->get(route('proclamatori.show', $this->proclamatore))->assertOk()
+ ->assertSeeInOrder(['Anno Teocratico 2025-2026', '01/10/2025', 'Anno Teocratico 2024-2025', '01/03/2025', '01/10/2024'])
+ ->getContent();
+
+ $this->assertSame(1, substr_count($html, '>Territorio'), "Un'intestazione sola: colonne in linea");
+ $this->assertSame(2, substr_count($html, 'tm-riga-gruppo'));
+ }
+}