Aggiunta selezione negozio

This commit is contained in:
2026-02-12 10:43:16 +01:00
parent d8bef12f30
commit 5afe4f4745
22 changed files with 397 additions and 2 deletions

View File

@@ -2,6 +2,8 @@
using SteUp.Maui.Core.Services; using SteUp.Maui.Core.Services;
using SteUp.Maui.Core.System; using SteUp.Maui.Core.System;
using SteUp.Maui.Core.System.Network; using SteUp.Maui.Core.System.Network;
using SteUp.Shared.Core.Data;
using SteUp.Shared.Core.Data.Contracts;
using SteUp.Shared.Core.Interface; using SteUp.Shared.Core.Interface;
using SteUp.Shared.Core.Interface.IntegryApi; using SteUp.Shared.Core.Interface.IntegryApi;
using SteUp.Shared.Core.Interface.System; using SteUp.Shared.Core.Interface.System;
@@ -16,11 +18,14 @@ public static class CoreModule
{ {
builder.Services.AddSingleton<IFormFactor, FormFactor>(); builder.Services.AddSingleton<IFormFactor, FormFactor>();
builder.Services.AddSingleton<IGenericSystemService, GenericSystemService>(); builder.Services.AddSingleton<IGenericSystemService, GenericSystemService>();
builder.Services.AddScoped<ISteupDataService, SteupDataService>();
} }
public static void RegisterIntegryServices(this MauiAppBuilder builder) public static void RegisterIntegryServices(this MauiAppBuilder builder)
{ {
builder.Services.AddScoped<IIntegryApiService, IntegryApiService>(); builder.Services.AddScoped<IIntegryApiService, IntegryApiService>();
builder.Services.AddScoped<IIntegrySteupService, IntegrySteupService>();
} }
public static void RegisterSystemService(this MauiAppBuilder builder) public static void RegisterSystemService(this MauiAppBuilder builder)

View File

@@ -20,6 +20,7 @@
<link rel="stylesheet" href="_content/SteUp.Shared/css/app.css"/> <link rel="stylesheet" href="_content/SteUp.Shared/css/app.css"/>
<link rel="stylesheet" href="_content/SteUp.Shared/css/form.css"/> <link rel="stylesheet" href="_content/SteUp.Shared/css/form.css"/>
<link rel="stylesheet" href="_content/SteUp.Shared/css/default-theme.css"/> <link rel="stylesheet" href="_content/SteUp.Shared/css/default-theme.css"/>
<link rel="stylesheet" href="_content/SteUp.Shared/css/custom-mudBlazor.css"/>
<link rel="stylesheet" href="SteUp.Maui.styles.css"/> <link rel="stylesheet" href="SteUp.Maui.styles.css"/>
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
</head> </head>

View File

@@ -1,4 +1,5 @@
@using System.Globalization @using System.Globalization
@using SteUp.Shared.Components.SingleElements
@using SteUp.Shared.Core.Interface.IntegryApi @using SteUp.Shared.Core.Interface.IntegryApi
@using SteUp.Shared.Core.Interface.System.Network @using SteUp.Shared.Core.Interface.System.Network
@inherits LayoutComponentBase @inherits LayoutComponentBase
@@ -10,6 +11,8 @@
<MudDialogProvider/> <MudDialogProvider/>
<MudSnackbarProvider/> <MudSnackbarProvider/>
<ConnectionState IsNetworkAvailable="IsNetworkAvailable" ServicesIsDown="ServicesIsDown" ShowWarning="ShowWarning" />
<div class="page"> <div class="page">
<NavMenu/> <NavMenu/>

View File

@@ -1,5 +1,6 @@
@using SteUp.Shared.Core.Interface.System.Network @using SteUp.Shared.Core.Interface.System.Network
@inject INetworkService NetworkService @inject INetworkService NetworkService
@inject IDialogService Dialog
<div class="container animated-navbar @(IsVisible ? "show-nav" : "hide-nav") @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")"> <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")"> <nav class="navbar @(IsVisible ? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
@@ -69,5 +70,6 @@
private void NewActivity() private void NewActivity()
{ {
_ = ModalHelper.OpenSelectShop(Dialog);
} }
} }

