++ gruppi: chi fa parte di un gruppo di servizio e' segnalato
Badge "Gruppo ..." / "Sorvegliante del gruppo ..." nell'elenco dei proclamatori (con il filtro "In un gruppo di servizio") e in testa alla scheda; nella scelta del proclamatore (Assegna, Registro) la nota "gruppo ..." accanto al nome, che resta sceglibile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -181,6 +181,13 @@ Decisioni successive dell'utente (20 ottobre 2026):
|
||||
accesso dal registro), oppure il gruppo di cui è membro, e i gruppi sciolti
|
||||
che ha guidato. Il *Profilo* del sorvegliante mostra il suo gruppo con gli
|
||||
stessi numeri e "Torna al mio gruppo".
|
||||
- **Anche i membri si segnalano**: `Proclamatore::gruppo_etichetta`
|
||||
("Gruppo Zisa" / "Sorvegliante del gruppo Zisa") come badge nell'elenco
|
||||
(più il filtro *In un gruppo di servizio*, `filtroStato=gruppo`) e in testa
|
||||
alla scheda; nelle opzioni di `<x-scelta>` come `dettaglio` (Assegna,
|
||||
Registro), non come `motivo`: assegnare direttamente a un membro resta
|
||||
possibile. In "Il mio gruppo" la nota non c'è (`opzioniScelta(...,
|
||||
conGruppo: false)`).
|
||||
- L'utenza del sorvegliante porta nome ed email del proclamatore: nel registro
|
||||
delle attività `User::tapActivity()` li sostituisce con "proclamatore #id",
|
||||
come vale per tutti i proclamatori. Restano invece in chiaro nella tabella
|
||||
|
||||
@@ -766,6 +766,11 @@ docker compose --profile dev up -d --build
|
||||
gruppo. Un membro non attivo resta nel gruppo ma non riceve territori; nel
|
||||
cestino esce dal gruppo. Anonimizzando un ex sorvegliante si anonimizza
|
||||
anche la sua utenza
|
||||
- **Chi fa parte di un gruppo è segnalato**: badge "Gruppo …" o
|
||||
"Sorvegliante del gruppo …" nell'elenco dei proclamatori (con il filtro
|
||||
*In un gruppo di servizio*) e in testa alla scheda; nella scelta del
|
||||
proclamatore (Assegna, Registro) la nota "gruppo …" accanto al nome, che
|
||||
resta comunque sceglibile
|
||||
- **Dettagli del gruppo**: la scheda del proclamatore dice se è sorvegliante
|
||||
(gruppo, membri, territori nel pacchetto e da dare, assegnazioni nate dal
|
||||
gruppo, stato dell'utenza e ultimo accesso), membro, o sorvegliante di
|
||||
|
||||
@@ -184,7 +184,7 @@ class MioGruppo extends Component
|
||||
'gruppo' => $gruppo,
|
||||
'territori' => $territori,
|
||||
'membri' => $membri,
|
||||
'opzioniMembri' => Proclamatore::opzioniScelta($membri),
|
||||
'opzioniMembri' => Proclamatore::opzioniScelta($membri, conGruppo: false),
|
||||
'invio' => $this->invioCorrente(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class ProclamatoreIndex extends Component
|
||||
// withCount rather than letting the view call
|
||||
// ->assegnazioni()->aperte()->count() per row, which issued one
|
||||
// COUNT(*) per displayed proclamatore on every render.
|
||||
$query = Proclamatore::query()->withCount([
|
||||
$query = Proclamatore::query()->with('gruppo')->withCount([
|
||||
'assegnazioni as assegnazioni_aperte_count' => fn ($q) => $q->whereNull('returned_at'),
|
||||
]);
|
||||
|
||||
@@ -124,6 +124,8 @@ class ProclamatoreIndex extends Component
|
||||
$filtered = $filtered->where('attivo', true);
|
||||
} elseif ($this->filtroStato === 'inattivo') {
|
||||
$filtered = $filtered->where('attivo', false);
|
||||
} elseif ($this->filtroStato === 'gruppo') {
|
||||
$filtered = $filtered->whereNotNull('gruppo_servizio_id');
|
||||
}
|
||||
|
||||
// Sort in-memory
|
||||
@@ -148,6 +150,8 @@ class ProclamatoreIndex extends Component
|
||||
$query->where('attivo', true);
|
||||
} elseif ($this->filtroStato === 'inattivo') {
|
||||
$query->where('attivo', false);
|
||||
} elseif ($this->filtroStato === 'gruppo') {
|
||||
$query->whereNotNull('gruppo_servizio_id');
|
||||
}
|
||||
|
||||
// cognome/nome encrypted, so sort in-memory
|
||||
|
||||
@@ -103,9 +103,13 @@ class Proclamatore extends Model
|
||||
*/
|
||||
public function opzioneScelta(?string $motivo = null): array
|
||||
{
|
||||
// Il gruppo di servizio come nota, solo se già caricato (opzioniScelta).
|
||||
$gruppo = $this->relationLoaded('gruppo') ? $this->gruppo_etichetta : null;
|
||||
|
||||
return array_filter([
|
||||
'valore' => (string) $this->id,
|
||||
'etichetta' => $this->nome_completo,
|
||||
'dettaglio' => $gruppo !== null ? lcfirst($gruppo) : null,
|
||||
'chiavi' => [(string) $this->cognome, (string) $this->nome],
|
||||
'motivo' => $motivo,
|
||||
], fn ($v) => $v !== null);
|
||||
@@ -113,10 +117,16 @@ class Proclamatore extends Model
|
||||
|
||||
/**
|
||||
* Le opzioni di un elenco di proclamatori, in ordine di cognome e nome.
|
||||
* L'ordine si fa qui: in SQL ordinerebbe i testi cifrati.
|
||||
* L'ordine si fa qui: in SQL ordinerebbe i testi cifrati. Con $conGruppo
|
||||
* chi fa parte di un gruppo di servizio lo dice accanto al nome (non serve
|
||||
* dove sono tutti dello stesso gruppo, come in "Il mio gruppo").
|
||||
*/
|
||||
public static function opzioniScelta(iterable $proclamatori): array
|
||||
public static function opzioniScelta(iterable $proclamatori, bool $conGruppo = true): array
|
||||
{
|
||||
if ($conGruppo && $proclamatori instanceof \Illuminate\Database\Eloquent\Collection) {
|
||||
$proclamatori->loadMissing('gruppo');
|
||||
}
|
||||
|
||||
return collect($proclamatori)
|
||||
->sortBy(fn (self $p) => mb_strtolower($p->cognome . ' ' . $p->nome))
|
||||
->map(fn (self $p) => $p->opzioneScelta($p->attivo ? null : 'non attivo'))
|
||||
@@ -124,6 +134,22 @@ class Proclamatore extends Model
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* "Sorvegliante del gruppo Zisa" o "Gruppo Zisa"; null fuori dai gruppi.
|
||||
* Senza gruppo_servizio_id non fa query.
|
||||
*/
|
||||
public function getGruppoEtichettaAttribute(): ?string
|
||||
{
|
||||
$gruppo = $this->gruppo;
|
||||
if (! $gruppo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $gruppo->sorvegliante_id === $this->id
|
||||
? "Sorvegliante del gruppo {$gruppo->nome}"
|
||||
: "Gruppo {$gruppo->nome}";
|
||||
}
|
||||
|
||||
public function assegnazioni()
|
||||
{
|
||||
return $this->hasMany(Assegnazione::class, 'proclamatore_id');
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<option value="">Tutti</option>
|
||||
<option value="attivo">Attivi</option>
|
||||
<option value="inattivo">Inattivi</option>
|
||||
<option value="gruppo">In un gruppo di servizio</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,11 +54,16 @@
|
||||
<td data-label="Cognome" class="px-4 py-3 text-sm font-medium text-gray-900">{{ $proclamatore->cognome }}</td>
|
||||
<td data-label="Nome" class="px-4 py-3 text-sm text-gray-700">{{ $proclamatore->nome }}</td>
|
||||
<td data-label="Stato" class="px-4 py-3 text-sm">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@if($proclamatore->attivo)
|
||||
<span class="inline-flex px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-800">Attivo</span>
|
||||
@else
|
||||
<span class="inline-flex px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 text-gray-600">Inattivo</span>
|
||||
@endif
|
||||
@if($proclamatore->gruppo_etichetta)
|
||||
<span class="inline-flex px-2 py-0.5 text-xs font-medium rounded-full bg-purple-100 text-purple-800">{{ $proclamatore->gruppo_etichetta }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="Territori assegnati" class="px-4 py-3 text-sm text-gray-600">{{ $proclamatore->assegnazioni_aperte_count ?? $proclamatore->assegnazioni()->aperte()->count() }}</td>
|
||||
<td class="px-4 py-3 text-sm text-right cell-actions">
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
{{ $ritorno ? 'Torna indietro' : 'Torna alla lista' }}
|
||||
</a>
|
||||
<h1 class="text-2xl font-bold text-gray-900">{{ $proclamatore->nome_completo }}</h1>
|
||||
@if($proclamatore->gruppo_etichetta)
|
||||
<span class="mt-1 inline-flex px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800">{{ $proclamatore->gruppo_etichetta }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@can('settings.manage')
|
||||
<x-btn data-ritorno variante="secondary" :href="route('proclamatori.edit', $proclamatore)">
|
||||
|
||||
@@ -472,6 +472,33 @@ class GruppiServizioTest extends TestCase
|
||||
->assertDontSee('Assegnazioni dal gruppo');
|
||||
}
|
||||
|
||||
public function test_chi_fa_parte_di_un_gruppo_e_segnalato_in_elenco_scheda_e_scelta(): void
|
||||
{
|
||||
$g = $this->gruppo();
|
||||
$this->proclamatore('Neri');
|
||||
|
||||
Livewire::test(\App\Livewire\Proclamatori\ProclamatoreIndex::class)
|
||||
->assertSee('Sorvegliante del gruppo Zisa')
|
||||
->assertSee('Gruppo Zisa')
|
||||
->set('filtroStato', 'gruppo')
|
||||
->assertSee('Verdi')->assertSee('Bianchi')->assertSee('Rossi')
|
||||
->assertDontSee('Neri');
|
||||
|
||||
$this->get(route('proclamatori.show', $g['verdi']))->assertOk()->assertSee('Gruppo Zisa');
|
||||
|
||||
// Nella tendina di Assegna la nota accanto al nome...
|
||||
$opzioni = collect(Livewire::test(Assegna::class)->viewData('opzioniProclamatori'))->keyBy('etichetta');
|
||||
$this->assertSame('gruppo Zisa', $opzioni['Verdi Anna']['dettaglio']);
|
||||
$this->assertSame('sorvegliante del gruppo Zisa', $opzioni['Rossi Mario']['dettaglio']);
|
||||
$this->assertArrayNotHasKey('dettaglio', $opzioni['Neri Test']);
|
||||
$this->assertArrayNotHasKey('motivo', $opzioni['Verdi Anna'], 'Si può comunque scegliere');
|
||||
|
||||
// ...che in "Il mio gruppo", dove sono tutti dello stesso gruppo, non serve.
|
||||
$this->flushSession();
|
||||
$membri = Livewire::actingAs($g['sorvegliante'])->test(MioGruppo::class)->viewData('opzioniMembri');
|
||||
$this->assertSame([], collect($membri)->pluck('dettaglio')->filter()->all());
|
||||
}
|
||||
|
||||
public function test_il_profilo_del_sorvegliante_mostra_il_suo_gruppo(): void
|
||||
{
|
||||
$g = $this->gruppo();
|
||||
|
||||
Reference in New Issue
Block a user