diff --git a/SteUp.Maui/Core/CoreModule.cs b/SteUp.Maui/Core/CoreModule.cs index ab9cf4f..5a18cb0 100644 --- a/SteUp.Maui/Core/CoreModule.cs +++ b/SteUp.Maui/Core/CoreModule.cs @@ -17,9 +17,7 @@ using SteUp.Shared.Core.Interface.IntegryApi; using SteUp.Shared.Core.Interface.LocalDb; using SteUp.Shared.Core.Interface.System; using SteUp.Shared.Core.Interface.System.Network; -using SteUp.Shared.Core.Messages.Ispezione; using SteUp.Shared.Core.Messages.Scanner; -using SteUp.Shared.Core.Messages.Scheda; using SteUp.Shared.Core.Messages.System; using SteUp.Shared.Core.Services; @@ -63,9 +61,7 @@ public static class CoreModule public void RegisterMessageServices() { builder.Services.AddSingleton(); - builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); builder.Services.AddSingleton(); } diff --git a/SteUp.Shared/Components/Layout/NavMenu.razor b/SteUp.Shared/Components/Layout/NavMenu.razor index 34f3f85..e7cb265 100644 --- a/SteUp.Shared/Components/Layout/NavMenu.razor +++ b/SteUp.Shared/Components/Layout/NavMenu.razor @@ -1,15 +1,3 @@ -@using CommunityToolkit.Mvvm.Messaging -@using SteUp.Shared.Components.Layout.Sheet -@using SteUp.Shared.Components.SingleElements.MessageBox -@using SteUp.Shared.Core.Enum -@using SteUp.Shared.Core.Interface.LocalDb -@using SteUp.Shared.Core.Messages.Ispezione -@using SteUp.Shared.Core.Messages.Scheda -@inject INetworkService NetworkService -@inject IDialogService Dialog -@inject IMessenger Messenger -@inject IIspezioniService IspezioniService -@inject IJSRuntime Js @implements IDisposable @* @@ -17,6 +5,10 @@ sotto, le destinazioni restano a portata di pollice. Icona outline a riposo, piena da selezionata, etichetta sempre visibile in entrambe. Da 720px diventa una colonna laterale (vedi components.css). + + Qui stanno SOLO le destinazioni. Le azioni di una schermata appartengono + alla schermata, e si trovano nella sua testata: un "+" incastrato nella + barra non dice cosa crea, e cambia significato da una pagina all'altra. *@ - -
- @if (OnInspectionPage) - { - @if (!_isCompleted) - { - - } - - @if (ShowCompleteInspection) - { - - } - - - } - else - { - - } - - @if (!NetworkService.IsNetworkAvailable()) - { - - } -
-
- - - @code { private bool IsVisible { get; set; } = true; - private bool ActionsVisible { get; set; } = true; - private bool OnInspectionPage { get; set; } - private bool ShowCompleteInspection { get; set; } - private bool _isCompleted; - private bool _sheetOpen; - - private ConfirmMessageBox _messageBox = null!; private static readonly string[] NavigableRoutes = ["ispezioni", "ispezione", "user"]; - private string SheetTitle => OnInspectionPage ? "Azioni ispezione" : "Nuova ispezione"; - - private string? SheetSubtitle => OnInspectionPage - ? SteupDataService.InspectionPageState.Ispezione.CodMdep - : null; - private string CurrentRoute => NavigationManager.Uri.Remove(0, NavigationManager.BaseUri.Length).Split('?')[0]; private bool IsOn(string route) => CurrentRoute.EqualsIgnoreCase(route); - protected override Task OnInitializedAsync() + protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; Refresh(); - return Task.CompletedTask; } - private void OnLocationChanged(object? sender, LocationChangedEventArgs args) - { - // Cambiando schermata un menu aperto non ha piu' senso. - _sheetOpen = false; - Refresh(); - } + private void OnLocationChanged(object? sender, LocationChangedEventArgs args) => Refresh(); private void Refresh() { - var route = CurrentRoute; - - IsVisible = NavigableRoutes.Contains(route, StringComparer.OrdinalIgnoreCase); - OnInspectionPage = route.EqualsIgnoreCase("ispezione"); - ActionsVisible = IsVisible && !route.EqualsIgnoreCase("user"); - - if (OnInspectionPage) - { - var ispezione = SteupDataService.InspectionPageState.Ispezione; - _isCompleted = ispezione.Stato == StatusEnum.Completata; - // "Concludi" non ha senso su un'ispezione mai inviata o gia' conclusa. - ShowCompleteInspection = !ispezione.ActivityId.IsNullOrEmpty() && !_isCompleted; - - // Su un'ispezione conclusa resta solo la consultazione. - if (_isCompleted) ActionsVisible = false; - } - + IsVisible = NavigableRoutes.Contains(CurrentRoute, StringComparer.OrdinalIgnoreCase); StateHasChanged(); } - private void OpenActions() - { - Refresh(); - _sheetOpen = true; - } - - private async Task NewInspection() - { - _sheetOpen = false; - - if (await SteupDataService.CanOpenNewInspection()) - { - _ = Dialog.OpenSelectShop(); - } - else - { - await Dialog.ShowWarning( - "Per aprire una nuova ispezione devi prima concludere tutte le ispezioni aperte da piu' di 20 giorni."); - } - } - - private async Task CompleteInspection() - { - _sheetOpen = false; - - var result = await _messageBox.ShowAsync(); - if (result is true) Messenger.Send(new CompleteInspectionMessage()); - } - - private async Task NewScheda() - { - _sheetOpen = false; - - var ispezione = SteupDataService.InspectionPageState.Ispezione; - var modal = await Dialog.OpenFormScheda(ispezione.CodMdep, ispezione.Data, true); - - if (modal is { Canceled: false }) Messenger.Send(new NewSchedaMessage()); - } - - private async Task DeleteInspection() - { - _sheetOpen = false; - - try - { - var ispezione = SteupDataService.InspectionPageState.Ispezione; - await IspezioniService.DeleteIspezioneAsync(ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore); - - await Js.InvokeVoidAsync("goBack"); - } - catch (Exception e) - { - await Dialog.ShowError(e.Message); - } - } - public void Dispose() => NavigationManager.LocationChanged -= OnLocationChanged; } diff --git a/SteUp.Shared/Components/Pages/IspezionePage.razor b/SteUp.Shared/Components/Pages/IspezionePage.razor index 1cefd21..94f9987 100644 --- a/SteUp.Shared/Components/Pages/IspezionePage.razor +++ b/SteUp.Shared/Components/Pages/IspezionePage.razor @@ -2,6 +2,7 @@ @using Microsoft.Extensions.Logging @using SteUp.Shared.Components.Layout @using SteUp.Shared.Components.Layout.Overlay +@using SteUp.Shared.Components.Layout.Sheet @using SteUp.Shared.Components.SingleElements @using SteUp.Shared.Components.SingleElements.Card @using SteUp.Shared.Components.SingleElements.MessageBox @@ -12,18 +13,66 @@ @using SteUp.Shared.Core.Interface.IntegryApi @using SteUp.Shared.Core.Interface.LocalDb @using SteUp.Shared.Core.Interface.System -@using SteUp.Shared.Core.Messages.Ispezione -@using SteUp.Shared.Core.Messages.Scheda -@inject NewSchedaService NewScheda -@inject CompleteInspectionService CompleteInspection @inject IIspezioniService IspezioniService @inject IDialogService Dialog @inject IIntegrySteupService IntegrySteupService @inject IFileManager FileManager @inject ILogger Logger -@implements IDisposable - + + + @* Su un'ispezione conclusa resta solo la consultazione: niente azioni. *@ + @if (!IsCompleted) + { + @* Qui le azioni sono piu' d'una e non hanno lo stesso peso: lo + sheet le mostra con nome, icona e tono, invece di nasconderle + tutte dietro lo stesso glifo. *@ + + } + + + + +
+ + + @if (!Ispezione.ActivityId.IsNullOrEmpty()) + { + + } + + + + @if (!NetworkService.IsNetworkAvailable()) + { + + } +
+
@@ -92,8 +141,16 @@ @if (!IsCompleted) {

- Tocca + nella barra in basso per creare la prima scheda di questa ispezione. + Aggiungi la prima scheda di questa ispezione.

+
+ + Nuova scheda + +
}
} @@ -149,7 +206,14 @@
- + + @code { @@ -162,6 +226,10 @@ private string? _progressMessage; private ConfirmMessageBox _exportRepartoBox = null!; + private ConfirmMessageBox _completeBox = null!; + private ConfirmMessageBox _deleteBox = null!; + + private bool _actionsOpen; private Ispezione Ispezione => SteupDataService.InspectionPageState.Ispezione; private bool IsCompleted => Ispezione.Stato == StatusEnum.Completata; @@ -185,13 +253,7 @@ private string Sottotitolo => Ispezione.CodMdep; - protected override void OnInitialized() - { - NewScheda.OnNewScheda += LoadSchede; - CompleteInspection.OnComplete += HandleCompleteInspection; - - LoadSchede(); - } + protected override void OnInitialized() => LoadSchede(); private void LoadSchede() { @@ -208,6 +270,44 @@ }); } + private async Task NewScheda() + { + _actionsOpen = false; + + var modal = await Dialog.OpenFormScheda(Ispezione.CodMdep, Ispezione.Data, true); + if (modal is { Canceled: false }) LoadSchede(); + } + + private async Task ConfirmCompleteInspection() + { + _actionsOpen = false; + + var confirmed = await _completeBox.ShowAsync(); + if (confirmed is true) HandleCompleteInspection(); + } + + private async Task ConfirmDeleteInspection() + { + _actionsOpen = false; + + // Prima non c'era conferma: un tocco cancellava l'ispezione e tutte le + // sue schede senza appello. + var confirmed = await _deleteBox.ShowAsync(); + if (confirmed is not true) return; + + try + { + var ispezione = Ispezione; + await IspezioniService.DeleteIspezioneAsync(ispezione.CodMdep, ispezione.Data, ispezione.Rilevatore); + + NavigationManager.NavigateTo("/ispezioni"); + } + catch (Exception e) + { + OnError(e, e.Message); + } + } + private async void HandleCompleteInspection() { // Guardia anti doppio-invio: il completamento è irreversibile. @@ -496,10 +596,4 @@ ); } - void IDisposable.Dispose() - { - NewScheda.OnNewScheda -= LoadSchede; - CompleteInspection.OnComplete -= HandleCompleteInspection; - } - } diff --git a/SteUp.Shared/Components/Pages/IspezioniPage.razor b/SteUp.Shared/Components/Pages/IspezioniPage.razor index 952db60..cc02113 100644 --- a/SteUp.Shared/Components/Pages/IspezioniPage.razor +++ b/SteUp.Shared/Components/Pages/IspezioniPage.razor @@ -5,8 +5,21 @@ @using SteUp.Shared.Core.Entities @using SteUp.Shared.Core.Interface.LocalDb @inject IIspezioniService IspezioniService +@inject IDialogService Dialog - + + + @* L'unica azione della schermata: porta dritta alla scelta del punto + vendita. Un passaggio intermedio per scegliere fra una cosa sola non + e' una scelta, e' un tocco in piu'. *@ + + +
@@ -32,8 +45,16 @@

Nessuna ispezione

- Tocca + nella barra in basso per aprire la prima ispezione su un punto vendita. + Apri la prima ispezione scegliendo il punto vendita da controllare.

+
+ + Nuova ispezione + +
} else @@ -65,6 +86,22 @@ StateHasChanged(); } + private async Task NewInspection() + { + if (await SteupDataService.CanOpenNewInspection()) + { + var modal = await Dialog.OpenSelectShop(); + // La scelta del punto vendita naviga da sola sull'ispezione; se + // l'utente torna indietro, la lista dev'essere quella aggiornata. + if (modal is { Canceled: true }) return; + } + else + { + await Dialog.ShowWarning( + "Per aprire una nuova ispezione devi prima concludere tutte le ispezioni aperte da piu' di 20 giorni."); + } + } + private void OnClickIspezione(Ispezione ispezione) { SteupDataService.InspectionPageState.Ispezione = ispezione; diff --git a/SteUp.Shared/Core/Messages/Ispezione/CompleteInspectionMessage.cs b/SteUp.Shared/Core/Messages/Ispezione/CompleteInspectionMessage.cs deleted file mode 100644 index 252c97d..0000000 --- a/SteUp.Shared/Core/Messages/Ispezione/CompleteInspectionMessage.cs +++ /dev/null @@ -1,5 +0,0 @@ -using CommunityToolkit.Mvvm.Messaging.Messages; - -namespace SteUp.Shared.Core.Messages.Ispezione; - -public class CompleteInspectionMessage(object? value = null) : ValueChangedMessage(value); \ No newline at end of file diff --git a/SteUp.Shared/Core/Messages/Ispezione/CompleteInspectionService.cs b/SteUp.Shared/Core/Messages/Ispezione/CompleteInspectionService.cs deleted file mode 100644 index d1a7b86..0000000 --- a/SteUp.Shared/Core/Messages/Ispezione/CompleteInspectionService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using CommunityToolkit.Mvvm.Messaging; - -namespace SteUp.Shared.Core.Messages.Ispezione; - -public class CompleteInspectionService -{ - public event Action? OnComplete; - - public CompleteInspectionService(IMessenger messenger) - { - messenger.Register(this, (_, _) => { OnComplete?.Invoke(); }); - } -} \ No newline at end of file diff --git a/SteUp.Shared/Core/Messages/Scheda/NewSchedaMessage.cs b/SteUp.Shared/Core/Messages/Scheda/NewSchedaMessage.cs deleted file mode 100644 index da592d4..0000000 --- a/SteUp.Shared/Core/Messages/Scheda/NewSchedaMessage.cs +++ /dev/null @@ -1,5 +0,0 @@ -using CommunityToolkit.Mvvm.Messaging.Messages; - -namespace SteUp.Shared.Core.Messages.Scheda; - -public class NewSchedaMessage(object? value = null) : ValueChangedMessage(value); \ No newline at end of file diff --git a/SteUp.Shared/Core/Messages/Scheda/NewSchedaService.cs b/SteUp.Shared/Core/Messages/Scheda/NewSchedaService.cs deleted file mode 100644 index e9c9773..0000000 --- a/SteUp.Shared/Core/Messages/Scheda/NewSchedaService.cs +++ /dev/null @@ -1,13 +0,0 @@ -using CommunityToolkit.Mvvm.Messaging; - -namespace SteUp.Shared.Core.Messages.Scheda; - -public class NewSchedaService -{ - public event Action? OnNewScheda; - - public NewSchedaService(IMessenger messenger) - { - messenger.Register(this, (_, _) => { OnNewScheda?.Invoke(); }); - } -} \ No newline at end of file diff --git a/SteUp.Shared/wwwroot/css/components.css b/SteUp.Shared/wwwroot/css/components.css index 1a260a3..bd09060 100644 --- a/SteUp.Shared/wwwroot/css/components.css +++ b/SteUp.Shared/wwwroot/css/components.css @@ -565,39 +565,18 @@ .navbar__label { font-size: var(--label-md-size); line-height: 1.2; - font-weight: 600; + font-weight: 500; } /* Icona piena e pastiglia di selezione sulla voce attiva. */ .navbar__item.active { color: var(--primary-dark); } +.navbar__item.active .navbar__label { font-weight: 600; } .navbar__item.active .navbar__icon { background-color: var(--info-surface); } .navbar__item:active .navbar__icon { transform: scale(0.94); } -/* Azione primaria della schermata, dentro la barra e a portata di pollice. */ -.navbar__fab { - flex: 0 0 auto; - display: flex; - align-items: center; - justify-content: center; - width: 56px; - height: 44px; - margin: 12px var(--space-sm) 12px 0; - border: none; - border-radius: var(--radius-control); - background-color: var(--primary); - color: var(--on-primary); - font-size: 26px; - transition: transform var(--duration-fast) var(--ease-out), - background-color var(--duration-fast) var(--ease-out); -} - -.navbar__fab:active { transform: scale(0.94); background-color: var(--primary-pressed); } -.navbar__fab:disabled { background-color: var(--divider); color: var(--ink-muted); } - @media (prefers-reduced-motion: reduce) { - .navbar__item:active .navbar__icon, - .navbar__fab:active { transform: none; } + .navbar__item:active .navbar__icon { transform: none; } } /* Da 720px la navigazione diventa una colonna laterale (rail). */ @@ -617,13 +596,6 @@ .navbar__item { flex: 0 0 auto; padding-block: var(--space-xs); } - .navbar__fab { - order: -1; - width: 56px; - height: 48px; - margin: var(--space-xs) auto var(--space-sm); - } - .app-shell { padding-left: calc(96px + var(--space-lg)); } .page-body { padding-bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px)); } } @@ -1123,3 +1095,93 @@ .skeleton--line { height: 15px; width: 100%; } .skeleton--line-short { height: 15px; width: 40%; } .skeleton--badge { height: 24px; width: 110px; border-radius: var(--radius-pill); } + +/* ----------------------------------------------------------------------------- + Azioni della testata + L'azione primaria di una schermata sta qui, con il suo nome: dice cosa fa + prima di essere toccata, e resta la stessa cosa in ogni pagina. Il vecchio + "+" dentro la barra di navigazione cambiava significato da una schermata + all'altra e non lo dichiarava mai. + -------------------------------------------------------------------------- */ + +.appbar__action { + display: inline-flex; + align-items: center; + gap: var(--space-6); + min-height: 40px; + padding: 0 var(--space-14) 0 var(--space-sm); + border: none; + border-radius: var(--radius-pill); + background-color: var(--primary); + color: var(--on-primary); + font-family: inherit; + font-size: var(--label-lg-size); + font-weight: 600; + white-space: nowrap; + transition: transform var(--duration-fast) var(--ease-out), + background-color var(--duration-fast) var(--ease-out); +} + +.appbar__action i { font-size: 20px; } + +.appbar__action:active { transform: scale(0.96); background-color: var(--primary-pressed); } +.appbar__action:disabled { background-color: var(--divider); color: var(--ink-muted); } + +/* Variante a sola icona, per le schermate dove le azioni sono piu' d'una e + vivono in un bottom sheet. */ +.appbar__icon-action { + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--touch-min); + height: var(--touch-min); + border: none; + border-radius: var(--radius-pill); + background: none; + color: var(--ink); + font-size: 22px; + transition: background-color var(--duration-fast) var(--ease-out); +} + +.appbar__icon-action:active { background-color: var(--surface-muted); } + +@media (prefers-reduced-motion: reduce) { + .appbar__action:active { transform: none; } +} + +/* ----------------------------------------------------------------------------- + Riga di scelta di un punto vendita + E' una card, ma si comporta da riga: testo a sinistra, chevron a destra. + -------------------------------------------------------------------------- */ + +.shop-card { + display: flex; + align-items: center; + gap: var(--space-sm); + width: 100%; + border: none; + font-family: inherit; + text-align: left; +} + +.shop-card__text { + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; +} + +.shop-card__chevron { + flex: 0 0 auto; + font-size: 22px; + color: var(--ink-muted); +} + +/* L'indirizzo serve a distinguere due negozi con lo stesso nome, non a essere + letto per intero: due righe bastano e tengono l'elenco scorrevole. */ +.shop-card .list-card__meta { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +}