Rifacimento della UI secondo STILE_UI.md
Adotta lo stile condiviso delle app Integry: superfici piatte, raggi ampi, spaziature a scala fissa, gerarchia data dallo spazio prima che dalla decorazione. La palette storica di SteUP resta intatta; sono stati aggiunti solo i ruoli che mancavano (workspace, surfaceMuted, divider, inkMuted e le triplette dei toni semantici). Fondamenta - tokens.css: ruoli colore, scale di spazio/raggi/tipografia, curve di movimento e scala z-index semantica. Unica fonte di verita': pilotano anche le variabili CSS di MudBlazor. - Inter variabile ospitata in locale (133KB) al posto di Nunito da Google Fonts: l'app lavora offline, un font che arriva dalla rete in campo non arriva. - base.css: shell applicativa e comportamenti da app nativa — niente selezione testo, niente menu al tocco lungo, niente lampo grigio sul tap, niente rimbalzo elastico, niente zoom a doppio tocco, scroll con inerzia. - mudblazor-overrides.css: MudBlazor appiattito (elevation 0, nessun tint, target 44/48, etichette in caso naturale). - Rimossi Bootstrap (CSS, icone e JS) e i 996 righe di CSS con ambito per componente del vecchio sistema: erano tutti inutilizzati. Struttura - Navigazione flottante in basso con icona piena sulla voce attiva; da 720px diventa colonna laterale. Il MudFabMenu e' sostituito da un bottom sheet: stessa funzione, bersagli molto piu' grandi. - Testata alta col titolo a sinistra, fusa con la pagina. - Nel form scheda il salvataggio sta in una barra ancorata in fondo, non in coda a uno scroll lungo. - Scelta di punto vendita e articoli a schermo intero con ricerca; la selezione multipla usa righe da 56px invece di un menu a tendina. - Urgenza scelta a pillole invece che da una select. Stato e riscontro - StatusBadgeHelper: tono, icona ed etichetta di ogni stato in un punto solo, cosi' lo stesso stato si presenta identico ovunque. - Scheletri al posto degli spinner; l'overlay bloccante dice sempre cosa sta facendo; gli stati vuoti suggeriscono la mossa successiva. - Rimosso il ritardo artificiale di 250ms in InspectionCard, che non attendeva nulla. DESIGN.md riscritto sul nuovo sistema. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,67 +1,56 @@
|
||||
@inject IJSRuntime Js
|
||||
|
||||
<div class="@(Back ? "" : "container") header">
|
||||
<div class="header-content @(Back ? "with-back" : "no-back")">
|
||||
@if (!SmallHeader)
|
||||
{
|
||||
if (Back)
|
||||
{
|
||||
<div class="left-section">
|
||||
<MudButton StartIcon="@(!Cancel ? Icons.Material.Outlined.ArrowBackIosNew : "")"
|
||||
OnClick="@GoBack"
|
||||
Color="Color.Info"
|
||||
Style="text-transform: none"
|
||||
Variant="Variant.Text">
|
||||
@BackTo
|
||||
</MudButton>
|
||||
</div>
|
||||
}
|
||||
@*
|
||||
Testata di schermata (STILE_UI §5): alta, titolo a sinistra, sfondo uguale
|
||||
alla pagina. Non e' una AppBar colorata col titolo centrato: quella e' una
|
||||
firma Android di serie, e il documento la esclude esplicitamente.
|
||||
*@
|
||||
|
||||
<h3 class="page-title">@Title</h3>
|
||||
<header class="appbar @(Compact ? "appbar--compact" : "")">
|
||||
@if (Back || Cancel)
|
||||
{
|
||||
<div class="appbar__lead">
|
||||
<MudIconButton Icon="@(Cancel ? Icons.Material.Rounded.Close : Icons.Material.Rounded.ArrowBack)"
|
||||
Color="Color.Inherit"
|
||||
aria-label="@(Cancel ? "Chiudi" : "Indietro")"
|
||||
title="@(Cancel ? "Chiudi" : "Indietro")"
|
||||
OnClick="@GoBack"/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="right-section">
|
||||
@if (!LabelSave.IsNullOrEmpty())
|
||||
{
|
||||
<MudButton OnClick="@OnSave"
|
||||
Color="Color.Info"
|
||||
Style="text-transform: none"
|
||||
Variant="Variant.Text">
|
||||
@LabelSave
|
||||
</MudButton>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
<div class="appbar__titles">
|
||||
<h1 class="appbar__title">@Title</h1>
|
||||
@if (!string.IsNullOrWhiteSpace(Subtitle))
|
||||
{
|
||||
<div class="title">
|
||||
<MudText Typo="Typo.h6">
|
||||
<b>@Title</b>
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Rounded.Close" OnClick="@GoBack" />
|
||||
</div>
|
||||
<p class="appbar__subtitle">@Subtitle</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code{
|
||||
[Parameter] public string? Title { get; set; }
|
||||
[Parameter] public bool Back { get; set; }
|
||||
[Parameter] public string BackTo { get; set; } = "";
|
||||
|
||||
[Parameter] public EventCallback OnFilterToggle { get; set; }
|
||||
|
||||
[Parameter] public bool Cancel { get; set; }
|
||||
[Parameter] public EventCallback OnCancel { get; set; }
|
||||
[Parameter] public string? LabelSave { get; set; }
|
||||
[Parameter] public EventCallback OnSave { get; set; }
|
||||
|
||||
[Parameter] public bool SmallHeader { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
@if (Actions is not null)
|
||||
{
|
||||
Back = !Back ? !Back && Cancel : Back;
|
||||
BackTo = Cancel ? "Annulla" : BackTo;
|
||||
<div class="appbar__actions">
|
||||
@Actions
|
||||
</div>
|
||||
}
|
||||
</header>
|
||||
|
||||
@code {
|
||||
[Parameter] public string? Title { get; set; }
|
||||
[Parameter] public string? Subtitle { get; set; }
|
||||
|
||||
/// Mostra la freccia indietro (navigazione tra schermate).
|
||||
[Parameter] public bool Back { get; set; }
|
||||
|
||||
/// Mostra la X di chiusura e delega a OnCancel (schermate modali).
|
||||
[Parameter] public bool Cancel { get; set; }
|
||||
|
||||
[Parameter] public EventCallback OnCancel { get; set; }
|
||||
|
||||
/// Testata ridotta per le schermate a compito singolo, dove il titolo
|
||||
/// non deve rubare spazio al contenuto.
|
||||
[Parameter] public bool Compact { get; set; }
|
||||
|
||||
[Parameter] public RenderFragment? Actions { get; set; }
|
||||
|
||||
private async Task GoBack()
|
||||
{
|
||||
@@ -73,5 +62,4 @@
|
||||
|
||||
await Js.InvokeVoidAsync("goBack");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.header {
|
||||
min-height: var(--mh-header);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
width: 100%;
|
||||
line-height: normal;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-content > .title { width: 100%; }
|
||||
|
||||
.header-content.with-back .page-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin: 0;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.left-section ::deep button, .right-section ::deep button { font-size: 1.1rem; }
|
||||
|
||||
.left-section ::deep .mud-button-icon-start { margin-right: 3px !important; }
|
||||
|
||||
.header-content.no-back .page-title { margin: 0; }
|
||||
@@ -3,30 +3,24 @@
|
||||
@using SteUp.Shared.Core.Interface.IntegryApi
|
||||
@using SteUp.Shared.Core.Interface.System.Network
|
||||
@inherits LayoutComponentBase
|
||||
@implements IDisposable
|
||||
@inject INetworkService NetworkService
|
||||
@inject IIntegryApiService IntegryApiService
|
||||
|
||||
<MudThemeProvider Theme="_currentTheme" @ref="@_mudThemeProvider" @bind-IsDarkMode="@IsDarkMode" />
|
||||
<MudThemeProvider Theme="_theme" IsDarkMode="false"/>
|
||||
<MudPopoverProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudDialogProvider CloseButton="false" NoHeader="true" BackdropClick="false"/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
<ConnectionState IsNetworkAvailable="IsNetworkAvailable" ServicesIsDown="ServicesIsDown" ShowWarning="ShowWarning" />
|
||||
<ConnectionState IsNetworkAvailable="IsNetworkAvailable" ServicesIsDown="ServicesIsDown" ShowWarning="ShowWarning"/>
|
||||
|
||||
<div class="page">
|
||||
<NavMenu/>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
<div class="app-shell">
|
||||
@Body
|
||||
</div>
|
||||
|
||||
<NavMenu/>
|
||||
|
||||
@code {
|
||||
private MudThemeProvider _mudThemeProvider = null!;
|
||||
private bool IsDarkMode { get; set; }
|
||||
private string _mainContentClass = "";
|
||||
|
||||
//Connection state
|
||||
private bool _isNetworkAvailable;
|
||||
@@ -71,58 +65,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MudTheme _currentTheme = new()
|
||||
// La palette resta quella di SteUP. Forma, ritmo e tipografia arrivano dai
|
||||
// token CSS (tokens.css), che pilotano anche le variabili di MudBlazor:
|
||||
// qui si dichiara solo il minimo che Mud deve conoscere lato C#.
|
||||
private readonly MudTheme _theme = new()
|
||||
{
|
||||
PaletteLight = new PaletteLight()
|
||||
PaletteLight = new PaletteLight
|
||||
{
|
||||
Primary = "#ec4c41",
|
||||
Secondary = "#002339",
|
||||
Tertiary = "#dff2ff",
|
||||
TextPrimary = "#000"
|
||||
Success = "#26b050",
|
||||
Error = "#e50000",
|
||||
Warning = "#b26a00",
|
||||
Info = "#1877d8",
|
||||
Background = "#e9edf2",
|
||||
Surface = "#ffffff",
|
||||
TextPrimary = "#000000",
|
||||
TextSecondary = "#465563",
|
||||
LinesDefault = "#d9e0e8",
|
||||
LinesInputs = "#d9e0e8",
|
||||
Divider = "#d9e0e8"
|
||||
},
|
||||
PaletteDark = new PaletteDark
|
||||
LayoutProperties = new LayoutProperties
|
||||
{
|
||||
Primary = "#ec4c41",
|
||||
Secondary = "#002339",
|
||||
Tertiary = "#dff2ff",
|
||||
Surface = "#000406",
|
||||
Background = "#000406",
|
||||
TextPrimary = "#fff",
|
||||
GrayDark = "#E0E0E0"
|
||||
DefaultBorderRadius = "16px"
|
||||
}
|
||||
};
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// if (firstRender)
|
||||
// {
|
||||
// var isDarkMode = LocalStorage.GetString("isDarkMode");
|
||||
|
||||
// if (isDarkMode == null && _mudThemeProvider != null)
|
||||
// {
|
||||
// IsDarkMode = await _mudThemeProvider.GetSystemPreference();
|
||||
// await _mudThemeProvider.WatchSystemPreference(OnSystemPreferenceChanged);
|
||||
// LocalStorage.SetString("isDarkMode", IsDarkMode.ToString());
|
||||
// StateHasChanged();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// IsDarkMode = bool.Parse(isDarkMode!);
|
||||
// }
|
||||
|
||||
// if (IsDarkMode)
|
||||
// {
|
||||
// _mainContentClass += "is-dark";
|
||||
// StateHasChanged();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private async Task OnSystemPreferenceChanged(bool newValue)
|
||||
{
|
||||
IsDarkMode = newValue;
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_cts = new CancellationTokenSource();
|
||||
@@ -170,4 +140,4 @@
|
||||
_cts?.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@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
|
||||
@@ -9,154 +10,205 @@
|
||||
@inject IMessenger Messenger
|
||||
@inject IIspezioniService IspezioniService
|
||||
@inject IJSRuntime Js
|
||||
@implements IDisposable
|
||||
|
||||
<div
|
||||
class="container animated-navbar @(IsVisible ? "show-nav" : "hide-nav") @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
|
||||
<nav class="navbar @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
|
||||
<div class="container-navbar">
|
||||
<ul class="navbar-nav flex-row nav-justified align-items-center w-100 text-center">
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="ispezioni" Match="NavLinkMatch.All">
|
||||
<div class="d-flex flex-column">
|
||||
<i class="ri-file-list-3-line"></i>
|
||||
<span>Ispezioni</span>
|
||||
</div>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="user" Match="NavLinkMatch.All">
|
||||
<div class="d-flex flex-column">
|
||||
<i class="ri-user-line"></i>
|
||||
<span>Profilo</span>
|
||||
</div>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@*
|
||||
Navigazione flottante in basso (STILE_UI §6.15): il contenuto le scorre
|
||||
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).
|
||||
*@
|
||||
|
||||
@if (PlusVisible)
|
||||
<nav class="navbar @(IsVisible ? "" : "navbar--hidden")" aria-label="Navigazione principale">
|
||||
<NavLink class="navbar__item pressable" href="ispezioni" Match="NavLinkMatch.All">
|
||||
<span class="navbar__icon">
|
||||
<i class="@(IsOn("ispezioni") ? "ri-file-list-3-fill" : "ri-file-list-3-line")" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="navbar__label">Ispezioni</span>
|
||||
</NavLink>
|
||||
|
||||
<NavLink class="navbar__item pressable" href="user" Match="NavLinkMatch.All">
|
||||
<span class="navbar__icon">
|
||||
<i class="@(IsOn("user") ? "ri-user-fill" : "ri-user-line")" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="navbar__label">Profilo</span>
|
||||
</NavLink>
|
||||
|
||||
@if (ActionsVisible)
|
||||
{
|
||||
<button type="button" class="navbar__fab" @onclick="OpenActions"
|
||||
aria-label="Azioni" title="Azioni">
|
||||
<i class="ri-add-line" aria-hidden="true"></i>
|
||||
</button>
|
||||
}
|
||||
</nav>
|
||||
|
||||
<ActionSheet @bind-Open="_sheetOpen" Title="@SheetTitle" Subtitle="@SheetSubtitle">
|
||||
<div class="stack stack-xs">
|
||||
@if (OnInspectionPage)
|
||||
{
|
||||
<MudFabMenu ButtonClass="custom-plus-button" MenuClass="custom-menu-fab" OnClick="@OnOpenMenu"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
IconColor="Color.Primary" Color="Color.Surface" AlignItems="AlignItems.End"
|
||||
Size="Size.Medium" IconSize="Size.Medium">
|
||||
@if (SchedaVisible)
|
||||
{
|
||||
@if (!_isCompleted)
|
||||
{
|
||||
<MudFabMenuItem Disabled="!NetworkService.IsNetworkAvailable()" OnClick="@NewScheda"
|
||||
Label="Nuova scheda" Color="Color.Surface"/>
|
||||
}
|
||||
@if (!_isCompleted)
|
||||
{
|
||||
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
|
||||
@onclick="NewScheda">
|
||||
<span class="action-tile__icon action-tile__icon--primary">
|
||||
<i class="ri-add-line" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="action-tile__label">Nuova scheda</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
<MudFabMenuItem Disabled="!NetworkService.IsNetworkAvailable()" OnClick="@DeleteInspection"
|
||||
Label="Cancella ispezione" Color="Color.Surface"/>
|
||||
@if (ShowCompleteInspection)
|
||||
{
|
||||
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
|
||||
@onclick="CompleteInspection">
|
||||
<span class="action-tile__icon action-tile__icon--success">
|
||||
<i class="ri-check-double-line" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="action-tile__label">Concludi ispezione</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
if (ShowCompleteInspection)
|
||||
{
|
||||
<MudFabMenuItem Disabled="!NetworkService.IsNetworkAvailable()" OnClick="@CompleteInspection"
|
||||
Label="Concludi ispezione" Color="Color.Surface"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudFabMenuItem Disabled="!NetworkService.IsNetworkAvailable()" OnClick="@NewInspection"
|
||||
Label="Nuova ispezione" Color="Color.Surface"/>
|
||||
}
|
||||
</MudFabMenu>
|
||||
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
|
||||
@onclick="DeleteInspection">
|
||||
<span class="action-tile__icon action-tile__icon--danger">
|
||||
<i class="ri-delete-bin-line" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="action-tile__label">Elimina ispezione</span>
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="action-tile" disabled="@(!NetworkService.IsNetworkAvailable())"
|
||||
@onclick="NewInspection">
|
||||
<span class="action-tile__icon action-tile__icon--primary">
|
||||
<i class="ri-add-line" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="action-tile__label">Nuova ispezione</span>
|
||||
</button>
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<ConfirmMessageBox @ref="_messageBox" NoText="No" YesText="Si" YesColor="Color.Primary"
|
||||
Message="Completando l'ispezione non sarà più possibile editarla o aggiungere ulteriori segnalazioni. Procedere?"/>
|
||||
@if (!NetworkService.IsNetworkAvailable())
|
||||
{
|
||||
<div class="feedback feedback--warning" role="status">
|
||||
<i class="feedback__icon ri-wifi-off-line" aria-hidden="true"></i>
|
||||
<span>Senza connessione queste azioni non sono disponibili. Il lavoro gia' fatto resta sul dispositivo.</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ActionSheet>
|
||||
|
||||
<ConfirmMessageBox @ref="_messageBox" NoText="Annulla" YesText="Concludi" YesColor="Color.Primary"
|
||||
Message="Una volta conclusa, l'ispezione non sara' piu' modificabile e non potrai aggiungere altre schede. Procedere?"/>
|
||||
|
||||
@code
|
||||
{
|
||||
private bool IsVisible { get; set; } = true;
|
||||
private bool PlusVisible { get; set; } = true;
|
||||
private bool ActionsVisible { get; set; } = true;
|
||||
private bool OnInspectionPage { get; set; }
|
||||
private bool ShowCompleteInspection { get; set; }
|
||||
private bool SchedaVisible { 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()
|
||||
{
|
||||
NavigationManager.LocationChanged += (_, args) =>
|
||||
{
|
||||
var location = args.Location.Remove(0, NavigationManager.BaseUri.Length);
|
||||
|
||||
var newIsVisible = new List<string> { "ispezioni", "ispezione", "user" }
|
||||
.Contains(location);
|
||||
|
||||
var newPlusVisible = new List<string> { "ispezioni", "ispezione", "user" }
|
||||
.Contains(location);
|
||||
|
||||
if (IsVisible == newIsVisible && PlusVisible == newPlusVisible) return;
|
||||
|
||||
IsVisible = newIsVisible;
|
||||
PlusVisible = newPlusVisible;
|
||||
|
||||
if (location.EqualsIgnoreCase("ispezione"))
|
||||
{
|
||||
var ispezione = SteupDataService.InspectionPageState.Ispezione;
|
||||
if (ispezione.Stato == StatusEnum.Completata) PlusVisible = false;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
};
|
||||
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 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;
|
||||
}
|
||||
|
||||
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 è necessario concludere prima tutte le ispezioni aperte più vecchie di 20 giorni!");
|
||||
await Dialog.ShowWarning(
|
||||
"Per aprire una nuova ispezione devi prima concludere tutte le ispezioni aperte da piu' di 20 giorni.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CompleteInspection()
|
||||
{
|
||||
var result = await _messageBox.ShowAsync();
|
||||
_sheetOpen = false;
|
||||
|
||||
if (result is true)
|
||||
Messenger.Send(new CompleteInspectionMessage());
|
||||
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 void OnOpenMenu()
|
||||
{
|
||||
var location = NavigationManager.Uri.Remove(0, NavigationManager.BaseUri.Length);
|
||||
|
||||
SchedaVisible = new List<string> { "ispezione" }.Contains(location);
|
||||
|
||||
var ispezione = SteupDataService.InspectionPageState.Ispezione;
|
||||
_isCompleted = ispezione.Stato == StatusEnum.Completata;
|
||||
// Concludi non ha senso su un'ispezione gia completata
|
||||
ShowCompleteInspection = !ispezione.ActivityId.IsNullOrEmpty() && !_isCompleted;
|
||||
StateHasChanged();
|
||||
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)
|
||||
@@ -164,4 +216,6 @@
|
||||
await Dialog.ShowError(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() => NavigationManager.LocationChanged -= OnLocationChanged;
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
.animated-navbar {
|
||||
background: transparent;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 1001;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.animated-navbar.show-nav {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.animated-navbar.hide-nav {
|
||||
transform: translateY(150%);
|
||||
}
|
||||
|
||||
.animated-navbar.with-plus {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
padding-bottom: 1rem;
|
||||
padding-top: 0 !important;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.navbar.with-plus {
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.navbar.without-plus {
|
||||
transform: translateX(0);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container-navbar {
|
||||
background: var(--mud-palette-surface);
|
||||
border-radius: 50px;
|
||||
padding: 0 10px;
|
||||
box-shadow: var(--custom-box-shadow);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.nav-item.plus-button {
|
||||
position: relative;
|
||||
bottom: 15px;
|
||||
}
|
||||
|
||||
.navbar ::deep .custom-plus-button {
|
||||
background: var(--mud-palette-surface);
|
||||
}
|
||||
|
||||
.navbar ::deep .custom-menu-fab .mud-fab-label {
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.navbar ::deep .custom-menu-fab .mud-button-root.mud-fab {
|
||||
background: var(--mud-palette-surface);
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.2;
|
||||
justify-content: center;
|
||||
padding-top: .25rem !important;
|
||||
padding-bottom: .25rem !important;
|
||||
}
|
||||
|
||||
.nav-item ::deep a > div {
|
||||
-webkit-transition: all .1s ease-out;
|
||||
transition: all .1s ease-out;
|
||||
min-width: 75px;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active > div {
|
||||
color: var(--mud-palette-primary);
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active > div > i {
|
||||
/*background-color: color-mix(in srgb, var(--mud-palette-primary) 20%, transparent);*/
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active > div > span {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:not(.active) > div {
|
||||
color: var(--mud-palette-text-primary);
|
||||
}
|
||||
|
||||
.nav-item ::deep a i {
|
||||
font-size: 1.65rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a span {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
.navbar {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,36 @@
|
||||
@using SteUp.Shared.Components.Layout.Spinner
|
||||
@*
|
||||
Overlay di operazione bloccante (invio al server, cancellazione).
|
||||
Mostra sempre cosa sta succedendo: un'attesa muta in campo, con la rete
|
||||
incerta, e' il momento in cui il rilevatore perde fiducia nello strumento.
|
||||
*@
|
||||
|
||||
<MudOverlay Visible="VisibleOverlay" LightBackground="true">
|
||||
@if (SuccessAnimation)
|
||||
{
|
||||
<div class="overlay-content">
|
||||
<div class="success-checkmark">
|
||||
<div class="check-icon">
|
||||
<span class="icon-line line-tip"></span>
|
||||
<span class="icon-line line-long"></span>
|
||||
<div class="icon-circle"></div>
|
||||
<div class="icon-fix"></div>
|
||||
</div>
|
||||
@if (VisibleOverlay)
|
||||
{
|
||||
<div class="overlay" role="status" aria-live="polite">
|
||||
@if (SuccessAnimation)
|
||||
{
|
||||
<div class="checkmark">
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true">
|
||||
<path d="M6 17 L13 24 L26 9"/>
|
||||
</svg>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Message))
|
||||
{
|
||||
<p class="overlay-message">@Message</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="overlay-content">
|
||||
<SpinnerLayout/>
|
||||
@if (!string.IsNullOrWhiteSpace(Message))
|
||||
{
|
||||
<p class="overlay-message">@Message</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</MudOverlay>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="spinner spinner--lg" aria-hidden="true"></span>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Message))
|
||||
{
|
||||
<p class="overlay__message">@Message</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public required bool SuccessAnimation { get; set; }
|
||||
[Parameter] public bool SuccessAnimation { get; set; }
|
||||
[Parameter] public required bool VisibleOverlay { get; set; }
|
||||
|
||||
/// Testo opzionale mostrato sotto lo spinner/spunta (es. "Invio scheda 3 di 7…").
|
||||
/// Testo mostrato sotto lo spinner (es. "Invio scheda 3 di 7…").
|
||||
[Parameter] public string? Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
.overlay-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
padding: 0 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.overlay-message {
|
||||
margin: 0;
|
||||
max-width: 30ch;
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.4;
|
||||
/* Inchiostro su fondo chiaro dell'overlay: leggibile anche al sole */
|
||||
color: var(--mud-palette-text-primary);
|
||||
}
|
||||
|
||||
.success-checkmark {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Reduced motion: mostra la spunta di successo statica, senza coreografia */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.success-checkmark .check-icon::after,
|
||||
.icon-line.line-tip,
|
||||
.icon-line.line-long {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.success-checkmark .check-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
box-sizing: content-box;
|
||||
border: 4px solid var(--mud-palette-success);
|
||||
}
|
||||
|
||||
.success-checkmark .check-icon::before {
|
||||
top: 3px;
|
||||
left: -2px;
|
||||
width: 30px;
|
||||
transform-origin: 100% 50%;
|
||||
border-radius: 100px 0 0 100px;
|
||||
}
|
||||
|
||||
.success-checkmark .check-icon::after {
|
||||
top: 0;
|
||||
left: 30px;
|
||||
width: 60px;
|
||||
transform-origin: 0 50%;
|
||||
border-radius: 0 100px 100px 0;
|
||||
animation: rotate-circle 4.25s ease-in;
|
||||
}
|
||||
|
||||
.success-checkmark .check-icon::before,
|
||||
.success-checkmark .check-icon::after {
|
||||
content: '';
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.icon-line {
|
||||
height: 5px;
|
||||
background-color: var(--mud-palette-success);
|
||||
display: block;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.icon-line.line-tip {
|
||||
top: 46px;
|
||||
left: 14px;
|
||||
width: 25px;
|
||||
transform: rotate(45deg);
|
||||
animation: icon-line-tip 0.75s;
|
||||
}
|
||||
|
||||
.icon-line.line-long {
|
||||
top: 38px;
|
||||
right: 8px;
|
||||
width: 47px;
|
||||
transform: rotate(-45deg);
|
||||
animation: icon-line-long 0.75s;
|
||||
}
|
||||
|
||||
|
||||
.icon-circle {
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
z-index: 10;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
border: 4px solid var(--mud-palette-success);
|
||||
}
|
||||
|
||||
.icon-fix {
|
||||
top: 8px;
|
||||
width: 5px;
|
||||
left: 26px;
|
||||
z-index: 1;
|
||||
height: 85px;
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
@keyframes rotate-circle {
|
||||
0% { transform: rotate(-45deg); }
|
||||
|
||||
5% { transform: rotate(-45deg); }
|
||||
|
||||
12% { transform: rotate(-405deg); }
|
||||
|
||||
100% { transform: rotate(-405deg); }
|
||||
}
|
||||
|
||||
@keyframes icon-line-tip {
|
||||
0% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px;
|
||||
}
|
||||
|
||||
54% {
|
||||
width: 0;
|
||||
left: 1px;
|
||||
top: 19px;
|
||||
}
|
||||
|
||||
70% {
|
||||
width: 50px;
|
||||
left: -8px;
|
||||
top: 37px;
|
||||
}
|
||||
|
||||
84% {
|
||||
width: 17px;
|
||||
left: 21px;
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 25px;
|
||||
left: 14px;
|
||||
top: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes icon-line-long {
|
||||
0% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px;
|
||||
}
|
||||
|
||||
65% {
|
||||
width: 0;
|
||||
right: 46px;
|
||||
top: 54px;
|
||||
}
|
||||
|
||||
84% {
|
||||
width: 55px;
|
||||
right: 0px;
|
||||
top: 35px;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 47px;
|
||||
right: 8px;
|
||||
top: 38px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
@*
|
||||
Bottom sheet (STILE_UI §6.14): sale dal basso, maniglia di trascinamento,
|
||||
testata con titolo e chiusura, un'unica linea di struttura, corpo scorrevole.
|
||||
Sostituisce i menu flottanti: stessa funzione, bersagli molto piu' grandi.
|
||||
*@
|
||||
|
||||
@if (Open)
|
||||
{
|
||||
<div class="sheet-scrim" @onclick="Close" aria-hidden="true"></div>
|
||||
|
||||
<div class="sheet @(Tall ? "sheet--tall" : "")" role="dialog" aria-modal="true"
|
||||
aria-label="@Title">
|
||||
<div class="sheet__handle"></div>
|
||||
|
||||
<div class="sheet__head">
|
||||
<div class="sheet__titles">
|
||||
<h2 class="sheet__title">@Title</h2>
|
||||
@if (!string.IsNullOrWhiteSpace(Subtitle))
|
||||
{
|
||||
<p class="sheet__subtitle">@Subtitle</p>
|
||||
}
|
||||
</div>
|
||||
<MudIconButton Icon="@Icons.Material.Rounded.Close" Color="Color.Inherit"
|
||||
aria-label="Chiudi" title="Chiudi" OnClick="Close"/>
|
||||
</div>
|
||||
|
||||
<div class="sheet__divider"></div>
|
||||
|
||||
<div class="sheet__body">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public bool Open { get; set; }
|
||||
[Parameter] public EventCallback<bool> OpenChanged { get; set; }
|
||||
[Parameter] public string? Title { get; set; }
|
||||
[Parameter] public string? Subtitle { get; set; }
|
||||
|
||||
/// Sheet di compilazione (~92% dello schermo) invece che di consultazione.
|
||||
[Parameter] public bool Tall { get; set; }
|
||||
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private async Task Close()
|
||||
{
|
||||
Open = false;
|
||||
await OpenChanged.InvokeAsync(false);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<MudOverlay Visible="Visible" DarkBackground="false">
|
||||
<div class="overlay-container">
|
||||
<span>@(Text ?? "Caricamento")</span>
|
||||
|
||||
<MudProgressLinear Color="Color.Primary" Rounded="true" Size="Size.Medium" Indeterminate="true" />
|
||||
</div>
|
||||
</MudOverlay>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public bool Visible { get; set; }
|
||||
[Parameter] public string? Text { get; set; }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
.overlay-container {
|
||||
background: var(--mud-palette-background);
|
||||
width: 20rem;
|
||||
height: 6rem;
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border-radius: 20px;
|
||||
box-shadow: var(--custom-box-shadow);
|
||||
}
|
||||
|
||||
.overlay-container > span {
|
||||
text-align: center;
|
||||
font-size: medium;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
<div class="spinner-container @(FullScreen ? "" : "not-fullScreen")">
|
||||
<span class="loader"></span>
|
||||
@* Indicatore di attesa. A schermo pieno solo quando non c'e' ancora nulla da
|
||||
mostrare; dentro una sezione resta contenuto e non fa saltare il layout. *@
|
||||
|
||||
<div style="display:flex; align-items:center; justify-content:center; @(FullScreen ? "height:100dvh;" : "padding: var(--space-xl) 0;")">
|
||||
<span class="spinner @(FullScreen ? "spinner--lg" : "")" role="status" aria-label="Caricamento"></span>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public bool FullScreen { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: calc(100vh - 10.1rem);
|
||||
align-items: center;
|
||||
color: var(--mud-palette-primary);
|
||||
}
|
||||
|
||||
.not-fullScreen {
|
||||
height: auto !important;
|
||||
padding: 2rem 0 !important;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 50px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
border: 8px solid #0000;
|
||||
border-right-color: var(--mud-palette-primary);
|
||||
position: relative;
|
||||
animation: l24 1s infinite linear;
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -8px;
|
||||
border-radius: 50%;
|
||||
border: inherit;
|
||||
animation: inherit;
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
animation-duration: 4s;
|
||||
}
|
||||
|
||||
@keyframes l24 {
|
||||
100% {
|
||||
transform: rotate(1turn)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user