View File

@@ -88,6 +88,7 @@ else
{ {
await UserAccountService.Login(UserData.Username, UserData.Password, UserData.CodHash); await UserAccountService.Login(UserData.Username, UserData.Password, UserData.CodHash);
AuthenticationStateProvider.NotifyAuthenticationState(); //Chiamato per forzare il refresh AuthenticationStateProvider.NotifyAuthenticationState(); //Chiamato per forzare il refresh
await SteupDataService.Init();
LocalStorage.SetString("codHash", UserData.CodHash); LocalStorage.SetString("codHash", UserData.CodHash);
NavigationManager.NavigateTo("/"); NavigationManager.NavigateTo("/");

View File

@@ -45,4 +45,9 @@
private ErrorBoundary? ErrorBoundary { get; set; } private ErrorBoundary? ErrorBoundary { get; set; }
private ExceptionModal ExceptionModal { get; set; } private ExceptionModal ExceptionModal { get; set; }
protected override async Task OnInitializedAsync()
{
await SteupDataService.Init();
}
} }

View File

@@ -0,0 +1,35 @@
@using SteUp.Shared.Core.Dto
<div class="shop-card ripple-container">
<div class="shop-body-section">
<div class="title-section">
<MudText Class="shop-title" Typo="Typo.subtitle1">
<b>@PuntoVendita.CodMdep</b> - @PuntoVendita.Descrizione
</MudText>
</div>
@if (!PuntoVendita.Indirizzo.IsNullOrEmpty())
{
<div class="subtitle-section">
<MudText Class="shop-title" Typo="Typo.subtitle1">
@PuntoVendita.Indirizzo
</MudText>
</div>
<div class="sub-info-section">
<MudChip T="string" Icon="@Icons.Material.Filled.LocationCity" Size="Size.Small" Color="Color.Default">
@PuntoVendita.Citta
</MudChip>
<MudChip T="string" Size="Size.Small" Color="Color.Default">
@PuntoVendita.Cap
</MudChip>
<MudChip T="string" Size="Size.Small" Color="Color.Default">
@PuntoVendita.Provincia
</MudChip>
</div>
}
</div>
</div>
@code {
[Parameter] public PuntoVenditaDto PuntoVendita { get; set; } = null!;
}

View File

@@ -0,0 +1,10 @@
.shop-card{
padding: .5rem 1rem;
background-color: var(--mud-palette-background-gray);
border-radius: 1em;
}
.sub-info-section{
display: flex;
justify-content: space-evenly;
}

View File

@@ -0,0 +1,26 @@
<div class="Connection @(ShowWarning ? "Show" : "Hide") @(IsNetworkAvailable? ServicesIsDown ? "ServicesIsDown" : "SystemOk" : "NetworkKo")">
@if (IsNetworkAvailable)
{
if(ServicesIsDown)
{
<i class="ri-cloud-off-fill"></i>
<span>Servizi offline</span>
}
else
{
<i class="ri-cloud-fill"></i>
<span>Online</span>
}
}
else
{
<i class="ri-wifi-off-line"></i>
<span>Nessuna connessione</span>
}
</div>
@code{
[Parameter] public bool IsNetworkAvailable { get; set; }
[Parameter] public bool ServicesIsDown { get; set; }
[Parameter] public bool ShowWarning { get; set; }
}

View File

@@ -0,0 +1,86 @@
@using SteUp.Shared.Components.SingleElements.Card
@using SteUp.Shared.Core.Dto
<MudDialog OnBackdropClick="Cancel">
<DialogContent>
<div class="select-shop-content">
<div class="shop-header">
<div class="shop-title">
<MudText Typo="Typo.h5"><b>Seleziona il negozio</b></MudText>
<MudIconButton Icon="@Icons.Material.Rounded.Close" Size="Size.Small" OnClick="@Cancel"/>
</div>
<div class="input-card clearButton">
<MudTextField T="string?" Placeholder="Cerca..." Variant="Variant.Text"
@bind-Value="FilterText" DebounceInterval="500"
OnDebounceIntervalElapsed="ApplyFilters"/>
</div>
</div>
<div class="shop-body">
@if (_afterRender)
{
if (FilteredList.IsNullOrEmpty())
{
<MudText Typo="Typo.body2">Nessun negozio trovato</MudText>
}
else
{
<Virtualize Items="FilteredList" Context="puntoVendita">
<ShopCard PuntoVendita="puntoVendita"/>
</Virtualize>
}
}
</div>
</div>
</DialogContent>
</MudDialog>
@code {
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!;
private List<PuntoVenditaDto>? FilteredList { get; set; }
private string? FilterText { get; set; }
private bool _afterRender;
private void Cancel() => MudDialog.Cancel();
protected override void OnInitialized()
{
_afterRender = false;
}
protected override void OnAfterRender(bool firstRender)
{
if (_afterRender) return;
_afterRender = true;
ApplyFilters();
}
private void ApplyFilters()
{
if (FilterText.IsNullOrEmpty())
{
FilteredList = SteupDataService.PuntiVenditaList;
StateHasChanged();
return;
}
FilteredList = SteupDataService.PuntiVenditaList.FindAll(x =>
(x.Indirizzo != null && x.Indirizzo.ContainsIgnoreCase(FilterText!)) ||
(x.Descrizione != null && x.Descrizione.ContainsIgnoreCase(FilterText!)) ||
(x.CodMdep != null && x.CodMdep.ContainsIgnoreCase(FilterText!)) ||
(x.Citta != null && x.Citta.ContainsIgnoreCase(FilterText!)) ||
(x.Cap != null && x.Cap.ContainsIgnoreCase(FilterText!)) ||
(x.Provincia != null && x.Provincia.ContainsIgnoreCase(FilterText!))
);
StateHasChanged();
}
}

View File

@@ -0,0 +1,18 @@
.shop-header{
display: flex;
flex-direction: column;
gap: .5rem;
}
.shop-title{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.shop-body{
display: flex;
flex-direction: column;
gap: 1.5rem;
}

View File

@@ -0,0 +1,10 @@
using SteUp.Shared.Core.Dto;
namespace SteUp.Shared.Core.Data.Contracts;
public interface ISteupDataService
{
Task Init();
List<PuntoVenditaDto> PuntiVenditaList { get; }
}

View File

@@ -0,0 +1,25 @@
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account;
using SteUp.Shared.Core.Data.Contracts;
using SteUp.Shared.Core.Dto;
using SteUp.Shared.Core.Interface.IntegryApi;
namespace SteUp.Shared.Core.Data;
public class SteupDataService(
IIntegrySteupService integrySteupService,
IUserSession userSession) : ISteupDataService
{
public Task Init()
{
return LoadDataAsync();
}
private async Task LoadDataAsync()
{
if (!await userSession.IsLoggedIn()) return;
PuntiVenditaList = await integrySteupService.RetrievePuntiVendita();
}
public List<PuntoVenditaDto> PuntiVenditaList { get; private set; } = [];
}

View File

@@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace SteUp.Shared.Core.Dto;
public class PuntoVenditaDto
{
[JsonPropertyName("codMdep")]
public string? CodMdep { get; set; }
[JsonPropertyName("descrizione")]
public string? Descrizione { get; set; }
[JsonPropertyName("indirizzo")]
public string? Indirizzo { get; set; }
[JsonPropertyName("cap")]
public string? Cap { get; set; }
[JsonPropertyName("citta")]
public string? Citta { get; set; }
[JsonPropertyName("provincia")]
public string? Provincia { get; set; }
}

View File

@@ -0,0 +1,24 @@
using MudBlazor;
using SteUp.Shared.Components.SingleElements.Modal;
namespace SteUp.Shared.Core.Helpers;
public class ModalHelper
{
public static async Task<DialogResult?> OpenSelectShop(IDialogService dialog)
{
var modal = await dialog.ShowAsync<ModalSelectShop>(
"ModalSelectShop",
new DialogParameters(),
new DialogOptions
{
FullScreen = false,
CloseButton = false,
NoHeader = true,
BackdropClick = true
}
);
return await modal.Result;
}
}

View File

@@ -0,0 +1,9 @@
using SteUp.Shared.Core.Dto;
namespace SteUp.Shared.Core.Interface.IntegryApi;
public interface IIntegrySteupService
{
//Retrieve
Task<List<PuntoVenditaDto>> RetrievePuntiVendita();
}

View File

@@ -0,0 +1,17 @@
using IntegryApiClient.Core.Domain.RestClient.Contacts;
using SteUp.Shared.Core.Dto;
using SteUp.Shared.Core.Interface.IntegryApi;
namespace SteUp.Shared.Core.Services;
public class IntegrySteupService(IIntegryApiRestClient integryApiRestClient) : IIntegrySteupService
{
private const string BaseRequest = "steup";
#region Retrieve
public Task<List<PuntoVenditaDto>> RetrievePuntiVendita() =>
integryApiRestClient.AuthorizedGet<List<PuntoVenditaDto>>($"{BaseRequest}/getPuntiVendita")!;
#endregion
}

View File

@@ -11,9 +11,12 @@
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Authorization
@using MudBlazor @using MudBlazor
@using SteUp.Shared.Core.Data.Contracts
@using SteUp.Shared.Core.Helpers
@using static InteractiveRenderSettings @using static InteractiveRenderSettings
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserSession UserSession @inject IUserSession UserSession
@inject ILocalStorage LocalStorage @inject ILocalStorage LocalStorage
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject ISteupDataService SteupDataService

View File

@@ -274,4 +274,32 @@ h1:focus { outline: none; }
.flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); }
.customDialog-form .mud-dialog-content { margin-top: env(safe-area-inset-top); } .customDialog-form .mud-dialog-content { margin-top: env(safe-area-inset-top); }
}
/*Ripple*/
.ripple-container {
position: relative;
overflow: hidden;
cursor: pointer;
transform: translateZ(0);
}
.ripple {
position: absolute;
border-radius: 50%;
transform: scale(0);
animation: ripple-effect 0.6s linear;
background-color: rgba(0, 0, 0, 0.2);
pointer-events: none;
}
@keyframes ripple-effect {
to {
transform: scale(4);
opacity: 0;
}
}
.ripple-white .ripple {
background-color: rgba(255, 255, 255, 0.5);
} }

