Pagina profilo e modiche al theme

This commit is contained in:
2025-05-12 10:39:51 +02:00
parent eb1dc8daa2
commit bc3809b61c
21 changed files with 457 additions and 12 deletions

View File

@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
using MudBlazor.Services;
using Template.Maui.Services;
using Template.Shared;
using Template.Shared.Core.Interface;
using Template.Shared.Core.Services;
using Template.Shared.Interfaces;
@@ -34,6 +35,8 @@ namespace Template.Maui
builder.Services.AddScoped<AuthenticationStateProvider>(provider =>
provider.GetRequiredService<AppAuthenticationStateProvider>());
builder.Services.AddScoped<INetworkService, NetworkService>();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();

View File

@@ -0,0 +1,12 @@
using Template.Shared.Core.Interface;
namespace Template.Maui.Services;
public class NetworkService : INetworkService
{
public bool IsNetworkAvailable()
{
return Connectivity.Current.NetworkAccess == NetworkAccess.Internet;
}
}

View File

@@ -1,10 +1,14 @@
<div class="header">
<div class="header-content">
<h3 class="page-title">@Title</h3>
<MudIconButton Icon="@Icons.Material.Filled.FilterAlt" Color="Color.Dark" />
@if (ShowFilter)
{
<MudIconButton Icon="@Icons.Material.Filled.FilterAlt" Color="Color.Dark" />
}
</div>
</div>
@code{
[Parameter] public string? Title { get; set; }
[Parameter] public bool ShowFilter { get; set; }
}

View File

@@ -1,6 +1,6 @@
@inherits LayoutComponentBase
<MudThemeProvider />
<MudThemeProvider Theme="_currentTheme" />
<MudPopoverProvider />
<MudDialogProvider />
<MudSnackbarProvider />
@@ -15,3 +15,17 @@
</main>
</div>
@code {
private readonly MudTheme _currentTheme = new()
{
PaletteLight = new PaletteLight()
{
Primary = "#ABA9BF",
Secondary = "#BEB7DF",
Tertiary = "#D4F2D2",
Background = "#f6f6f8"
}
};
}

View File

