++ storico assegnazioni: una tabella sola con le colonne in linea, dal piu' recente

Nelle schede di territorio e proclamatore ogni anno teocratico era una
tabella a se', con le colonne dimensionate sul proprio contenuto. Ora una
tabella con un'intestazione, colonne fisse, l'anno come riga di
separazione (tm-riga-gruppo, titolo anche su telefono); ordine esplicito
dal piu' recente. Nella scheda del proclamatore Campagna "Si'/No" e badge
"via gruppo".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Francesco Picone
2026-10-20 15:43:15 +02:00
parent ab7d26ae41
commit 6f88c65d73
8 changed files with 210 additions and 93 deletions

View File

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

View File

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

View File

@@ -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(),

View File

@@ -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,

View File

@@ -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] {

View File

@@ -101,52 +101,56 @@
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Storico Assegnazioni</h3>
@forelse($assegnazioniPerAnno as $annoLabel => $assegnazioni)
<div class="mb-6">
<h4 class="text-sm font-semibold text-indigo-600 mb-2">Anno Teocratico {{ $annoLabel }}</h4>
{{-- 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())
<p class="text-gray-500 text-sm">Nessuna assegnazione registrata.</p>
@else
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm table-cards">
<table class="min-w-full table-fixed text-sm table-cards">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Territorio</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Assegnato</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Rientrato</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Giorni</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Campagna</th>
<th class="w-[36%] px-3 py-2 text-left text-xs font-medium text-gray-500">Territorio</th>
<th class="w-[15%] px-3 py-2 text-left text-xs font-medium text-gray-500">Assegnato</th>
<th class="w-[15%] px-3 py-2 text-left text-xs font-medium text-gray-500">Rientrato</th>
<th class="w-[9%] px-3 py-2 text-right text-xs font-medium text-gray-500">Giorni</th>
<th class="w-[25%] px-3 py-2 text-left text-xs font-medium text-gray-500">Campagna</th>
</tr>
</thead>
@foreach($assegnazioniPerAnno as $annoLabel => $assegnazioni)
<tbody class="divide-y divide-gray-100">
<tr class="tm-riga-gruppo">
<th colspan="5" scope="colgroup" class="px-3 pt-5 pb-2 text-left text-sm font-semibold text-indigo-600 bg-white">Anno Teocratico {{ $annoLabel }}</th>
</tr>
@foreach($assegnazioni as $assegnazione)
<tr>
<td data-label="Territorio" class="px-3 py-2">
<a data-ritorno href="{{ route('territori.show', $assegnazione->territorio_id) }}" class="text-indigo-600 hover:underline">
{{ $assegnazione->territorio?->numero ?? 'N/A' }}
</a>
<a data-ritorno href="{{ route('territori.show', $assegnazione->territorio_id) }}" class="text-indigo-600 hover:underline">N° {{ $assegnazione->territorio?->numero ?? 'N/A' }}</a>@if($assegnazione->gruppo_nome)<span class="ml-1 inline-flex rounded-full bg-purple-50 px-2 py-0.5 text-xs text-purple-700">via gruppo {{ $assegnazione->gruppo_nome }}</span>@endif
</td>
<td data-label="Assegnato" class="px-3 py-2">{{ $assegnazione->assigned_at->format('d/m/Y') }}</td>
<td data-label="Rientrato" class="px-3 py-2">
<td data-label="Assegnato" class="px-3 py-2 tabular-nums">{{ $assegnazione->assigned_at->format('d/m/Y') }}</td>
<td data-label="Rientrato" class="px-3 py-2 tabular-nums">
@if($assegnazione->returned_at)
{{ $assegnazione->returned_at->format('d/m/Y') }}
@else
<span class="text-amber-600 font-medium">In corso</span>
@endif
</td>
<td data-label="Giorni" class="px-3 py-2">{{ $assegnazione->giorni }}</td>
<td data-label="Giorni" class="px-3 py-2 text-right tabular-nums">{{ $assegnazione->giorni }}</td>
<td data-label="Campagna" class="px-3 py-2">
@if($assegnazione->counted_in_campaign)
<span class="text-green-600">{{ $assegnazione->campagna?->descrizione }}</span>
<span class="text-green-600">Sì — {{ $assegnazione->campagna?->descrizione }}</span>
@elseif($assegnazione->counted_in_campaign === false)
<span class="text-gray-400">No</span>
@else
<span class="text-gray-400">-</span>
<span class="text-gray-300">-</span>
@endif
</td>
</tr>
@endforeach
</tbody>
@endforeach
</table>
</div>
</div>
@empty
<p class="text-gray-500 text-sm">Nessuna assegnazione registrata.</p>
@endforelse
@endif
</div>
</div>

View File

@@ -161,33 +161,41 @@
<h3 class="text-lg font-semibold text-gray-900">Storico Assegnazioni</h3>
</div>
@forelse($assegnazioniPerAnno as $annoLabel => $assegnazioni)
<div class="mb-6">
<h4 class="text-sm font-semibold text-indigo-600 mb-2">Anno Teocratico {{ $annoLabel }}</h4>
{{-- 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())
<p class="text-gray-500 text-sm">Nessuna assegnazione registrata.</p>
@else
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm table-cards">
<table class="min-w-full table-fixed text-sm table-cards">
<thead class="bg-gray-50">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Proclamatore</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Assegnato</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Rientrato</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Giorni</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500">Campagna</th>
<th class="w-[36%] px-3 py-2 text-left text-xs font-medium text-gray-500">Proclamatore</th>
<th class="w-[15%] px-3 py-2 text-left text-xs font-medium text-gray-500">Assegnato</th>
<th class="w-[15%] px-3 py-2 text-left text-xs font-medium text-gray-500">Rientrato</th>
<th class="w-[9%] px-3 py-2 text-right text-xs font-medium text-gray-500">Giorni</th>
<th class="w-[25%] px-3 py-2 text-left text-xs font-medium text-gray-500">Campagna</th>
</tr>
</thead>
@foreach($assegnazioniPerAnno as $annoLabel => $assegnazioni)
<tbody class="divide-y divide-gray-100">
<tr class="tm-riga-gruppo">
<th colspan="5" scope="colgroup" class="px-3 pt-5 pb-2 text-left text-sm font-semibold text-indigo-600 bg-white">Anno Teocratico {{ $annoLabel }}</th>
</tr>
@foreach($assegnazioni as $assegnazione)
<tr>
<td data-label="Proclamatore" class="px-3 py-2">{{ $assegnazione->proclamatore?->nome_completo ?? 'N/A' }}@if($assegnazione->gruppo_nome)<span class="ml-1 inline-flex rounded-full bg-purple-50 px-2 py-0.5 text-xs text-purple-700">via gruppo {{ $assegnazione->gruppo_nome }}</span>@endif</td>
<td data-label="Assegnato" class="px-3 py-2">{{ $assegnazione->assigned_at->format('d/m/Y') }}</td>
<td data-label="Rientrato" class="px-3 py-2">
<td data-label="Assegnato" class="px-3 py-2 tabular-nums">{{ $assegnazione->assigned_at->format('d/m/Y') }}</td>
<td data-label="Rientrato" class="px-3 py-2 tabular-nums">
@if($assegnazione->returned_at)
{{ $assegnazione->returned_at->format('d/m/Y') }}
@else
<span class="text-amber-600 font-medium">In corso</span>
@endif
</td>
<td data-label="Giorni" class="px-3 py-2">{{ $assegnazione->giorni }}</td>
<td data-label="Giorni" class="px-3 py-2 text-right tabular-nums">{{ $assegnazione->giorni }}</td>
<td data-label="Campagna" class="px-3 py-2">
@if($assegnazione->counted_in_campaign)
<span class="text-green-600">Sì — {{ $assegnazione->campagna?->descrizione }}</span>
@@ -200,11 +208,9 @@
</tr>
@endforeach
</tbody>
@endforeach
</table>
</div>
</div>
@empty
<p class="text-gray-500 text-sm">Nessuna assegnazione registrata.</p>
@endforelse
@endif
</div>
</div>

View File

@@ -0,0 +1,74 @@
<?php
namespace Tests\Feature;
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 Database\Seeders\RolesAndPermissionsSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* Lo storico delle assegnazioni nelle schede di territorio e proclamatore
* (20 ottobre 2026): una tabella sola con le colonne in linea, l'anno come
* riga di separazione, dal più recente.
*/
class StoricoAssegnazioniTest extends TestCase
{
use RefreshDatabase;
private User $admin;
private Territorio $territorio;
private Proclamatore $proclamatore;
protected function setUp(): void
{
parent::setUp();
$this->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</th>'), "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</th>'), "Un'intestazione sola: colonne in linea");
$this->assertSame(2, substr_count($html, 'tm-riga-gruppo'));
}
}