feat: Implementa pagina di gestione profili avanzata
- Aggiunge nuova pagina ProfilesManagement (/profiles) con dashboard completo - Implementa statistiche profili (totali, attivi, recenti, non utilizzati) - Aggiunge filtri avanzati per ricerca e ordinamento profili - Implementa visualizzazione dettagli profili con mappature campi - Aggiunge funzionalità di eliminazione profili con conferma - Implementa esportazione profili in formato JSON - Aggiunge sistema di notifiche toast per feedback utente - Integra navigazione nel menu principale - Risolve errori di compilazione e duplicazione file - Migliora UX con design responsive e interfaccia moderna
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using CredentialManager.Models;
|
||||
using CredentialManager.Services;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Data_Coupler.Pages;
|
||||
|
||||
public partial class ProfilesManagement : ComponentBase
|
||||
{
|
||||
[Inject] private IDataCouplerProfileService ProfileService { get; set; } = null!;
|
||||
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
|
||||
[Inject] private ILogger<ProfilesManagement> Logger { get; set; } = null!;
|
||||
|
||||
// State delle liste
|
||||
private List<DataCouplerProfile> allProfiles = new();
|
||||
|
||||
// State dei filtri
|
||||
private string searchTerm = "";
|
||||
private string selectedSourceType = "";
|
||||
private string selectedUsageFilter = "";
|
||||
private string sortOrder = "name";
|
||||
|
||||
// State delle modali
|
||||
private bool showDetailsModal = false;
|
||||
private bool showDeleteModal = false;
|
||||
private DataCouplerProfile? selectedProfile = null;
|
||||
private DataCouplerProfile? profileToDelete = null;
|
||||
|
||||
// State operazioni
|
||||
private bool isLoading = true;
|
||||
private bool isDeleting = false;
|
||||
|
||||
// Toast notifications
|
||||
private string toastMessage = "";
|
||||
private string toastType = "info";
|
||||
|
||||
// Statistiche
|
||||
private int totalProfiles => allProfiles.Count;
|
||||
private int activeProfiles => allProfiles.Count(p => p.LastUsedAt.HasValue && p.LastUsedAt.Value > DateTime.Now.AddDays(-30));
|
||||
private int profilesThisWeek => allProfiles.Count(p => p.CreatedAt > DateTime.Now.AddDays(-7));
|
||||
private int unusedProfiles => allProfiles.Count(p => !p.LastUsedAt.HasValue);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadProfiles();
|
||||
}
|
||||
|
||||
private async Task LoadProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
isLoading = true;
|
||||
var profiles = await ProfileService.GetAllProfilesAsync();
|
||||
allProfiles = profiles.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore nel caricamento dei profili");
|
||||
ShowToast("Errore nel caricamento dei profili: " + ex.Message, "error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshProfiles()
|
||||
{
|
||||
await LoadProfiles();
|
||||
}
|
||||
|
||||
// Filtri e ricerca
|
||||
private IEnumerable<DataCouplerProfile> GetFilteredProfiles()
|
||||
{
|
||||
var filtered = allProfiles.AsEnumerable();
|
||||
|
||||
// Filtro per testo
|
||||
if (!string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
filtered = filtered.Where(p =>
|
||||
p.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ||
|
||||
(p.Description?.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ?? false));
|
||||
}
|
||||
|
||||
// Filtro per tipo sorgente
|
||||
if (!string.IsNullOrEmpty(selectedSourceType))
|
||||
{
|
||||
filtered = filtered.Where(p => p.SourceType.Equals(selectedSourceType, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
// Filtro per utilizzo
|
||||
if (!string.IsNullOrEmpty(selectedUsageFilter))
|
||||
{
|
||||
filtered = selectedUsageFilter switch
|
||||
{
|
||||
"used" => filtered.Where(p => p.LastUsedAt.HasValue),
|
||||
"unused" => filtered.Where(p => !p.LastUsedAt.HasValue),
|
||||
"recent" => filtered.Where(p => p.LastUsedAt.HasValue && p.LastUsedAt.Value > DateTime.Now.AddDays(-7)),
|
||||
_ => filtered
|
||||
};
|
||||
}
|
||||
|
||||
// Ordinamento
|
||||
filtered = sortOrder switch
|
||||
{
|
||||
"created" => filtered.OrderByDescending(p => p.CreatedAt),
|
||||
"lastused" => filtered.OrderByDescending(p => p.LastUsedAt ?? DateTime.MinValue),
|
||||
_ => filtered.OrderBy(p => p.Name)
|
||||
};
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private void FilterProfiles()
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ClearSearch()
|
||||
{
|
||||
searchTerm = "";
|
||||
FilterProfiles();
|
||||
}
|
||||
|
||||
// Operazioni sui profili
|
||||
private void ShowDetails(DataCouplerProfile profile)
|
||||
{
|
||||
selectedProfile = profile;
|
||||
showDetailsModal = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void CloseDetailsModal()
|
||||
{
|
||||
showDetailsModal = false;
|
||||
selectedProfile = null;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ConfirmDelete(DataCouplerProfile profile)
|
||||
{
|
||||
profileToDelete = profile;
|
||||
showDeleteModal = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void CancelDelete()
|
||||
{
|
||||
showDeleteModal = false;
|
||||
profileToDelete = null;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteProfile()
|
||||
{
|
||||
if (profileToDelete == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
isDeleting = true;
|
||||
StateHasChanged();
|
||||
|
||||
await ProfileService.DeleteProfileAsync(profileToDelete.Id);
|
||||
allProfiles.Remove(profileToDelete);
|
||||
|
||||
ShowToast("Profilo eliminato con successo", "success");
|
||||
CancelDelete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore nell'eliminazione del profilo {ProfileId}", profileToDelete.Id);
|
||||
ShowToast("Errore nell'eliminazione: " + ex.Message, "error");
|
||||
}
|
||||
finally
|
||||
{
|
||||
isDeleting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profilesToExport = GetFilteredProfiles().ToList();
|
||||
var json = JsonSerializer.Serialize(profilesToExport, new JsonSerializerOptions { WriteIndented = true });
|
||||
var fileName = $"profiles_export_{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
||||
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(json);
|
||||
var stream = new MemoryStream(bytes);
|
||||
|
||||
using var streamRef = new DotNetStreamReference(stream);
|
||||
await JSRuntime.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
|
||||
|
||||
ShowToast("Esportazione completata", "success");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore nell'esportazione dei profili");
|
||||
ShowToast("Errore nell'esportazione: " + ex.Message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods per la UI
|
||||
private string GetSourceTypeBadgeClass(string sourceType)
|
||||
{
|
||||
return sourceType?.ToLower() switch
|
||||
{
|
||||
"database" => "primary",
|
||||
"file" => "success",
|
||||
"rest" => "info",
|
||||
_ => "secondary"
|
||||
};
|
||||
}
|
||||
|
||||
private string GetSourceTypeDisplayName(string sourceType)
|
||||
{
|
||||
return sourceType?.ToLower() switch
|
||||
{
|
||||
"database" => "Database",
|
||||
"file" => "File",
|
||||
"rest" => "REST API",
|
||||
_ => "Sconosciuto"
|
||||
};
|
||||
}
|
||||
|
||||
private int GetMappingCount(DataCouplerProfile profile)
|
||||
{
|
||||
if (string.IsNullOrEmpty(profile.FieldMappingJson))
|
||||
return 0;
|
||||
|
||||
try
|
||||
{
|
||||
var mappings = JsonSerializer.Deserialize<List<FieldMappingDto>>(profile.FieldMappingJson);
|
||||
return mappings?.Count ?? 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private List<FieldMappingDto> GetProfileMappings(DataCouplerProfile profile)
|
||||
{
|
||||
if (string.IsNullOrEmpty(profile.FieldMappingJson))
|
||||
return new List<FieldMappingDto>();
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<FieldMappingDto>>(profile.FieldMappingJson) ?? new List<FieldMappingDto>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<FieldMappingDto>();
|
||||
}
|
||||
}
|
||||
|
||||
// Toast notifications
|
||||
private void ShowToast(string message, string type = "info")
|
||||
{
|
||||
toastMessage = message;
|
||||
toastType = type;
|
||||
StateHasChanged();
|
||||
|
||||
// Auto-hide dopo 5 secondi
|
||||
_ = Task.Delay(5000).ContinueWith(_ => ClearToast());
|
||||
}
|
||||
|
||||
private void ClearToast()
|
||||
{
|
||||
toastMessage = "";
|
||||
toastType = "info";
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user