From 014e2ffc41cae37d3cdb0fe721943b7ca1232a33 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Fri, 5 Sep 2025 09:41:22 +0200 Subject: [PATCH] Aggiunta pagination nella tab Commesse --- .../Core/Services/NetworkService.cs | 1 + salesbook.Shared/Components/Pages/User.razor | 591 ++++++++++++++---- .../Components/Pages/User.razor.css | 17 +- salesbook.Shared/wwwroot/css/app.css | 15 +- 4 files changed, 489 insertions(+), 135 deletions(-) diff --git a/salesbook.Maui/Core/Services/NetworkService.cs b/salesbook.Maui/Core/Services/NetworkService.cs index 31a45b0..4545bcc 100644 --- a/salesbook.Maui/Core/Services/NetworkService.cs +++ b/salesbook.Maui/Core/Services/NetworkService.cs @@ -6,6 +6,7 @@ public class NetworkService : INetworkService { public bool IsNetworkAvailable() { + return false; return Connectivity.Current.NetworkAccess == NetworkAccess.Internet; } diff --git a/salesbook.Shared/Components/Pages/User.razor b/salesbook.Shared/Components/Pages/User.razor index 0ef50bc..04f9e07 100644 --- a/salesbook.Shared/Components/Pages/User.razor +++ b/salesbook.Shared/Components/Pages/User.razor @@ -9,21 +9,28 @@ @using salesbook.Shared.Components.SingleElements @using salesbook.Shared.Core.Dto.JobProgress @using salesbook.Shared.Core.Dto.PageState +@implements IAsyncDisposable @inject IManageDataService ManageData @inject IMapper Mapper @inject IDialogService Dialog @inject IIntegryApiService IntegryApiService @inject UserPageState UserState - + @if (IsLoading) { - + } else { -
+
@@ -32,11 +39,11 @@ else
@Anag.RagSoc - @if (Anag.Indirizzo != null) + @if (!string.IsNullOrEmpty(Anag.Indirizzo)) { @Anag.Indirizzo } - @if (Anag is { Citta: not null, Cap: not null, Prov: not null }) + @if (!string.IsNullOrEmpty(Anag.Cap) && !string.IsNullOrEmpty(Anag.Citta) && !string.IsNullOrEmpty(Anag.Prov)) { @($"{Anag.Cap} - {Anag.Citta} ({Anag.Prov})") } @@ -51,9 +58,7 @@ else {
Telefono - - @Anag.Telefono - + @Anag.Telefono
} @@ -61,9 +66,7 @@ else {
P. IVA - - @Anag.PartIva - + @Anag.PartIva
}
@@ -73,9 +76,7 @@ else {
E-mail - - @Anag.EMail - + @Anag.EMail
} @@ -83,47 +84,41 @@ else {
Agente - - @Agente.FullName - + @Agente.FullName
}
- - + +
  • - +
  • - +
-
-
- - @if (PersRif is { Count: > 0 }) + +
+ @if (PersRif?.Count > 0) {
- - @{ - var index = PersRif.IndexOf(person); - var isLast = index == PersRif.Count - 1; - } - - @if (!isLast) + @foreach (var person in PersRif) + { + + @if (person != PersRif.Last()) {
} -
+ }
} @@ -137,26 +132,90 @@ else
-
- - @if (LoadCommessa) + + +
+ @if (IsLoadingCommesse) { - + } - else + else if (Commesse?.Count == 0) { - @if (Commesse.IsNullOrEmpty()) - { - - } - else - { -
- - - -
- } + + } + else if (Commesse != null) + { + +
+ +
+ +
+ @if (IsLoadingSteps) + { + + } + + @foreach (var commessa in CurrentPageCommesse) + { +
+ @if (Steps.TryGetValue(commessa.CodJcom, out var steps)) + { + + } + else + { + + @if (IsLoadingStep(commessa.CodJcom)) + { + + } + } +
+ } + + @if (TotalPages > 1) + { +
+ +
+ +
+ + 5 + 10 + 15 + +
+ + + + + } + + @*
+ + Visualizzate: @CurrentPageCommesse.Count() / @FilteredCommesse.Count() + + @if (!string.IsNullOrEmpty(SearchTerm)) + { + + Filtro: "@SearchTerm" + + } +
*@ +
}
@@ -164,34 +223,173 @@ else } @code { - [Parameter] public string CodContact { get; set; } + [Parameter] public string CodContact { get; set; } = string.Empty; [Parameter] public bool IsContact { get; set; } + // Dati principali private ContactDTO Anag { get; set; } = new(); private List? PersRif { get; set; } private List? Commesse { get; set; } private StbUser? Agente { get; set; } - private Dictionary?> Steps { get; set; } = []; + private Dictionary?> Steps { get; set; } = new(); + // Stati di caricamento private bool IsLoading { get; set; } = true; - private bool LoadCommessa { get; set; } = true; + private bool IsLoadingCommesse { get; set; } = true; + private bool IsLoadingSteps { get; set; } = false; + private readonly HashSet _loadingSteps = new(); + + // Gestione tab + private int ActiveTab { get; set; } = 0; + + // Paginazione e filtri + private int _currentPage = 1; + private int _selectedPageSize = 5; + private string _searchTerm = string.Empty; + private List _filteredCommesse = new(); + + // Cancellation tokens per gestire le richieste asincrone + private CancellationTokenSource? _loadingCts; + private CancellationTokenSource? _stepsCts; + + // Timer per il debounce della ricerca + private Timer? _searchTimer; + private const int SearchDelayMs = 300; + + #region Properties + + private int CurrentPage + { + get => _currentPage; + set + { + if (_currentPage != value) + { + _currentPage = value; + _ = LoadStepsForCurrentPageAsync(); + } + } + } + + private int SelectedPageSize + { + get => _selectedPageSize; + set + { + if (_selectedPageSize != value) + { + _selectedPageSize = value; + _currentPage = 1; + ApplyFilters(); + _ = LoadStepsForCurrentPageAsync(); + } + } + } + + private string SearchTerm + { + get => _searchTerm; + set + { + if (_searchTerm != value) + { + _searchTerm = value; + + // Debounce della ricerca + _searchTimer?.Dispose(); + _searchTimer = new Timer(async _ => await InvokeAsync(ApplyFilters), null, SearchDelayMs, Timeout.Infinite); + } + } + } + + private List FilteredCommesse + { + get => _filteredCommesse; + set + { + _filteredCommesse = value; + StateHasChanged(); + } + } + + private int TotalPages => + FilteredCommesse.Count == 0 ? 1 : (int)Math.Ceiling(FilteredCommesse.Count / (double)SelectedPageSize); + + private IEnumerable CurrentPageCommesse => + FilteredCommesse.Skip((CurrentPage - 1) * SelectedPageSize).Take(SelectedPageSize); + + #endregion + + #region Lifecycle Methods protected override async Task OnInitializedAsync() { - if (UserState.CodUser != null && UserState.CodUser.Equals(CodContact)) + try { - LoadDataFromSession(); - } - else - { - await LoadData(); - } + _loadingCts = new CancellationTokenSource(); - IsLoading = false; - StateHasChanged(); + if (UserState.CodUser?.Equals(CodContact) == true) + { + LoadDataFromSession(); + } + else + { + await LoadDataAsync(); + } + } + catch (Exception ex) + { + // Log dell'errore + Console.WriteLine($"Errore in OnInitializedAsync: {ex.Message}"); + } + finally + { + IsLoading = false; + StateHasChanged(); + } } - private async Task LoadData() + public async ValueTask DisposeAsync() + { + _loadingCts?.Cancel(); + _loadingCts?.Dispose(); + _stepsCts?.Cancel(); + _stepsCts?.Dispose(); + _searchTimer?.Dispose(); + } + + #endregion + + #region Data Loading Methods + + private async Task LoadDataAsync() + { + try + { + // Caricamento dati principali + await LoadAnagAsync(); + await LoadPersRifAsync(); + + // Caricamento agente + if (!string.IsNullOrEmpty(Anag.CodVage)) + { + Agente = (await ManageData.GetTable(x => x.UserCode != null && x.UserCode.Equals(Anag.CodVage))) + .LastOrDefault(); + } + + // Salvataggio in sessione + SaveDataToSession(); + + // Caricamento commesse in background + _ = Task.Run(async () => await LoadCommesseAsync()); + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel caricamento dati: {ex.Message}"); + } + } + + private async Task LoadAnagAsync() { if (IsContact) { @@ -203,67 +401,9 @@ else var pros = (await ManageData.GetTable(x => x.CodPpro.Equals(CodContact))).Last(); Anag = Mapper.Map(pros); } - - await LoadPersRif(); - _ = LoadCommesse(); - - if (Anag.CodVage != null) - { - Agente = (await ManageData.GetTable(x => x.UserCode != null && x.UserCode.Equals(Anag.CodVage))).LastOrDefault(); - } - - SetDataSession(); } - private void LoadDataFromSession() - { - Anag = UserState.Anag; - PersRif = UserState.PersRif; - Commesse = UserState.Commesse; - Agente = UserState.Agente; - Steps = UserState.Steps; - - SortCommesse(); - - LoadCommessa = false; - StateHasChanged(); - } - - private void SetDataSession() - { - UserState.CodUser = CodContact; - UserState.Anag = Anag; - UserState.PersRif = PersRif; - UserState.Agente = Agente; - UserState.Steps = Steps; - } - - private async Task LoadCommesse() - { - await Task.Run(async () => - { - Commesse = await ManageData.GetTable(x => x.CodAnag != null && x.CodAnag.Equals(CodContact)); - UserState.Commesse = Commesse; - - foreach (var commessa in Commesse) - { - var steps = (await IntegryApiService.RetrieveJobProgress(commessa.CodJcom)).Steps; - Steps.Add(commessa.CodJcom, steps); - } - - LoadCommessa = false; - }); - - SortCommesse(); - StateHasChanged(); - } - - private void SortCommesse() - { - Commesse = Commesse?.OrderBy(x => x.LastUpd.HasValue).ThenBy(x => x.LastUpd).ToList(); - } - - private async Task LoadPersRif() + private async Task LoadPersRifAsync() { if (IsContact) { @@ -277,19 +417,224 @@ else } } + private async Task LoadCommesseAsync() + { + try + { + IsLoadingCommesse = true; + + Commesse = await ManageData.GetTable(x => x.CodAnag != null && x.CodAnag.Equals(CodContact)); + + // Ordinamento ottimizzato + Commesse = Commesse? + .OrderByDescending(x => x.LastUpd ?? DateTime.MinValue) + .ThenByDescending(x => x.CodJcom) + .ToList(); + + UserState.Commesse = Commesse; + + ApplyFilters(); + + // Caricamento steps per la prima pagina + await LoadStepsForCurrentPageAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel caricamento commesse: {ex.Message}"); + } + finally + { + IsLoadingCommesse = false; + await InvokeAsync(StateHasChanged); + } + } + + private void LoadDataFromSession() + { + Anag = UserState.Anag; + PersRif = UserState.PersRif; + Commesse = UserState.Commesse; + Agente = UserState.Agente; + Steps = UserState.Steps ?? new Dictionary?>(); + + ApplyFilters(); + + IsLoadingCommesse = false; + } + + private void SaveDataToSession() + { + UserState.CodUser = CodContact; + UserState.Anag = Anag; + UserState.PersRif = PersRif; + UserState.Agente = Agente; + UserState.Steps = Steps; + } + + #endregion + + #region Steps Loading Methods + + private async Task LoadStepsForCurrentPageAsync() + { + if (CurrentPageCommesse?.Any() != true) return; + + try + { + _stepsCts?.Cancel(); + _stepsCts = new CancellationTokenSource(); + var token = _stepsCts.Token; + + IsLoadingSteps = true; + + var tasksToLoad = new List(); + var semaphore = new SemaphoreSlim(3); // Limita a 3 richieste simultanee + + foreach (var commessa in CurrentPageCommesse.Where(c => !Steps.ContainsKey(c.CodJcom))) + { + if (token.IsCancellationRequested) break; + + tasksToLoad.Add(LoadStepForCommessaAsync(commessa.CodJcom, semaphore, token)); + } + + if (tasksToLoad.Any()) + { + await Task.WhenAll(tasksToLoad); + UserState.Steps = Steps; + } + } + catch (OperationCanceledException) + { + // Operazione annullata, non fare nulla + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel caricamento steps: {ex.Message}"); + } + finally + { + IsLoadingSteps = false; + await InvokeAsync(StateHasChanged); + } + } + + private async Task LoadStepForCommessaAsync(string codJcom, SemaphoreSlim semaphore, CancellationToken token) + { + await semaphore.WaitAsync(token); + + try + { + _loadingSteps.Add(codJcom); + await InvokeAsync(StateHasChanged); + + var jobProgress = await IntegryApiService.RetrieveJobProgress(codJcom); + + if (!token.IsCancellationRequested) + { + Steps[codJcom] = jobProgress.Steps; + await InvokeAsync(StateHasChanged); + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel caricamento step per {codJcom}: {ex.Message}"); + if (!token.IsCancellationRequested) + { + Steps[codJcom] = null; + } + } + finally + { + _loadingSteps.Remove(codJcom); + semaphore.Release(); + } + } + + private bool IsLoadingStep(string codJcom) => _loadingSteps.Contains(codJcom); + + #endregion + + #region UI Methods + + private void SwitchTab(int tabIndex) + { + ActiveTab = tabIndex; + + // Se si passa alle commesse e non sono ancora state caricate + if (tabIndex == 1 && Commesse == null) + { + _ = Task.Run(async () => await LoadCommesseAsync()); + } else if (tabIndex == 1 && Steps.IsNullOrEmpty()) + { + _ = Task.Run(async () => await LoadStepsForCurrentPageAsync()); + } + } + + private void ApplyFilters() + { + if (Commesse == null) + { + FilteredCommesse = new List(); + return; + } + + var filtered = Commesse.AsEnumerable(); + + // Filtro per testo di ricerca + if (!string.IsNullOrWhiteSpace(SearchTerm)) + { + var searchLower = SearchTerm.ToLowerInvariant(); + filtered = filtered.Where(c => + c.CodJcom?.ToLowerInvariant().Contains(searchLower) == true || + c.Descrizione?.ToLowerInvariant().Contains(searchLower) == true || + c.CodAnag?.ToLowerInvariant().Contains(searchLower) == true); + } + + FilteredCommesse = filtered.ToList(); + + // Reset della pagina se necessario + if (CurrentPage > TotalPages && TotalPages > 0) + { + _currentPage = 1; + } + + // Carica gli steps per la pagina corrente + _ = LoadStepsForCurrentPageAsync(); + } + + private void ClearSearch() + { + SearchTerm = string.Empty; + ApplyFilters(); + } + + #endregion + + #region Modal Methods + private async Task OpenPersRifForm() { var result = await ModalHelpers.OpenPersRifForm(Dialog, null, Anag, PersRif); if (result is { Canceled: false, Data: not null } && result.Data.GetType() == typeof(PersRifDTO)) { - await LoadPersRif(); + await LoadPersRifAsync(); + UserState.PersRif = PersRif; } } private async Task OpenUserForm(ContactDTO anag) { var result = await ModalHelpers.OpenUserForm(Dialog, anag); + + if (result is { Canceled: false }) + { + // Aggiorna i dati se necessario + await LoadAnagAsync(); + SaveDataToSession(); + } } + #endregion + } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/User.razor.css b/salesbook.Shared/Components/Pages/User.razor.css index a47a4b1..af8db86 100644 --- a/salesbook.Shared/Components/Pages/User.razor.css +++ b/salesbook.Shared/Components/Pages/User.razor.css @@ -165,10 +165,6 @@ gap: 1.25rem; } -.commesse-container ::deep > div:first-child:not(.activity-card) { - display: none; -} - /*-------------- TabPanel ----------------*/ @@ -242,14 +238,13 @@ .tab-container { display: flex; flex-direction: column; - overflow: hidden; width: -webkit-fill-available; } .tab-content { display: none; flex: 1; - overflow-y: auto; + overflow-y: hidden; animation: fade .3s ease; padding: .5rem; } @@ -274,4 +269,14 @@ opacity: 1; transform: translateY(0); } +} + +.custom-pagination ::deep ul { padding-left: 0 !important; } + +.SelectedPageSize { + width: 100%; +} + +.FilterSection { + display: flex; } \ No newline at end of file diff --git a/salesbook.Shared/wwwroot/css/app.css b/salesbook.Shared/wwwroot/css/app.css index 31a3a3c..ba538cb 100644 --- a/salesbook.Shared/wwwroot/css/app.css +++ b/salesbook.Shared/wwwroot/css/app.css @@ -38,6 +38,13 @@ a, .btn-link { height: 90vh; } +.content::-webkit-scrollbar { width: 3px; } + +.content::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 2px; +} + h1:focus { outline: none; } .valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } @@ -97,9 +104,7 @@ h1:focus { outline: none; } color: var(--mud-palette-text-primary) !important; } - .custom_popover .mud-divider { - border-color: var(--mud-palette-text-primary) !important; - } +.custom_popover .mud-divider { border-color: var(--mud-palette-text-primary) !important; } .custom_popover .mud-list-padding { padding: 3px 0px 3px 0px !important; } @@ -196,9 +201,7 @@ h1:focus { outline: none; } padding-left: calc(var(--m-page-x) * 0.5) !important; } -.mud-message-box > .mud-dialog-title > h6 { - font-weight: 800 !important; -} +.mud-message-box > .mud-dialog-title > h6 { font-weight: 800 !important; } .mud-dialog-actions button { margin-left: .5rem !important;