-
- @Scheda.ActivityTypeId
-
+
+
+ @Scheda.ActivityTypeId
+
+
+ @{
+ var syncState = Scheda.GetSyncState();
+ }
+
+ @syncState.ToLabel()
+
+
@@ -31,29 +41,26 @@
@if (Ispezione.Stato != StatusEnum.Completata)
{
- if (NetworkService.IsNetworkAvailable())
+
+
+
+
+ @if (NetworkService.IsNetworkAvailable() && syncState == SyncState.DaInviare)
{
-
-
-
- }
- else
- {
-
-
+
}
}
else
{
-
-
+
+
+
+
}
@@ -112,52 +119,74 @@
private async Task ExportScheda()
{
+ // Guardia anti doppio-tap: invio irreversibile al server.
+ if (VisibleOverlay) return;
+
VisibleOverlay = true;
StateHasChanged();
- var apiResponse = await IntegrySteupService.SaveScheda(
- new SaveRequestDto
- {
- LocalIdScheda = Scheda.Id,
- ActivityTypeId = Scheda.ActivityTypeId,
- CodJfas = Scheda.CodJfas,
- CodMdep = Ispezione.CodMdep,
- DataCreazione = Ispezione.Data,
- Note = Scheda.Note,
- PersonaRif = Scheda.Responsabile,
- Barcodes = Scheda.Articoli.ConvertAll(x => x.Barcode),
- Scandeza = (ScadenzaEnum)Scheda.Scadenza,
- ParentActivityId = Scheda.Ispezione?.ActivityId
- }
- );
-
- if (apiResponse != null)
+ try
{
- Scheda.ActivityId = apiResponse.ActivityIdScheda;
- SteupDataService.InspectionPageState.Ispezione.ActivityId = apiResponse.ActivityIdIspezione;
-
- await IspezioniService.UpdateActivityIdIspezioneAsync(Ispezione.CodMdep, Ispezione.Data,
- UserSession.User.Username, apiResponse.ActivityIdIspezione
+ var apiResponse = await IntegrySteupService.SaveScheda(
+ new SaveRequestDto
+ {
+ LocalIdScheda = Scheda.Id,
+ ActivityTypeId = Scheda.ActivityTypeId,
+ CodJfas = Scheda.CodJfas,
+ CodMdep = Ispezione.CodMdep,
+ DataCreazione = Ispezione.Data,
+ Note = Scheda.Note,
+ PersonaRif = Scheda.Responsabile,
+ Barcodes = Scheda.Articoli.ConvertAll(x => x.Barcode),
+ Scandeza = (ScadenzaEnum)Scheda.Scadenza,
+ ParentActivityId = Scheda.Ispezione?.ActivityId
+ }
);
- await IspezioniService.UpdateActivityIdSchedaAsync(Scheda.Id, Scheda.ActivityId);
- }
- VisibleOverlay = false;
- StateHasChanged();
+ if (apiResponse != null)
+ {
+ Scheda.ActivityId = apiResponse.ActivityIdScheda;
+ SteupDataService.InspectionPageState.Ispezione.ActivityId = apiResponse.ActivityIdIspezione;
+
+ await IspezioniService.UpdateActivityIdIspezioneAsync(Ispezione.CodMdep, Ispezione.Data,
+ UserSession.User.Username, apiResponse.ActivityIdIspezione
+ );
+ await IspezioniService.UpdateActivityIdSchedaAsync(Scheda.Id, Scheda.ActivityId);
+
+ // Notifica il genitore così il contatore "da inviare" del reparto si aggiorna.
+ await OnSchedaModified.InvokeAsync(Scheda);
+ }
+ }
+ finally
+ {
+ VisibleOverlay = false;
+ StateHasChanged();
+ }
}
private async Task ExportImg()
{
- if (Scheda.ImageNames == null) return;
+ if (VisibleOverlay || Scheda.ImageNames == null) return;
- var fileList = (await FileManager.GetInspectionFiles(Ispezione, Scheda.ImageNames))?
- .Where(x => x.ToUpload).ToList();
+ VisibleOverlay = true;
+ StateHasChanged();
- if (fileList == null) return;
-
- foreach (var file in fileList)
+ try
{
- await IntegrySteupService.UploadFile(Scheda.ActivityId!, file.FileBytes!, file.Name!);
+ var fileList = (await FileManager.GetInspectionFiles(Ispezione, Scheda.ImageNames))?
+ .Where(x => x.ToUpload).ToList();
+
+ if (fileList == null) return;
+
+ foreach (var file in fileList)
+ {
+ await IntegrySteupService.UploadFile(Scheda.ActivityId!, file.FileBytes!, file.Name!);
+ }
+ }
+ finally
+ {
+ VisibleOverlay = false;
+ StateHasChanged();
}
}
}
\ No newline at end of file
diff --git a/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor.css b/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor.css
index c313d26..3bf9f12 100644
--- a/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor.css
+++ b/SteUp.Shared/Components/SingleElements/Card/SchedaCard.razor.css
@@ -1,23 +1,48 @@
.scheda-card{
- padding: .75rem 1.25rem;
- background-color: var(--light-card-background);
+ padding: .55rem .85rem;
+ background-color: var(--mud-palette-surface);
+ border: 1px solid var(--card-border-color);
border-radius: 1em;
+ box-shadow: var(--card-box-shadow);
}
.scheda-body-section{
display: flex;
flex-direction: column;
- gap: 1rem;
+ gap: .5rem;
}
.title-section{
display: flex;
align-items: center;
justify-content: space-between;
+ gap: .5rem;
+}
+
+.scheda-title-group{
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: .15rem;
+ min-width: 0;
+}
+
+.sync-badge{
+ margin: 0;
}
.scheda-card-action{
display: flex;
- gap: 1rem;
- justify-content: center;
+ gap: .5rem;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+/* Azioni secondarie (modifica/elimina) raggruppate e ancorate a sinistra;
+ l'azione primaria "Invia" (corallo) resta separata e a portata di pollice a destra. */
+.action-secondary{
+ display: flex;
+ gap: .5rem;
+ align-items: center;
}
\ No newline at end of file
diff --git a/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor b/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor
index 6e86e65..892ace5 100644
--- a/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor
+++ b/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor
@@ -187,7 +187,7 @@
@if (!LabelSave.IsNullOrEmpty())
{
-
+
@LabelSave
@@ -626,7 +626,7 @@
#endregion
- private void SuggestActivityDescription()
+ private async Task SuggestActivityDescription()
{
if (Scheda.ActivityTypeId == null)
{
@@ -637,18 +637,25 @@
VisibleOverlay = true;
StateHasChanged();
- _ = Task.Run(async () =>
+ try
{
var activityDescriptions = await IntegryApiService.SuggestActivityDescription(Scheda.ActivityTypeId);
var modal = await Dialog.OpenSuggestActivityDescription(activityDescriptions);
- if (modal is { Canceled: false, Data: not null })
- Scheda.Note = modal.Data!.ToString();
-
+ if (modal is { Canceled: false, Data: string descrizione } && !string.IsNullOrWhiteSpace(descrizione))
+ {
+ Scheda.Note = descrizione;
+ // Mostra la nota selezionata nel campo e aggiorna lo stato (dirty → Salva/Aggiorna),
+ // come farebbe l'input manuale dell'utente.
+ OnAfterChangeValue();
+ }
+ }
+ finally
+ {
VisibleOverlay = false;
- await InvokeAsync(StateHasChanged);
- });
+ StateHasChanged();
+ }
}
#region Scanner
diff --git a/SteUp.Shared/Core/Helpers/SchedaSyncHelper.cs b/SteUp.Shared/Core/Helpers/SchedaSyncHelper.cs
new file mode 100644
index 0000000..5268910
--- /dev/null
+++ b/SteUp.Shared/Core/Helpers/SchedaSyncHelper.cs
@@ -0,0 +1,56 @@
+using MudBlazor;
+using SteUp.Shared.Core.Entities;
+
+namespace SteUp.Shared.Core.Helpers;
+
+///
+/// Stato di sincronizzazione di una scheda rispetto al server Integry.
+/// Principio "Offline è la verità": il rilevatore deve sempre vedere cosa è
+/// ancora solo sul dispositivo e cosa è già arrivato al server.
+///
+public enum SyncState
+{
+ /// Salvata solo in locale, non ancora inviata al server.
+ DaInviare = 0,
+
+ /// Inviata al server (ha un ActivityId).
+ Inviato = 1
+}
+
+public static class SchedaSyncHelper
+{
+ public static SyncState GetSyncState(this Scheda scheda) =>
+ string.IsNullOrEmpty(scheda.ActivityId) ? SyncState.DaInviare : SyncState.Inviato;
+
+ public static string ToLabel(this SyncState state) =>
+ state switch
+ {
+ SyncState.DaInviare => "Da inviare",
+ SyncState.Inviato => "Inviato",
+ _ => string.Empty
+ };
+
+ ///
+ /// Colore del badge. Corallo (Primary) = azione richiesta (da inviare);
+ /// verde (Success) = fatto (inviato). Rispetta la Regola dell'Unico Accento.
+ ///
+ public static Color GetColor(this SyncState state) =>
+ state switch
+ {
+ SyncState.DaInviare => Color.Primary,
+ SyncState.Inviato => Color.Success,
+ _ => Color.Default
+ };
+
+ public static string GetIcon(this SyncState state) =>
+ state switch
+ {
+ SyncState.DaInviare => Icons.Material.Rounded.PhonelinkOff,
+ SyncState.Inviato => Icons.Material.Rounded.CloudDone,
+ _ => string.Empty
+ };
+
+ /// Numero di schede non ancora inviate in una lista.
+ public static int CountDaInviare(this IEnumerable
schede) =>
+ schede.Count(s => s.GetSyncState() == SyncState.DaInviare);
+}
diff --git a/SteUp.Shared/Core/Helpers/StatusEnumHelper.cs b/SteUp.Shared/Core/Helpers/StatusEnumHelper.cs
index 997a99a..2ee3f97 100644
--- a/SteUp.Shared/Core/Helpers/StatusEnumHelper.cs
+++ b/SteUp.Shared/Core/Helpers/StatusEnumHelper.cs
@@ -7,12 +7,13 @@ public static class StatusEnumHelper
{
public static string ConvertToHumanReadable(this StatusEnum enumValue)
{
+ // Sentence-case: l'enfasi la dà il peso del chip, non il maiuscolo (Regola del Peso, non del Maiuscolo).
return enumValue switch
{
- StatusEnum.InCorso => "IN CORSO",
- StatusEnum.Completata => "COMPLETATA",
- StatusEnum.Verifica => "VERIFICA",
- StatusEnum.Annullata => "ANNULLATA",
+ StatusEnum.InCorso => "In corso",
+ StatusEnum.Completata => "Completata",
+ StatusEnum.Verifica => "Verifica",
+ StatusEnum.Annullata => "Annullata",
_ => throw new ArgumentOutOfRangeException(nameof(enumValue), enumValue, null)
};
}
diff --git a/SteUp.Shared/wwwroot/css/default-theme.css b/SteUp.Shared/wwwroot/css/default-theme.css
index ade39dd..dc9cf7b 100644
--- a/SteUp.Shared/wwwroot/css/default-theme.css
+++ b/SteUp.Shared/wwwroot/css/default-theme.css
@@ -5,6 +5,8 @@
/*Utility*/
--exception-box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.3);
--custom-box-shadow: 1px 2px 5px var(--gray-for-shadow);
+ /* Ombra morbida a due livelli, tinta col navy del brand: stacca le card dallo sfondo senza pesare */
+ --card-box-shadow: 0 1px 2px rgba(0, 35, 57, 0.08), 0 6px 16px rgba(0, 35, 57, 0.10);
--mud-default-borderradius: 20px !important;
--m-page-x: 1rem;
--mh-header: 4rem;