View File

@@ -0,0 +1,25 @@
/*---Blazor custom---*/
.mud-snackbar {
border-radius: 1em !important;
}
.mud-paper {
border-radius: 1em !important;
}
.mud-dialog {
border-radius: 1em !important;
}
.mud-overlay.on-top {
z-index: 10001 !important;
}
.mud-popover.mud-popover-open {
border-radius: 1em !important;
}
.mud-picker-popover-paper {
border-radius: 1em !important;
}

View File

@@ -33,7 +33,6 @@
background: var(--mud-palette-background-gray); background: var(--mud-palette-background-gray);
border-radius: 9px; border-radius: 9px;
padding: .5rem 1rem; padding: .5rem 1rem;
margin-bottom: 1.5rem;
} }
.input-card.clearButton { .input-card.clearButton {

View File

@@ -45,3 +45,42 @@ document.addEventListener("DOMContentLoaded", () => {
} }
} }
}); });
(function () {
// Ascoltiamo l'evento 'mousedown' su tutto il documento
document.addEventListener('mousedown', function (event) {
// Cerca se l'elemento cliccato (o un suo genitore) ha la classe .ripple-container
const target = event.target.closest('.ripple-container');
if (target) {
createRipple(event, target);
}
});
function createRipple(event, element) {
const circle = document.createElement("span");
const diameter = Math.max(element.clientWidth, element.clientHeight);
const radius = diameter / 2;
const rect = element.getBoundingClientRect();
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - rect.left - radius}px`;
circle.style.top = `${event.clientY - rect.top - radius}px`;
circle.classList.add("ripple");
// Rimuovi l'elemento dal DOM alla fine dell'animazione per non intasare la memoria
const ripple = element.getElementsByClassName("ripple")[0];
if (ripple) {
ripple.remove();
}
element.appendChild(circle);
// Pulizia automatica dopo 600ms (durata animazione CSS)
setTimeout(() => {
circle.remove();
}, 600);
}
})();