Rimetti i reparti richiudibili e il "+" sul reparto

Le schede di un reparto erano un elenco fisso: con quattro reparti aperti
la pagina diventava un rotolo, e per aggiungere una scheda a un reparto
preciso bisognava passare dal comando generale e ridichiarare il reparto.

La testata del reparto torna a essere un comando: si tocca per richiudere
il gruppo, e la freccia dice da che parte sta. I conteggi salgono nella
testata ("3 schede · 1 da inviare · 1 da completare") invece di stare nel
corpo: un reparto compattato non deve nascondere quello che gli manca.

Accanto torna il "+", che apre la scheda nuova con il reparto gia'
scelto. E' l'unica differenza con il "+" della barra, ed e' quella che
toglie un passaggio a chi sta lavorando su un reparto alla volta.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 16:45:50 +02:00
parent b9de075f45
commit 70a0d0f5fa
2 changed files with 177 additions and 33 deletions
@@ -151,41 +151,55 @@
} }
else else
{ {
@* Un reparto per volta: la testata si tocca per richiudere il
gruppo, e i conteggi restano scritti li' anche da chiuso — un
reparto compattato non deve nascondere quello che gli manca. *@
@foreach (var group in SchedeGrouped) @foreach (var group in SchedeGrouped)
{ {
var reparto = group.Key;
var chiuso = IsCollapsed(reparto);
var daInviare = group.Value.CountDaInviare(); var daInviare = group.Value.CountDaInviare();
var daCompletare = group.Value.CountBozze();
<section> <section class="reparto @(chiuso ? "reparto--collapsed" : "")">
<div class="section-header"> <div class="reparto__head">
<div class="section-header__text"> <button type="button" class="reparto__toggle" @onclick="@(() => Toggle(reparto))"
<h2 class="section-header__title">@group.Key.Descrizione</h2> aria-expanded="@((!chiuso).ToString().ToLowerInvariant())">
<p class="section-header__desc"> <i class="reparto__chevron ri-arrow-down-s-line" aria-hidden="true"></i>
@(group.Value.Count == 1 ? "1 scheda" : $"{group.Value.Count} schede") <span class="reparto__text">
</p> <span class="reparto__title">@reparto.Descrizione</span>
</div> <span class="reparto__meta">@Conteggi(group.Value)</span>
</span>
</button>
@if (!IsCompleted && NetworkService.IsNetworkAvailable() && daInviare > 0) <div class="reparto__actions">
@if (!IsCompleted)
{
@* Da qui la scheda nasce gia' con il suo reparto: e'
l'unica differenza con il "+" della barra. *@
<button type="button" class="reparto__add"
disabled="@(!NetworkService.IsNetworkAvailable())"
aria-label="@($"Nuova scheda nel reparto {reparto.Descrizione}")"
title="@($"Nuova scheda in {reparto.Descrizione}")"
@onclick="@(() => CreateNewScheda(reparto))">
<i class="ri-add-line" aria-hidden="true"></i>
</button>
@if (NetworkService.IsNetworkAvailable() && daInviare > 0)
{ {
<div class="section-header__aside">
<MudButton Variant="Variant.Outlined" Size="Size.Small" <MudButton Variant="Variant.Outlined" Size="Size.Small"
StartIcon="@Icons.Material.Rounded.CloudUpload" StartIcon="@Icons.Material.Rounded.CloudUpload"
aria-label="@($"Invia le schede del reparto {group.Key.Descrizione}")" aria-label="@($"Invia le schede del reparto {reparto.Descrizione}")"
OnClick="@(() => ExportReparto(group.Key))"> OnClick="@(() => ExportReparto(reparto))">
Invia @daInviare Invia @daInviare
</MudButton> </MudButton>
</div> }
} }
</div> </div>
</div>
@if (daCompletare > 0) @if (!chiuso)
{ {
<div class="badge-row" style="margin-bottom: var(--space-10);"> <div class="card-list reparto__body">
<Badge Spec="@SyncState.Bozza.ToBadge()" Text="@($"{daCompletare} da completare")"/>
</div>
}
<div class="card-list">
@foreach (var scheda in group.Value) @foreach (var scheda in group.Value)
{ {
<SchedaCard Scheda="scheda" Ispezione="Ispezione" <SchedaCard Scheda="scheda" Ispezione="Ispezione"
@@ -194,6 +208,7 @@
OnBusyChanged="@(b => VisibleOverlay = b)"/> OnBusyChanged="@(b => VisibleOverlay = b)"/>
} }
</div> </div>
}
</section> </section>
} }
} }
@@ -296,6 +311,43 @@
if (modal is { Canceled: false }) LoadSchede(); if (modal is { Canceled: false }) LoadSchede();
} }
/// Stessa scheda nuova, ma con il reparto gia' scelto: partendo da un
/// reparto non ha senso richiederlo.
private async Task CreateNewScheda(JtbFasiDto reparto)
{
var modal = await Dialog.OpenFormScheda(
Ispezione.CodMdep, Ispezione.Data, true, new Scheda { Reparto = reparto }
);
if (modal is { Canceled: false }) LoadSchede();
}
// I reparti richiusi stanno qui e non nell'entita': e' uno stato della
// schermata, non del documento.
private readonly HashSet<string> _collapsed = new(StringComparer.OrdinalIgnoreCase);
private bool IsCollapsed(JtbFasiDto reparto) =>
reparto.CodJfas is not null && _collapsed.Contains(reparto.CodJfas);
private void Toggle(JtbFasiDto reparto)
{
if (reparto.CodJfas is null) return;
if (!_collapsed.Remove(reparto.CodJfas)) _collapsed.Add(reparto.CodJfas);
}
private static string Conteggi(List<Scheda> schede)
{
var parti = new List<string> { schede.Count == 1 ? "1 scheda" : $"{schede.Count} schede" };
var daInviare = schede.CountDaInviare();
if (daInviare > 0) parti.Add($"{daInviare} da inviare");
var bozze = schede.CountBozze();
if (bozze > 0) parti.Add($"{bozze} da completare");
return string.Join(" · ", parti);
}
private async Task ConfirmCompleteInspection() private async Task ConfirmCompleteInspection()
{ {
_actionsOpen = false; _actionsOpen = false;
+92
View File
@@ -140,6 +140,98 @@
.section-header__aside { flex: 0 0 auto; } .section-header__aside { flex: 0 0 auto; }
/* -----------------------------------------------------------------------------
Reparto — gruppo di schede richiudibile
La testata e' un comando a tutta riga: si tocca ovunque per chiudere il
gruppo. I conteggi stanno nella testata e non nel corpo, cosi' restano
leggibili anche da chiuso.
-------------------------------------------------------------------------- */
.reparto__head {
display: flex;
align-items: center;
gap: var(--space-xs);
margin-bottom: var(--space-sm);
}
.reparto__toggle {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
gap: var(--space-4);
min-height: var(--touch-min);
padding: 0;
border: none;
background: none;
font-family: inherit;
text-align: left;
color: var(--ink);
}
.reparto__chevron {
flex: 0 0 auto;
font-size: 24px;
color: var(--ink-muted);
transition: transform var(--duration-base) var(--ease-out);
}
.reparto--collapsed .reparto__chevron { transform: rotate(-90deg); }
.reparto__text {
flex: 1 1 auto;
min-width: 0;
}
.reparto__title {
display: block;
font-size: var(--title-lg-size);
line-height: var(--title-lg-height);
font-weight: 700;
letter-spacing: -0.01em;
}
.reparto__meta {
display: block;
margin-top: var(--space-2);
font-size: var(--body-md-size);
line-height: var(--body-md-height);
font-weight: 400;
color: var(--ink-muted);
}
.reparto__actions {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: var(--space-6);
}
/* Il "+" del reparto non porta l'accento: l'accento e' del comando principale
della schermata, e qui i bottoni sono due in fila. */
.reparto__add {
display: flex;
align-items: center;
justify-content: center;
width: var(--touch-min);
height: var(--touch-min);
flex: 0 0 auto;
border: 1px solid var(--divider);
border-radius: var(--radius-pill);
background-color: var(--surface);
color: var(--ink);
font-size: 22px;
transition: transform var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out);
}
.reparto__add:active { transform: scale(0.94); background-color: var(--surface-muted); }
.reparto__add:disabled { color: var(--ink-muted); background-color: var(--surface-muted); }
@media (prefers-reduced-motion: reduce) {
.reparto__add:active { transform: none; }
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
§6.4 Badge — stato in pillola §6.4 Badge — stato in pillola
Mai il solo colore: sempre icona + etichetta. Mai il solo colore: sempre icona + etichetta.