Salvataggio come bozza: il lavoro in corso non si perde piu'
Uscendo da una scheda a meta' (tasto indietro del dispositivo o "Annulla")
il lavoro veniva scartato. Il tasto indietro non era nemmeno gestito: non
arriva alla WebView e non c'e' stack di navigazione, quindi mandava l'app
in background con i dati solo in memoria.
Nuovo stato Bozza ("Da completare"): salvata in locale, mai inviabile
finche' non viene conclusa.
- Scheda.Bozza + migration additiva (default false: le schede esistenti
non cambiano stato).
- SchedaSyncHelper: terzo SyncState, CountBozze(). CountDaInviare()
esclude le bozze, quindi il badge "da inviare" resta corretto.
- ModalFormScheda: SaveDraft() salva solo in locale senza validazione;
Save() azzera il flag; Submit() avvisa quando il form e' invalido
(prima il tocco su "Salva" sembrava non fare nulla se i campi mancanti
erano fuori schermo); Update() prende preserveActivityId cosi' una
bozza di scheda gia' inviata mantiene il link al server e al
completamento lo aggiorna invece di duplicarlo.
- ConfirmUpdateActivity: il pulsante bozza sta su NoText, non CancelText
(CancelText ritorna null, NoText ritorna false).
- BackButtonService + MainActivity.OnBackPressed: il tasto indietro passa
dallo stesso percorso di "Annulla". Ignorato mentre e' aperto un dialog
figlio, per non mostrare il prompt sotto un altro dialog.
- IspezionePage: "Concludi ispezione" si blocca se ci sono bozze,
"Esporta reparto" le salta, chip "N da completare" per reparto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
<MudMessageBox @ref="_confirmSave" Title="Attenzione!" CancelText="Non salvare">
|
||||
@* NoText -> false (salva bozza), YesText -> true (salva), chiusura senza scelta -> null.
|
||||
Niente CancelText: da qui non si esce buttando via il lavoro. *@
|
||||
<MudMessageBox @ref="_confirmSave" Title="Lavoro non salvato" NoText="Salva come bozza">
|
||||
<MessageContent>
|
||||
Sono state apportate delle modifiche. Vuoi salvarle prima di continuare?
|
||||
Hai delle modifiche non salvate. Salva la scheda, oppure tienila come bozza
|
||||
da completare più tardi: in ogni caso non perdi nulla.
|
||||
</MessageContent>
|
||||
<YesButton>
|
||||
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Primary">
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
@using SteUp.Shared.Core.Interface.LocalDb
|
||||
@using SteUp.Shared.Core.Interface.System
|
||||
@using SteUp.Shared.Core.Messages.Scanner
|
||||
@using SteUp.Shared.Core.Messages.System
|
||||
@inject BackButtonService BackButton
|
||||
@inject INetworkService NetworkService
|
||||
@inject IDialogService Dialog
|
||||
@inject IIntegryApiService IntegryApiService
|
||||
@@ -20,6 +22,7 @@
|
||||
@inject OnScannerService OnScannerService
|
||||
@inject IAttachedService AttachedService
|
||||
@inject ILogger<ModalFormScheda> Logger
|
||||
@implements IDisposable
|
||||
|
||||
<MudDialog Class="customDialog-form">
|
||||
<DialogContent>
|
||||
@@ -237,6 +240,34 @@
|
||||
private bool _attachmentsDirty;
|
||||
private List<AttachedDto>? AttachedList { get; set; }
|
||||
|
||||
private IDisposable? _backRegistration;
|
||||
// Il tasto indietro puo' arrivare mentre il prompt di uscita e' gia' a video.
|
||||
private bool _closing;
|
||||
// Dialog aperti sopra al form (articoli, suggerimenti, errori).
|
||||
private int _childDialogs;
|
||||
|
||||
private Task HandleBackPressed()
|
||||
{
|
||||
// Con un dialog figlio a video il back non deve agire sul form sottostante:
|
||||
// meglio ignorarlo che far comparire il prompt di uscita sotto un altro dialog.
|
||||
if (_childDialogs > 0) return Task.CompletedTask;
|
||||
|
||||
return Cancel();
|
||||
}
|
||||
|
||||
private async Task<T> WithChildDialog<T>(Func<Task<T>> open)
|
||||
{
|
||||
_childDialogs++;
|
||||
try
|
||||
{
|
||||
return await open();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_childDialogs--;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter;
|
||||
@@ -248,6 +279,10 @@
|
||||
_originalBarcodes = Scheda.Articoli.ConvertAll(x => x.Barcode);
|
||||
Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter;
|
||||
|
||||
// Il tasto indietro del dispositivo deve passare da qui, non mandare l'app
|
||||
// in background lasciando il lavoro non salvato solo in memoria.
|
||||
_backRegistration = BackButton.Register(() => InvokeAsync(HandleBackPressed));
|
||||
|
||||
LoadAttached();
|
||||
}
|
||||
|
||||
@@ -284,7 +319,10 @@
|
||||
{
|
||||
VisibleOverlay = true;
|
||||
StateHasChanged();
|
||||
|
||||
|
||||
// Salvataggio completo: la scheda non è più una bozza.
|
||||
Scheda.Bozza = false;
|
||||
|
||||
SaveSchedaResponseDto? apiResponse = null;
|
||||
try
|
||||
{
|
||||
@@ -369,7 +407,7 @@
|
||||
await IspezioniService.AddSchedaAsync(CodMdep, Data, UserSession.User.Username, Scheda);
|
||||
}
|
||||
|
||||
private async Task Update(SaveSchedaResponseDto? apiResponse)
|
||||
private async Task Update(SaveSchedaResponseDto? apiResponse, bool preserveActivityId = false)
|
||||
{
|
||||
if (!AttachedList.IsNullOrEmpty())
|
||||
{
|
||||
@@ -399,7 +437,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
Scheda.ActivityId = apiResponse?.ActivityIdScheda;
|
||||
// Salvando una bozza non tocchiamo l'ActivityId: se la scheda era già sul server,
|
||||
// al completamento va aggiornata, non ricreata.
|
||||
if (!preserveActivityId) Scheda.ActivityId = apiResponse?.ActivityIdScheda;
|
||||
|
||||
await IspezioniService.UpdateSchedaAsync(Scheda);
|
||||
|
||||
if (apiResponse != null)
|
||||
@@ -440,11 +481,21 @@
|
||||
|
||||
private async Task Cancel()
|
||||
{
|
||||
if (await CheckSavePreAction())
|
||||
if (_closing) return;
|
||||
_closing = true;
|
||||
|
||||
try
|
||||
{
|
||||
await FileManager.CleanTempStorageAsync();
|
||||
DisposeMessage();
|
||||
MudDialog.Cancel();
|
||||
if (await CheckSavePreAction())
|
||||
{
|
||||
await FileManager.CleanTempStorageAsync();
|
||||
DisposeMessage();
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_closing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,17 +552,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ritorna true se il form può chiudersi scartando (nessuna modifica in sospeso).
|
||||
/// Con del lavoro in corso non si perde mai nulla: o si salva per intero, o si
|
||||
/// salva come bozza da riprendere.
|
||||
/// </summary>
|
||||
private async Task<bool> CheckSavePreAction()
|
||||
{
|
||||
if (!IsDirty) return true;
|
||||
|
||||
var resul = await _confirmUpdateMessage.ShowAsync();
|
||||
if (resul is not true) return true;
|
||||
var result = await WithChildDialog(() => _confirmUpdateMessage.ShowAsync());
|
||||
|
||||
// Prompt chiuso senza scegliere (tap fuori / indietro): si resta sulla scheda.
|
||||
if (result is null) return false;
|
||||
|
||||
VisibleOverlay = true;
|
||||
StateHasChanged();
|
||||
|
||||
await Submit();
|
||||
if (result is true) await Submit();
|
||||
else await SaveDraft();
|
||||
|
||||
VisibleOverlay = false;
|
||||
StateHasChanged();
|
||||
@@ -519,10 +578,48 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Salva il lavoro in corso solo sul dispositivo, marcato come da completare.
|
||||
/// Niente chiamate al server e niente validazione: una bozza può essere incompleta.
|
||||
/// </summary>
|
||||
private async Task SaveDraft()
|
||||
{
|
||||
try
|
||||
{
|
||||
Scheda.Bozza = true;
|
||||
|
||||
if (IsNew) await NewSave(null);
|
||||
else await Update(null, preserveActivityId: true);
|
||||
|
||||
await FileManager.CleanTempStorageAsync();
|
||||
|
||||
Snackbar.Clear();
|
||||
Snackbar.Add("Scheda salvata come bozza, la ritrovi nell'ispezione", Severity.Success);
|
||||
|
||||
DisposeMessage();
|
||||
MudDialog.Close(Scheda);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Scheda.Bozza = false;
|
||||
OnError(e, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await _form.Validate();
|
||||
if (_form.IsValid) await Save();
|
||||
|
||||
if (_form.IsValid)
|
||||
{
|
||||
await Save();
|
||||
return;
|
||||
}
|
||||
|
||||
// Il form resta aperto sugli errori: senza un avviso il tocco su "Salva"
|
||||
// sembra non fare nulla se i campi mancanti sono fuori schermo.
|
||||
Snackbar.Clear();
|
||||
Snackbar.Add("Compila i campi obbligatori, oppure esci salvando come bozza", Severity.Warning);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -621,7 +718,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var modal = await Dialog.OpenSelectArt(articoli);
|
||||
var modal = await WithChildDialog(() => Dialog.OpenSelectArt(articoli));
|
||||
|
||||
await InvokeAsync(() =>
|
||||
{
|
||||
@@ -679,7 +776,7 @@
|
||||
{
|
||||
var activityDescriptions = await IntegryApiService.SuggestActivityDescription(Scheda.ActivityTypeId);
|
||||
|
||||
var modal = await Dialog.OpenSuggestActivityDescription(activityDescriptions);
|
||||
var modal = await WithChildDialog(() => Dialog.OpenSuggestActivityDescription(activityDescriptions));
|
||||
|
||||
if (modal is { Canceled: false, Data: string descrizione } && !string.IsNullOrWhiteSpace(descrizione))
|
||||
{
|
||||
@@ -783,6 +880,16 @@
|
||||
{
|
||||
OnScannerService.OnNewScanSuccessful -= HandleNewScanSuccessful;
|
||||
OnScannerService.OnErrorScan -= OnErrorScan;
|
||||
|
||||
_backRegistration?.Dispose();
|
||||
_backRegistration = null;
|
||||
}
|
||||
|
||||
// Rete di sicurezza: se il dialog viene chiuso per altre vie, il tasto indietro
|
||||
// non deve restare agganciato a un form che non esiste più.
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
DisposeMessage();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user