@@ -1,5 +1,5 @@
.navbar {
background: var(--mud-palette-background-gray);
background: var(--mud-palette-surface);
position: fixed;
bottom: 0;
width: 100%;
@@ -12,7 +12,7 @@
.nav-item ::deep a {
display: flex;
align-items: center;
line-height: 1.4;
line-height: 1.2;
justify-content: center;
}
@@ -22,7 +22,7 @@
min-width: 60px;
}
.nav-item ::deep a.active > div { color: var(--mud-palette-primary); }
.nav-item ::deep a.active > div { color: var(--mud-palette-secondary-darken); }
.nav-item ::deep a.active > div > i {
/*background-color: color-mix(in srgb, var(--mud-palette-primary) 20%, transparent);*/

View File

@@ -2,7 +2,7 @@
@attribute [Authorize]
@using Template.Shared.Components.Layout
<HeaderLayout Title="Agenda" />
<HeaderLayout Title="Agenda" ShowFilter="true" />
<div class="content">
<MudButtonGroup Size="Size.Small" Color="Color.Surface" OverrideStyles="true" Variant="Variant.Filled">

View File

@@ -1,10 +1,23 @@
@page "/"
@using Template.Shared.Core.Interface
@using Template.Shared.Interfaces
@attribute [Authorize]
@inject IFormFactor FormFactor
@inject INetworkService NetworkService
@code
{
protected override Task OnInitializedAsync()
{
var lastSyncDate = DateOnly.FromDateTime(LocalStorage.Get<DateTime>("last-sync"));
if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate < DateOnly.FromDateTime(DateTime.Now))
{
//NavigationManager.NavigateTo("/sync");
NavigationManager.NavigateTo("/Calendar");
return base.OnInitializedAsync();
}
NavigationManager.NavigateTo("/Calendar");
return base.OnInitializedAsync();
}

View File

@@ -11,7 +11,7 @@
else
{
<div class="center-box container d-flex justify-content-center align-items-center min-vh-100">
<div class="row border rounded-4 bg-white shadow box-area">
<div class="row rounded-4 bg-white">
<div class="appName rounded-4 d-flex justify-content-center align-items-center flex-column">
<span>Nome App</span>
@@ -23,7 +23,7 @@ else
<MudTextField @bind-Value="UserData.Username" Label="Username" Variant="Variant.Text"/>
</div>
<div class="input-group mb-2">
<MudTextField @bind-Value="UserData.Password" Label="Password" Variant="Variant.Text"/>
<MudTextField InputType="@_passwordInput" @bind-Value="UserData.Password" Label="Password" Variant="Variant.Text" Adornment="Adornment.End" AdornmentIcon="@_passwordInputIcon" OnAdornmentClick="ShowPassword" AdornmentAriaLabel="Show Password" />
</div>
<div class="input-group mb-4">
<MudTextField @bind-Value="UserData.CodHash" Label="Profilo azienda" Variant="Variant.Text"/>
@@ -55,6 +55,26 @@ else
private string ErrorMessage { get; set; } = "";
private bool _attemptFailed;
private bool _isShow;
private InputType _passwordInput = InputType.Password;
private string _passwordInputIcon = Icons.Material.Rounded.VisibilityOff;
private void ShowPassword()
{
@if (_isShow)
{
_isShow = false;
_passwordInputIcon = Icons.Material.Rounded.VisibilityOff;
_passwordInput = InputType.Password;
}
else
{
_isShow = true;
_passwordInputIcon = Icons.Material.Rounded.Visibility;
_passwordInput = InputType.Text;
}
}
protected override void OnInitialized()
{
UserData.CodHash = LocalStorage.GetString("codHash");

View File

@@ -35,9 +35,8 @@
.button-login {
text-align: center;
border: 2px solid var(--mud-palette-primary);
background-color: var(--mud-palette-primary);
border-radius: 25px;
border-radius: 6px;
padding: .3rem 2rem;
width: 100%;
font-weight: 700;

View File

@@ -1,9 +1,113 @@
@page "/PersonalInfo"
@attribute [Authorize]
@using Template.Shared.Components.Layout
@using Template.Shared.Core.Authorization.Enum
@using Template.Shared.Core.Interface
@using Template.Shared.Core.Services
@using Template.Shared.Core.Utility
@using Template.Shared.Interfaces
@inject AppAuthenticationStateProvider AuthenticationStateProvider
@inject INetworkService NetworkService
@inject IFormFactor FormFactor
<HeaderLayout Title="Profilo" />
<div class="content">
<div class="section-primary-info">
<MudAvatar Style="height:85px; width:85px; font-size:2rem;">
<MudImage Src="@($"https://ui-avatars.com/api/?name={UserSession.User.Username}&size=80&background={UtilityColor.CalcHexColor(UserSession.User.Username)}&bold=true")"></MudImage>
</MudAvatar>
<div class="personal-info">
<span class="info-nome">@UserSession.User.Fullname</span>
@if (UserSession.User.KeyGroup is not null)
{
<span class="info-section">@(((KeyGroupEnum)UserSession.User.KeyGroup).ConvertToHumanReadable())</span>
}
</div>
</div>
<div class="section-info">
<div class="section-personal-info">
<div>
<span class="info-title">Telefono</span>
<span class="info-text">000 0000000</span> @*Todo: to implement*@
</div>
<div>
<span class="info-title">Status</span>
@if (NetworkService.IsNetworkAvailable())
{
<div class="status online">
<i class="ri-wifi-line"></i>
<span>Online</span>
</div>
}
else
{
<div class="status offline">
<i class="ri-wifi-off-line"></i>
<span>Offline</span>
</div>
}
</div>
</div>
<div class="section-personal-info">
<div>
<span class="info-title">E-mail</span>
<span class="info-text">
@if (string.IsNullOrEmpty(UserSession.User.Email))
{
@("Nessuna mail configurata")
}
else
{
@UserSession.User.Email
}
</span>
</div>
<div>
<span class="info-title">Ultima sincronizzazione</span>
<span class="info-text">@LastSync.ToString("g")</span>
</div>
</div>
</div>
<div class="user-button">
<span>Impostazioni account</span>
</div>
<div class="user-button logout" @onclick="Logout">
<span>Esci</span>
<i class="ri-logout-box-line"></i>
</div>
</div>
@code {
private bool Unavailable { get; set; }
private DateTime LastSync { get; set; }
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private void Logout()
{
AuthenticationStateProvider.SignOut();
}
private async Task LoadData()
{
await Task.Run(() =>
{
Unavailable = FormFactor.IsWeb() || !NetworkService.IsNetworkAvailable();
LastSync = LocalStorage.Get<DateTime>("last-sync");
});
StateHasChanged();
}
}

View File

@@ -0,0 +1,95 @@
.section-primary-info {
display: flex;
flex-direction: column;
align-items: center;
}
.personal-info {
display: flex;
flex-direction: column;
align-items: center;
line-height: normal;
margin: 2rem 0;
}
.info-nome {
color: var(--mud-palette-text-primary);
font-weight: 800;
font-size: x-large;
}
.info-section {
color: var(--mud-palette-gray-default);
font-size: medium;
font-weight: 600;
}
.section-info {
width: 100%;
margin-bottom: 1rem;
border-radius: 12px;
display: flex;
justify-content: space-between;
flex-direction: row;
padding: .8rem 1.2rem;
background: var(--mud-palette-surface);
}
.section-personal-info {
display: flex;
flex-direction: column;
}
.section-personal-info > div {
display: flex;
flex-direction: column;
line-height: normal;
margin: .25rem 0;
}
.info-title {
color: var(--mud-palette-gray-darker);
font-weight: 800;
}
.info-text {
color: var(--mud-palette-text-secondary);
font-weight: 700;
font-size: small;
}
.user-button {
border: 2px solid var(--mud-palette-overlay-dark);
margin-top: 1rem;
background: transparent;
text-align: center;
border-radius: 6px;
padding: .45rem 2rem;
width: 100%;
font-weight: 700;
line-height: normal;
}
.user-button.logout {
border: 2px solid var(--mud-palette-error);
color: var(--mud-palette-error);
}
.user-button > i { font-size: large; }
.user-button > span {
font-size: medium;
font-weight: 600;
}
.status {
font-weight: 700;
}
.status.online {
color: var(--mud-palette-success);
}
.status.offline {
color: var(--mud-palette-error);
}

View File

@@ -1,4 +1,4 @@
<div class="activity-card @Type shadow box-area">
<div class="activity-card @Type">
<div class="activity-left-section">
<div class="activity-hours-section">
<span class="activity-hours">14:00</span>

View File

@@ -7,6 +7,7 @@
padding: .5rem .7rem;
border-radius: 12px;
line-height: normal;
background: var(--mud-palette-surface);
}
.activity-card.memo { border-left: 5px solid var(--mud-palette-info-darken); }

View File

@@ -0,0 +1,8 @@
namespace Template.Shared.Core.Authorization.Enum;
public enum KeyGroupEnum
{
UtenteAziendale = 2,
Agenti = 5,
Tecnico = 22
}

View File

@@ -0,0 +1,17 @@
using Template.Shared.Core.Authorization.Enum;
namespace Template.Shared.Core.Helpers;
public static class KeyGroupHelper
{
public static string ConvertToHumanReadable(this KeyGroupEnum keyGroup)
{
return keyGroup switch
{
KeyGroupEnum.Agenti => "Agenti",
KeyGroupEnum.Tecnico => "Tecnico",
KeyGroupEnum.UtenteAziendale => "Utente Aziendale",
_ => throw new ArgumentOutOfRangeException(nameof(keyGroup), keyGroup, null)
};
}
}

View File

@@ -0,0 +1,6 @@
namespace Template.Shared.Core.Interface;
public interface INetworkService
{
public bool IsNetworkAvailable();
}

View File

@@ -0,0 +1,118 @@
namespace Template.Shared.Core.Utility;
public static class UtilityColor
{
public static string CalcHexColor(string input)
{
try
{
var hue = (int)(Math.Abs(input.GetHashCode()) * 137.508 % 360);
var data = new HSL(hue, 0.90f, 0.85f);
var myColor = HSLToRGB(data);
return myColor.R.ToString("X2") + myColor.G.ToString("X2") + myColor.B.ToString("X2");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return "dddddd";
}
}
private struct RGB(byte r, byte g, byte b)
{
public byte R
{
get => r;
set => r = value;
}
public byte G
{
get => g;
set => g = value;
}
public byte B
{
get => b;
set => b = value;
}
public bool Equals(RGB rgb)
{
return (this.R == rgb.R) && (this.G == rgb.G) && (this.B == rgb.B);
}
}
private struct HSL(int h, float s, float l)
{
public int H
{
get => h;
set => h = value;
}
public float S
{
get => s;
set => s = value;
}
public float L
{
get => l;
set => l = value;
}
public bool Equals(HSL hsl)
{
return H == hsl.H && (this.S == hsl.S) && (this.L == hsl.L);
}
}
private static RGB HSLToRGB(HSL hsl)
{
byte r;
byte g;
byte b;
var hue = (float)hsl.H / 360;
if (hsl.S == 0)
{
r = g = b = (byte)(hsl.L * 255);
}
else
{
var v2 = hsl.L < 0.5 ? hsl.L * (1 + hsl.S) : hsl.L + hsl.S - hsl.L * hsl.S;
var v1 = 2 * hsl.L - v2;
r = (byte)(255 * HueToRGB(v1, v2, hue + 1.0f / 3));
g = (byte)(255 * HueToRGB(v1, v2, hue));
b = (byte)(255 * HueToRGB(v1, v2, hue - 1.0f / 3));
}
return new RGB(r, g, b);
}
private static float HueToRGB(float v1, float v2, float vH)
{
if (vH < 0)
vH += 1;
if (vH > 1)
vH -= 1;
if (6 * vH < 1)
return v1 + (v2 - v1) * 6 * vH;
if (2 * vH < 1)
return v2;
if (3 * vH < 2)
return v1 + (v2 - v1) * (2.0f / 3 - vH) * 6;
return v1;
}
}

View File

@@ -0,0 +1,11 @@
namespace Template.Shared.Core.Utility;
public static class UtilityString
{
public static string ExtractInitials(string fullname)
{
return string.Concat(fullname
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Select(word => char.ToUpper(word[0])));
}
}

View File

@@ -4,4 +4,10 @@ public interface IFormFactor
{
public string GetFormFactor();
public string GetPlatform();
public bool IsWeb()
{
var formFactor = GetFormFactor();
return formFactor == "Web";
}
}

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor.Services;
using Template.Shared.Components;
using Template.Shared.Core.Interface;
using Template.Shared.Core.Services;
using Template.Shared.Interfaces;
using Template.Web.Services;
@@ -14,6 +15,7 @@ builder.Services.AddMudServices();
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<IFormFactor, FormFactor>();
builder.Services.AddScoped<INetworkService, NetworkService>();
builder.Services.AddScoped<AppAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<AppAuthenticationStateProvider>());

View File

@@ -0,0 +1,12 @@
using Template.Shared.Core.Interface;
namespace Template.Web.Services;
public class NetworkService : INetworkService
{
public bool IsNetworkAvailable()
{
return true;
}
}