Edit utenti

This commit is contained in:
2026-02-02 09:07:38 +01:00
parent 7b51eb1065
commit 09edc1f2b7
11 changed files with 286 additions and 25 deletions

View File

@ -102,7 +102,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("IdUtenteModifica"); b.HasIndex("IdUtenteModifica");
b.ToTable("Cliente"); b.ToTable("Cliente", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Commessa", b => modelBuilder.Entity("TecniStamp.Domain.Commessa", b =>
@ -167,7 +167,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("IdUtenteModifica"); b.HasIndex("IdUtenteModifica");
b.ToTable("Commessa"); b.ToTable("Commessa", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.ComuneIstat", b => modelBuilder.Entity("TecniStamp.Domain.ComuneIstat", b =>
@ -226,7 +226,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("ProvinciaIstatId"); b.HasIndex("ProvinciaIstatId");
b.ToTable("ComuneIstat"); b.ToTable("ComuneIstat", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Feature", b => modelBuilder.Entity("TecniStamp.Domain.Feature", b =>
@ -275,7 +275,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("SezioneId"); b.HasIndex("SezioneId");
b.ToTable("Feature"); b.ToTable("Feature", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Lavorazione", b => modelBuilder.Entity("TecniStamp.Domain.Lavorazione", b =>
@ -327,7 +327,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("IdUtenteModifica"); b.HasIndex("IdUtenteModifica");
b.ToTable("Lavorazione"); b.ToTable("Lavorazione", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Macchinario", b => modelBuilder.Entity("TecniStamp.Domain.Macchinario", b =>
@ -376,7 +376,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("IdUtenteModifica"); b.HasIndex("IdUtenteModifica");
b.ToTable("Macchinario"); b.ToTable("Macchinario", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Permission", b => modelBuilder.Entity("TecniStamp.Domain.Permission", b =>
@ -416,7 +416,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("RuoloId"); b.HasIndex("RuoloId");
b.ToTable("Permission"); b.ToTable("Permission", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.ProvinciaIstat", b => modelBuilder.Entity("TecniStamp.Domain.ProvinciaIstat", b =>
@ -454,7 +454,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("IdUtenteModifica"); b.HasIndex("IdUtenteModifica");
b.ToTable("ProvinciaIstat"); b.ToTable("ProvinciaIstat", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Ruolo", b => modelBuilder.Entity("TecniStamp.Domain.Ruolo", b =>
@ -488,7 +488,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("IdUtenteModifica"); b.HasIndex("IdUtenteModifica");
b.ToTable("Ruolo"); b.ToTable("Ruolo", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Sezione", b => modelBuilder.Entity("TecniStamp.Domain.Sezione", b =>
@ -540,7 +540,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("ParentId"); b.HasIndex("ParentId");
b.ToTable("Sezione"); b.ToTable("Sezione", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Utente", b => modelBuilder.Entity("TecniStamp.Domain.Utente", b =>
@ -598,7 +598,7 @@ namespace TecniStamp.Infrastructure.Migrations
b.HasIndex("RuoloId"); b.HasIndex("RuoloId");
b.ToTable("Utente"); b.ToTable("Utente", (string)null);
}); });
modelBuilder.Entity("TecniStamp.Domain.Cliente", b => modelBuilder.Entity("TecniStamp.Domain.Cliente", b =>

View File

@ -3,6 +3,7 @@
public interface IManagerService public interface IManagerService
{ {
IPermissionService PermissionService { get; set; } IPermissionService PermissionService { get; set; }
IRuoloService RuoloService{ get; set; }
ISezioneService SezioneService { get; set; } ISezioneService SezioneService { get; set; }
IUserService UtenteService { get; set; } IUserService UtenteService { get; set; }
} }

View File

@ -0,0 +1,9 @@
using OAService.Service.Repository;
using OAService.Service.Servizi.Interfacce;
using TecniStamp.Domain;
namespace TecniStamp.Service.Interfaces;
public interface IRuoloService : ITService<Ruolo>
{
}

View File

@ -4,14 +4,16 @@ namespace TecniStamp.Service;
public class ManagerService : IManagerService public class ManagerService : IManagerService
{ {
public ManagerService(IUserService userService, ISezioneService sezioneService, IPermissionService permissionService) public ManagerService(IUserService userService, ISezioneService sezioneService, IPermissionService permissionService, IRuoloService ruoloService)
{ {
UtenteService = userService; UtenteService = userService;
SezioneService = sezioneService; SezioneService = sezioneService;
PermissionService = permissionService; PermissionService = permissionService;
RuoloService = ruoloService;
} }
public IPermissionService PermissionService { get; set; } public IPermissionService PermissionService { get; set; }
public IRuoloService RuoloService { get; set; }
public ISezioneService SezioneService { get; set; } public ISezioneService SezioneService { get; set; }
public IUserService UtenteService { get; set; } public IUserService UtenteService { get; set; }
} }

View File

@ -0,0 +1,13 @@
using OAService.Service.Servizi.Implementazioni;
using TecniStamp.Domain;
using TecniStamp.Service.Interfaces;
using TecniStamp.Service.Repository;
namespace TecniStamp.Service;
public class RuoloService : TService<Ruolo>, IRuoloService
{
public RuoloService(ITecniStampUnitOfWork unitOfWork) : base(unitOfWork)
{
}
}

View File

@ -1,6 +1,9 @@
@page "/Anagrafiche/Operatori" @page "/Anagrafiche/Operatori"
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using TecniStamp.Domain @using TecniStamp.Domain
@using TecniStamp.Model
@rendermode InteractiveServer
<PageTitle>Operatori</PageTitle> <PageTitle>Operatori</PageTitle>
@ -15,7 +18,7 @@
</div> </div>
<div class="col-auto ms-auto"> <div class="col-auto ms-auto">
<div class="btn-list"> <div class="btn-list">
<a href="/management/Utenti/Modifica" class="btn btn-primary btn-5 d-none d-sm-inline-block"> <a href="/Anagrafiche/Operatore/Modifica" class="btn btn-primary btn-5 d-none d-sm-inline-block">
Nuovo Operatore Nuovo Operatore
</a> </a>
</div> </div>
@ -27,17 +30,14 @@
AllowPaging="true" PagerHorizontalAlign="HorizontalAlign.Left" ShowPagingSummary="true" AllowPaging="true" PagerHorizontalAlign="HorizontalAlign.Left" ShowPagingSummary="true"
Data="@utenti" ColumnWidth="300px" LogicalFilterOperator="LogicalFilterOperator.Or" SelectionMode="DataGridSelectionMode.Single"> Data="@utenti" ColumnWidth="300px" LogicalFilterOperator="LogicalFilterOperator.Or" SelectionMode="DataGridSelectionMode.Single">
<Columns> <Columns>
<RadzenDataGridColumn Property="@nameof(Utente.Nome)" Title="Nome" Width="160px" /> <RadzenDataGridColumn Property="@nameof(UserViewModel.Nome)" Title="Nome" Width="160px" />
<RadzenDataGridColumn Property="@nameof(Utente.Cognome)" Title="Cognome" Width="160px" /> <RadzenDataGridColumn Property="@nameof(UserViewModel.Cognome)" Title="Cognome" Width="160px" />
<RadzenDataGridColumn Property="@nameof(Utente.Email)" Title="Mail" Width="200px" /> <RadzenDataGridColumn Property="@nameof(UserViewModel.Email)" Title="Mail" Width="200px" />
<RadzenDataGridColumn Property="Ruolo.Nome" Title="Ruolo" Width="200px" /> <RadzenDataGridColumn Property="@nameof(UserViewModel.Ruolo)" Title="Ruolo" Width="200px" />
@* <RadzenDataGridColumn Property="Capoarea.Nome" Title="Capoarea" Width="200px" /> *@
<RadzenDataGridColumn Context="order" Filterable="false" Sortable="false" TextAlign="TextAlign.Center" Width="200px"> <RadzenDataGridColumn Context="order" Filterable="false" Sortable="false" TextAlign="TextAlign.Center" Width="200px">
<Template Context="user"> <Template Context="user">
@* <RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" class="rz-my-1 rz-ms-1" Click="@(args => EditRow(user))" @onclick:stopPropagation="true" /> *@ <RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" class="rz-my-1 rz-ms-1" Click="@(args => EditRow(user))" @onclick:stopPropagation="true" />
<RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" class="rz-my-1 rz-ms-1" @onclick:stopPropagation="true" />
@* <RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Variant="Variant.Flat" Size="ButtonSize.Medium" Shade="Shade.Lighter" class="rz-my-1 rz-ms-1" Click="@(args => DeleteRow(user))" @onclick:stopPropagation="true" /> *@
<RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Variant="Variant.Flat" Size="ButtonSize.Medium" Shade="Shade.Lighter" class="rz-my-1 rz-ms-1" @onclick:stopPropagation="true" /> <RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Variant="Variant.Flat" Size="ButtonSize.Medium" Shade="Shade.Lighter" class="rz-my-1 rz-ms-1" @onclick:stopPropagation="true" />
</Template> </Template>
</RadzenDataGridColumn> </RadzenDataGridColumn>
@ -52,8 +52,8 @@
</div> </div>
@code { @code {
IQueryable<Utente> utenti; List<UserViewModel> utenti;
RadzenDataGrid<Utente> userGrid; RadzenDataGrid<UserViewModel> userGrid;
/// <summary> /// <summary>
/// Carica la lista degli utenti non eliminati, ordinandoli per cognome e nome. /// Carica la lista degli utenti non eliminati, ordinandoli per cognome e nome.
@ -62,10 +62,18 @@
{ {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
utenti = await _managerService.UtenteService.RicercaQueryable( utenti = (await _managerService.UtenteService.RicercaQueryable(
x => x.Eliminato == false, x => x.Eliminato == false,
includi: x => x.Include(y => y.Ruolo), includi: x => x.Include(y => y.Ruolo),
ordinamento: x => x.OrderBy(y => y.Cognome).ThenBy(z => z.Nome)); ordinamento: x => x.OrderBy(y => y.Cognome).ThenBy(z => z.Nome)))
.Select(x => (UserViewModel)x).ToList();
} }
/// <summary>
/// Apre la pagina di modifica per il cliente selezionato.
/// </summary>
private async Task EditRow(UserViewModel cliente)
{
_navManager.NavigateTo($"/Anagrafiche/Operatori/Modifica/{cliente.Id}");
}
} }

View File

@ -0,0 +1,152 @@
@page "/Anagrafiche/Operatori/Modifica"
@page "/Anagrafiche/Operatori/Modifica/{UserId:guid}"
@using Microsoft.AspNetCore.Identity
@using Microsoft.EntityFrameworkCore
@using TecniStamp.Domain
@using TecniStamp.Model
@rendermode InteractiveServer
<PageTitle>@pageTitle</PageTitle>
<div class="page-wrapper">
<!-- BEGIN PAGE HEADER -->
<div class="page-header d-print-none" aria-label="Page header">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">@pageTitle</h2>
</div>
</div>
</div>
</div>
<!-- END PAGE HEADER -->
<!-- BEGIN PAGE BODY -->
<div class="page-body">
<div class="container-xl">
<div class="row row-cards">
<div class="col-lg-12">
<div class="card">
<div class="card-body">
<div class="row g-5">
<EditForm Model="Model" OnValidSubmit="onUtenteSave" FormName="editUserForm">
<DataAnnotationsValidator />
<div class="col-12">
<div class="row">
<div class="col-3 mb-3">
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.H3">Nome</RadzenText>
<RadzenTextBox Style="width: 100%" aria-label="Nome" @bind-Value="@Model.Nome" />
<ValidationMessage For="@(() => Model.Nome)" />
</div>
<div class="col-3 mb-3">
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.H3">Cognome</RadzenText>
<RadzenTextBox Style="width: 100%" aria-label="Cognome" @bind-Value="@Model.Cognome" />
<ValidationMessage For="@(() => Model.Cognome)" />
</div>
<div class="col-3 mb-3">
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.H3">Email</RadzenText>
<RadzenTextBox Style="width: 100%" aria-label="Email" @bind-Value="@Model.Email" />
<ValidationMessage For="@(() => Model.Email)" />
</div>
<div class="col-3 mb-3">
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.H3">Ruolo</RadzenText>
<RadzenDropDown Style="width: 100%" TValue="Guid" @bind-Value=@Model.RuoloId Data=@ruoli TextProperty="Nome" ValueProperty="Id" Name="ruoliDrop" />
</div>
</div>
<div class="row">
<div class="col-3 mb-3">
<RadzenText TextStyle="TextStyle.Subtitle2" TagName="TagName.H3">Password</RadzenText>
<RadzenPassword Style="width: 100%" aria-label="Password" @bind-Value="@Model.Password"/>
</div>
</div>
<div class="row">
<div class="col-4 mb-3">
<button type="button" class="btn btn-default w-100" @onclick="backToHome">
Annulla
</button>
</div>
<div class="col-4 mb-3">
<button type="submit" class="btn btn-primary w-100">
Salva
</button>
</div>
</div>
</div>
</EditForm>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@code {
[Parameter] public Guid? UserId { get; set; }
public UserViewModel Model { get; set; } = new();
private string pageTitle => Model?.Id == Guid.Empty ? "Nuovo operatore" : $"Modifica operatore {Model}";
private List<RuoloViewModel> ruoli { get; set; } = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
Model = UserId.GetValueOrDefault() == Guid.Empty
? new UserViewModel()
: await _managerService.UtenteService.RicercaPer(x => x.Id == UserId,
includi:x => x.Include(y => y.Ruolo));
ruoli = (await _managerService.RuoloService.RicercaQueryable(x => x.Eliminato == false))
.Select(x => (RuoloViewModel)x).ToList();
}
/// <summary>
/// Salva lutente: recupera o crea il modello, applica le modifiche dalla UI,
/// gestisce lhash della password se inserita e registra tutto a database
/// usando lID dellutente autenticato.
/// </summary>
private async Task onUtenteSave()
{
var state = await _auth.GetAuthenticationStateAsync();
var idClaim = state.User.FindFirst("UserId")?.Value;
if (string.IsNullOrEmpty(idClaim))
{
// gestisci errore (utente non autenticato o claim mancante)
return;
}
var model = await _managerService.UtenteService.RicercaPer(x => x.Id == Model.Id, solaLettura: false)
?? new Utente();
model = Model.Map(model);
if (!string.IsNullOrWhiteSpace(Model.Password))
{
var hasher = new PasswordHasher<Utente>();
model.Password = hasher.HashPassword(model, Model.Password);
}
model.RuoloId = Model.RuoloId;
await _managerService.UtenteService.Salva(model, Guid.Parse(idClaim));
_navManager.NavigateTo($"/Anagrafiche/Operatori/Modifica/{Model.Id}");
}
/// <summary>
/// Torna allelenco utenti senza applicare altre azioni.
/// </summary>
private void backToHome()
{
_navManager.NavigateTo("/Anagrafiche/Operatori");
}
}

View File

@ -0,0 +1,8 @@
namespace TecniStamp.Model;
public abstract class BaseViewModel
{
public Guid Id { get; set; }
public abstract void Validate();
}

View File

@ -0,0 +1,20 @@
using TecniStamp.Domain;
namespace TecniStamp.Model;
public class RuoloViewModel
{
public Guid Id { get; set; }
public string Nome { get; set; }
public static implicit operator RuoloViewModel(Ruolo model)
{
return model == null
? null
: new RuoloViewModel()
{
Nome = model.Nome,
Id = model.Id
};
}
}

View File

@ -0,0 +1,48 @@
using TecniStamp.Domain;
namespace TecniStamp.Model;
public class UserViewModel : BaseViewModel
{
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Nome { get; set; }
public string Cognome { get; set; }
public Guid RuoloId { get; set; }
public string Ruolo { get; set; }
public override void Validate()
{
}
public override string ToString()
{
return $"{Nome} {Cognome}";
}
public static implicit operator UserViewModel(Utente model)
{
return model == null
? null
: new UserViewModel()
{
Id = model.Id,
Username = model.Username,
Email = model.Email,
Cognome = model.Cognome,
Nome = model.Nome,
Ruolo = model.Ruolo?.Nome ?? string.Empty,
RuoloId = model.RuoloId.GetValueOrDefault()
};
}
public Utente Map(Utente model)
{
model.Nome = Nome;
model.Cognome = Cognome;
model.Email = Email;
return model;
}
}