From 9957229e7064c6307cf37c314382462b4e2112a2 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Thu, 21 Aug 2025 10:51:32 +0200 Subject: [PATCH 01/24] Vario --- .../SingleElements/Modal/ActivityForm.razor | 40 +++++++++++++++++-- .../SingleElements/Modal/ContactForm.razor | 5 ++- .../SingleElements/Modal/ViewAttached.razor | 14 +++++++ .../Modal/ViewAttached.razor.css | 7 ++++ salesbook.Shared/Core/Helpers/ModalHelpers.cs | 19 +++++++++ 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor create mode 100644 salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css diff --git a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor index 8443d96..b91bc5e 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -1,7 +1,6 @@ @using System.Globalization @using System.Text.RegularExpressions @using CommunityToolkit.Mvvm.Messaging -@using Java.Util.Jar @using salesbook.Shared.Core.Dto @using salesbook.Shared.Components.Layout @using salesbook.Shared.Core.Entity @@ -126,13 +125,13 @@ { @if (item.p.Type == AttachedDTO.TypeAttached.Position) { - + @item.p.Description } else { - + @item.p.Name } @@ -518,7 +517,7 @@ if (resultNamePosition is true) attached.Description = NamePosition; - attached.Name = NamePosition!; + attached.Name = NamePosition!; } AttachedList ??= []; @@ -542,4 +541,37 @@ StateHasChanged(); } + private async Task OpenAttached(AttachedDTO attached) + { + if (attached is { FileContent: not null, MimeType: not null }) + { + var fileViewerUrl = $"data:{attached.MimeType};base64,{Convert.ToBase64String(attached.FileContent)}"; + await ModalHelpers.OpenViewAttach(Dialog, fileViewerUrl); + } + else + { + Snackbar.Clear(); + Snackbar.Add("Impossibile aprire il file", Severity.Error); + } + } + + private void OpenPosition(AttachedDTO attached) + { + if (attached is { Lat: not null, Lng: not null }) + { + const string baseUrl = "https://www.google.it/maps/place/"; + NavigationManager.NavigateTo( + $"{baseUrl}{AdjustCoordinate(attached.Lat.Value)},{AdjustCoordinate(attached.Lng.Value)}" + ); + } + else + { + Snackbar.Clear(); + Snackbar.Add("Impossibile aprire la posizione", Severity.Error); + } + } + + private static string AdjustCoordinate(double coordinate) => + coordinate.ToString(CultureInfo.InvariantCulture).Replace(",", "."); + } \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor index 7985f88..f070e1f 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor @@ -559,6 +559,9 @@ private async Task ConvertProspectToContact() { - + await IntegryApiService.TransferProspect(new CRMTransferProspectRequestDTO + { + CodPpro = ContactModel.CodContact + }); } } \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor b/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor new file mode 100644 index 0000000..0dc97c5 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor @@ -0,0 +1,14 @@ + + + @if (!string.IsNullOrEmpty(FileViewerUrl)) + { + + } + + + +@code { + [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } + + [Parameter] public string? FileViewerUrl { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css b/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css new file mode 100644 index 0000000..6eeaddd --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css @@ -0,0 +1,7 @@ +.content.attached { + display: flex; + flex-direction: column; + gap: 2rem; + padding: 1rem; + height: unset; +} diff --git a/salesbook.Shared/Core/Helpers/ModalHelpers.cs b/salesbook.Shared/Core/Helpers/ModalHelpers.cs index fa199f6..574a8ba 100644 --- a/salesbook.Shared/Core/Helpers/ModalHelpers.cs +++ b/salesbook.Shared/Core/Helpers/ModalHelpers.cs @@ -85,4 +85,23 @@ public class ModalHelpers return await modal.Result; } + + public static async Task OpenViewAttach(IDialogService dialog, string? fileViewUrl) + { + var modal = await dialog.ShowAsync( + "View attached", + new DialogParameters + { + { x => x.FileViewerUrl, fileViewUrl } + }, + new DialogOptions + { + FullScreen = true, + CloseButton = true, + NoHeader = true + } + ); + + return await modal.Result; + } } \ No newline at end of file From 833a1e456f6972479ca14cfdddd5ceca4a3c1170 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Mon, 25 Aug 2025 10:00:41 +0200 Subject: [PATCH 02/24] Iniziata implementazione notifiche firebase --- .../IntegryApi/Dto/RegisterDeviceDTO.cs | 10 +++++ .../IntegryApi/Dto/WtbUserDeviceTokenDTO.cs | 22 +++++++++++ .../IntegryNotificationRestClient.cs | 38 +++++++++++++++++++ .../Network}/NetworkService.cs | 2 +- .../FirebaseNotificationService.cs | 16 ++++++++ .../Push/PushNotificationDelegate.cs | 31 +++++++++++++++ salesbook.Maui/GoogleService-Info.plist | 30 +++++++++++++++ salesbook.Maui/MauiProgram.cs | 11 ++++++ .../Platforms/Android/AndroidManifest.xml | 11 +++++- .../Platforms/Android/MainActivity.cs | 6 +++ salesbook.Maui/Platforms/iOS/Info.plist | 4 ++ salesbook.Maui/google-services.json | 29 ++++++++++++++ salesbook.Maui/salesbook.Maui.csproj | 12 ++++++ salesbook.Shared/Components/Pages/Home.razor | 10 +++++ .../Interface/IFirebaseNotificationService.cs | 6 +++ .../IIntegryNotificationRestClient.cs | 8 ++++ 16 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs create mode 100644 salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs create mode 100644 salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs rename salesbook.Maui/Core/{Services => System/Network}/NetworkService.cs (82%) create mode 100644 salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs create mode 100644 salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs create mode 100644 salesbook.Maui/GoogleService-Info.plist create mode 100644 salesbook.Maui/google-services.json create mode 100644 salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs create mode 100644 salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs new file mode 100644 index 0000000..fc7c01d --- /dev/null +++ b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Maui.Core.RestClient.IntegryApi.Dto; + +public class RegisterDeviceDTO +{ + [JsonPropertyName("userDeviceToken")] + public WtbUserDeviceTokenDTO UserDeviceToken { get; set; } + +} \ No newline at end of file diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs new file mode 100644 index 0000000..136bd4e --- /dev/null +++ b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Maui.Core.RestClient.IntegryApi.Dto; + +public class WtbUserDeviceTokenDTO +{ + [JsonPropertyName("type")] + public string Type => "wtb_user_device_tokens"; + + [JsonPropertyName("deviceToken")] + public string DeviceToken { get; set; } + + [JsonPropertyName("userName")] + public string Username { get; set; } + + [JsonPropertyName("appName")] + public int AppName => 7; //salesbook + + [JsonPropertyName("platform")] + public string Platform { get; set; } + +} \ No newline at end of file diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs new file mode 100644 index 0000000..d18ff4c --- /dev/null +++ b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs @@ -0,0 +1,38 @@ +using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account; +using IntegryApiClient.Core.Domain.RestClient.Contacts; +using Microsoft.Extensions.Logging; +using salesbook.Maui.Core.RestClient.IntegryApi.Dto; +using salesbook.Shared.Core.Interface; + +namespace salesbook.Maui.Core.RestClient.IntegryApi; + +public class IntegryNotificationRestClient( + ILogger logger, + IUserSession userSession, + IIntegryApiRestClient integryApiRestClient +) : IIntegryNotificationRestClient +{ + public async Task Register(string fcmToken, ILogger? logger1 = null) + { + logger1 ??= logger; + + var userDeviceToken = new RegisterDeviceDTO() + { + UserDeviceToken = new WtbUserDeviceTokenDTO() + { + DeviceToken = fcmToken, + Platform = OperatingSystem.IsAndroid() ? "Android" : "iOS", + Username = userSession.User.Username + } + }; + try + { + await integryApiRestClient.AuthorizedPost($"device_tokens/insert", userDeviceToken, + logger: logger1); + } + catch (Exception ex) + { + SentrySdk.CaptureException(ex); + } + } +} \ No newline at end of file diff --git a/salesbook.Maui/Core/Services/NetworkService.cs b/salesbook.Maui/Core/System/Network/NetworkService.cs similarity index 82% rename from salesbook.Maui/Core/Services/NetworkService.cs rename to salesbook.Maui/Core/System/Network/NetworkService.cs index 31a45b0..7fb8b96 100644 --- a/salesbook.Maui/Core/Services/NetworkService.cs +++ b/salesbook.Maui/Core/System/Network/NetworkService.cs @@ -1,6 +1,6 @@ using salesbook.Shared.Core.Interface; -namespace salesbook.Maui.Core.Services; +namespace salesbook.Maui.Core.System.Network; public class NetworkService : INetworkService { diff --git a/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs new file mode 100644 index 0000000..e0b7c28 --- /dev/null +++ b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs @@ -0,0 +1,16 @@ +using salesbook.Shared.Core.Interface; +using Shiny; +using Shiny.Push; + +namespace salesbook.Maui.Core.System.Notification; + +public class FirebaseNotificationService(IPushManager pushManager, IIntegryNotificationRestClient integryNotificationRestClient) : IFirebaseNotificationService +{ + public async Task InitFirebase() + { + var (accessState, token) = await pushManager.RequestAccess(); + + if (accessState == AccessState.Denied || token is null) return; + await integryNotificationRestClient.Register(token); + } +} \ No newline at end of file diff --git a/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs new file mode 100644 index 0000000..14cf03d --- /dev/null +++ b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs @@ -0,0 +1,31 @@ +using Shiny.Push; + +namespace salesbook.Maui.Core.System.Notification.Push; + +public class PushNotificationDelegate : IPushDelegate +{ + public Task OnEntry(PushNotification notification) + { + // fires when the user taps on a push notification + return Task.CompletedTask; + } + + public Task OnReceived(PushNotification notification) + { + // fires when a push notification is received (silient or notification) + //notification.Data["content-available"] = "1"; + return Task.CompletedTask; + } + + public Task OnNewToken(string token) + { + // fires when a push notification change is set by the operating system or provider + return Task.CompletedTask; + } + + public Task OnUnRegistered(string token) + { + // fires when a push notification change is set by the operating system or provider + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/salesbook.Maui/GoogleService-Info.plist b/salesbook.Maui/GoogleService-Info.plist new file mode 100644 index 0000000..4ed039f --- /dev/null +++ b/salesbook.Maui/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyC_QtQpsVortjzgl-B7__IQZ-85lOct55E + GCM_SENDER_ID + 830771692001 + PLIST_VERSION + 1 + BUNDLE_ID + it.integry.salesbook + PROJECT_ID + salesbook-smetar + STORAGE_BUCKET + salesbook-smetar.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:830771692001:ios:59d8b1d8570ac81f3752a0 + + \ No newline at end of file diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index 2b3aa2a..c4d0361 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -6,7 +6,11 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; using MudBlazor.Services; using MudExtensions.Services; +using salesbook.Maui.Core.RestClient.IntegryApi; using salesbook.Maui.Core.Services; +using salesbook.Maui.Core.System.Network; +using salesbook.Maui.Core.System.Notification; +using salesbook.Maui.Core.System.Notification.Push; using salesbook.Shared; using salesbook.Shared.Core.Dto; using salesbook.Shared.Core.Helpers; @@ -16,6 +20,7 @@ using salesbook.Shared.Core.Messages.Activity.New; using salesbook.Shared.Core.Messages.Back; using salesbook.Shared.Core.Messages.Contact; using salesbook.Shared.Core.Services; +using Shiny; namespace salesbook.Maui { @@ -64,6 +69,12 @@ namespace salesbook.Maui builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + + //Notification + builder.Services.AddNotifications(); + builder.Services.AddPush(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); diff --git a/salesbook.Maui/Platforms/Android/AndroidManifest.xml b/salesbook.Maui/Platforms/Android/AndroidManifest.xml index ddb278b..a8b23a0 100644 --- a/salesbook.Maui/Platforms/Android/AndroidManifest.xml +++ b/salesbook.Maui/Platforms/Android/AndroidManifest.xml @@ -1,6 +1,15 @@  - + + + + + + + + + + diff --git a/salesbook.Maui/Platforms/Android/MainActivity.cs b/salesbook.Maui/Platforms/Android/MainActivity.cs index 1c93fe9..75ed53c 100644 --- a/salesbook.Maui/Platforms/Android/MainActivity.cs +++ b/salesbook.Maui/Platforms/Android/MainActivity.cs @@ -7,6 +7,12 @@ namespace salesbook.Maui MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] + [IntentFilter([Shiny.ShinyPushIntents.NotificationClickAction], + Categories = new[] + { + "android.intent.category.DEFAULT" + } + )] public class MainActivity : MauiAppCompatActivity { } diff --git a/salesbook.Maui/Platforms/iOS/Info.plist b/salesbook.Maui/Platforms/iOS/Info.plist index a094b3e..0c883a2 100644 --- a/salesbook.Maui/Platforms/iOS/Info.plist +++ b/salesbook.Maui/Platforms/iOS/Info.plist @@ -45,5 +45,9 @@ NSPhotoLibraryAddUsageDescription Permette all'app di salvare file o immagini nella tua libreria fotografica se necessario. + UIBackgroundModes + + remote-notification + diff --git a/salesbook.Maui/google-services.json b/salesbook.Maui/google-services.json new file mode 100644 index 0000000..ef98865 --- /dev/null +++ b/salesbook.Maui/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "830771692001", + "project_id": "salesbook-smetar", + "storage_bucket": "salesbook-smetar.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:830771692001:android:06bc5a9706bc9bef3752a0", + "android_client_info": { + "package_name": "it.integry.salesbook" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyB43ai_Ph0phO_OkBC1wAOazKZUV9KsLaM" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/salesbook.Maui/salesbook.Maui.csproj b/salesbook.Maui/salesbook.Maui.csproj index 4c87907..3120900 100644 --- a/salesbook.Maui/salesbook.Maui.csproj +++ b/salesbook.Maui/salesbook.Maui.csproj @@ -93,6 +93,16 @@ --> + + + PreserveNewest + + + + + + + @@ -128,6 +138,8 @@ + + diff --git a/salesbook.Shared/Components/Pages/Home.razor b/salesbook.Shared/Components/Pages/Home.razor index 6e62825..190c941 100644 --- a/salesbook.Shared/Components/Pages/Home.razor +++ b/salesbook.Shared/Components/Pages/Home.razor @@ -4,6 +4,7 @@ @using salesbook.Shared.Components.Layout.Spinner @inject IFormFactor FormFactor @inject INetworkService NetworkService +@inject IFirebaseNotificationService FirebaseNotificationService @@ -11,6 +12,15 @@ { protected override async Task OnInitializedAsync() { + try + { + await FirebaseNotificationService.InitFirebase(); + } + catch (Exception e) + { + Console.WriteLine($"Firebase init: {e.Message}"); + } + var lastSyncDate = LocalStorage.Get("last-sync"); if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate.Equals(DateTime.MinValue)) diff --git a/salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs b/salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs new file mode 100644 index 0000000..47c6707 --- /dev/null +++ b/salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs @@ -0,0 +1,6 @@ +namespace salesbook.Shared.Core.Interface; + +public interface IFirebaseNotificationService +{ + Task InitFirebase(); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs b/salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs new file mode 100644 index 0000000..7eef3df --- /dev/null +++ b/salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Logging; + +namespace salesbook.Shared.Core.Interface; + +public interface IIntegryNotificationRestClient +{ + Task Register(string fcmToken, ILogger? logger1 = null); +} \ No newline at end of file From 588dbe308aab84e275d049ea9cec8da2863d7019 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Fri, 29 Aug 2025 18:20:07 +0200 Subject: [PATCH 03/24] Creata pagina step della commessa --- salesbook.Maui/MauiProgram.cs | 8 +- .../Components/Layout/NavMenu.razor | 4 +- .../Components/Pages/Commessa.razor | 95 ++++++++ .../Components/Pages/Commessa.razor.css | 215 ++++++++++++++++++ salesbook.Shared/Components/Pages/User.razor | 125 ++++++++-- .../Components/Pages/User.razor.css | 118 ++++++++++ salesbook.Shared/Components/Pages/Users.razor | 50 ++-- .../SingleElements/Card/CommessaCard.razor | 56 ++++- .../Card/CommessaCard.razor.css | 16 +- .../JobProgress/CRMJobProgressResponseDTO.cs | 9 + .../Core/Dto/JobProgress/CRMJobStatusDTO.cs | 15 ++ .../Core/Dto/JobProgress/CRMJobStepDTO.cs | 15 ++ .../Core/Dto/PageState/JobSteps.cs | 8 + .../Core/Dto/PageState/UserListState.cs | 9 + .../Core/Dto/PageState/UserPageState.cs | 15 ++ .../Core/Dto/Users/UserDisplayItem.cs | 8 + salesbook.Shared/Core/Entity/JtbComt.cs | 3 + .../Core/Interface/IIntegryApiService.cs | 3 + .../Core/Services/IntegryApiService.cs | 8 + salesbook.Shared/wwwroot/css/app.css | 4 +- 20 files changed, 732 insertions(+), 52 deletions(-) create mode 100644 salesbook.Shared/Components/Pages/Commessa.razor create mode 100644 salesbook.Shared/Components/Pages/Commessa.razor.css create mode 100644 salesbook.Shared/Core/Dto/JobProgress/CRMJobProgressResponseDTO.cs create mode 100644 salesbook.Shared/Core/Dto/JobProgress/CRMJobStatusDTO.cs create mode 100644 salesbook.Shared/Core/Dto/JobProgress/CRMJobStepDTO.cs create mode 100644 salesbook.Shared/Core/Dto/PageState/JobSteps.cs create mode 100644 salesbook.Shared/Core/Dto/PageState/UserListState.cs create mode 100644 salesbook.Shared/Core/Dto/PageState/UserPageState.cs create mode 100644 salesbook.Shared/Core/Dto/Users/UserDisplayItem.cs diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index 2b3aa2a..d6350f3 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -9,6 +9,7 @@ using MudExtensions.Services; using salesbook.Maui.Core.Services; using salesbook.Shared; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.PageState; using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; using salesbook.Shared.Core.Messages.Activity.Copy; @@ -58,6 +59,12 @@ namespace salesbook.Maui builder.Services.AddScoped(); builder.Services.AddScoped(); + //SessionData + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + //Message builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -73,7 +80,6 @@ namespace salesbook.Maui builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); return builder.Build(); } diff --git a/salesbook.Shared/Components/Layout/NavMenu.razor b/salesbook.Shared/Components/Layout/NavMenu.razor index 9cf2d2e..0bd0fee 100644 --- a/salesbook.Shared/Components/Layout/NavMenu.razor +++ b/salesbook.Shared/Components/Layout/NavMenu.razor @@ -67,10 +67,10 @@ { var location = args.Location.Remove(0, NavigationManager.BaseUri.Length); - var newIsVisible = new List { "Calendar", "Users", "Notifications" } + var newIsVisible = new List { "Calendar", "Users", "Notifications", "Commessa" } .Contains(location); - var newPlusVisible = new List { "Calendar", "Users" } + var newPlusVisible = new List { "Calendar", "Users", "Commessa" } .Contains(location); if (IsVisible == newIsVisible && PlusVisible == newPlusVisible) return; diff --git a/salesbook.Shared/Components/Pages/Commessa.razor b/salesbook.Shared/Components/Pages/Commessa.razor new file mode 100644 index 0000000..15a88fe --- /dev/null +++ b/salesbook.Shared/Components/Pages/Commessa.razor @@ -0,0 +1,95 @@ +@page "/commessa/{CodJcom}" +@page "/commessa/{CodJcom}/{RagSoc}" +@attribute [Authorize] +@using salesbook.Shared.Components.Layout +@using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.JobProgress +@using salesbook.Shared.Core.Dto.PageState +@using salesbook.Shared.Core.Entity +@using salesbook.Shared.Core.Interface +@inject JobSteps JobSteps +@inject IManageDataService ManageData + + + +@if (IsLoading) +{ + +} +else +{ +
+ @if (CommessaModel == null) + { + + } + else + { + + + + +
+
    +
  • +
  • +
  • +
+
+ + +
+
+ @if (Steps != null) + { +
+
+ @foreach (var step in Steps) + { +
+
+
+
@step.StepName
+ @if (step.Date is not null) + { +
@($"{step.Date.Value:D}") @(step.Status!.Progress ? "(In corso...)" : "")
+ } +
+
+ } +
+
+ } +
+
Contenuto 2
+
Contenuto 3
+
+ } +
+} + +@code { + [Parameter] public string CodJcom { get; set; } = ""; + [Parameter] public string RagSoc { get; set; } = ""; + + private List? Steps { get; set; } + private JtbComt? CommessaModel { get; set; } + + private bool IsLoading { get; set; } = true; + + protected override async Task OnInitializedAsync() + { + await LoadData(); + } + + private async Task LoadData() + { + CommessaModel = (await ManageData.GetTable(x => x.CodJcom.Equals(CodJcom))).LastOrDefault(); + Steps = JobSteps.Steps; + + IsLoading = false; + } + +} + diff --git a/salesbook.Shared/Components/Pages/Commessa.razor.css b/salesbook.Shared/Components/Pages/Commessa.razor.css new file mode 100644 index 0000000..6ae66ef --- /dev/null +++ b/salesbook.Shared/Components/Pages/Commessa.razor.css @@ -0,0 +1,215 @@ +/* Container scrollabile */ + +.timeline-container { + height: 100%; + overflow-y: auto; + padding-right: 10px; +} + +.timeline { + display: flex; + flex-direction: column; + gap: 1.5rem; + position: relative; + padding-left: 40px; /* spazio per linea e cerchi */ +} + +.step { + display: flex; + align-items: flex-start; + gap: 15px; + position: relative; +} + +/* Linea sopra e sotto ogni step */ + +.step::before, +.step::after { + content: ""; + position: absolute; + left: -31px; + width: 2px; + background: #ddd; +} + +.step::after { + top: 30%; + bottom: -1.5rem; +} + +.step:first-child::before { display: none; } + +.step:last-child::after { display: none; } + +/* Cerchio base */ + +.circle, +.in-progress { + width: 20px; + height: 20px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + z-index: 1; + margin-left: -40px; + background-color: var(--mud-palette-tertiary); +} + +/* Stato skippato */ + +.skipped { background: #ccc; } + +/* Stato completato */ + +.completed { + background: var(--mud-palette-primary); + color: white; + font-weight: bold; +} + +/* Stato in corso con spinner */ + +.in-progress { + border: 2px solid var(--mud-palette-primary); + border-top: 2px solid transparent; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + + 100% { transform: rotate(360deg); } +} + +/* Label con titolo + sottotitolo */ + +.label { + display: flex; + flex-direction: column; +} + +.titleStep { + font-size: 1.1rem; + font-weight: 600; + color: #111; + line-height: normal; +} + +.subtitleStep { + font-size: .90rem; + color: #666; + line-height: normal; +} + +.timeline-container::-webkit-scrollbar { width: 6px; } + +.timeline-container::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 3px; +} + +/*-------------- + TabPanel +----------------*/ + +.box { + display: flex; + flex-direction: column; + width: -webkit-fill-available; + box-shadow: var(--custom-box-shadow); + border-radius: 16px; + overflow: clip; + margin-bottom: 1rem; +} + +/* nascondo gli input */ + +.tab-toggle { display: none; } + +.tab-list { + margin: 0; + padding: 0; + list-style: none; + display: flex; + position: relative; + z-index: 1; + background: var(--mud-palette-surface); +} + +/* la lineetta */ + +.tab-list::before { + content: ''; + display: block; + height: 3px; + width: calc(100% / 3); + position: absolute; + bottom: 0; + left: 0; + background-color: var(--mud-palette-primary); + transition: transform .3s; +} + +.tab-item { + flex: 1; + text-align: center; + transition: .3s; + opacity: 0.5; +} + +.tab-trigger { + display: block; + padding: 10px 0; + cursor: pointer; +} + +/* tab attivo */ + +#tab1:checked ~ .box .tab-list .tab-item:nth-child(1), +#tab2:checked ~ .box .tab-list .tab-item:nth-child(2), +#tab3:checked ~ .box .tab-list .tab-item:nth-child(3) { + opacity: 1; + font-weight: bold; + display: block; +} + +/* spostamento lineetta */ + +#tab1:checked ~ .box .tab-list::before { transform: translateX(0%); } + +#tab2:checked ~ .box .tab-list::before { transform: translateX(100%); } + +#tab3:checked ~ .box .tab-list::before { transform: translateX(200%); } + +.tab-container { + display: flex; + flex-direction: column; + overflow: hidden; + width: -webkit-fill-available; +} + +.tab-content { + display: none; + flex: 1; + overflow-y: auto; + animation: fade .3s ease; + padding: .5rem; +} + +#tab1:checked ~ .tab-container .tab-content:nth-child(1), +#tab2:checked ~ .tab-container .tab-content:nth-child(2), +#tab3:checked ~ .tab-container .tab-content:nth-child(3) { display: block; } + +@keyframes fade { + from { + opacity: 0; + transform: translateY(5px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/User.razor b/salesbook.Shared/Components/Pages/User.razor index dedce52..0ef50bc 100644 --- a/salesbook.Shared/Components/Pages/User.razor +++ b/salesbook.Shared/Components/Pages/User.razor @@ -7,12 +7,15 @@ @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Core.Dto @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.JobProgress +@using salesbook.Shared.Core.Dto.PageState @inject IManageDataService ManageData @inject IMapper Mapper @inject IDialogService Dialog -@inject INetworkService NetworkService +@inject IIntegryApiService IntegryApiService +@inject UserPageState UserState - + @if (IsLoading) { @@ -89,11 +92,27 @@ else - - + + + +
+
    +
  • + +
  • +
  • + +
  • +
+
+ + +
+
+ @if (PersRif is { Count: > 0 }) { -
+
@{ var index = PersRif.IndexOf(person); @@ -117,20 +136,30 @@ else Aggiungi contatto
- - - @if (Commesse.IsNullOrEmpty()) +
+
+ + @if (LoadCommessa) { - + } else { - - - + @if (Commesse.IsNullOrEmpty()) + { + + } + else + { +
+ + + +
+ } } - - +
+
} @@ -140,14 +169,26 @@ else private ContactDTO Anag { get; set; } = new(); private List? PersRif { get; set; } - private List Commesse { get; set; } + private List? Commesse { get; set; } private StbUser? Agente { get; set; } + private Dictionary?> Steps { get; set; } = []; private bool IsLoading { get; set; } = true; + private bool LoadCommessa { get; set; } = true; protected override async Task OnInitializedAsync() { - await LoadData(); + if (UserState.CodUser != null && UserState.CodUser.Equals(CodContact)) + { + LoadDataFromSession(); + } + else + { + await LoadData(); + } + + IsLoading = false; + StateHasChanged(); } private async Task LoadData() @@ -164,18 +205,64 @@ else } await LoadPersRif(); - - Commesse = await ManageData.GetTable(x => x.CodAnag != null && x.CodAnag.Equals(CodContact)); + _ = LoadCommesse(); if (Anag.CodVage != null) { Agente = (await ManageData.GetTable(x => x.UserCode != null && x.UserCode.Equals(Anag.CodVage))).LastOrDefault(); } - IsLoading = false; + 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() { if (IsContact) diff --git a/salesbook.Shared/Components/Pages/User.razor.css b/salesbook.Shared/Components/Pages/User.razor.css index 45277bc..a47a4b1 100644 --- a/salesbook.Shared/Components/Pages/User.razor.css +++ b/salesbook.Shared/Components/Pages/User.razor.css @@ -87,6 +87,7 @@ box-shadow: var(--custom-box-shadow); padding: .25rem 0; border-radius: 16px; + margin-bottom: 0 !important; } .container-button .divider { @@ -156,4 +157,121 @@ display: flex; gap: 1rem; flex-direction: column; +} + +.commesse-container { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.commesse-container ::deep > div:first-child:not(.activity-card) { + display: none; +} + +/*-------------- + TabPanel +----------------*/ + +.box { + display: flex; + flex-direction: column; + width: -webkit-fill-available; + box-shadow: var(--custom-box-shadow); + border-radius: 16px; + overflow: clip; + margin-bottom: 1rem; +} + +/* nascondo gli input */ + +.tab-toggle { display: none; } + +.tab-list { + margin: 0; + padding: 0; + list-style: none; + display: flex; + position: relative; + z-index: 1; + background: var(--mud-palette-surface); +} + +/* la lineetta */ + +.tab-list::before { + content: ''; + display: block; + height: 3px; + width: calc(100% / 2); + position: absolute; + bottom: 0; + left: 0; + background-color: var(--mud-palette-primary); + transition: transform .3s; +} + +.tab-item { + flex: 1; + text-align: center; + transition: .3s; + opacity: 0.5; +} + +.tab-trigger { + display: block; + padding: 10px 0; + cursor: pointer; +} + +/* tab attivo */ + +#tab1:checked ~ .box .tab-list .tab-item:nth-child(1), +#tab2:checked ~ .box .tab-list .tab-item:nth-child(2) { + opacity: 1; + font-weight: bold; + display: block; +} + +/* spostamento lineetta */ + +#tab1:checked ~ .box .tab-list::before { transform: translateX(0%); } + +#tab2:checked ~ .box .tab-list::before { transform: translateX(100%); } + +.tab-container { + display: flex; + flex-direction: column; + overflow: hidden; + width: -webkit-fill-available; +} + +.tab-content { + display: none; + flex: 1; + overflow-y: auto; + animation: fade .3s ease; + padding: .5rem; +} + +.tab-content::-webkit-scrollbar { width: 3px; } + +.tab-content::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 2px; +} + +#tab1:checked ~ .tab-container .tab-content:nth-child(1), +#tab2:checked ~ .tab-container .tab-content:nth-child(2) { display: block; } + +@keyframes fade { + from { + opacity: 0; + transform: translateY(5px); + } + + to { + opacity: 1; + transform: translateY(0); + } } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/Users.razor b/salesbook.Shared/Components/Pages/Users.razor index 666fae8..76b12b5 100644 --- a/salesbook.Shared/Components/Pages/Users.razor +++ b/salesbook.Shared/Components/Pages/Users.razor @@ -6,11 +6,14 @@ @using salesbook.Shared.Components.SingleElements.BottomSheet @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.PageState +@using salesbook.Shared.Core.Dto.Users @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Messages.Contact @inject IManageDataService ManageData @inject NewContactService NewContact @inject FilterUserDTO Filter +@inject UserListState UserState @@ -70,22 +73,46 @@ protected override void OnInitialized() { NewContact.OnContactCreated += async response => await OnUserCreated(response); - Console.WriteLine($"Filter HashCode: {Filter.GetHashCode()} - IsInitialized: {Filter.IsInitialized}"); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - await LoadData(); + IsLoading = true; + StateHasChanged(); + + if (UserState.FilteredGroupedUserList == null && UserState.GroupedUserList == null) + { + await LoadData(); + SetDataSession(); + } + else + { + LoadFromSession(); + } + + FilterUsers(); + + IsLoading = false; + StateHasChanged(); } } + private void LoadFromSession() + { + GroupedUserList = UserState.GroupedUserList!; + FilteredGroupedUserList = UserState.FilteredGroupedUserList!; + } + + private void SetDataSession() + { + UserState.GroupedUserList = GroupedUserList; + UserState.FilteredGroupedUserList = FilteredGroupedUserList; + } + private async Task LoadData() { - IsLoading = true; - StateHasChanged(); - if (!Filter.IsInitialized) { var loggedUser = (await ManageData.GetTable(x => x.UserName.Equals(UserSession.User.Username))).Last(); @@ -126,19 +153,6 @@ HeaderLetter = currentLetter }); } - - FilterUsers(); - - IsLoading = false; - StateHasChanged(); - } - - - private class UserDisplayItem - { - public required ContactDTO User { get; set; } - public bool ShowHeader { get; set; } - public string? HeaderLetter { get; set; } } private void FilterUsers() => FilterUsers(false); diff --git a/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor b/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor index d8fb918..00ed37a 100644 --- a/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor @@ -1,24 +1,68 @@ +@using salesbook.Shared.Core.Dto.JobProgress +@using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity +@inject JobSteps JobSteps -
+
- @Commessa.Descrizione + @Commessa.CodJcom
- - @Commessa.CodJcom - + @if (LastUpd is not null) + { + Aggiornato il @($"{LastUpd:d}") + }
+ @Commessa.Descrizione
- Stato + @if (Stato is not null) + { + @Stato + }
@code { [Parameter] public JtbComt Commessa { get; set; } = new(); + [Parameter] public string RagSoc { get; set; } = ""; + [Parameter] public List? Steps { get; set; } + + private string? Stato { get; set; } + private DateTime? LastUpd { get; set; } + + protected override async Task OnParametersSetAsync() + { + GetStepInfo(); + } + + private void GetStepInfo() + { + if (Steps is null) return; + + var lastBeforeSkip = Steps + .TakeWhile(s => s.Status is { Skip: false }) + .LastOrDefault(); + + if (lastBeforeSkip is not null) Stato = lastBeforeSkip.StepName; + + LastUpd = Steps + .Where(s => s.Date.HasValue) + .Select(s => s.Date!.Value) + .DefaultIfEmpty() + .Max(); + + if (LastUpd.Equals(DateTime.MinValue)) LastUpd = null; + } + + private void OpenPageCommessa() + { + JobSteps.Steps = Steps; + NavigationManager.NavigateTo($"commessa/{Commessa.CodJcom}/{RagSoc}"); + } + } \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor.css b/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor.css index 947044b..40a538d 100644 --- a/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor.css +++ b/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor.css @@ -28,8 +28,9 @@ } .activity-hours { - font-weight: 700; - color: var(--mud-palette-text-primary); + color: var(--mud-palette-gray-darker); + font-weight: 600; + font-size: .8rem; } .activity-hours-section ::deep .mud-chip { margin: 5px 0 0 !important; } @@ -40,13 +41,18 @@ flex-direction: column; } -.title-section ::deep > .activity-title { +.activity-title { font-weight: 800 !important; margin: 0 !important; line-height: normal !important; color: var(--mud-palette-text-primary); } +.title-section ::deep > .activity-title { + font-weight: 600; + color: var(--mud-palette-text-primary); +} + .activity-body-section ::deep > .activity-subtitle { color: var(--mud-palette-gray-darker); margin: .2rem 0 !important; @@ -55,5 +61,7 @@ .activity-info-section { display: flex; - flex-wrap: wrap; + align-items: center; + justify-content: space-between; + margin-top: .25rem; } \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/JobProgress/CRMJobProgressResponseDTO.cs b/salesbook.Shared/Core/Dto/JobProgress/CRMJobProgressResponseDTO.cs new file mode 100644 index 0000000..ecec944 --- /dev/null +++ b/salesbook.Shared/Core/Dto/JobProgress/CRMJobProgressResponseDTO.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.JobProgress; + +public class CRMJobProgressResponseDTO +{ + [JsonPropertyName("steps")] + public List Steps { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/JobProgress/CRMJobStatusDTO.cs b/salesbook.Shared/Core/Dto/JobProgress/CRMJobStatusDTO.cs new file mode 100644 index 0000000..7318f3c --- /dev/null +++ b/salesbook.Shared/Core/Dto/JobProgress/CRMJobStatusDTO.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.JobProgress; + +public class CRMJobStatusDTO +{ + [JsonPropertyName("completed")] + public bool Completed { get; set; } + + [JsonPropertyName("progress")] + public bool Progress { get; set; } + + [JsonPropertyName("skip")] + public bool Skip { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/JobProgress/CRMJobStepDTO.cs b/salesbook.Shared/Core/Dto/JobProgress/CRMJobStepDTO.cs new file mode 100644 index 0000000..13c5cf8 --- /dev/null +++ b/salesbook.Shared/Core/Dto/JobProgress/CRMJobStepDTO.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.JobProgress; + +public class CRMJobStepDTO +{ + [JsonPropertyName("stepName")] + public string? StepName { get; set; } + + [JsonPropertyName("status")] + public CRMJobStatusDTO? Status { get; set; } + + [JsonPropertyName("date")] + public DateTime? Date { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/PageState/JobSteps.cs b/salesbook.Shared/Core/Dto/PageState/JobSteps.cs new file mode 100644 index 0000000..ec31be7 --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/JobSteps.cs @@ -0,0 +1,8 @@ +using salesbook.Shared.Core.Dto.JobProgress; + +namespace salesbook.Shared.Core.Dto.PageState; + +public class JobSteps +{ + public List? Steps { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/PageState/UserListState.cs b/salesbook.Shared/Core/Dto/PageState/UserListState.cs new file mode 100644 index 0000000..c1eb635 --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/UserListState.cs @@ -0,0 +1,9 @@ +using salesbook.Shared.Core.Dto.Users; + +namespace salesbook.Shared.Core.Dto.PageState; + +public class UserListState +{ + public List? GroupedUserList { get; set; } + public List? FilteredGroupedUserList { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/PageState/UserPageState.cs b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs new file mode 100644 index 0000000..a437ea1 --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs @@ -0,0 +1,15 @@ +using salesbook.Shared.Core.Dto.JobProgress; +using salesbook.Shared.Core.Entity; + +namespace salesbook.Shared.Core.Dto.PageState; + +public class UserPageState +{ + public string? CodUser { get; set; } + + public ContactDTO Anag { get; set; } + public List? PersRif { get; set; } + public List Commesse { get; set; } + public StbUser? Agente { get; set; } + public Dictionary?> Steps { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/Users/UserDisplayItem.cs b/salesbook.Shared/Core/Dto/Users/UserDisplayItem.cs new file mode 100644 index 0000000..53e007f --- /dev/null +++ b/salesbook.Shared/Core/Dto/Users/UserDisplayItem.cs @@ -0,0 +1,8 @@ +namespace salesbook.Shared.Core.Dto.Users; + +public class UserDisplayItem +{ + public required ContactDTO User { get; set; } + public bool ShowHeader { get; set; } + public string? HeaderLetter { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Entity/JtbComt.cs b/salesbook.Shared/Core/Entity/JtbComt.cs index 6c80f64..9d03496 100644 --- a/salesbook.Shared/Core/Entity/JtbComt.cs +++ b/salesbook.Shared/Core/Entity/JtbComt.cs @@ -107,4 +107,7 @@ public class JtbComt [Column("note_tecniche"), JsonPropertyName("noteTecniche")] public string NoteTecniche { get; set; } + + [Ignore, JsonIgnore] + public DateTime? LastUpd { get; set; } } \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IIntegryApiService.cs b/salesbook.Shared/Core/Interface/IIntegryApiService.cs index 3635223..90eb85c 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IIntegryApiService.cs @@ -1,4 +1,5 @@ using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.JobProgress; using salesbook.Shared.Core.Entity; namespace salesbook.Shared.Core.Interface; @@ -22,6 +23,8 @@ public interface IIntegryApiService Task> GetActivityFile(string activityId); Task DownloadFile(string activityId, string fileName); + Task RetrieveJobProgress(string codJcom); + //Position Task SavePosition(PositionDTO position); Task RetrievePosition(string id); diff --git a/salesbook.Shared/Core/Services/IntegryApiService.cs b/salesbook.Shared/Core/Services/IntegryApiService.cs index 5cc6714..112f87e 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -1,6 +1,7 @@ using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account; using IntegryApiClient.Core.Domain.RestClient.Contacts; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.JobProgress; using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Interface; using System.Net.Http.Headers; @@ -146,4 +147,11 @@ public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUser return integryApiRestClient.Get("retrievePosition", queryParams)!; } + + public Task RetrieveJobProgress(string codJcom) + { + var queryParams = new Dictionary { { "codJcom", codJcom } }; + + return integryApiRestClient.Get("crm/retrieveJobProgress", queryParams)!; + } } \ No newline at end of file diff --git a/salesbook.Shared/wwwroot/css/app.css b/salesbook.Shared/wwwroot/css/app.css index e224251..31a3a3c 100644 --- a/salesbook.Shared/wwwroot/css/app.css +++ b/salesbook.Shared/wwwroot/css/app.css @@ -31,11 +31,11 @@ a, .btn-link { .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; } .content { - /*padding-top: 1.1rem;*/ + padding-top: 1rem; display: flex; align-items: center; flex-direction: column; - height: 84vh; + height: 90vh; } h1:focus { outline: none; } From 8be3fa9f9ee8fb00ad2a35e4b2bed8731ba2b4d1 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Mon, 1 Sep 2025 17:38:16 +0200 Subject: [PATCH 04/24] Completata gestione allegati e riepilogo commessa --- .../Core/Services/AttachedService.cs | 48 +++++++++++- salesbook.Maui/Core/Services/SyncDbService.cs | 5 +- .../Components/Pages/Commessa.razor | 78 ++++++++++++++++++- .../Components/Pages/Commessa.razor.css | 15 ++++ .../SingleElements/Card/AttachCard.razor | 44 +++++++++++ .../SingleElements/Card/AttachCard.razor.css | 61 +++++++++++++++ .../SingleElements/Modal/ActivityForm.razor | 25 ++++-- .../SingleElements/Modal/ViewAttached.razor | 14 ---- .../Modal/ViewAttached.razor.css | 7 -- salesbook.Shared/Core/Dto/AttachedDTO.cs | 8 +- .../Core/Dto/CRMAttachedResponseDTO.cs | 27 +++++++ .../Core/Dto/CRMRetrieveActivityRequestDTO.cs | 13 ++++ .../Core/Helpers/IconConstants.cs | 2 + salesbook.Shared/Core/Helpers/ModalHelpers.cs | 19 ----- .../Core/Interface/IAttachedService.cs | 1 + .../Core/Interface/IIntegryApiService.cs | 4 +- .../Core/Services/IntegryApiService.cs | 30 +++++-- 17 files changed, 341 insertions(+), 60 deletions(-) create mode 100644 salesbook.Shared/Components/SingleElements/Card/AttachCard.razor create mode 100644 salesbook.Shared/Components/SingleElements/Card/AttachCard.razor.css delete mode 100644 salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor delete mode 100644 salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css create mode 100644 salesbook.Shared/Core/Dto/CRMAttachedResponseDTO.cs create mode 100644 salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs diff --git a/salesbook.Maui/Core/Services/AttachedService.cs b/salesbook.Maui/Core/Services/AttachedService.cs index fa432d3..1aa4fe4 100644 --- a/salesbook.Maui/Core/Services/AttachedService.cs +++ b/salesbook.Maui/Core/Services/AttachedService.cs @@ -57,9 +57,53 @@ public class AttachedService : IAttachedService Name = file.FileName, Path = file.FullPath, MimeType = file.ContentType, - DimensionBytes= ms.Length, - FileContent = ms.ToArray(), + DimensionBytes = ms.Length, + FileBytes = ms.ToArray(), Type = type }; } + + private static async Task SaveToTempStorage(Stream file, string fileName) + { + var cacheDirectory = FileSystem.CacheDirectory; + var targetDirectory = Path.Combine(cacheDirectory, "file"); + + if (!Directory.Exists(targetDirectory)) Directory.CreateDirectory(targetDirectory); + + var tempFilePath = Path.Combine(targetDirectory, fileName + ".temp"); + var filePath = Path.Combine(targetDirectory, fileName); + + if (File.Exists(filePath)) return filePath; + + try + { + await using var fileStream = + new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None); + await file.CopyToAsync(fileStream); + + File.Move(tempFilePath, filePath); + } + catch (Exception e) + { + Console.WriteLine($"Errore durante il salvataggio dello stream: {e.Message}"); + return null; + } + finally + { + if (File.Exists(tempFilePath)) File.Delete(tempFilePath); + } + + return filePath; + } + + public async Task OpenFile(Stream file, string fileName) + { + var filePath = await SaveToTempStorage(file, fileName); + + if (filePath is null) return; + await Launcher.OpenAsync(new OpenFileRequest + { + File = new ReadOnlyFile(filePath) + }); + } } \ No newline at end of file diff --git a/salesbook.Maui/Core/Services/SyncDbService.cs b/salesbook.Maui/Core/Services/SyncDbService.cs index 8178aa6..a3d21ed 100644 --- a/salesbook.Maui/Core/Services/SyncDbService.cs +++ b/salesbook.Maui/Core/Services/SyncDbService.cs @@ -1,4 +1,5 @@ -using salesbook.Shared.Core.Helpers; +using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; namespace salesbook.Maui.Core.Services; @@ -7,7 +8,7 @@ public class SyncDbService(IIntegryApiService integryApiService, LocalDbService { public async Task GetAndSaveActivity(string? dateFilter) { - var allActivity = await integryApiService.RetrieveActivity(dateFilter); + var allActivity = await integryApiService.RetrieveActivity(new CRMRetrieveActivityRequestDTO{DateFilter = dateFilter}); if (!allActivity.IsNullOrEmpty()) if (dateFilter is null) diff --git a/salesbook.Shared/Components/Pages/Commessa.razor b/salesbook.Shared/Components/Pages/Commessa.razor index 15a88fe..a51fb84 100644 --- a/salesbook.Shared/Components/Pages/Commessa.razor +++ b/salesbook.Shared/Components/Pages/Commessa.razor @@ -1,15 +1,19 @@ @page "/commessa/{CodJcom}" @page "/commessa/{CodJcom}/{RagSoc}" @attribute [Authorize] +@using AutoMapper @using salesbook.Shared.Components.Layout @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto @using salesbook.Shared.Core.Dto.JobProgress @using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Interface @inject JobSteps JobSteps @inject IManageDataService ManageData +@inject IIntegryApiService IntegryApiService +@inject IMapper Mapper @@ -62,8 +66,48 @@ else
} -
Contenuto 2
-
Contenuto 3
+
+ @if (ActivityIsLoading) + { + + } + else + { + @if (ActivityList is { Count: > 0 }) + { +
+ + + +
+ } + else + { + + } + } +
+
+ @if (AttachedIsLoading) + { + + } + else + { + @if (ListAttached != null) + { +
+ + + +
+ } + else + { + + } + } +
} @@ -74,9 +118,13 @@ else [Parameter] public string RagSoc { get; set; } = ""; private List? Steps { get; set; } + private List ActivityList { get; set; } = []; + private List? ListAttached { get; set; } private JtbComt? CommessaModel { get; set; } private bool IsLoading { get; set; } = true; + private bool ActivityIsLoading { get; set; } = true; + private bool AttachedIsLoading { get; set; } = true; protected override async Task OnInitializedAsync() { @@ -88,8 +136,34 @@ else CommessaModel = (await ManageData.GetTable(x => x.CodJcom.Equals(CodJcom))).LastOrDefault(); Steps = JobSteps.Steps; + _ = LoadActivity(); + _ = LoadAttached(); + IsLoading = false; } + private async Task LoadActivity() + { + await Task.Run(async () => + { + var activities = await IntegryApiService.RetrieveActivity(new CRMRetrieveActivityRequestDTO { CodJcom = CodJcom }); + ActivityList = Mapper.Map>(activities); + }); + + ActivityIsLoading = false; + StateHasChanged(); + } + + private async Task LoadAttached() + { + await Task.Run(async () => + { + ListAttached = await IntegryApiService.RetrieveAttached(CodJcom); + }); + + AttachedIsLoading = false; + StateHasChanged(); + } + } diff --git a/salesbook.Shared/Components/Pages/Commessa.razor.css b/salesbook.Shared/Components/Pages/Commessa.razor.css index 6ae66ef..bc18e3a 100644 --- a/salesbook.Shared/Components/Pages/Commessa.razor.css +++ b/salesbook.Shared/Components/Pages/Commessa.razor.css @@ -198,6 +198,21 @@ padding: .5rem; } +.contentFlex { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.tab-content::-webkit-scrollbar { width: 6px; } + +.tab-content::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 3px; +} + +.contentFlex ::deep > div:first-child:not(.activity-card) { display: none; } + #tab1:checked ~ .tab-container .tab-content:nth-child(1), #tab2:checked ~ .tab-container .tab-content:nth-child(2), #tab3:checked ~ .tab-container .tab-content:nth-child(3) { display: block; } diff --git a/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor new file mode 100644 index 0000000..26d27a8 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor @@ -0,0 +1,44 @@ +@using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Interface +@inject IIntegryApiService IntegryApiService +@inject IAttachedService AttachedService + +
+
+
+
+ + @(Attached.Description.IsNullOrEmpty() ? Attached.FileName : Attached.Description) + +
+ @($"{Attached.DateAttached:g}") +
+
+
+
+ +
+ @if (Attached.IsActivity) + { + + @Attached.RefAttached + + } + else + { + + @Attached.RefAttached + + } +
+
+ +@code { + [Parameter] public CRMAttachedResponseDTO Attached { get; set; } = new(); + + private async Task OpenAttached() + { + var bytes = await IntegryApiService.DownloadFileFromRefUuid(Attached.RefUuid, Attached.FileName); + await AttachedService.OpenFile(bytes, Attached.FileName); + } +} \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor.css b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor.css new file mode 100644 index 0000000..0e0c335 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor.css @@ -0,0 +1,61 @@ +.activity-card { + width: 100%; + display: flex; + flex-direction: column; + padding: .5rem .5rem; + border-radius: 12px; + line-height: normal; + box-shadow: var(--custom-box-shadow); +} + +.activity-card.memo { border-left: 5px solid var(--mud-palette-info-darken); } + +.activity-card.interna { border-left: 5px solid var(--mud-palette-success-darken); } + +.activity-card.commessa { border-left: 5px solid var(--mud-palette-warning); } + +.activity-left-section { + display: flex; + align-items: center; + margin-left: 4px; +} + +.title-section { + display: flex; + flex-direction: column; + width: 100%; +} + +.activity-hours { + color: var(--mud-palette-gray-darker); + font-weight: 600; + font-size: .8rem; +} + +.activity-hours-section ::deep .mud-chip { margin: 5px 0 0 !important; } + +.activity-body-section { + width: 100%; + display: flex; + flex-direction: column; +} + +.title-section ::deep > .activity-title { + font-weight: 700 !important; + margin: 0 !important; + line-height: normal !important; + color: var(--mud-palette-text-primary); +} + +.activity-body-section ::deep > .activity-subtitle { + color: var(--mud-palette-gray-darker); + margin: .2rem 0 !important; + line-height: normal !important; +} + +.activity-info-section { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: .25rem; +} \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor index b91bc5e..40dbd59 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -13,6 +13,7 @@ @inject IIntegryApiService IntegryApiService @inject IMessenger Messenger @inject IDialogService Dialog +@inject IAttachedService AttachedService @@ -142,7 +143,7 @@ { foreach (var file in ActivityFileList) { - + @file.FileName } @@ -315,7 +316,7 @@ await ManageData.InsertOrUpdate(newActivity); - await SaveAttached(newActivity.ActivityId); + await SaveAttached(newActivity.ActivityId!); SuccessAnimation = true; StateHasChanged(); @@ -353,7 +354,7 @@ { if (attached.FileContent is not null && attached.Type != AttachedDTO.TypeAttached.Position) { - await IntegryApiService.UploadFile(activityId, attached.FileContent, attached.Name); + await IntegryApiService.UploadFile(activityId, attached.FileBytes, attached.Name); } } } @@ -541,12 +542,26 @@ StateHasChanged(); } + private async Task OpenAttached(string idAttached, string fileName) + { + try + { + var bytes = await IntegryApiService.DownloadFile(ActivityModel.ActivityId!, fileName); + await AttachedService.OpenFile(bytes, fileName); + } + catch (Exception ex) + { + Snackbar.Clear(); + Snackbar.Add("Impossibile aprire il file", Severity.Error); + Console.WriteLine($"Errore durante l'apertura del file: {ex.Message}"); + } + } + private async Task OpenAttached(AttachedDTO attached) { if (attached is { FileContent: not null, MimeType: not null }) { - var fileViewerUrl = $"data:{attached.MimeType};base64,{Convert.ToBase64String(attached.FileContent)}"; - await ModalHelpers.OpenViewAttach(Dialog, fileViewerUrl); + await AttachedService.OpenFile(attached.FileContent!, attached.Name); } else { diff --git a/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor b/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor deleted file mode 100644 index 0dc97c5..0000000 --- a/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor +++ /dev/null @@ -1,14 +0,0 @@ - - - @if (!string.IsNullOrEmpty(FileViewerUrl)) - { - - } - - - -@code { - [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } - - [Parameter] public string? FileViewerUrl { get; set; } -} \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css b/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css deleted file mode 100644 index 6eeaddd..0000000 --- a/salesbook.Shared/Components/SingleElements/Modal/ViewAttached.razor.css +++ /dev/null @@ -1,7 +0,0 @@ -.content.attached { - display: flex; - flex-direction: column; - gap: 2rem; - padding: 1rem; - height: unset; -} diff --git a/salesbook.Shared/Core/Dto/AttachedDTO.cs b/salesbook.Shared/Core/Dto/AttachedDTO.cs index 74e91b3..d125cbf 100644 --- a/salesbook.Shared/Core/Dto/AttachedDTO.cs +++ b/salesbook.Shared/Core/Dto/AttachedDTO.cs @@ -8,8 +8,12 @@ public class AttachedDTO public string? MimeType { get; set; } public long? DimensionBytes { get; set; } public string? Path { get; set; } - public byte[]? FileContent { get; set; } - + + public byte[]? FileBytes { get; set; } + + public Stream? FileContent => + FileBytes is null ? null : new MemoryStream(FileBytes); + public double? Lat { get; set; } public double? Lng { get; set; } diff --git a/salesbook.Shared/Core/Dto/CRMAttachedResponseDTO.cs b/salesbook.Shared/Core/Dto/CRMAttachedResponseDTO.cs new file mode 100644 index 0000000..9914954 --- /dev/null +++ b/salesbook.Shared/Core/Dto/CRMAttachedResponseDTO.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto; + +public class CRMAttachedResponseDTO +{ + [JsonPropertyName("fileName")] + public string FileName { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("dateAttached")] + public DateTime DateAttached { get; set; } + + [JsonPropertyName("fileSize")] + public decimal FileSize { get; set; } + + [JsonPropertyName("refUuid")] + public string RefUuid { get; set; } + + [JsonPropertyName("refAttached")] + public string RefAttached { get; set; } + + [JsonPropertyName("activity")] + public bool IsActivity { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs b/salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs new file mode 100644 index 0000000..4953c16 --- /dev/null +++ b/salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using Java.Time; + +namespace salesbook.Shared.Core.Dto; + +public class CRMRetrieveActivityRequestDTO +{ + [JsonPropertyName("dateFilter")] + public string? DateFilter { get; set; } + + [JsonPropertyName("codJcom")] + public string? CodJcom { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Helpers/IconConstants.cs b/salesbook.Shared/Core/Helpers/IconConstants.cs index 23df9cc..c7b6e97 100644 --- a/salesbook.Shared/Core/Helpers/IconConstants.cs +++ b/salesbook.Shared/Core/Helpers/IconConstants.cs @@ -7,5 +7,7 @@ class IconConstants public const string Stato = "ri-list-check-3 fa-fw fa-chip"; public const string User = "ri-user-fill fa-fw fa-chip"; public const string Time = "ri-time-line fa-fw fa-chip"; + public const string FileTextLine = "ri-file-text-line fa-fw fa-chip"; + public const string Tag = "ri-price-tag-3-fill fa-fw fa-chip"; } } \ No newline at end of file diff --git a/salesbook.Shared/Core/Helpers/ModalHelpers.cs b/salesbook.Shared/Core/Helpers/ModalHelpers.cs index 574a8ba..fa199f6 100644 --- a/salesbook.Shared/Core/Helpers/ModalHelpers.cs +++ b/salesbook.Shared/Core/Helpers/ModalHelpers.cs @@ -85,23 +85,4 @@ public class ModalHelpers return await modal.Result; } - - public static async Task OpenViewAttach(IDialogService dialog, string? fileViewUrl) - { - var modal = await dialog.ShowAsync( - "View attached", - new DialogParameters - { - { x => x.FileViewerUrl, fileViewUrl } - }, - new DialogOptions - { - FullScreen = true, - CloseButton = true, - NoHeader = true - } - ); - - return await modal.Result; - } } \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IAttachedService.cs b/salesbook.Shared/Core/Interface/IAttachedService.cs index 6897b19..a038c49 100644 --- a/salesbook.Shared/Core/Interface/IAttachedService.cs +++ b/salesbook.Shared/Core/Interface/IAttachedService.cs @@ -7,4 +7,5 @@ public interface IAttachedService Task SelectImage(); Task SelectFile(); Task SelectPosition(); + Task OpenFile(Stream file, string fileName); } \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IIntegryApiService.cs b/salesbook.Shared/Core/Interface/IIntegryApiService.cs index 90eb85c..a04484d 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IIntegryApiService.cs @@ -6,11 +6,12 @@ namespace salesbook.Shared.Core.Interface; public interface IIntegryApiService { - Task?> RetrieveActivity(string? dateFilter = null); + Task?> RetrieveActivity(CRMRetrieveActivityRequestDTO activityRequest); Task?> RetrieveAllCommesse(string? dateFilter = null); Task RetrieveAnagClie(string? dateFilter = null); Task RetrieveProspect(string? dateFilter = null); Task RetrieveSettings(); + Task?> RetrieveAttached(string codJcom); Task DeleteActivity(string activityId); @@ -22,6 +23,7 @@ public interface IIntegryApiService Task UploadFile(string id, byte[] file, string fileName); Task> GetActivityFile(string activityId); Task DownloadFile(string activityId, string fileName); + Task DownloadFileFromRefUuid(string refUuid, string fileName); Task RetrieveJobProgress(string codJcom); diff --git a/salesbook.Shared/Core/Services/IntegryApiService.cs b/salesbook.Shared/Core/Services/IntegryApiService.cs index 112f87e..a29e251 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -11,12 +11,8 @@ namespace salesbook.Shared.Core.Services; public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUserSession userSession) : IIntegryApiService { - public Task?> RetrieveActivity(string? dateFilter) - { - var queryParams = new Dictionary { { "dateFilter", dateFilter ?? "2020-01-01" } }; - - return integryApiRestClient.AuthorizedGet?>("crm/retrieveActivity", queryParams); - } + public Task?> RetrieveActivity(CRMRetrieveActivityRequestDTO activityRequest) => + integryApiRestClient.AuthorizedPost?>("crm/retrieveActivity", activityRequest); public Task?> RetrieveAllCommesse(string? dateFilter) { @@ -57,6 +53,17 @@ public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUser public Task RetrieveSettings() => integryApiRestClient.AuthorizedGet("crm/retrieveSettings")!; + public Task?> RetrieveAttached(string codJcom) + { + var queryParams = new Dictionary + { + { "codJcom", codJcom } + }; + + return integryApiRestClient.AuthorizedGet?>("crm/retrieveAttachedForCodJcom", + queryParams); + } + public Task DeleteActivity(string activityId) { var queryParams = new Dictionary @@ -138,6 +145,17 @@ public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUser public Task DownloadFile(string activityId, string fileName) => integryApiRestClient.Download($"downloadStbFileAttachment/{activityId}/{fileName}")!; + public Task DownloadFileFromRefUuid(string refUuid, string fileName) + { + var queryParams = new Dictionary + { + { "refUuid", refUuid }, + { "fileName", fileName } + }; + + return integryApiRestClient.Download("downloadFileFromRefUuid", queryParams); + } + public Task SavePosition(PositionDTO position) => integryApiRestClient.Post("savePosition", position)!; From 374b99501e6e8ac3feac78d9b5a6ab3b2e378e8e Mon Sep 17 00:00:00 2001 From: MarcoE Date: Wed, 3 Sep 2025 15:46:49 +0200 Subject: [PATCH 05/24] Prima parte di migliorie per la sincronizzazione dei dati --- .../Core/Services/ManageDataService.cs | 184 +++++++++++++++++- salesbook.Maui/Core/Services/SyncDbService.cs | 54 +---- .../Components/Layout/NavMenu.razor | 1 + .../Components/Pages/Calendar.razor | 16 +- .../Components/Pages/Calendar.razor.css | 2 +- .../Components/Pages/Commessa.razor | 1 + .../Components/Pages/SyncPage.razor | 31 --- salesbook.Shared/Components/Pages/Users.razor | 3 +- .../BottomSheet/FilterActivity.razor | 1 + .../BottomSheet/SelectEsito.razor | 1 + .../SingleElements/Card/ActivityCard.razor | 1 + .../SingleElements/Modal/ActivityForm.razor | 14 +- .../SingleElements/Modal/ContactForm.razor | 5 +- .../Core/Dto/{ => Activity}/ActivityDTO.cs | 2 +- .../Activity/CRMRetrieveActivityRequestDTO.cs | 21 ++ .../Dto/{ => Activity}/FilterActivityDTO.cs | 2 +- .../Core/Dto/Activity/WhereCondActivity.cs | 11 ++ .../Core/Dto/CRMRetrieveActivityRequestDTO.cs | 13 -- .../Core/Dto/Contact/CRMAnagRequestDTO.cs | 19 ++ .../Core/Dto/Contact/CRMProspectRequestDTO.cs | 17 ++ .../Core/Dto/Contact/WhereCondContact.cs | 10 + ...ResponseDTO.cs => UsersSyncResponseDTO.cs} | 2 +- .../Core/Helpers/MappingProfile.cs | 1 + salesbook.Shared/Core/Helpers/ModalHelpers.cs | 1 + .../Core/Interface/IIntegryApiService.cs | 6 +- .../Core/Interface/IManageDataService.cs | 15 +- .../Core/Interface/ISyncDbService.cs | 3 - .../Activity/Copy/CopyActivityMessage.cs | 2 +- .../Activity/Copy/CopyActivityService.cs | 2 +- .../Core/Services/IntegryApiService.cs | 28 +-- .../Core/Services/ManageDataService.cs | 18 +- salesbook.Web/Core/Services/SyncDbService.cs | 5 +- 32 files changed, 326 insertions(+), 166 deletions(-) rename salesbook.Shared/Core/Dto/{ => Activity}/ActivityDTO.cs (99%) create mode 100644 salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs rename salesbook.Shared/Core/Dto/{ => Activity}/FilterActivityDTO.cs (91%) create mode 100644 salesbook.Shared/Core/Dto/Activity/WhereCondActivity.cs delete mode 100644 salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs create mode 100644 salesbook.Shared/Core/Dto/Contact/CRMAnagRequestDTO.cs create mode 100644 salesbook.Shared/Core/Dto/Contact/CRMProspectRequestDTO.cs create mode 100644 salesbook.Shared/Core/Dto/Contact/WhereCondContact.cs rename salesbook.Shared/Core/Dto/{TaskSyncResponseDTO.cs => UsersSyncResponseDTO.cs} (94%) diff --git a/salesbook.Maui/Core/Services/ManageDataService.cs b/salesbook.Maui/Core/Services/ManageDataService.cs index 74f2a98..44555b0 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -1,21 +1,133 @@ using AutoMapper; -using System.Linq.Expressions; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; +using salesbook.Shared.Core.Dto.Contact; using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Helpers.Enum; using salesbook.Shared.Core.Interface; +using Sentry.Protocol; +using System.Linq.Expressions; namespace salesbook.Maui.Core.Services; -public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManageDataService +public class ManageDataService( + LocalDbService localDb, + IMapper mapper, + IIntegryApiService integryApiService, + INetworkService networkService +) : IManageDataService { public Task> GetTable(Expression>? whereCond = null) where T : new() => localDb.Get(whereCond); - public async Task> GetContact() + public async Task> GetClienti(WhereCondContact? whereCond) { - var contactList = await localDb.Get(x => x.FlagStato.Equals("A")); - var prospectList = await localDb.Get(); + List clienti = []; + whereCond ??= new WhereCondContact(); + whereCond.OnlyContact = true; + + if (networkService.IsNetworkAvailable()) + { + var response = await integryApiService.RetrieveAnagClie( + new CRMAnagRequestDTO + { + CodAnag = whereCond.CodAnag, + FlagStato = whereCond.FlagStato, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact + } + ); + _ = UpdateDbUsers(response); + + clienti = response.AnagClie ?? []; + } + else + { + clienti = await localDb.Get(x => + (whereCond.FlagStato != null && x.FlagStato.Equals(whereCond.FlagStato)) || + (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) || + (whereCond.PartIva == null && whereCond.FlagStato == null) + ); + } + + return clienti; + } + + public async Task> GetProspect(WhereCondContact? whereCond) + { + List prospect = []; + whereCond ??= new WhereCondContact(); + whereCond.OnlyContact = true; + + if (networkService.IsNetworkAvailable()) + { + var response = await integryApiService.RetrieveProspect( + new CRMProspectRequestDTO + { + CodPpro = whereCond.CodAnag, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact + } + ); + _ = UpdateDbUsers(response); + + prospect = response.PtbPros ?? []; + } + else + { + prospect = await localDb.Get(x => + (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) || + (whereCond.PartIva == null) + ); + } + + return prospect; + } + + public async Task> GetContact(WhereCondContact? whereCond) + { + List? contactList; + List? prospectList; + whereCond ??= new WhereCondContact(); + + if (networkService.IsNetworkAvailable()) + { + var clienti = await integryApiService.RetrieveAnagClie( + new CRMAnagRequestDTO + { + CodAnag = whereCond.CodAnag, + FlagStato = whereCond.FlagStato, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact + } + ); + _ = UpdateDbUsers(clienti); + + var prospect = await integryApiService.RetrieveProspect( + new CRMProspectRequestDTO + { + CodPpro = whereCond.CodAnag, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact + } + ); + _ = UpdateDbUsers(prospect); + + contactList = clienti.AnagClie; + prospectList = prospect.PtbPros; + } + else + { + contactList = await localDb.Get(x => + (whereCond.FlagStato != null && x.FlagStato.Equals(whereCond.FlagStato)) || + (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) || + (whereCond.PartIva == null && whereCond.FlagStato == null) + ); + prospectList = await localDb.Get(x => + (whereCond.PartIva != null && x.PartIva.Equals(whereCond.PartIva)) || + (whereCond.PartIva == null) + ); + } // Mappa i contatti var contactMapper = mapper.Map>(contactList); @@ -46,9 +158,35 @@ public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManage } } - public async Task> GetActivity(Expression>? whereCond = null) + public async Task> GetActivity(WhereCondActivity whereCond, bool useLocalDb) { - var activities = await localDb.Get(whereCond); + List? activities; + + if (networkService.IsNetworkAvailable() && !useLocalDb) + { + activities = await integryApiService.RetrieveActivity( + new CRMRetrieveActivityRequestDTO + { + StarDate = whereCond.Start, + EndDate = whereCond.End, + ActivityId = whereCond.ActivityId + } + ); + + _ = UpdateDb(activities); + } + else + { + activities = await localDb.Get(x => + (whereCond.ActivityId != null && x.ActivityId != null && whereCond.ActivityId.Equals(x.ActivityId)) || + (whereCond.Start != null && whereCond.End != null && x.EffectiveDate == null && + x.EstimatedDate >= whereCond.Start && x.EstimatedDate <= whereCond.End) || + (x.EffectiveDate >= whereCond.Start && x.EffectiveDate <= whereCond.End) || + (whereCond.ActivityId == null && (whereCond.Start == null || whereCond.End == null)) + ); + } + + if (activities == null) return []; var codJcomList = activities .Select(x => x.CodJcom) @@ -103,7 +241,37 @@ public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManage return returnDto; } - public Task InsertOrUpdate(List listToSave) => + private Task UpdateDbUsers(UsersSyncResponseDTO response) + { + return Task.Run(async () => + { + if (response.AnagClie != null) + { + await localDb.InsertOrUpdate(response.AnagClie); + + if (response.VtbDest != null) await localDb.InsertOrUpdate(response.VtbDest); + if (response.VtbCliePersRif != null) await localDb.InsertOrUpdate(response.VtbCliePersRif); + } + + if (response.PtbPros != null) + { + await localDb.InsertOrUpdate(response.PtbPros); + + if (response.PtbProsRif != null) await localDb.InsertOrUpdate(response.PtbProsRif); + } + }); + } + + private Task UpdateDb(List? entityList) + { + return Task.Run(() => + { + if (entityList == null) return; + _ = localDb.InsertOrUpdate(entityList); + }); + } + + public Task InsertOrUpdate(List listToSave) => localDb.InsertOrUpdate(listToSave); public Task InsertOrUpdate(T objectToSave) => diff --git a/salesbook.Maui/Core/Services/SyncDbService.cs b/salesbook.Maui/Core/Services/SyncDbService.cs index a3d21ed..f6f9be2 100644 --- a/salesbook.Maui/Core/Services/SyncDbService.cs +++ b/salesbook.Maui/Core/Services/SyncDbService.cs @@ -1,22 +1,10 @@ -using salesbook.Shared.Core.Dto; -using salesbook.Shared.Core.Helpers; +using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; namespace salesbook.Maui.Core.Services; public class SyncDbService(IIntegryApiService integryApiService, LocalDbService localDb) : ISyncDbService { - public async Task GetAndSaveActivity(string? dateFilter) - { - var allActivity = await integryApiService.RetrieveActivity(new CRMRetrieveActivityRequestDTO{DateFilter = dateFilter}); - - if (!allActivity.IsNullOrEmpty()) - if (dateFilter is null) - await localDb.InsertAll(allActivity!); - else - await localDb.InsertOrUpdate(allActivity!); - } - public async Task GetAndSaveCommesse(string? dateFilter) { var allCommesse = await integryApiService.RetrieveAllCommesse(dateFilter); @@ -28,46 +16,6 @@ public class SyncDbService(IIntegryApiService integryApiService, LocalDbService await localDb.InsertOrUpdate(allCommesse!); } - public async Task GetAndSaveProspect(string? dateFilter) - { - var taskSyncResponseDto = await integryApiService.RetrieveProspect(dateFilter); - - if (!taskSyncResponseDto.PtbPros.IsNullOrEmpty()) - if (dateFilter is null) - await localDb.InsertAll(taskSyncResponseDto.PtbPros!); - else - await localDb.InsertOrUpdate(taskSyncResponseDto.PtbPros!); - - if (!taskSyncResponseDto.PtbProsRif.IsNullOrEmpty()) - if (dateFilter is null) - await localDb.InsertAll(taskSyncResponseDto.PtbProsRif!); - else - await localDb.InsertOrUpdate(taskSyncResponseDto.PtbProsRif!); - } - - public async Task GetAndSaveClienti(string? dateFilter) - { - var taskSyncResponseDto = await integryApiService.RetrieveAnagClie(dateFilter); - - if (!taskSyncResponseDto.AnagClie.IsNullOrEmpty()) - if (dateFilter is null) - await localDb.InsertAll(taskSyncResponseDto.AnagClie!); - else - await localDb.InsertOrUpdate(taskSyncResponseDto.AnagClie!); - - if (!taskSyncResponseDto.VtbDest.IsNullOrEmpty()) - if (dateFilter is null) - await localDb.InsertAll(taskSyncResponseDto.VtbDest!); - else - await localDb.InsertOrUpdate(taskSyncResponseDto.VtbDest!); - - if (!taskSyncResponseDto.VtbCliePersRif.IsNullOrEmpty()) - if (dateFilter is null) - await localDb.InsertAll(taskSyncResponseDto.VtbCliePersRif!); - else - await localDb.InsertOrUpdate(taskSyncResponseDto.VtbCliePersRif!); - } - public async Task GetAndSaveSettings(string? dateFilter) { if (dateFilter is not null) diff --git a/salesbook.Shared/Components/Layout/NavMenu.razor b/salesbook.Shared/Components/Layout/NavMenu.razor index 0bd0fee..a4cdf53 100644 --- a/salesbook.Shared/Components/Layout/NavMenu.razor +++ b/salesbook.Shared/Components/Layout/NavMenu.razor @@ -1,5 +1,6 @@ @using CommunityToolkit.Mvvm.Messaging @using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Messages.Activity.Copy @using salesbook.Shared.Core.Messages.Activity.New diff --git a/salesbook.Shared/Components/Pages/Calendar.razor b/salesbook.Shared/Components/Pages/Calendar.razor index 4dac748..3429e2b 100644 --- a/salesbook.Shared/Components/Pages/Calendar.razor +++ b/salesbook.Shared/Components/Pages/Calendar.razor @@ -1,10 +1,10 @@ @page "/Calendar" -@using salesbook.Shared.Core.Dto -@using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout -@using salesbook.Shared.Components.SingleElements @using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Components.SingleElements @using salesbook.Shared.Components.SingleElements.BottomSheet +@using salesbook.Shared.Core.Dto.Activity +@using salesbook.Shared.Core.Interface @using salesbook.Shared.Core.Messages.Activity.New @inject IManageDataService ManageData @inject IJSRuntime JS @@ -471,9 +471,7 @@ var start = CurrentMonth; var end = start.AddDays(DaysInMonth - 1); - var activities = await ManageData.GetActivity(x => - (x.EffectiveDate == null && x.EstimatedDate >= start && x.EstimatedDate <= end) || - (x.EffectiveDate >= start && x.EffectiveDate <= end)); + var activities = await ManageData.GetActivity(new WhereCondActivity{Start = start, End = end}); MonthActivities = activities.OrderBy(x => x.EffectiveDate ?? x.EstimatedDate).ToList(); PrepareRenderingData(); @@ -541,7 +539,7 @@ await ManageData.DeleteActivity(activity); - var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId.Equals(activity.ActivityId)); + var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId!.Equals(activity.ActivityId)); if (indexActivity != null) { @@ -558,7 +556,7 @@ { IsLoading = true; - var activity = (await ManageData.GetActivity(x => x.ActivityId.Equals(activityId))).LastOrDefault(); + var activity = (await ManageData.GetActivity(new WhereCondActivity {ActivityId = activityId}, true)).LastOrDefault(); if (activity == null) { @@ -583,7 +581,7 @@ private async Task OnActivityChanged(string activityId) { - var newActivity = await ManageData.GetActivity(x => x.ActivityId.Equals(activityId)); + var newActivity = await ManageData.GetActivity(new WhereCondActivity { ActivityId = activityId }, true); var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId.Equals(activityId)); if (indexActivity != null && !newActivity.IsNullOrEmpty()) diff --git a/salesbook.Shared/Components/Pages/Calendar.razor.css b/salesbook.Shared/Components/Pages/Calendar.razor.css index b2b2641..8475a60 100644 --- a/salesbook.Shared/Components/Pages/Calendar.razor.css +++ b/salesbook.Shared/Components/Pages/Calendar.razor.css @@ -122,7 +122,7 @@ flex-direction: column; -ms-overflow-style: none; scrollbar-width: none; - padding-bottom: 70px; + padding-bottom: 16vh; height: calc(100% - 130px); } diff --git a/salesbook.Shared/Components/Pages/Commessa.razor b/salesbook.Shared/Components/Pages/Commessa.razor index a51fb84..42675a2 100644 --- a/salesbook.Shared/Components/Pages/Commessa.razor +++ b/salesbook.Shared/Components/Pages/Commessa.razor @@ -6,6 +6,7 @@ @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements @using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Dto.JobProgress @using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity diff --git a/salesbook.Shared/Components/Pages/SyncPage.razor b/salesbook.Shared/Components/Pages/SyncPage.razor index 80484a0..77b8cf4 100644 --- a/salesbook.Shared/Components/Pages/SyncPage.razor +++ b/salesbook.Shared/Components/Pages/SyncPage.razor @@ -17,10 +17,6 @@ protected override void OnInitialized() { - Elements["Attività"] = false; - Elements["Commesse"] = false; - Elements["Clienti"] = false; - Elements["Prospect"] = false; Elements["Impostazioni"] = false; } @@ -36,9 +32,6 @@ } await Task.WhenAll( - RunAndTrack(SetActivity), - RunAndTrack(SetClienti), - RunAndTrack(SetProspect), RunAndTrack(SetCommesse), RunAndTrack(SetSettings) ); @@ -62,30 +55,6 @@ } } - private async Task SetActivity() - { - await Task.Run(async () => { await syncDb.GetAndSaveActivity(DateFilter); }); - - Elements["Attività"] = true; - StateHasChanged(); - } - - private async Task SetClienti() - { - await Task.Run(async () => { await syncDb.GetAndSaveClienti(DateFilter); }); - - Elements["Clienti"] = true; - StateHasChanged(); - } - - private async Task SetProspect() - { - await Task.Run(async () => { await syncDb.GetAndSaveProspect(DateFilter); }); - - Elements["Prospect"] = true; - StateHasChanged(); - } - private async Task SetCommesse() { await Task.Run(async () => { await syncDb.GetAndSaveCommesse(DateFilter); }); diff --git a/salesbook.Shared/Components/Pages/Users.razor b/salesbook.Shared/Components/Pages/Users.razor index 76b12b5..449f7f1 100644 --- a/salesbook.Shared/Components/Pages/Users.razor +++ b/salesbook.Shared/Components/Pages/Users.razor @@ -6,6 +6,7 @@ @using salesbook.Shared.Components.SingleElements.BottomSheet @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.Contact @using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Dto.Users @using salesbook.Shared.Core.Entity @@ -123,7 +124,7 @@ Filter.IsInitialized = true; } - var users = await ManageData.GetContact(); + var users = await ManageData.GetContact(new WhereCondContact {FlagStato = "A"}); var sortedUsers = users .Where(u => !string.IsNullOrWhiteSpace(u.RagSoc)) diff --git a/salesbook.Shared/Components/SingleElements/BottomSheet/FilterActivity.razor b/salesbook.Shared/Components/SingleElements/BottomSheet/FilterActivity.razor index cc17dc3..048276f 100644 --- a/salesbook.Shared/Components/SingleElements/BottomSheet/FilterActivity.razor +++ b/salesbook.Shared/Components/SingleElements/BottomSheet/FilterActivity.razor @@ -1,4 +1,5 @@ @using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Helpers.Enum @using salesbook.Shared.Core.Interface diff --git a/salesbook.Shared/Components/SingleElements/BottomSheet/SelectEsito.razor b/salesbook.Shared/Components/SingleElements/BottomSheet/SelectEsito.razor index ff98a7c..89895e5 100644 --- a/salesbook.Shared/Components/SingleElements/BottomSheet/SelectEsito.razor +++ b/salesbook.Shared/Components/SingleElements/BottomSheet/SelectEsito.razor @@ -1,4 +1,5 @@ @using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Interface @inject IManageDataService ManageData diff --git a/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor index b81a5f6..9217641 100644 --- a/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor @@ -1,4 +1,5 @@ @using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Helpers.Enum @inject IDialogService Dialog diff --git a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor index 40dbd59..c0ad846 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -1,12 +1,14 @@ @using System.Globalization @using System.Text.RegularExpressions @using CommunityToolkit.Mvvm.Messaging -@using salesbook.Shared.Core.Dto @using salesbook.Shared.Components.Layout -@using salesbook.Shared.Core.Entity -@using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Overlay @using salesbook.Shared.Components.SingleElements.BottomSheet +@using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity +@using salesbook.Shared.Core.Dto.Contact +@using salesbook.Shared.Core.Entity +@using salesbook.Shared.Core.Interface @using salesbook.Shared.Core.Messages.Activity.Copy @inject IManageDataService ManageData @inject INetworkService NetworkService @@ -279,7 +281,7 @@ LabelSave = IsNew ? "Aggiungi" : null; if (!Id.IsNullOrEmpty()) - ActivityModel = (await ManageData.GetActivity(x => x.ActivityId.Equals(Id))).Last(); + ActivityModel = (await ManageData.GetActivity(new WhereCondActivity { ActivityId = Id }, true)).Last(); if (ActivityCopied != null) { @@ -379,8 +381,8 @@ Users = await ManageData.GetTable(); ActivityResult = await ManageData.GetTable(); - Clienti = await ManageData.GetTable(x => x.FlagStato.Equals("A")); - Pros = await ManageData.GetTable(); + Clienti = await ManageData.GetClienti(new WhereCondContact {FlagStato = "A"}); + Pros = await ManageData.GetProspect(); ActivityType = await ManageData.GetTable(x => x.FlagTipologia.Equals("A")); } diff --git a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor index f070e1f..337fa94 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor @@ -4,6 +4,7 @@ @using salesbook.Shared.Components.Layout.Overlay @using salesbook.Shared.Core.Entity @using salesbook.Shared.Components.SingleElements.BottomSheet +@using salesbook.Shared.Core.Dto.Contact @inject IManageDataService ManageData @inject INetworkService NetworkService @inject IIntegryApiService IntegryApiService @@ -473,11 +474,11 @@ var pIva = ContactModel.PartIva.Trim(); - var clie = (await ManageData.GetTable(x => x.PartIva.Equals(pIva))).LastOrDefault(); + var clie = (await ManageData.GetClienti(new WhereCondContact {PartIva = pIva})).LastOrDefault(); if (clie == null) { - var pros = (await ManageData.GetTable(x => x.PartIva.Equals(pIva))).LastOrDefault(); + var pros = (await ManageData.GetProspect(new WhereCondContact {PartIva = pIva})).LastOrDefault(); if (pros == null) { diff --git a/salesbook.Shared/Core/Dto/ActivityDTO.cs b/salesbook.Shared/Core/Dto/Activity/ActivityDTO.cs similarity index 99% rename from salesbook.Shared/Core/Dto/ActivityDTO.cs rename to salesbook.Shared/Core/Dto/Activity/ActivityDTO.cs index bd5f262..9888ece 100644 --- a/salesbook.Shared/Core/Dto/ActivityDTO.cs +++ b/salesbook.Shared/Core/Dto/Activity/ActivityDTO.cs @@ -1,7 +1,7 @@ using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Helpers.Enum; -namespace salesbook.Shared.Core.Dto; +namespace salesbook.Shared.Core.Dto.Activity; public class ActivityDTO : StbActivity { diff --git a/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs b/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs new file mode 100644 index 0000000..44e201a --- /dev/null +++ b/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.Activity; + +public class CRMRetrieveActivityRequestDTO +{ + [JsonPropertyName("dateFilter")] + public string? DateFilter { get; set; } + + [JsonPropertyName("activityId")] + public string? ActivityId { get; set; } + + [JsonPropertyName("codJcom")] + public string? CodJcom { get; set; } + + [JsonPropertyName("startDate")] + public DateTime? StarDate { get; set; } + + [JsonPropertyName("endDate")] + public DateTime? EndDate { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/FilterActivityDTO.cs b/salesbook.Shared/Core/Dto/Activity/FilterActivityDTO.cs similarity index 91% rename from salesbook.Shared/Core/Dto/FilterActivityDTO.cs rename to salesbook.Shared/Core/Dto/Activity/FilterActivityDTO.cs index dac5241..7dafe5b 100644 --- a/salesbook.Shared/Core/Dto/FilterActivityDTO.cs +++ b/salesbook.Shared/Core/Dto/Activity/FilterActivityDTO.cs @@ -1,7 +1,7 @@ using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Helpers.Enum; -namespace salesbook.Shared.Core.Dto; +namespace salesbook.Shared.Core.Dto.Activity; public class FilterActivityDTO { diff --git a/salesbook.Shared/Core/Dto/Activity/WhereCondActivity.cs b/salesbook.Shared/Core/Dto/Activity/WhereCondActivity.cs new file mode 100644 index 0000000..b9d1dc2 --- /dev/null +++ b/salesbook.Shared/Core/Dto/Activity/WhereCondActivity.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.Activity; + +public class WhereCondActivity +{ + public DateTime? Start { get; set; } + public DateTime? End { get; set; } + + public string? ActivityId { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs b/salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs deleted file mode 100644 index 4953c16..0000000 --- a/salesbook.Shared/Core/Dto/CRMRetrieveActivityRequestDTO.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; -using Java.Time; - -namespace salesbook.Shared.Core.Dto; - -public class CRMRetrieveActivityRequestDTO -{ - [JsonPropertyName("dateFilter")] - public string? DateFilter { get; set; } - - [JsonPropertyName("codJcom")] - public string? CodJcom { get; set; } -} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/Contact/CRMAnagRequestDTO.cs b/salesbook.Shared/Core/Dto/Contact/CRMAnagRequestDTO.cs new file mode 100644 index 0000000..a28268d --- /dev/null +++ b/salesbook.Shared/Core/Dto/Contact/CRMAnagRequestDTO.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.Contact; + +public class CRMAnagRequestDTO +{ + [JsonPropertyName("filterDate")] + public DateTime? FilterDate { get; set; } + + [JsonPropertyName("flagStato")] + public string? FlagStato { get; set; } + [JsonPropertyName("partitaIva")] + public string? PartIva { get; set; } + [JsonPropertyName("codAnag")] + public string? CodAnag { get; set; } + + [JsonPropertyName("returnPersRif")] + public bool ReturnPersRif { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/Contact/CRMProspectRequestDTO.cs b/salesbook.Shared/Core/Dto/Contact/CRMProspectRequestDTO.cs new file mode 100644 index 0000000..660e334 --- /dev/null +++ b/salesbook.Shared/Core/Dto/Contact/CRMProspectRequestDTO.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto.Contact; + +public class CRMProspectRequestDTO +{ + [JsonPropertyName("filterDate")] + public DateTime? FilterDate { get; set; } + + [JsonPropertyName("partitaIva")] + public string? PartIva { get; set; } + [JsonPropertyName("codPpro")] + public string? CodPpro { get; set; } + + [JsonPropertyName("returnPersRif")] + public bool ReturnPersRif { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/Contact/WhereCondContact.cs b/salesbook.Shared/Core/Dto/Contact/WhereCondContact.cs new file mode 100644 index 0000000..3e7be13 --- /dev/null +++ b/salesbook.Shared/Core/Dto/Contact/WhereCondContact.cs @@ -0,0 +1,10 @@ +namespace salesbook.Shared.Core.Dto.Contact; + +public class WhereCondContact +{ + public string? FlagStato { get; set; } + public string? PartIva { get; set; } + public string? CodAnag { get; set; } + + public bool OnlyContact { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/TaskSyncResponseDTO.cs b/salesbook.Shared/Core/Dto/UsersSyncResponseDTO.cs similarity index 94% rename from salesbook.Shared/Core/Dto/TaskSyncResponseDTO.cs rename to salesbook.Shared/Core/Dto/UsersSyncResponseDTO.cs index 5ea1bbc..4d790cb 100644 --- a/salesbook.Shared/Core/Dto/TaskSyncResponseDTO.cs +++ b/salesbook.Shared/Core/Dto/UsersSyncResponseDTO.cs @@ -3,7 +3,7 @@ using salesbook.Shared.Core.Entity; namespace salesbook.Shared.Core.Dto; -public class TaskSyncResponseDTO +public class UsersSyncResponseDTO { [JsonPropertyName("anagClie")] public List? AnagClie { get; set; } diff --git a/salesbook.Shared/Core/Helpers/MappingProfile.cs b/salesbook.Shared/Core/Helpers/MappingProfile.cs index 8ac3142..d967823 100644 --- a/salesbook.Shared/Core/Helpers/MappingProfile.cs +++ b/salesbook.Shared/Core/Helpers/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; using salesbook.Shared.Core.Entity; namespace salesbook.Shared.Core.Helpers; diff --git a/salesbook.Shared/Core/Helpers/ModalHelpers.cs b/salesbook.Shared/Core/Helpers/ModalHelpers.cs index fa199f6..da4bad7 100644 --- a/salesbook.Shared/Core/Helpers/ModalHelpers.cs +++ b/salesbook.Shared/Core/Helpers/ModalHelpers.cs @@ -1,6 +1,7 @@ using MudBlazor; using salesbook.Shared.Components.SingleElements.Modal; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; namespace salesbook.Shared.Core.Helpers; diff --git a/salesbook.Shared/Core/Interface/IIntegryApiService.cs b/salesbook.Shared/Core/Interface/IIntegryApiService.cs index a04484d..80d7915 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IIntegryApiService.cs @@ -1,4 +1,6 @@ using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; +using salesbook.Shared.Core.Dto.Contact; using salesbook.Shared.Core.Dto.JobProgress; using salesbook.Shared.Core.Entity; @@ -8,8 +10,8 @@ public interface IIntegryApiService { Task?> RetrieveActivity(CRMRetrieveActivityRequestDTO activityRequest); Task?> RetrieveAllCommesse(string? dateFilter = null); - Task RetrieveAnagClie(string? dateFilter = null); - Task RetrieveProspect(string? dateFilter = null); + Task RetrieveAnagClie(CRMAnagRequestDTO request); + Task RetrieveProspect(CRMProspectRequestDTO request); Task RetrieveSettings(); Task?> RetrieveAttached(string codJcom); diff --git a/salesbook.Shared/Core/Interface/IManageDataService.cs b/salesbook.Shared/Core/Interface/IManageDataService.cs index 0729793..4bd7446 100644 --- a/salesbook.Shared/Core/Interface/IManageDataService.cs +++ b/salesbook.Shared/Core/Interface/IManageDataService.cs @@ -1,17 +1,22 @@ -using System.Linq.Expressions; -using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; +using salesbook.Shared.Core.Dto.Contact; using salesbook.Shared.Core.Entity; +using System.Linq.Expressions; namespace salesbook.Shared.Core.Interface; public interface IManageDataService { Task> GetTable(Expression>? whereCond = null) where T : new(); - - Task> GetActivity(Expression>? whereCond = null); - Task> GetContact(); + + Task> GetClienti(WhereCondContact? whereCond = null); + Task> GetProspect(WhereCondContact? whereCond = null); + Task> GetContact(WhereCondContact whereCond); Task GetSpecificContact(string codAnag, bool IsContact); + Task> GetActivity(WhereCondActivity whereCond, bool useLocalDb = false); + Task InsertOrUpdate(T objectToSave); Task InsertOrUpdate(List listToSave); diff --git a/salesbook.Shared/Core/Interface/ISyncDbService.cs b/salesbook.Shared/Core/Interface/ISyncDbService.cs index b762c6c..48ed35f 100644 --- a/salesbook.Shared/Core/Interface/ISyncDbService.cs +++ b/salesbook.Shared/Core/Interface/ISyncDbService.cs @@ -2,9 +2,6 @@ public interface ISyncDbService { - Task GetAndSaveActivity(string? dateFilter = null); Task GetAndSaveCommesse(string? dateFilter = null); - Task GetAndSaveProspect(string? dateFilter = null); - Task GetAndSaveClienti(string? dateFilter = null); Task GetAndSaveSettings(string? dateFilter = null); } \ No newline at end of file diff --git a/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityMessage.cs b/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityMessage.cs index 7324da0..068e051 100644 --- a/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityMessage.cs +++ b/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityMessage.cs @@ -1,5 +1,5 @@ using CommunityToolkit.Mvvm.Messaging.Messages; -using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; namespace salesbook.Shared.Core.Messages.Activity.Copy; diff --git a/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityService.cs b/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityService.cs index 836b2d5..34e96cf 100644 --- a/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityService.cs +++ b/salesbook.Shared/Core/Messages/Activity/Copy/CopyActivityService.cs @@ -1,5 +1,5 @@ using CommunityToolkit.Mvvm.Messaging; -using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; namespace salesbook.Shared.Core.Messages.Activity.Copy; diff --git a/salesbook.Shared/Core/Services/IntegryApiService.cs b/salesbook.Shared/Core/Services/IntegryApiService.cs index a29e251..b12cb7a 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -1,10 +1,12 @@ using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account; using IntegryApiClient.Core.Domain.RestClient.Contacts; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; using salesbook.Shared.Core.Dto.JobProgress; using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Interface; using System.Net.Http.Headers; +using salesbook.Shared.Core.Dto.Contact; namespace salesbook.Shared.Core.Services; @@ -26,29 +28,11 @@ public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUser return integryApiRestClient.AuthorizedGet?>("crm/retrieveCommesse", queryParams); } - public Task RetrieveAnagClie(string? dateFilter) - { - var queryParams = new Dictionary(); + public Task RetrieveAnagClie(CRMAnagRequestDTO request) => + integryApiRestClient.AuthorizedPost("crm/retrieveClienti", request)!; - if (dateFilter != null) - { - queryParams.Add("dateFilter", dateFilter); - } - - return integryApiRestClient.AuthorizedGet("crm/retrieveClienti", queryParams)!; - } - - public Task RetrieveProspect(string? dateFilter) - { - var queryParams = new Dictionary(); - - if (dateFilter != null) - { - queryParams.Add("dateFilter", dateFilter); - } - - return integryApiRestClient.AuthorizedGet("crm/retrieveProspect", queryParams)!; - } + public Task RetrieveProspect(CRMProspectRequestDTO request) => + integryApiRestClient.AuthorizedPost("crm/retrieveProspect", request)!; public Task RetrieveSettings() => integryApiRestClient.AuthorizedGet("crm/retrieveSettings")!; diff --git a/salesbook.Web/Core/Services/ManageDataService.cs b/salesbook.Web/Core/Services/ManageDataService.cs index 3775113..b32c298 100644 --- a/salesbook.Web/Core/Services/ManageDataService.cs +++ b/salesbook.Web/Core/Services/ManageDataService.cs @@ -1,5 +1,7 @@ using System.Linq.Expressions; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; +using salesbook.Shared.Core.Dto.Contact; using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Interface; @@ -12,17 +14,27 @@ public class ManageDataService : IManageDataService throw new NotImplementedException(); } - public Task> GetActivity(Expression>? whereCond = null) + public Task> GetClienti(WhereCondContact? whereCond) { throw new NotImplementedException(); } - public Task> GetContact() + public Task> GetProspect(WhereCondContact? whereCond) { throw new NotImplementedException(); } - public Task GetSpecificContact(string codAnag, bool IsContact) + public Task> GetContact(WhereCondContact whereCond) + { + throw new NotImplementedException(); + } + + public Task GetSpecificContact(string codAnag, bool IsContact) + { + throw new NotImplementedException(); + } + + public Task> GetActivity(WhereCondActivity whereCond, bool useLocalDb = false) { throw new NotImplementedException(); } diff --git a/salesbook.Web/Core/Services/SyncDbService.cs b/salesbook.Web/Core/Services/SyncDbService.cs index 5d29b0d..81a2e3c 100644 --- a/salesbook.Web/Core/Services/SyncDbService.cs +++ b/salesbook.Web/Core/Services/SyncDbService.cs @@ -1,10 +1,11 @@ -using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Entity; +using salesbook.Shared.Core.Interface; namespace salesbook.Web.Core.Services; public class SyncDbService : ISyncDbService { - public Task GetAndSaveActivity(string? dateFilter = null) + public Task GetAndSaveActivity(List? allActivity) { throw new NotImplementedException(); } From 85088203500102370566c0037a57f7bb8fdd9fc8 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Wed, 3 Sep 2025 17:26:55 +0200 Subject: [PATCH 06/24] Migliorata sincronizzazione dei dati --- .../Core/Services/ManageDataService.cs | 2 - salesbook.Maui/MauiProgram.cs | 1 + salesbook.Shared/Components/Pages/Home.razor | 11 +++ salesbook.Shared/Components/Pages/Users.razor | 84 ++++++------------- .../Core/Dto/PageState/UserListState.cs | 14 +++- .../Core/Services/PreloadService.cs | 55 ++++++++++++ 6 files changed, 107 insertions(+), 60 deletions(-) create mode 100644 salesbook.Shared/Core/Services/PreloadService.cs diff --git a/salesbook.Maui/Core/Services/ManageDataService.cs b/salesbook.Maui/Core/Services/ManageDataService.cs index 44555b0..bf9fc14 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -37,7 +37,6 @@ public class ManageDataService( ReturnPersRif = !whereCond.OnlyContact } ); - _ = UpdateDbUsers(response); clienti = response.AnagClie ?? []; } @@ -69,7 +68,6 @@ public class ManageDataService( ReturnPersRif = !whereCond.OnlyContact } ); - _ = UpdateDbUsers(response); prospect = response.PtbPros ?? []; } diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index d6350f3..6fed6d9 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -58,6 +58,7 @@ namespace salesbook.Maui builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); //SessionData builder.Services.AddSingleton(); diff --git a/salesbook.Shared/Components/Pages/Home.razor b/salesbook.Shared/Components/Pages/Home.razor index 6e62825..c3b7444 100644 --- a/salesbook.Shared/Components/Pages/Home.razor +++ b/salesbook.Shared/Components/Pages/Home.razor @@ -2,8 +2,10 @@ @attribute [Authorize] @using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Core.Services @inject IFormFactor FormFactor @inject INetworkService NetworkService +@inject PreloadService PreloadService @@ -19,6 +21,15 @@ return; } + _ = StartSyncUser(); NavigationManager.NavigateTo("/Calendar"); } + + private Task StartSyncUser() + { + return Task.Run(() => + { + _ = PreloadService.PreloadUsersAsync(); + }); + } } diff --git a/salesbook.Shared/Components/Pages/Users.razor b/salesbook.Shared/Components/Pages/Users.razor index 449f7f1..610d3c7 100644 --- a/salesbook.Shared/Components/Pages/Users.razor +++ b/salesbook.Shared/Components/Pages/Users.razor @@ -6,7 +6,6 @@ @using salesbook.Shared.Components.SingleElements.BottomSheet @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements -@using salesbook.Shared.Core.Dto.Contact @using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Dto.Users @using salesbook.Shared.Core.Entity @@ -15,6 +14,7 @@ @inject NewContactService NewContact @inject FilterUserDTO Filter @inject UserListState UserState +@implements IDisposable @@ -71,33 +71,37 @@ private bool OpenFilter { get; set; } private string TypeUser { get; set; } = "all"; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { + IsLoading = true; + + if (!UserState.IsLoaded) + { + UserState.OnUsersLoaded += OnUsersLoaded; + } + else + { + await LoadData(); + LoadFromSession(); + FilterUsers(); + } + NewContact.OnContactCreated += async response => await OnUserCreated(response); } - protected override async Task OnAfterRenderAsync(bool firstRender) + private void OnUsersLoaded() { - if (firstRender) + InvokeAsync(async () => { - IsLoading = true; - StateHasChanged(); - - if (UserState.FilteredGroupedUserList == null && UserState.GroupedUserList == null) - { - await LoadData(); - SetDataSession(); - } - else - { - LoadFromSession(); - } - + await LoadData(); + LoadFromSession(); FilterUsers(); + }); + } - IsLoading = false; - StateHasChanged(); - } + void IDisposable.Dispose() + { + UserState.OnUsersLoaded -= OnUsersLoaded; } private void LoadFromSession() @@ -106,12 +110,6 @@ FilteredGroupedUserList = UserState.FilteredGroupedUserList!; } - private void SetDataSession() - { - UserState.GroupedUserList = GroupedUserList; - UserState.FilteredGroupedUserList = FilteredGroupedUserList; - } - private async Task LoadData() { if (!Filter.IsInitialized) @@ -123,37 +121,6 @@ Filter.IsInitialized = true; } - - var users = await ManageData.GetContact(new WhereCondContact {FlagStato = "A"}); - - var sortedUsers = users - .Where(u => !string.IsNullOrWhiteSpace(u.RagSoc)) - .OrderBy(u => - { - var firstChar = char.ToUpper(u.RagSoc[0]); - return char.IsLetter(firstChar) ? firstChar.ToString() : "ZZZ"; - }) - .ThenBy(u => u.RagSoc) - .ToList(); - - GroupedUserList = []; - - string? lastHeader = null; - foreach (var user in sortedUsers) - { - var firstChar = char.ToUpper(user.RagSoc[0]); - var currentLetter = char.IsLetter(firstChar) ? firstChar.ToString() : "#"; - - var showHeader = currentLetter != lastHeader; - lastHeader = currentLetter; - - GroupedUserList.Add(new UserDisplayItem - { - User = user, - ShowHeader = showHeader, - HeaderLetter = currentLetter - }); - } } private void FilterUsers() => FilterUsers(false); @@ -220,6 +187,9 @@ } FilteredGroupedUserList = result; + + IsLoading = false; + StateHasChanged(); } private async Task OnUserCreated(CRMCreateContactResponseDTO response) diff --git a/salesbook.Shared/Core/Dto/PageState/UserListState.cs b/salesbook.Shared/Core/Dto/PageState/UserListState.cs index c1eb635..c1451b0 100644 --- a/salesbook.Shared/Core/Dto/PageState/UserListState.cs +++ b/salesbook.Shared/Core/Dto/PageState/UserListState.cs @@ -6,4 +6,16 @@ public class UserListState { public List? GroupedUserList { get; set; } public List? FilteredGroupedUserList { get; set; } -} \ No newline at end of file + + public bool IsLoaded { get; set; } + public bool IsLoading { get; set; } + + public event Action? OnUsersLoaded; + + public void NotifyUsersLoaded() + { + IsLoaded = true; + IsLoading = false; + OnUsersLoaded?.Invoke(); + } +} diff --git a/salesbook.Shared/Core/Services/PreloadService.cs b/salesbook.Shared/Core/Services/PreloadService.cs new file mode 100644 index 0000000..2712494 --- /dev/null +++ b/salesbook.Shared/Core/Services/PreloadService.cs @@ -0,0 +1,55 @@ +using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Contact; +using salesbook.Shared.Core.Dto.PageState; +using salesbook.Shared.Core.Dto.Users; +using salesbook.Shared.Core.Interface; + +namespace salesbook.Shared.Core.Services; + +public class PreloadService(IManageDataService manageData, UserListState userState) +{ + public async Task PreloadUsersAsync() + { + if (userState.IsLoaded || userState.IsLoading) + return; + + userState.IsLoading = true; + + var users = await manageData.GetContact(new WhereCondContact { FlagStato = "A" }); + + var sorted = users + .Where(u => !string.IsNullOrWhiteSpace(u.RagSoc)) + .OrderBy(u => char.IsLetter(char.ToUpper(u.RagSoc[0])) ? char.ToUpper(u.RagSoc[0]).ToString() : "ZZZ") + .ThenBy(u => u.RagSoc) + .ToList(); + + userState.GroupedUserList = BuildGroupedList(sorted); + userState.FilteredGroupedUserList = userState.GroupedUserList; + + userState.NotifyUsersLoaded(); + } + + private static List BuildGroupedList(List users) + { + var grouped = new List(); + string? lastHeader = null; + + foreach (var user in users) + { + var firstChar = char.ToUpper(user.RagSoc[0]); + var currentLetter = char.IsLetter(firstChar) ? firstChar.ToString() : "#"; + + var showHeader = currentLetter != lastHeader; + lastHeader = currentLetter; + + grouped.Add(new UserDisplayItem + { + User = user, + ShowHeader = showHeader, + HeaderLetter = currentLetter + }); + } + + return grouped; + } +} \ No newline at end of file From 014e2ffc41cae37d3cdb0fe721943b7ca1232a33 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Fri, 5 Sep 2025 09:41:22 +0200 Subject: [PATCH 07/24] 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; From 82d268d9f830f427c7cac2b9e01e6565456f0dd5 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Mon, 8 Sep 2025 10:24:50 +0200 Subject: [PATCH 08/24] =?UTF-8?q?Implementato=20controllo=20conessione=20d?= =?UTF-8?q?ispositivo=20e=20servizi.=20Completata=20pagination=20commesse?= =?UTF-8?q?=20e=20attivit=C3=A0=20per=20cliente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Services/ManageDataService.cs | 8 +- .../Core/Services/NetworkService.cs | 4 +- salesbook.Maui/MauiProgram.cs | 2 +- .../Components/Layout/MainLayout.razor | 110 +++++- .../Components/Pages/Commessa.razor | 7 +- salesbook.Shared/Components/Pages/Home.razor | 2 +- salesbook.Shared/Components/Pages/Login.razor | 4 +- .../Components/Pages/PersonalInfo.razor | 4 +- salesbook.Shared/Components/Pages/User.razor | 337 +++++++++++++----- .../Components/Pages/User.razor.css | 19 +- .../SingleElements/Card/ActivityCard.razor | 22 +- .../SingleElements/Card/UserCard.razor | 19 +- .../SingleElements/Modal/ActivityForm.razor | 18 +- .../SingleElements/Modal/ContactForm.razor | 4 +- .../SingleElements/Modal/PersRifForm.razor | 2 +- .../Activity/CRMRetrieveActivityRequestDTO.cs | 3 + .../Core/Dto/PageState/UserPageState.cs | 4 +- .../Core/Interface/IIntegryApiService.cs | 2 + .../Core/Interface/INetworkService.cs | 2 + .../Core/Services/IntegryApiService.cs | 13 + salesbook.Shared/wwwroot/css/app.css | 36 ++ salesbook.Shared/wwwroot/js/main.js | 45 ++- salesbook.Web/Core/Services/NetworkService.cs | 2 + 23 files changed, 530 insertions(+), 139 deletions(-) diff --git a/salesbook.Maui/Core/Services/ManageDataService.cs b/salesbook.Maui/Core/Services/ManageDataService.cs index bf9fc14..9b6dc26 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -26,7 +26,7 @@ public class ManageDataService( whereCond ??= new WhereCondContact(); whereCond.OnlyContact = true; - if (networkService.IsNetworkAvailable()) + if (networkService.ConnectionAvailable) { var response = await integryApiService.RetrieveAnagClie( new CRMAnagRequestDTO @@ -58,7 +58,7 @@ public class ManageDataService( whereCond ??= new WhereCondContact(); whereCond.OnlyContact = true; - if (networkService.IsNetworkAvailable()) + if (networkService.ConnectionAvailable) { var response = await integryApiService.RetrieveProspect( new CRMProspectRequestDTO @@ -88,7 +88,7 @@ public class ManageDataService( List? prospectList; whereCond ??= new WhereCondContact(); - if (networkService.IsNetworkAvailable()) + if (networkService.ConnectionAvailable) { var clienti = await integryApiService.RetrieveAnagClie( new CRMAnagRequestDTO @@ -160,7 +160,7 @@ public class ManageDataService( { List? activities; - if (networkService.IsNetworkAvailable() && !useLocalDb) + if (networkService.ConnectionAvailable && !useLocalDb) { activities = await integryApiService.RetrieveActivity( new CRMRetrieveActivityRequestDTO diff --git a/salesbook.Maui/Core/Services/NetworkService.cs b/salesbook.Maui/Core/Services/NetworkService.cs index 4545bcc..5920c78 100644 --- a/salesbook.Maui/Core/Services/NetworkService.cs +++ b/salesbook.Maui/Core/Services/NetworkService.cs @@ -4,9 +4,11 @@ namespace salesbook.Maui.Core.Services; public class NetworkService : INetworkService { + public bool ConnectionAvailable { get; set; } + public bool IsNetworkAvailable() { - return false; + //return false; return Connectivity.Current.NetworkAccess == NetworkAccess.Internet; } diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index 6fed6d9..fac4479 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -54,7 +54,6 @@ namespace salesbook.Maui builder.Services.AddScoped(provider => provider.GetRequiredService()); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -80,6 +79,7 @@ namespace salesbook.Maui builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); return builder.Build(); diff --git a/salesbook.Shared/Components/Layout/MainLayout.razor b/salesbook.Shared/Components/Layout/MainLayout.razor index 0130251..eb1300e 100644 --- a/salesbook.Shared/Components/Layout/MainLayout.razor +++ b/salesbook.Shared/Components/Layout/MainLayout.razor @@ -1,10 +1,11 @@ @using System.Globalization -@using CommunityToolkit.Mvvm.Messaging +@using salesbook.Shared.Core.Interface @using salesbook.Shared.Core.Messages.Back @inherits LayoutComponentBase @inject IJSRuntime JS -@inject IMessenger Messenger @inject BackNavigationService BackService +@inject INetworkService NetworkService +@inject IIntegryApiService IntegryApiService @@ -13,13 +14,33 @@
+ +
+ @if (IsNetworkAvailable) + { + if(ServicesIsDown) + { + + Servizi offline + } + else + { + + Online + } + } + else + { + + Nessuna connessione + } +
@Body
-
@code { @@ -27,6 +48,50 @@ private bool IsDarkMode { get; set; } private string _mainContentClass = ""; + //Connection state + private bool FirstCheck { get; set; } = true; + private bool _isNetworkAvailable; + private bool _servicesIsDown; + private bool _showWarning; + + private DateTime _lastApiCheck = DateTime.MinValue; + private int _delaySeconds = 3; + + private CancellationTokenSource? _cts; + + private bool ServicesIsDown + { + get => _servicesIsDown; + set + { + if (_servicesIsDown == value) return; + _servicesIsDown = value; + StateHasChanged(); + } + } + + private bool IsNetworkAvailable + { + get => _isNetworkAvailable; + set + { + if (_isNetworkAvailable == value) return; + _isNetworkAvailable = value; + StateHasChanged(); + } + } + + private bool ShowWarning + { + get => _showWarning; + set + { + if (_showWarning == value) return; + _showWarning = value; + StateHasChanged(); + } + } + private readonly MudTheme _currentTheme = new() { PaletteLight = new PaletteLight() @@ -81,6 +146,9 @@ protected override void OnInitialized() { + _cts = new CancellationTokenSource(); + _ = CheckConnectionState(_cts.Token); + BackService.OnHardwareBack += async () => { await JS.InvokeVoidAsync("goBack"); }; var culture = new CultureInfo("it-IT", false); @@ -89,4 +157,40 @@ CultureInfo.CurrentUICulture = culture; } + private Task CheckConnectionState(CancellationToken token) + { + return Task.Run(async () => + { + while (!token.IsCancellationRequested) + { + var isNetworkAvailable = NetworkService.IsNetworkAvailable(); + var servicesDown = ServicesIsDown; + + if (isNetworkAvailable && (DateTime.UtcNow - _lastApiCheck).TotalSeconds >= _delaySeconds) + { + servicesDown = !await IntegryApiService.SystemOk(); + _lastApiCheck = DateTime.UtcNow; + } + + await InvokeAsync(async () => + { + IsNetworkAvailable = isNetworkAvailable; + ServicesIsDown = servicesDown; + + await Task.Delay(1500, token); + ShowWarning = !(IsNetworkAvailable && !ServicesIsDown); + NetworkService.ConnectionAvailable = !ShowWarning; + }); + + await Task.Delay(500, token); + } + }, token); + } + + public void Dispose() + { + _cts?.Cancel(); + _cts?.Dispose(); + } + } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/Commessa.razor b/salesbook.Shared/Components/Pages/Commessa.razor index 42675a2..24d8b14 100644 --- a/salesbook.Shared/Components/Pages/Commessa.razor +++ b/salesbook.Shared/Components/Pages/Commessa.razor @@ -78,7 +78,7 @@ else {
- +
} @@ -148,7 +148,10 @@ else await Task.Run(async () => { var activities = await IntegryApiService.RetrieveActivity(new CRMRetrieveActivityRequestDTO { CodJcom = CodJcom }); - ActivityList = Mapper.Map>(activities); + ActivityList = Mapper.Map>(activities) + .OrderBy(x => + (x.EffectiveTime ?? x.EstimatedTime) ?? x.DataInsAct + ).ToList(); }); ActivityIsLoading = false; diff --git a/salesbook.Shared/Components/Pages/Home.razor b/salesbook.Shared/Components/Pages/Home.razor index c3b7444..d8e89ab 100644 --- a/salesbook.Shared/Components/Pages/Home.razor +++ b/salesbook.Shared/Components/Pages/Home.razor @@ -15,7 +15,7 @@ { var lastSyncDate = LocalStorage.Get("last-sync"); - if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate.Equals(DateTime.MinValue)) + if (!FormFactor.IsWeb() && NetworkService.ConnectionAvailable && lastSyncDate.Equals(DateTime.MinValue)) { NavigationManager.NavigateTo("/sync"); return; diff --git a/salesbook.Shared/Components/Pages/Login.razor b/salesbook.Shared/Components/Pages/Login.razor index fd39faf..baafd52 100644 --- a/salesbook.Shared/Components/Pages/Login.razor +++ b/salesbook.Shared/Components/Pages/Login.razor @@ -1,8 +1,10 @@ @page "/login" @using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Core.Interface @using salesbook.Shared.Core.Services @inject IUserAccountService UserAccountService @inject AppAuthenticationStateProvider AuthenticationStateProvider +@inject INetworkService NetworkService @if (Spinner) { @@ -26,7 +28,7 @@ else
- Login + Login @if (_attemptFailed) { @ErrorMessage diff --git a/salesbook.Shared/Components/Pages/PersonalInfo.razor b/salesbook.Shared/Components/Pages/PersonalInfo.razor index b3f2bd3..036fcbf 100644 --- a/salesbook.Shared/Components/Pages/PersonalInfo.razor +++ b/salesbook.Shared/Components/Pages/PersonalInfo.razor @@ -39,7 +39,7 @@
Status - @if (NetworkService.IsNetworkAvailable()) + @if (NetworkService.ConnectionAvailable) {
@@ -129,7 +129,7 @@ { await Task.Run(() => { - Unavailable = FormFactor.IsWeb() || !NetworkService.IsNetworkAvailable(); + Unavailable = FormFactor.IsWeb() || !NetworkService.ConnectionAvailable; LastSync = LocalStorage.Get("last-sync"); }); diff --git a/salesbook.Shared/Components/Pages/User.razor b/salesbook.Shared/Components/Pages/User.razor index 04f9e07..0da73a8 100644 --- a/salesbook.Shared/Components/Pages/User.razor +++ b/salesbook.Shared/Components/Pages/User.razor @@ -7,6 +7,7 @@ @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Core.Dto @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Dto.JobProgress @using salesbook.Shared.Core.Dto.PageState @implements IAsyncDisposable @@ -93,6 +94,7 @@ else +
    @@ -102,6 +104,9 @@ else
  • +
  • + +
@@ -150,8 +155,8 @@ else
@@ -179,46 +184,86 @@ else
} - @if (TotalPages > 1) + @if (TotalPagesCommesse > 1) {
- +
- 5 10 - 15 + 15
+ } +
+ } + - - - + +
+ @if (ActivityIsLoading) + { + + } + else if (ActivityList?.Count == 0) + { + + } + else if (ActivityList != null) + { + +
+ +
+ +
+ @foreach (var activity in CurrentPageActivity) + { + } - @*
- - Visualizzate: @CurrentPageCommesse.Count() / @FilteredCommesse.Count() - - @if (!string.IsNullOrEmpty(SearchTerm)) - { - - Filtro: "@SearchTerm" - - } -
*@ + @if (TotalPagesActivity > 1) + { +
+ +
+ +
+ + 5 + 15 + 30 + +
+ }
}
+ + + + } @@ -230,75 +275,77 @@ else private ContactDTO Anag { get; set; } = new(); private List? PersRif { get; set; } private List? Commesse { get; set; } + private List ActivityList { get; set; } = []; private StbUser? Agente { get; set; } private Dictionary?> Steps { get; set; } = new(); // Stati di caricamento private bool IsLoading { get; set; } = true; private bool IsLoadingCommesse { get; set; } = true; - private bool IsLoadingSteps { get; set; } = false; - private readonly HashSet _loadingSteps = new(); + private bool ActivityIsLoading { get; set; } = true; + private bool IsLoadingSteps { get; set; } + private readonly HashSet _loadingSteps = []; // Gestione tab - private int ActiveTab { get; set; } = 0; + private int ActiveTab { get; set; } - // Paginazione e filtri - private int _currentPage = 1; - private int _selectedPageSize = 5; - private string _searchTerm = string.Empty; - private List _filteredCommesse = new(); + // Paginazione e filtri per COMMESSE + private int _selectedPageCommesse = 1; + private int _selectedPageSizeCommesse = 5; + private string _searchTermCommesse = string.Empty; + private List _filteredCommesse = []; + + // Paginazione e filtri per ATTIVITÀ + private int _currentPageActivity = 1; + private int _selectedPageSizeActivity = 5; + private string _searchTermActivity = string.Empty; + private List _filteredActivity = []; // Cancellation tokens per gestire le richieste asincrone private CancellationTokenSource? _loadingCts; private CancellationTokenSource? _stepsCts; // Timer per il debounce della ricerca - private Timer? _searchTimer; + private Timer? _searchTimerCommesse; + private Timer? _searchTimerActivity; private const int SearchDelayMs = 300; - #region Properties + #region Properties per Commesse - private int CurrentPage + private int SelectedPageCommesse { - get => _currentPage; + get => _selectedPageCommesse; set { - if (_currentPage != value) - { - _currentPage = value; - _ = LoadStepsForCurrentPageAsync(); - } + if (_selectedPageCommesse == value) return; + _selectedPageCommesse = value; + _ = LoadStepsForCurrentPageAsync(); } } - private int SelectedPageSize + private int SelectedPageSizeCommesse { - get => _selectedPageSize; + get => _selectedPageSizeCommesse; set { - if (_selectedPageSize != value) - { - _selectedPageSize = value; - _currentPage = 1; - ApplyFilters(); - _ = LoadStepsForCurrentPageAsync(); - } + if (_selectedPageSizeCommesse == value) return; + _selectedPageSizeCommesse = value; + _selectedPageCommesse = 1; + ApplyFiltersCommesse(); + _ = LoadStepsForCurrentPageAsync(); } } - private string SearchTerm + private string SearchTermCommesse { - get => _searchTerm; + get => _searchTermCommesse; set { - if (_searchTerm != value) - { - _searchTerm = value; + if (_searchTermCommesse == value) return; + _searchTermCommesse = value; - // Debounce della ricerca - _searchTimer?.Dispose(); - _searchTimer = new Timer(async _ => await InvokeAsync(ApplyFilters), null, SearchDelayMs, Timeout.Infinite); - } + _searchTimerCommesse?.Dispose(); + _searchTimerCommesse = new Timer(async _ => await InvokeAsync(ApplyFiltersCommesse), null, SearchDelayMs, Timeout.Infinite); } } @@ -312,11 +359,67 @@ else } } - private int TotalPages => - FilteredCommesse.Count == 0 ? 1 : (int)Math.Ceiling(FilteredCommesse.Count / (double)SelectedPageSize); + private int TotalPagesCommesse => + FilteredCommesse.Count == 0 ? 1 : (int)Math.Ceiling(FilteredCommesse.Count / (double)SelectedPageSizeCommesse); private IEnumerable CurrentPageCommesse => - FilteredCommesse.Skip((CurrentPage - 1) * SelectedPageSize).Take(SelectedPageSize); + FilteredCommesse.Skip((SelectedPageCommesse - 1) * SelectedPageSizeCommesse).Take(SelectedPageSizeCommesse); + + #endregion + + #region Properties per Attività + + private int CurrentPageActivityIndex + { + get => _currentPageActivity; + set + { + if (_currentPageActivity == value) return; + _currentPageActivity = value; + StateHasChanged(); + } + } + + private int SelectedPageSizeActivity + { + get => _selectedPageSizeActivity; + set + { + if (_selectedPageSizeActivity == value) return; + _selectedPageSizeActivity = value; + _currentPageActivity = 1; + ApplyFiltersActivity(); + } + } + + private string SearchTermActivity + { + get => _searchTermActivity; + set + { + if (_searchTermActivity == value) return; + _searchTermActivity = value; + + _searchTimerActivity?.Dispose(); + _searchTimerActivity = new Timer(async _ => await InvokeAsync(ApplyFiltersActivity), null, SearchDelayMs, Timeout.Infinite); + } + } + + private List FilteredActivity + { + get => _filteredActivity; + set + { + _filteredActivity = value; + StateHasChanged(); + } + } + + private int TotalPagesActivity => + FilteredActivity.Count == 0 ? 1 : (int)Math.Ceiling(FilteredActivity.Count / (double)SelectedPageSizeActivity); + + private IEnumerable CurrentPageActivity => + FilteredActivity.Skip((CurrentPageActivityIndex - 1) * SelectedPageSizeActivity).Take(SelectedPageSizeActivity); #endregion @@ -339,7 +442,6 @@ else } catch (Exception ex) { - // Log dell'errore Console.WriteLine($"Errore in OnInitializedAsync: {ex.Message}"); } finally @@ -351,11 +453,12 @@ else public async ValueTask DisposeAsync() { - _loadingCts?.Cancel(); + _loadingCts?.CancelAsync(); _loadingCts?.Dispose(); - _stepsCts?.Cancel(); + _stepsCts?.CancelAsync(); _stepsCts?.Dispose(); - _searchTimer?.Dispose(); + _searchTimerCommesse?.DisposeAsync(); + _searchTimerActivity?.DisposeAsync(); } #endregion @@ -366,21 +469,18 @@ else { try { - // Caricamento dati principali await LoadAnagAsync(); await LoadPersRifAsync(); + _ = LoadActivity(); - // 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) @@ -393,12 +493,12 @@ else { if (IsContact) { - var clie = (await ManageData.GetTable(x => x.CodAnag.Equals(CodContact))).Last(); + var clie = (await ManageData.GetTable(x => x.CodAnag!.Equals(CodContact))).Last(); Anag = Mapper.Map(clie); } else { - var pros = (await ManageData.GetTable(x => x.CodPpro.Equals(CodContact))).Last(); + var pros = (await ManageData.GetTable(x => x.CodPpro!.Equals(CodContact))).Last(); Anag = Mapper.Map(pros); } } @@ -407,16 +507,35 @@ else { if (IsContact) { - var pers = await ManageData.GetTable(x => x.CodAnag.Equals(Anag.CodContact)); + var pers = await ManageData.GetTable(x => x.CodAnag!.Equals(Anag.CodContact)); PersRif = Mapper.Map>(pers); } else { - var pers = await ManageData.GetTable(x => x.CodPpro.Equals(Anag.CodContact)); + var pers = await ManageData.GetTable(x => x.CodPpro!.Equals(Anag.CodContact)); PersRif = Mapper.Map>(pers); } } + private async Task LoadActivity() + { + await Task.Run(async () => + { + var activities = await IntegryApiService.RetrieveActivity(new CRMRetrieveActivityRequestDTO { CodAnag = Anag.CodContact }); + ActivityList = Mapper.Map>(activities) + .OrderByDescending(x => + (x.EffectiveTime ?? x.EstimatedTime) ?? x.DataInsAct + ).ToList(); + }); + + UserState.Activitys = ActivityList; + + ApplyFiltersActivity(); + + ActivityIsLoading = false; + StateHasChanged(); + } + private async Task LoadCommesseAsync() { try @@ -425,17 +544,14 @@ else 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) + .OrderByDescending(x => x.CodJcom) .ToList(); UserState.Commesse = Commesse; - ApplyFilters(); + ApplyFiltersCommesse(); - // Caricamento steps per la prima pagina await LoadStepsForCurrentPageAsync(); } catch (Exception ex) @@ -455,11 +571,14 @@ else PersRif = UserState.PersRif; Commesse = UserState.Commesse; Agente = UserState.Agente; - Steps = UserState.Steps ?? new Dictionary?>(); + Steps = UserState.Steps; + ActivityList = UserState.Activitys; - ApplyFilters(); + ApplyFiltersCommesse(); + ApplyFiltersActivity(); IsLoadingCommesse = false; + ActivityIsLoading = false; } private void SaveDataToSession() @@ -469,6 +588,7 @@ else UserState.PersRif = PersRif; UserState.Agente = Agente; UserState.Steps = Steps; + UserState.Activitys = ActivityList; } #endregion @@ -560,30 +680,32 @@ else { 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()) + } + else if (tabIndex == 1 && Steps.IsNullOrEmpty()) { _ = Task.Run(async () => await LoadStepsForCurrentPageAsync()); } + else if (tabIndex == 2 && ActivityList?.Count == 0) + { + _ = Task.Run(async () => await LoadActivity()); + } } - private void ApplyFilters() + private void ApplyFiltersCommesse() { if (Commesse == null) { - FilteredCommesse = new List(); + FilteredCommesse = []; return; } var filtered = Commesse.AsEnumerable(); - - // Filtro per testo di ricerca - if (!string.IsNullOrWhiteSpace(SearchTerm)) + if (!string.IsNullOrWhiteSpace(SearchTermCommesse)) { - var searchLower = SearchTerm.ToLowerInvariant(); + var searchLower = SearchTermCommesse.ToLowerInvariant(); filtered = filtered.Where(c => c.CodJcom?.ToLowerInvariant().Contains(searchLower) == true || c.Descrizione?.ToLowerInvariant().Contains(searchLower) == true || @@ -591,21 +713,40 @@ else } FilteredCommesse = filtered.ToList(); + if (SelectedPageCommesse > TotalPagesCommesse && TotalPagesCommesse > 0) _selectedPageCommesse = 1; - // Reset della pagina se necessario - if (CurrentPage > TotalPages && TotalPages > 0) - { - _currentPage = 1; - } - - // Carica gli steps per la pagina corrente _ = LoadStepsForCurrentPageAsync(); } - private void ClearSearch() + private void ApplyFiltersActivity() { - SearchTerm = string.Empty; - ApplyFilters(); + var filtered = ActivityList.AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(SearchTermActivity)) + { + var searchLower = SearchTermActivity.ToLowerInvariant(); + filtered = filtered.Where(a => + a.ActivityDescription?.ToLowerInvariant().Contains(searchLower) == true || + a.ActivityId?.ToLowerInvariant().Contains(searchLower) == true); + } + + FilteredActivity = filtered.ToList(); + if (CurrentPageActivityIndex > TotalPagesActivity && TotalPagesActivity > 0) + { + _currentPageActivity = 1; + } + } + + private void ClearSearchCommesse() + { + SearchTermCommesse = string.Empty; + ApplyFiltersCommesse(); + } + + private void ClearSearchActivity() + { + SearchTermActivity = string.Empty; + ApplyFiltersActivity(); } #endregion @@ -629,12 +770,10 @@ else 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 af8db86..b69a8da 100644 --- a/salesbook.Shared/Components/Pages/User.razor.css +++ b/salesbook.Shared/Components/Pages/User.razor.css @@ -165,10 +165,15 @@ gap: 1.25rem; } +.attivita-container { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + /*-------------- TabPanel ----------------*/ - .box { display: flex; flex-direction: column; @@ -199,7 +204,7 @@ content: ''; display: block; height: 3px; - width: calc(100% / 2); + width: calc(100% / 3); position: absolute; bottom: 0; left: 0; @@ -223,7 +228,8 @@ /* tab attivo */ #tab1:checked ~ .box .tab-list .tab-item:nth-child(1), -#tab2:checked ~ .box .tab-list .tab-item:nth-child(2) { +#tab2:checked ~ .box .tab-list .tab-item:nth-child(2), +#tab3:checked ~ .box .tab-list .tab-item:nth-child(3) { opacity: 1; font-weight: bold; display: block; @@ -235,6 +241,8 @@ #tab2:checked ~ .box .tab-list::before { transform: translateX(100%); } +#tab3:checked ~ .box .tab-list::before { transform: translateX(200%); } + .tab-container { display: flex; flex-direction: column; @@ -257,7 +265,10 @@ } #tab1:checked ~ .tab-container .tab-content:nth-child(1), -#tab2:checked ~ .tab-container .tab-content:nth-child(2) { display: block; } +#tab2:checked ~ .tab-container .tab-content:nth-child(2), +#tab3:checked ~ .tab-container .tab-content:nth-child(3) { + display: block; +} @keyframes fade { from { diff --git a/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor index 9217641..7fa75f1 100644 --- a/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor @@ -29,11 +29,25 @@ @if (Activity.EffectiveTime is null) { - @($"{Activity.EstimatedTime:t}") + if (ShowDate) + { + @($"{Activity.EstimatedTime:g}") + } + else + { + @($"{Activity.EstimatedTime:t}") + } } else { - @($"{Activity.EffectiveTime:t}") + if (ShowDate) + { + @($"{Activity.EffectiveTime:g}") + } + else + { + @($"{Activity.EffectiveTime:t}") + } } @@ -68,6 +82,8 @@ [Parameter] public EventCallback ActivityChanged { get; set; } [Parameter] public EventCallback ActivityDeleted { get; set; } + [Parameter] public bool ShowDate { get; set; } + private TimeSpan? Durata { get; set; } protected override void OnParametersSet() @@ -82,7 +98,7 @@ private async Task OpenActivity() { - var result = await ModalHelpers.OpenActivityForm(Dialog, null, Activity.ActivityId); + var result = await ModalHelpers.OpenActivityForm(Dialog, Activity, null); switch (result) { diff --git a/salesbook.Shared/Components/SingleElements/Card/UserCard.razor b/salesbook.Shared/Components/SingleElements/Card/UserCard.razor index ec53244..ef46fd5 100644 --- a/salesbook.Shared/Components/SingleElements/Card/UserCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/UserCard.razor @@ -25,11 +25,23 @@ @if (!Commesse.IsNullOrEmpty()) {
- @foreach (var commessa in Commesse!) + + @for (var i = 0; i < Commesse!.Count; i++) { + var commessa = Commesse[i]; +
- @($"{commessa.CodJcom} - {commessa.Descrizione}") + @if (i > 5 && Commesse.Count - i > 1) + { + @($"E altre {Commesse.Count - i} commesse") + } + else + { + @($"{commessa.CodJcom} - {commessa.Descrizione}") + }
+ + @if (i > 5 && Commesse.Count - i > 1) break; }
} @@ -63,7 +75,8 @@ if (ShowSectionCommesse) { - Commesse = await ManageData.GetTable(x => x.CodAnag.Equals(User.CodContact)); + Commesse = (await ManageData.GetTable(x => x.CodAnag.Equals(User.CodContact))) + .OrderByDescending(x => x.CodJcom).ToList(); IsLoading = false; StateHasChanged(); return; diff --git a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor index c0ad846..b180839 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -250,8 +250,8 @@ private List Pros { get; set; } = []; private List? ActivityFileList { get; set; } - private bool IsNew => Id.IsNullOrEmpty(); - private bool IsView => !NetworkService.IsNetworkAvailable(); + private bool IsNew { get; set; } + private bool IsView => !NetworkService.ConnectionAvailable; private string? LabelSave { get; set; } @@ -276,18 +276,18 @@ { Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; - _ = LoadData(); - - LabelSave = IsNew ? "Aggiungi" : null; - - if (!Id.IsNullOrEmpty()) - ActivityModel = (await ManageData.GetActivity(new WhereCondActivity { ActivityId = Id }, true)).Last(); - if (ActivityCopied != null) { ActivityModel = ActivityCopied.Clone(); } + else if (!Id.IsNullOrEmpty()) + ActivityModel = (await ManageData.GetActivity(new WhereCondActivity { ActivityId = Id }, true)).Last(); + if (Id.IsNullOrEmpty()) Id = ActivityModel.ActivityId; + IsNew = Id.IsNullOrEmpty(); + LabelSave = IsNew ? "Aggiungi" : null; + + _ = LoadData(); await LoadCommesse(); if (IsNew) diff --git a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor index 337fa94..8b6ffbd 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor @@ -280,7 +280,7 @@ } else { - @if (NetworkService.IsNetworkAvailable() && !ContactModel.IsContact) + @if (NetworkService.ConnectionAvailable && !ContactModel.IsContact) { Users { get; set; } = []; private bool IsNew => OriginalModel is null; - private bool IsView => !NetworkService.IsNetworkAvailable(); + private bool IsView => !NetworkService.ConnectionAvailable; private string? LabelSave { get; set; } diff --git a/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor b/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor index b95a1ac..3e107ae 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor @@ -118,7 +118,7 @@ private PersRifDTO PersRifModel { get; set; } = new(); private bool IsNew => OriginalModel is null; - private bool IsView => !NetworkService.IsNetworkAvailable(); + private bool IsView => !NetworkService.ConnectionAvailable; private string? LabelSave { get; set; } diff --git a/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs b/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs index 44e201a..4da291b 100644 --- a/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs +++ b/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs @@ -10,6 +10,9 @@ public class CRMRetrieveActivityRequestDTO [JsonPropertyName("activityId")] public string? ActivityId { get; set; } + [JsonPropertyName("codAnag")] + public string? CodAnag { get; set; } + [JsonPropertyName("codJcom")] public string? CodJcom { get; set; } diff --git a/salesbook.Shared/Core/Dto/PageState/UserPageState.cs b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs index a437ea1..efecc2a 100644 --- a/salesbook.Shared/Core/Dto/PageState/UserPageState.cs +++ b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs @@ -1,4 +1,5 @@ -using salesbook.Shared.Core.Dto.JobProgress; +using salesbook.Shared.Core.Dto.Activity; +using salesbook.Shared.Core.Dto.JobProgress; using salesbook.Shared.Core.Entity; namespace salesbook.Shared.Core.Dto.PageState; @@ -12,4 +13,5 @@ public class UserPageState public List Commesse { get; set; } public StbUser? Agente { get; set; } public Dictionary?> Steps { get; set; } + public List Activitys { get; set; } } \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IIntegryApiService.cs b/salesbook.Shared/Core/Interface/IIntegryApiService.cs index 80d7915..a8fdc7d 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IIntegryApiService.cs @@ -8,6 +8,8 @@ namespace salesbook.Shared.Core.Interface; public interface IIntegryApiService { + Task SystemOk(); + Task?> RetrieveActivity(CRMRetrieveActivityRequestDTO activityRequest); Task?> RetrieveAllCommesse(string? dateFilter = null); Task RetrieveAnagClie(CRMAnagRequestDTO request); diff --git a/salesbook.Shared/Core/Interface/INetworkService.cs b/salesbook.Shared/Core/Interface/INetworkService.cs index d397c46..1bec94c 100644 --- a/salesbook.Shared/Core/Interface/INetworkService.cs +++ b/salesbook.Shared/Core/Interface/INetworkService.cs @@ -2,5 +2,7 @@ public interface INetworkService { + public bool ConnectionAvailable { get; set; } + public bool IsNetworkAvailable(); } \ No newline at end of file diff --git a/salesbook.Shared/Core/Services/IntegryApiService.cs b/salesbook.Shared/Core/Services/IntegryApiService.cs index b12cb7a..c138b68 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -13,6 +13,19 @@ namespace salesbook.Shared.Core.Services; public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUserSession userSession) : IIntegryApiService { + public async Task SystemOk() + { + try + { + await integryApiRestClient.Get("system/ok"); + return true; + } + catch (Exception e) + { + return false; + } + } + public Task?> RetrieveActivity(CRMRetrieveActivityRequestDTO activityRequest) => integryApiRestClient.AuthorizedPost?>("crm/retrieveActivity", activityRequest); diff --git a/salesbook.Shared/wwwroot/css/app.css b/salesbook.Shared/wwwroot/css/app.css index ba538cb..076314e 100644 --- a/salesbook.Shared/wwwroot/css/app.css +++ b/salesbook.Shared/wwwroot/css/app.css @@ -22,6 +22,42 @@ a, .btn-link { color: inherit; } +/*ServicesIsDown" : "SystemOk" : "NetworkKo*/ + +.Connection { + padding: 0 .75rem; + font-weight: 700; + display: flex; + flex-direction: row; + gap: 1rem; + font-size: larger; + transition: all 0.5s ease; + opacity: 0; + transform: translateY(-35px); + min-height: 35px; + align-items: center; +} + +.Connection.ServicesIsDown, .Connection.NetworkKo { + background-color: var(--mud-palette-error); + color: white; +} + +.Connection.SystemOk { + background-color: var(--mud-palette-success); + color: white; +} + +.Connection.Show { + opacity: 1; + transform: translateY(0); +} + +.page > .Connection.Hide ~ main { + transition: all 0.5s ease; + transform: translateY(-35px); +} + .btn-primary { color: #fff; background-color: var(--primary-color); diff --git a/salesbook.Shared/wwwroot/js/main.js b/salesbook.Shared/wwwroot/js/main.js index 5826476..b050ae1 100644 --- a/salesbook.Shared/wwwroot/js/main.js +++ b/salesbook.Shared/wwwroot/js/main.js @@ -38,16 +38,41 @@ function monitorExpandedClass(mutations) { }); } +// Funzione per monitorare bottom-sheet-container e gestire la navbar +function monitorBottomSheetClass(mutations) { + const bottomSheet = document.querySelector(".bottom-sheet-container"); + const navbar = document.querySelector(".animated-navbar"); + + if (!bottomSheet || !navbar) return; + + mutations.forEach(function (mutation) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target === bottomSheet) { + if (bottomSheet.classList.contains("show")) { + navbar.classList.remove("show-nav"); + navbar.classList.add("hide-nav"); + console.log("Navbar nascosta (hide-nav)"); + } else { + navbar.classList.remove("hide-nav"); + navbar.classList.add("show-nav"); + console.log("Navbar mostrata (show-nav)"); + } + } + }); +} + // Esegui la funzione tabindex inizialmente addTabindexToButtons(); -// Observer combinato per entrambe le funzionalità +// Observer combinato per tutte le funzionalità const observer = new MutationObserver((mutations) => { // Aggiungi tabindex ai nuovi bottoni addTabindexToButtons(); // Monitora le classi expanded monitorExpandedClass(mutations); + + // Monitora bottom-sheet-container + monitorBottomSheetClass(mutations); }); // Osserva sia i cambiamenti nel DOM che gli attributi @@ -56,4 +81,20 @@ observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['class'] -}); \ No newline at end of file +}); + +// Sync iniziale per la navbar (nel caso la pagina parte già con .show) +document.addEventListener("DOMContentLoaded", () => { + const bottomSheet = document.querySelector(".bottom-sheet-container"); + const navbar = document.querySelector(".animated-navbar"); + + if (bottomSheet && navbar) { + if (bottomSheet.classList.contains("show")) { + navbar.classList.remove("show-nav"); + navbar.classList.add("hide-nav"); + } else { + navbar.classList.remove("hide-nav"); + navbar.classList.add("show-nav"); + } + } +}); diff --git a/salesbook.Web/Core/Services/NetworkService.cs b/salesbook.Web/Core/Services/NetworkService.cs index 918d89e..64b4044 100644 --- a/salesbook.Web/Core/Services/NetworkService.cs +++ b/salesbook.Web/Core/Services/NetworkService.cs @@ -4,6 +4,8 @@ namespace salesbook.Web.Core.Services; public class NetworkService : INetworkService { + public bool ConnectionAvailable { get; set; } + public bool IsNetworkAvailable() { return true; From 93b1a94c88a711cff64d1031a39e5d3ce7b4979d Mon Sep 17 00:00:00 2001 From: MarcoE Date: Mon, 8 Sep 2025 12:19:17 +0200 Subject: [PATCH 09/24] =?UTF-8?q?Filtrati=20tipi=20attivit=C3=A0=20per=20u?= =?UTF-8?q?tente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Services/LocalDbService.cs | 3 + salesbook.Maui/Core/Services/SyncDbService.cs | 3 + .../Components/Layout/MainLayout.razor | 4 +- .../Components/Pages/SyncPage.razor | 1 + .../SingleElements/Modal/ActivityForm.razor | 20 ++++++- .../Core/Dto/SettingsResponseDTO.cs | 3 + .../Core/Entity/SrlActivityTypeUser.cs | 56 +++++++++++++++++++ 7 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 salesbook.Shared/Core/Entity/SrlActivityTypeUser.cs diff --git a/salesbook.Maui/Core/Services/LocalDbService.cs b/salesbook.Maui/Core/Services/LocalDbService.cs index abcfe8f..e8b22ec 100644 --- a/salesbook.Maui/Core/Services/LocalDbService.cs +++ b/salesbook.Maui/Core/Services/LocalDbService.cs @@ -22,6 +22,7 @@ public class LocalDbService _connection.CreateTableAsync(); _connection.CreateTableAsync(); _connection.CreateTableAsync(); + _connection.CreateTableAsync(); _connection.CreateTableAsync(); _connection.CreateTableAsync(); _connection.CreateTableAsync(); @@ -33,12 +34,14 @@ public class LocalDbService { await _connection.ExecuteAsync("DROP TABLE IF EXISTS stb_activity_result;"); await _connection.ExecuteAsync("DROP TABLE IF EXISTS stb_activity_type;"); + await _connection.ExecuteAsync("DROP TABLE IF EXISTS srl_activity_type_user;"); await _connection.ExecuteAsync("DROP TABLE IF EXISTS stb_user;"); await _connection.ExecuteAsync("DROP TABLE IF EXISTS vtb_tipi;"); await _connection.ExecuteAsync("DROP TABLE IF EXISTS nazioni;"); await _connection.CreateTableAsync(); await _connection.CreateTableAsync(); + await _connection.CreateTableAsync(); await _connection.CreateTableAsync(); await _connection.CreateTableAsync(); await _connection.CreateTableAsync(); diff --git a/salesbook.Maui/Core/Services/SyncDbService.cs b/salesbook.Maui/Core/Services/SyncDbService.cs index f6f9be2..d5c55bb 100644 --- a/salesbook.Maui/Core/Services/SyncDbService.cs +++ b/salesbook.Maui/Core/Services/SyncDbService.cs @@ -29,6 +29,9 @@ public class SyncDbService(IIntegryApiService integryApiService, LocalDbService if (!settingsResponse.ActivityTypes.IsNullOrEmpty()) await localDb.InsertAll(settingsResponse.ActivityTypes!); + if (!settingsResponse.ActivityTypeUsers.IsNullOrEmpty()) + await localDb.InsertAll(settingsResponse.ActivityTypeUsers!); + if (!settingsResponse.StbUsers.IsNullOrEmpty()) await localDb.InsertAll(settingsResponse.StbUsers!); diff --git a/salesbook.Shared/Components/Layout/MainLayout.razor b/salesbook.Shared/Components/Layout/MainLayout.razor index eb1300e..dc8d62b 100644 --- a/salesbook.Shared/Components/Layout/MainLayout.razor +++ b/salesbook.Shared/Components/Layout/MainLayout.razor @@ -55,7 +55,7 @@ private bool _showWarning; private DateTime _lastApiCheck = DateTime.MinValue; - private int _delaySeconds = 3; + private const int DelaySeconds = 60; private CancellationTokenSource? _cts; @@ -166,7 +166,7 @@ var isNetworkAvailable = NetworkService.IsNetworkAvailable(); var servicesDown = ServicesIsDown; - if (isNetworkAvailable && (DateTime.UtcNow - _lastApiCheck).TotalSeconds >= _delaySeconds) + if (isNetworkAvailable && (DateTime.UtcNow - _lastApiCheck).TotalSeconds >= DelaySeconds) { servicesDown = !await IntegryApiService.SystemOk(); _lastApiCheck = DateTime.UtcNow; diff --git a/salesbook.Shared/Components/Pages/SyncPage.razor b/salesbook.Shared/Components/Pages/SyncPage.razor index 77b8cf4..1597d9f 100644 --- a/salesbook.Shared/Components/Pages/SyncPage.razor +++ b/salesbook.Shared/Components/Pages/SyncPage.razor @@ -17,6 +17,7 @@ protected override void OnInitialized() { + Elements["Commesse"] = false; Elements["Impostazioni"] = false; } diff --git a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor index b180839..352cdc7 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -243,7 +243,7 @@ private ActivityDTO ActivityModel { get; set; } = new(); private List ActivityResult { get; set; } = []; - private List ActivityType { get; set; } = []; + private List ActivityType { get; set; } = []; private List Users { get; set; } = []; private List Commesse { get; set; } = []; private List Clienti { get; set; } = []; @@ -297,6 +297,8 @@ ActivityModel.UserName = UserSession.User.Username; } + await LoadActivityType(); + OriginalModel = ActivityModel.Clone(); } @@ -383,7 +385,15 @@ ActivityResult = await ManageData.GetTable(); Clienti = await ManageData.GetClienti(new WhereCondContact {FlagStato = "A"}); Pros = await ManageData.GetProspect(); - ActivityType = await ManageData.GetTable(x => x.FlagTipologia.Equals("A")); + } + + private async Task LoadActivityType() + { + if (ActivityModel.UserName is null) ActivityType = []; + + ActivityType = await ManageData.GetTable(x => + x.UserName != null && x.UserName.Equals(ActivityModel.UserName) + ); } private async Task LoadCommesse() => @@ -430,6 +440,12 @@ OnAfterChangeValue(); } + private async Task OnUserChanged() + { + await LoadActivityType(); + OnAfterChangeValue(); + } + private void OnAfterChangeValue() { if (!IsNew) diff --git a/salesbook.Shared/Core/Dto/SettingsResponseDTO.cs b/salesbook.Shared/Core/Dto/SettingsResponseDTO.cs index 0e8a49c..7680c3f 100644 --- a/salesbook.Shared/Core/Dto/SettingsResponseDTO.cs +++ b/salesbook.Shared/Core/Dto/SettingsResponseDTO.cs @@ -8,6 +8,9 @@ public class SettingsResponseDTO [JsonPropertyName("activityTypes")] public List? ActivityTypes { get; set; } + [JsonPropertyName("activityTypeUsers")] + public List? ActivityTypeUsers { get; set; } + [JsonPropertyName("activityResults")] public List? ActivityResults { get; set; } diff --git a/salesbook.Shared/Core/Entity/SrlActivityTypeUser.cs b/salesbook.Shared/Core/Entity/SrlActivityTypeUser.cs new file mode 100644 index 0000000..c39fb39 --- /dev/null +++ b/salesbook.Shared/Core/Entity/SrlActivityTypeUser.cs @@ -0,0 +1,56 @@ +using SQLite; +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Entity; + +[Table("srl_activity_type_user")] +public class SrlActivityTypeUser +{ + [PrimaryKey, Column("composite_key")] + public string CompositeKey { get; set; } + + private string? _activityTypeId; + + [Column("activity_type_id"), JsonPropertyName("activityTypeId"), Indexed(Name = "ActivityTypePK", Order = 1, Unique = true)] + public string? ActivityTypeId + { + get => _activityTypeId; + set + { + _activityTypeId = value; + if (_activityTypeId != null && _flagTipologia != null && _userName != null) + UpdateCompositeKey(); + } + } + + private string? _flagTipologia; + + [Column("flag_tipologia"), JsonPropertyName("flagTipologia"), Indexed(Name = "ActivityTypePK", Order = 2, Unique = true)] + public string? FlagTipologia + { + get => _flagTipologia; + set + { + _flagTipologia = value; + if (_activityTypeId != null && _flagTipologia != null && _userName != null) + UpdateCompositeKey(); + } + } + + private string? _userName; + + [Column("user_name"), JsonPropertyName("userName"), Indexed(Name = "ActivityTypePK", Order = 3, Unique = true)] + public string? UserName + { + get => _userName; + set + { + _userName = value; + if (_activityTypeId != null && _flagTipologia != null && _userName != null) + UpdateCompositeKey(); + } + } + + private void UpdateCompositeKey() => + CompositeKey = $"{ActivityTypeId}::{FlagTipologia}::{UserName}"; +} \ No newline at end of file From dfb86e3cd7895bf5844619fb752a347ee1409c0f Mon Sep 17 00:00:00 2001 From: MarcoE Date: Tue, 9 Sep 2025 11:43:07 +0200 Subject: [PATCH 10/24] Implementate notifiche --- .../IntegryNotificationRestClient.cs | 2 +- .../Core/Services/ManageDataService.cs | 2 + salesbook.Maui/Core/Services/SyncDbService.cs | 1 + .../Core/System/Network/NetworkService.cs | 1 + .../FirebaseNotificationService.cs | 25 +++- .../Notification/ShinyNotificationManager.cs | 9 ++ salesbook.Maui/MauiProgram.cs | 10 +- .../Platforms/Android/AndroidManifest.xml | 8 +- .../Platforms/Android/AndroidModule.cs | 13 +++ .../Core/BatteryOptimizationManagerService.cs | 28 +++++ .../Platforms/Android/MainActivity.cs | 33 +++++- .../Platforms/Android/MainApplication.cs | 22 ++-- salesbook.Maui/Platforms/iOS/AppDelegate.cs | 16 ++- .../Core/BatteryOptimizationManagerService.cs | 12 ++ .../Platforms/iOS/PrivacyInfo.xcprivacy | 110 ++++++++++++++++++ salesbook.Maui/Platforms/iOS/iOSModule.cs | 13 +++ salesbook.Maui/salesbook.Maui.csproj | 13 ++- .../Components/Layout/MainLayout.razor | 4 +- .../Components/Pages/Commessa.razor | 1 + salesbook.Shared/Components/Pages/Home.razor | 13 +++ salesbook.Shared/Components/Pages/Login.razor | 1 + .../Components/Pages/PersonalInfo.razor | 1 + salesbook.Shared/Components/Pages/User.razor | 1 + .../BottomSheet/SearchAddress.razor | 1 + .../SingleElements/Card/AttachCard.razor | 1 + .../SingleElements/Modal/ActivityForm.razor | 2 + .../SingleElements/Modal/ContactForm.razor | 2 + .../SingleElements/Modal/PersRifForm.razor | 2 + .../{ => IntegryApi}/IIntegryApiService.cs | 2 +- .../IIntegryNotificationRestClient.cs | 2 +- .../IBatteryOptimizationManagerService.cs | 8 ++ .../{ => System/Network}/INetworkService.cs | 2 +- .../Notification/IShinyNotificationManager.cs | 6 + .../Core/Services/IntegryApiService.cs | 1 + salesbook.Web/Core/Services/NetworkService.cs | 1 + salesbook.Web/Program.cs | 2 + 36 files changed, 338 insertions(+), 33 deletions(-) create mode 100644 salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs create mode 100644 salesbook.Maui/Platforms/Android/AndroidModule.cs create mode 100644 salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs create mode 100644 salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs create mode 100644 salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy create mode 100644 salesbook.Maui/Platforms/iOS/iOSModule.cs rename salesbook.Shared/Core/Interface/{ => IntegryApi}/IIntegryApiService.cs (97%) rename salesbook.Shared/Core/Interface/{ => IntegryApi}/IIntegryNotificationRestClient.cs (73%) create mode 100644 salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs rename salesbook.Shared/Core/Interface/{ => System/Network}/INetworkService.cs (67%) create mode 100644 salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs index d18ff4c..021fc6c 100644 --- a/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs +++ b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs @@ -2,7 +2,7 @@ using IntegryApiClient.Core.Domain.RestClient.Contacts; using Microsoft.Extensions.Logging; using salesbook.Maui.Core.RestClient.IntegryApi.Dto; -using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; namespace salesbook.Maui.Core.RestClient.IntegryApi; diff --git a/salesbook.Maui/Core/Services/ManageDataService.cs b/salesbook.Maui/Core/Services/ManageDataService.cs index 9b6dc26..9031c83 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -7,6 +7,8 @@ using salesbook.Shared.Core.Helpers.Enum; using salesbook.Shared.Core.Interface; using Sentry.Protocol; using System.Linq.Expressions; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Interface.System.Network; namespace salesbook.Maui.Core.Services; diff --git a/salesbook.Maui/Core/Services/SyncDbService.cs b/salesbook.Maui/Core/Services/SyncDbService.cs index d5c55bb..795f99a 100644 --- a/salesbook.Maui/Core/Services/SyncDbService.cs +++ b/salesbook.Maui/Core/Services/SyncDbService.cs @@ -1,5 +1,6 @@ using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; namespace salesbook.Maui.Core.Services; diff --git a/salesbook.Maui/Core/System/Network/NetworkService.cs b/salesbook.Maui/Core/System/Network/NetworkService.cs index 124578a..af1da33 100644 --- a/salesbook.Maui/Core/System/Network/NetworkService.cs +++ b/salesbook.Maui/Core/System/Network/NetworkService.cs @@ -1,4 +1,5 @@ using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.System.Network; namespace salesbook.Maui.Core.System.Network; diff --git a/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs index e0b7c28..2f766eb 100644 --- a/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs +++ b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs @@ -1,16 +1,37 @@ using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; using Shiny; +using Shiny.Notifications; using Shiny.Push; namespace salesbook.Maui.Core.System.Notification; -public class FirebaseNotificationService(IPushManager pushManager, IIntegryNotificationRestClient integryNotificationRestClient) : IFirebaseNotificationService +public class FirebaseNotificationService( + IPushManager pushManager, + IIntegryNotificationRestClient integryNotificationRestClient, + INotificationManager notificationManager +) : IFirebaseNotificationService { public async Task InitFirebase() { - var (accessState, token) = await pushManager.RequestAccess(); + CreateNotificationChannel(); + var (accessState, token) = await pushManager.RequestAccess(); + if (accessState == AccessState.Denied || token is null) return; await integryNotificationRestClient.Register(token); } + + private void CreateNotificationChannel() + { + var channel = new Channel + { + Identifier = "salesbook_push", + Description = "Notifiche push di SalesBook", + Importance = ChannelImportance.High, + Actions = [] + }; + + notificationManager.AddChannel(channel); + } } \ No newline at end of file diff --git a/salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs b/salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs new file mode 100644 index 0000000..ab1ce6b --- /dev/null +++ b/salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs @@ -0,0 +1,9 @@ +using salesbook.Shared.Core.Interface.System.Notification; +using Shiny.Notifications; + +namespace salesbook.Maui.Core.System.Notification; + +public class ShinyNotificationManager(INotificationManager notificationManager) : IShinyNotificationManager +{ + public Task RequestAccess() => notificationManager.RequestAccess(); +} \ No newline at end of file diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index d565c99..1743cbf 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -1,4 +1,3 @@ -using AutoMapper; using CommunityToolkit.Maui; using CommunityToolkit.Mvvm.Messaging; using IntegryApiClient.MAUI; @@ -16,6 +15,9 @@ using salesbook.Shared.Core.Dto; using salesbook.Shared.Core.Dto.PageState; using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Interface.System.Network; +using salesbook.Shared.Core.Interface.System.Notification; using salesbook.Shared.Core.Messages.Activity.Copy; using salesbook.Shared.Core.Messages.Activity.New; using salesbook.Shared.Core.Messages.Back; @@ -29,7 +31,7 @@ namespace salesbook.Maui { private const string AppToken = "f0484398-1f8b-42f5-ab79-5282c164e1d8"; - public static MauiApp CreateMauiApp() + public static MauiAppBuilder CreateMauiAppBuilder() { InteractiveRenderSettings.ConfigureBlazorHybridRenderModes(); @@ -38,6 +40,7 @@ namespace salesbook.Maui .UseMauiApp() .UseIntegry(appToken: AppToken, useLoginAzienda: true) .UseMauiCommunityToolkit() + .UseShiny() .UseSentry(options => { options.Dsn = "https://453b6b38f94fd67e40e0d5306d6caff8@o4508499810254848.ingest.de.sentry.io/4509605099667536"; @@ -82,6 +85,7 @@ namespace salesbook.Maui builder.Services.AddPush(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); @@ -93,7 +97,7 @@ namespace salesbook.Maui builder.Services.AddSingleton(); builder.Services.AddSingleton(); - return builder.Build(); + return builder; } } } \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/AndroidManifest.xml b/salesbook.Maui/Platforms/Android/AndroidManifest.xml index a8b23a0..e38ccd8 100644 --- a/salesbook.Maui/Platforms/Android/AndroidManifest.xml +++ b/salesbook.Maui/Platforms/Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + @@ -15,4 +15,10 @@ + + + + + + \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/AndroidModule.cs b/salesbook.Maui/Platforms/Android/AndroidModule.cs new file mode 100644 index 0000000..19ab9b1 --- /dev/null +++ b/salesbook.Maui/Platforms/Android/AndroidModule.cs @@ -0,0 +1,13 @@ +using salesbook.Maui.Core; +using salesbook.Shared.Core.Interface.System.Battery; + +namespace salesbook.Maui; + +public static class AndroidModule +{ + public static MauiAppBuilder RegisterAndroidAppServices(this MauiAppBuilder mauiAppBuilder) + { + mauiAppBuilder.Services.AddSingleton(); + return mauiAppBuilder; + } +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs b/salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs new file mode 100644 index 0000000..4e1f3ff --- /dev/null +++ b/salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs @@ -0,0 +1,28 @@ +using Android.App; +using Android.Content; +using Android.OS; +using Android.Provider; +using salesbook.Shared.Core.Interface.System.Battery; +using Application = Android.App.Application; + +namespace salesbook.Maui.Core; + +public class BatteryOptimizationManagerService : IBatteryOptimizationManagerService +{ + public bool IsBatteryOptimizationEnabled() + { + var packageName = AppInfo.PackageName; + + var pm = (PowerManager)Application.Context.GetSystemService(Context.PowerService)!; + return !pm.IsIgnoringBatteryOptimizations(packageName); + } + + public void OpenBatteryOptimizationSettings(Action onCompleted) + { + var packageName = AppInfo.PackageName; + + var intent = new Intent(Settings.ActionRequestIgnoreBatteryOptimizations); + intent.SetData(Android.Net.Uri.Parse("package:" + packageName)); + ((MainActivity)Platform.CurrentActivity!).StartActivityForResult(intent, (result, _) => { onCompleted(result == Result.Ok); }); + } +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/MainActivity.cs b/salesbook.Maui/Platforms/Android/MainActivity.cs index 75ed53c..dbc05ea 100644 --- a/salesbook.Maui/Platforms/Android/MainActivity.cs +++ b/salesbook.Maui/Platforms/Android/MainActivity.cs @@ -1,19 +1,42 @@ using Android.App; +using Android.Content; using Android.Content.PM; namespace salesbook.Maui { - [Activity(Theme = "@style/Maui.SplashTheme", + [Activity( + Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] - [IntentFilter([Shiny.ShinyPushIntents.NotificationClickAction], - Categories = new[] - { + + [IntentFilter( + [ + Shiny.ShinyPushIntents.NotificationClickAction + ], + Categories = + [ "android.intent.category.DEFAULT" - } + ] )] public class MainActivity : MauiAppCompatActivity { + private readonly IDictionary> _onActivityResultSubscriber = + new Dictionary>(); + + public void StartActivityForResult(Intent intent, Action onResultAction) + { + var requestCode = new Random(DateTime.Now.Millisecond).Next(); + _onActivityResultSubscriber.Add(requestCode, onResultAction); + StartActivityForResult(intent, requestCode); + } + + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + if (_onActivityResultSubscriber.TryGetValue(requestCode, out var value)) + value(resultCode, data); + + base.OnActivityResult(requestCode, resultCode, data); + } } } \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/MainApplication.cs b/salesbook.Maui/Platforms/Android/MainApplication.cs index 2820e44..ff3db19 100644 --- a/salesbook.Maui/Platforms/Android/MainApplication.cs +++ b/salesbook.Maui/Platforms/Android/MainApplication.cs @@ -1,16 +1,16 @@ using Android.App; using Android.Runtime; -namespace salesbook.Maui -{ - [Application(HardwareAccelerated = true)] - public class MainApplication : MauiApplication - { - public MainApplication(IntPtr handle, JniHandleOwnership ownership) - : base(handle, ownership) - { - } +namespace salesbook.Maui; - protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +[Application(HardwareAccelerated = true)] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { } -} + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiAppBuilder() + .RegisterAndroidAppServices().Build(); +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/iOS/AppDelegate.cs b/salesbook.Maui/Platforms/iOS/AppDelegate.cs index 57062da..0728e77 100644 --- a/salesbook.Maui/Platforms/iOS/AppDelegate.cs +++ b/salesbook.Maui/Platforms/iOS/AppDelegate.cs @@ -1,10 +1,24 @@ using Foundation; +using UIKit; namespace salesbook.Maui { [Register("AppDelegate")] public class AppDelegate : MauiUIApplicationDelegate { - protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiAppBuilder() + .RegisterIosAppServices().Build(); + + [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] + public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) + => global::Shiny.Hosting.Host.Lifecycle.OnRegisteredForRemoteNotifications(deviceToken); + + [Export("application:didFailToRegisterForRemoteNotificationsWithError:")] + public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) + => global::Shiny.Hosting.Host.Lifecycle.OnFailedToRegisterForRemoteNotifications(error); + + [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")] + public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action completionHandler) + => global::Shiny.Hosting.Host.Lifecycle.OnDidReceiveRemoteNotification(userInfo, completionHandler); } } diff --git a/salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs b/salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs new file mode 100644 index 0000000..3f9400d --- /dev/null +++ b/salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs @@ -0,0 +1,12 @@ +using salesbook.Shared.Core.Interface.System.Battery; + +namespace salesbook.Maui.Core; + +public class BatteryOptimizationManagerService : IBatteryOptimizationManagerService +{ + public bool IsBatteryOptimizationEnabled() => true; + + public void OpenBatteryOptimizationSettings(Action onCompleted) + { + } +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy b/salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..fcd26e7 --- /dev/null +++ b/salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy @@ -0,0 +1,110 @@ + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyCollectedDataTypes + + + + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataTypePhoneNumber + NSPrivacyCollectedDataTypeLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + + + diff --git a/salesbook.Maui/Platforms/iOS/iOSModule.cs b/salesbook.Maui/Platforms/iOS/iOSModule.cs new file mode 100644 index 0000000..071675e --- /dev/null +++ b/salesbook.Maui/Platforms/iOS/iOSModule.cs @@ -0,0 +1,13 @@ +using salesbook.Maui.Core; +using salesbook.Shared.Core.Interface.System.Battery; + +namespace salesbook.Maui; + +public static class iOSModule +{ + public static MauiAppBuilder RegisterIosAppServices(this MauiAppBuilder mauiAppBuilder) + { + mauiAppBuilder.Services.AddSingleton(); + return mauiAppBuilder; + } +} \ No newline at end of file diff --git a/salesbook.Maui/salesbook.Maui.csproj b/salesbook.Maui/salesbook.Maui.csproj index 3120900..fb9eb60 100644 --- a/salesbook.Maui/salesbook.Maui.csproj +++ b/salesbook.Maui/salesbook.Maui.csproj @@ -83,14 +83,16 @@ manual - + - --> + + + @@ -138,6 +140,7 @@ + diff --git a/salesbook.Shared/Components/Layout/MainLayout.razor b/salesbook.Shared/Components/Layout/MainLayout.razor index dc8d62b..514030f 100644 --- a/salesbook.Shared/Components/Layout/MainLayout.razor +++ b/salesbook.Shared/Components/Layout/MainLayout.razor @@ -1,5 +1,7 @@ @using System.Globalization @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Messages.Back @inherits LayoutComponentBase @inject IJSRuntime JS @@ -55,7 +57,7 @@ private bool _showWarning; private DateTime _lastApiCheck = DateTime.MinValue; - private const int DelaySeconds = 60; + private const int DelaySeconds = 180; private CancellationTokenSource? _cts; diff --git a/salesbook.Shared/Components/Pages/Commessa.razor b/salesbook.Shared/Components/Pages/Commessa.razor index 24d8b14..829abce 100644 --- a/salesbook.Shared/Components/Pages/Commessa.razor +++ b/salesbook.Shared/Components/Pages/Commessa.razor @@ -11,6 +11,7 @@ @using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.IntegryApi @inject JobSteps JobSteps @inject IManageDataService ManageData @inject IIntegryApiService IntegryApiService diff --git a/salesbook.Shared/Components/Pages/Home.razor b/salesbook.Shared/Components/Pages/Home.razor index c39cdd5..aadb903 100644 --- a/salesbook.Shared/Components/Pages/Home.razor +++ b/salesbook.Shared/Components/Pages/Home.razor @@ -2,10 +2,13 @@ @attribute [Authorize] @using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Core.Interface.System.Network +@using salesbook.Shared.Core.Interface.System.Notification @using salesbook.Shared.Core.Services @inject IFormFactor FormFactor @inject INetworkService NetworkService @inject IFirebaseNotificationService FirebaseNotificationService +@inject IShinyNotificationManager NotificationManager @inject PreloadService PreloadService @@ -14,6 +17,8 @@ { protected override async Task OnInitializedAsync() { + await CheckAndRequestPermissions(); + try { await FirebaseNotificationService.InitFirebase(); @@ -35,6 +40,14 @@ NavigationManager.NavigateTo("/Calendar"); } + private async Task CheckAndRequestPermissions() + { + await NotificationManager.RequestAccess(); + + // if (BatteryOptimizationManagerService.IsBatteryOptimizationEnabled()) + // BatteryOptimizationManagerService.OpenBatteryOptimizationSettings(_ => { }); + } + private Task StartSyncUser() { return Task.Run(() => diff --git a/salesbook.Shared/Components/Pages/Login.razor b/salesbook.Shared/Components/Pages/Login.razor index baafd52..bbf2048 100644 --- a/salesbook.Shared/Components/Pages/Login.razor +++ b/salesbook.Shared/Components/Pages/Login.razor @@ -1,6 +1,7 @@ @page "/login" @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Services @inject IUserAccountService UserAccountService @inject AppAuthenticationStateProvider AuthenticationStateProvider diff --git a/salesbook.Shared/Components/Pages/PersonalInfo.razor b/salesbook.Shared/Components/Pages/PersonalInfo.razor index 036fcbf..8ac584b 100644 --- a/salesbook.Shared/Components/Pages/PersonalInfo.razor +++ b/salesbook.Shared/Components/Pages/PersonalInfo.razor @@ -3,6 +3,7 @@ @using salesbook.Shared.Components.Layout @using salesbook.Shared.Core.Authorization.Enum @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Services @inject AppAuthenticationStateProvider AuthenticationStateProvider @inject INetworkService NetworkService diff --git a/salesbook.Shared/Components/Pages/User.razor b/salesbook.Shared/Components/Pages/User.razor index 0da73a8..aff2c4d 100644 --- a/salesbook.Shared/Components/Pages/User.razor +++ b/salesbook.Shared/Components/Pages/User.razor @@ -10,6 +10,7 @@ @using salesbook.Shared.Core.Dto.Activity @using salesbook.Shared.Core.Dto.JobProgress @using salesbook.Shared.Core.Dto.PageState +@using salesbook.Shared.Core.Interface.IntegryApi @implements IAsyncDisposable @inject IManageDataService ManageData @inject IMapper Mapper diff --git a/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor b/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor index 44a29f7..c6f0b7b 100644 --- a/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor +++ b/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor @@ -2,6 +2,7 @@ @using salesbook.Shared.Core.Dto @using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Overlay +@using salesbook.Shared.Core.Interface.IntegryApi @inject IIntegryApiService IntegryApiService
diff --git a/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor index 26d27a8..3d0d085 100644 --- a/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor @@ -1,5 +1,6 @@ @using salesbook.Shared.Core.Dto @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.IntegryApi @inject IIntegryApiService IntegryApiService @inject IAttachedService AttachedService diff --git a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor index 352cdc7..7e5a8af 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -9,6 +9,8 @@ @using salesbook.Shared.Core.Dto.Contact @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Messages.Activity.Copy @inject IManageDataService ManageData @inject INetworkService NetworkService diff --git a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor index 8b6ffbd..46f9fa9 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor @@ -5,6 +5,8 @@ @using salesbook.Shared.Core.Entity @using salesbook.Shared.Components.SingleElements.BottomSheet @using salesbook.Shared.Core.Dto.Contact +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @inject IManageDataService ManageData @inject INetworkService NetworkService @inject IIntegryApiService IntegryApiService diff --git a/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor b/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor index 3e107ae..c3813b2 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor @@ -2,6 +2,8 @@ @using salesbook.Shared.Components.Layout @using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Overlay +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @inject IManageDataService ManageData @inject INetworkService NetworkService @inject IIntegryApiService IntegryApiService diff --git a/salesbook.Shared/Core/Interface/IIntegryApiService.cs b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs similarity index 97% rename from salesbook.Shared/Core/Interface/IIntegryApiService.cs rename to salesbook.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs index a8fdc7d..1cd51af 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs @@ -4,7 +4,7 @@ using salesbook.Shared.Core.Dto.Contact; using salesbook.Shared.Core.Dto.JobProgress; using salesbook.Shared.Core.Entity; -namespace salesbook.Shared.Core.Interface; +namespace salesbook.Shared.Core.Interface.IntegryApi; public interface IIntegryApiService { diff --git a/salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryNotificationRestClient.cs similarity index 73% rename from salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs rename to salesbook.Shared/Core/Interface/IntegryApi/IIntegryNotificationRestClient.cs index 7eef3df..27de9c7 100644 --- a/salesbook.Shared/Core/Interface/IIntegryNotificationRestClient.cs +++ b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryNotificationRestClient.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace salesbook.Shared.Core.Interface; +namespace salesbook.Shared.Core.Interface.IntegryApi; public interface IIntegryNotificationRestClient { diff --git a/salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs b/salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs new file mode 100644 index 0000000..179f1ac --- /dev/null +++ b/salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs @@ -0,0 +1,8 @@ +namespace salesbook.Shared.Core.Interface.System.Battery; + +public interface IBatteryOptimizationManagerService +{ + bool IsBatteryOptimizationEnabled(); + + void OpenBatteryOptimizationSettings(Action onCompleted); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/INetworkService.cs b/salesbook.Shared/Core/Interface/System/Network/INetworkService.cs similarity index 67% rename from salesbook.Shared/Core/Interface/INetworkService.cs rename to salesbook.Shared/Core/Interface/System/Network/INetworkService.cs index 1bec94c..43b6acc 100644 --- a/salesbook.Shared/Core/Interface/INetworkService.cs +++ b/salesbook.Shared/Core/Interface/System/Network/INetworkService.cs @@ -1,4 +1,4 @@ -namespace salesbook.Shared.Core.Interface; +namespace salesbook.Shared.Core.Interface.System.Network; public interface INetworkService { diff --git a/salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs b/salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs new file mode 100644 index 0000000..ec8454b --- /dev/null +++ b/salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs @@ -0,0 +1,6 @@ +namespace salesbook.Shared.Core.Interface.System.Notification; + +public interface IShinyNotificationManager +{ + Task RequestAccess(); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Services/IntegryApiService.cs b/salesbook.Shared/Core/Services/IntegryApiService.cs index c138b68..4a3d943 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -7,6 +7,7 @@ using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Interface; using System.Net.Http.Headers; using salesbook.Shared.Core.Dto.Contact; +using salesbook.Shared.Core.Interface.IntegryApi; namespace salesbook.Shared.Core.Services; diff --git a/salesbook.Web/Core/Services/NetworkService.cs b/salesbook.Web/Core/Services/NetworkService.cs index 64b4044..16d9938 100644 --- a/salesbook.Web/Core/Services/NetworkService.cs +++ b/salesbook.Web/Core/Services/NetworkService.cs @@ -1,4 +1,5 @@ using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.System.Network; namespace salesbook.Web.Core.Services; diff --git a/salesbook.Web/Program.cs b/salesbook.Web/Program.cs index cbb65dc..fc2b24a 100644 --- a/salesbook.Web/Program.cs +++ b/salesbook.Web/Program.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; using salesbook.Shared.Components; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Interface.System.Network; using salesbook.Shared.Core.Services; using salesbook.Web.Core.Services; From 7319378e758750afafce80dadee893895f22a8ea Mon Sep 17 00:00:00 2001 From: MarcoE Date: Tue, 9 Sep 2025 16:30:51 +0200 Subject: [PATCH 11/24] Creata card notifiche --- .../Push/PushNotificationDelegate.cs | 24 ++++-- salesbook.Maui/MauiProgram.cs | 13 +-- salesbook.Maui/wwwroot/index.html | 1 + .../Components/Layout/NavMenu.razor | 28 ++++-- .../Components/Pages/Notifications.razor | 34 +++++++- .../Components/Pages/Notifications.razor.css | 6 ++ .../Card/NotificationCard.razor | 15 ++++ .../Card/NotificationCard.razor.css | 64 ++++++++++++++ .../Dto/Notification/PushNotificationDTO.cs | 9 ++ .../Core/Dto/PageState/NotificationState.cs | 8 ++ .../NewPushNotificationMessage.cs | 6 ++ .../NewPushNotificationService.cs | 14 +++ salesbook.Shared/wwwroot/js/notifications.js | 85 +++++++++++++++++++ 13 files changed, 289 insertions(+), 18 deletions(-) create mode 100644 salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor create mode 100644 salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css create mode 100644 salesbook.Shared/Core/Dto/Notification/PushNotificationDTO.cs create mode 100644 salesbook.Shared/Core/Dto/PageState/NotificationState.cs create mode 100644 salesbook.Shared/Core/Messages/Notification/NewPushNotificationMessage.cs create mode 100644 salesbook.Shared/Core/Messages/Notification/NewPushNotificationService.cs create mode 100644 salesbook.Shared/wwwroot/js/notifications.js diff --git a/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs index 14cf03d..8e69c40 100644 --- a/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs +++ b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs @@ -1,8 +1,15 @@ -using Shiny.Push; +using CommunityToolkit.Mvvm.Messaging; +using salesbook.Shared.Core.Dto.Notification; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Messages.Notification; +using Shiny.Push; namespace salesbook.Maui.Core.System.Notification.Push; -public class PushNotificationDelegate : IPushDelegate +public class PushNotificationDelegate( + IIntegryNotificationRestClient integryNotificationRestClient, + IMessenger messenger +) : IPushDelegate { public Task OnEntry(PushNotification notification) { @@ -12,14 +19,21 @@ public class PushNotificationDelegate : IPushDelegate public Task OnReceived(PushNotification notification) { - // fires when a push notification is received (silient or notification) - //notification.Data["content-available"] = "1"; + if (notification.Notification is null) return Task.CompletedTask; + var pushNotification = new PushNotificationDTO + { + Title = notification.Notification.Title, + Message = notification.Notification.Message + }; + + messenger.Send(new NewPushNotificationMessage(pushNotification)); + return Task.CompletedTask; } public Task OnNewToken(string token) { - // fires when a push notification change is set by the operating system or provider + integryNotificationRestClient.Register(token); return Task.CompletedTask; } diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index 1743cbf..c9c7290 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -22,6 +22,7 @@ using salesbook.Shared.Core.Messages.Activity.Copy; using salesbook.Shared.Core.Messages.Activity.New; using salesbook.Shared.Core.Messages.Back; using salesbook.Shared.Core.Messages.Contact; +using salesbook.Shared.Core.Messages.Notification; using salesbook.Shared.Core.Services; using Shiny; @@ -71,14 +72,16 @@ namespace salesbook.Maui builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); //Message - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); //Notification builder.Services.AddNotifications(); diff --git a/salesbook.Maui/wwwroot/index.html b/salesbook.Maui/wwwroot/index.html index 74faadd..7f7b15f 100644 --- a/salesbook.Maui/wwwroot/index.html +++ b/salesbook.Maui/wwwroot/index.html @@ -53,6 +53,7 @@ + diff --git a/salesbook.Shared/Components/Layout/NavMenu.razor b/salesbook.Shared/Components/Layout/NavMenu.razor index a4cdf53..6880a7d 100644 --- a/salesbook.Shared/Components/Layout/NavMenu.razor +++ b/salesbook.Shared/Components/Layout/NavMenu.razor @@ -1,13 +1,18 @@ @using CommunityToolkit.Mvvm.Messaging @using salesbook.Shared.Core.Dto @using salesbook.Shared.Core.Dto.Activity +@using salesbook.Shared.Core.Dto.Notification +@using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Messages.Activity.Copy @using salesbook.Shared.Core.Messages.Activity.New @using salesbook.Shared.Core.Messages.Contact +@using salesbook.Shared.Core.Messages.Notification @inject IDialogService Dialog @inject IMessenger Messenger @inject CopyActivityService CopyActivityService +@inject NewPushNotificationService NewPushNotificationService +@inject NotificationState Notification
@@ -63,8 +70,9 @@ protected override Task OnInitializedAsync() { CopyActivityService.OnCopyActivity += async dto => await CreateActivity(dto); + NewPushNotificationService.OnNotificationReceived += NewNotificationReceived; - NavigationManager.LocationChanged += (_, args) => + NavigationManager.LocationChanged += (_, args) => { var location = args.Location.Remove(0, NavigationManager.BaseUri.Length); @@ -104,4 +112,10 @@ Messenger.Send(new NewContactMessage((CRMCreateContactResponseDTO)result.Data)); } } + + private void NewNotificationReceived(PushNotificationDTO notification) + { + Notification.UnreadNotifications.Add(notification); + InvokeAsync(StateHasChanged); + } } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/Notifications.razor b/salesbook.Shared/Components/Pages/Notifications.razor index 9579556..494a3d4 100644 --- a/salesbook.Shared/Components/Pages/Notifications.razor +++ b/salesbook.Shared/Components/Pages/Notifications.razor @@ -2,13 +2,45 @@ @attribute [Authorize] @using salesbook.Shared.Components.Layout @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.Notification +@using salesbook.Shared.Core.Dto.PageState +@using salesbook.Shared.Core.Messages.Notification +@inject NotificationState Notification +@inject NewPushNotificationService NewPushNotificationService +@inject IJSRuntime JS
- + @if (Notification.UnreadNotifications.IsNullOrEmpty()) + { + + } + else + { +
+ @foreach(var notification in Notification.UnreadNotifications) + { + + } +
+ }
@code { + protected override Task OnInitializedAsync() + { + NewPushNotificationService.OnNotificationReceived += NewNotificationReceived; + return Task.CompletedTask; + } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await JS.InvokeVoidAsync("initNotifications"); + } + + private void NewNotificationReceived(PushNotificationDTO notification) + { + InvokeAsync(StateHasChanged); + } } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/Notifications.razor.css b/salesbook.Shared/Components/Pages/Notifications.razor.css index e69de29..6f08dcd 100644 --- a/salesbook.Shared/Components/Pages/Notifications.razor.css +++ b/salesbook.Shared/Components/Pages/Notifications.razor.css @@ -0,0 +1,6 @@ +.list { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} diff --git a/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor new file mode 100644 index 0000000..873e9f7 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor @@ -0,0 +1,15 @@ +@using salesbook.Shared.Core.Dto.Notification + +
+
+
+
+
@Notification.Title
+
@Notification.Message
+
+
+
+ +@code { + [Parameter] public PushNotificationDTO Notification { get; set; } = new(); +} \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css new file mode 100644 index 0000000..2c63bb0 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css @@ -0,0 +1,64 @@ +.row { + position: relative; + overflow: hidden; + border-radius: var(--mud-default-borderradius); + box-shadow: var(--custom-box-shadow); + width: 100%; +} + +.behind { + position: absolute; + inset: 0; + display: flex; + justify-content: flex-end; + align-items: center; + padding-right: 14px; + background: var(--mud-palette-error); + z-index: 0; +} + +.trash-btn { + color: white; + padding: 10px 15px; + cursor: pointer; +} + +.notification-card { + position: relative; + z-index: 1; + display: flex; + align-items: center; + padding: 12px; + background: var(--mud-palette-background); + transition: transform .2s ease; + touch-action: pan-y; + will-change: transform; + transform: translateX(0); +} + +.avatar { + width: 42px; + height: 42px; + border-radius: 12px; + display: grid; + place-items: center; + background: #0b1220; + border: 1px solid #1f2937; + font-weight: bold; + color: #22d3ee; +} + +.title { + font-weight: 700; + margin-bottom: unset !important; +} + +.subtitle { + font-size: 13px; + color: #94a3b8; +} + +.collapsing { + transition: height .22s ease, margin .22s ease, opacity .22s ease; + overflow: hidden; +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/Notification/PushNotificationDTO.cs b/salesbook.Shared/Core/Dto/Notification/PushNotificationDTO.cs new file mode 100644 index 0000000..91de6df --- /dev/null +++ b/salesbook.Shared/Core/Dto/Notification/PushNotificationDTO.cs @@ -0,0 +1,9 @@ +namespace salesbook.Shared.Core.Dto.Notification; + +public class PushNotificationDTO +{ + public string? Title { get; set; } + public string? Message { get; set; } + + public DateTime Hours => DateTime.Now; +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/PageState/NotificationState.cs b/salesbook.Shared/Core/Dto/PageState/NotificationState.cs new file mode 100644 index 0000000..cfaf940 --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/NotificationState.cs @@ -0,0 +1,8 @@ +using salesbook.Shared.Core.Dto.Notification; + +namespace salesbook.Shared.Core.Dto.PageState; + +public class NotificationState +{ + public List UnreadNotifications { get; set; } = []; +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Messages/Notification/NewPushNotificationMessage.cs b/salesbook.Shared/Core/Messages/Notification/NewPushNotificationMessage.cs new file mode 100644 index 0000000..ff48cec --- /dev/null +++ b/salesbook.Shared/Core/Messages/Notification/NewPushNotificationMessage.cs @@ -0,0 +1,6 @@ +using CommunityToolkit.Mvvm.Messaging.Messages; +using salesbook.Shared.Core.Dto.Notification; + +namespace salesbook.Shared.Core.Messages.Notification; + +public class NewPushNotificationMessage(PushNotificationDTO value) : ValueChangedMessage(value); \ No newline at end of file diff --git a/salesbook.Shared/Core/Messages/Notification/NewPushNotificationService.cs b/salesbook.Shared/Core/Messages/Notification/NewPushNotificationService.cs new file mode 100644 index 0000000..e863668 --- /dev/null +++ b/salesbook.Shared/Core/Messages/Notification/NewPushNotificationService.cs @@ -0,0 +1,14 @@ +using CommunityToolkit.Mvvm.Messaging; +using salesbook.Shared.Core.Dto.Notification; + +namespace salesbook.Shared.Core.Messages.Notification; + +public class NewPushNotificationService +{ + public event Action? OnNotificationReceived; + + public NewPushNotificationService(IMessenger messenger) + { + messenger.Register(this, (_, o) => { OnNotificationReceived?.Invoke(o.Value); }); + } +} \ No newline at end of file diff --git a/salesbook.Shared/wwwroot/js/notifications.js b/salesbook.Shared/wwwroot/js/notifications.js new file mode 100644 index 0000000..f38633c --- /dev/null +++ b/salesbook.Shared/wwwroot/js/notifications.js @@ -0,0 +1,85 @@ +const FIRST_THRESHOLD = 80; // rivela pulsante +const SECOND_THRESHOLD = 160; // elimina diretta +const CLOSE_THRESHOLD = 40; // swipe a destra per richiudere +const MAX_SWIPE = 200; + +window.initNotifications = () => { + document.querySelectorAll('.row').forEach(initRow); +}; + +function initRow(row) { + const card = row.querySelector('.notification-card'); + const btn = row.querySelector('.trash-btn'); + let startX = 0, currentX = 0, dragging = false; + let open = false; + + card.addEventListener('pointerdown', (e) => { + if (e.pointerType === 'mouse' && e.button !== 0) return; + dragging = true; + startX = e.clientX; + card.setPointerCapture(e.pointerId); + card.style.transition = 'none'; + }); + + card.addEventListener('pointermove', (e) => { + if (!dragging) return; + const dx = e.clientX - startX; + let translate = dx; + + if (!open) { + // swiping left to open/delete + translate = Math.max(-MAX_SWIPE, Math.min(0, dx)); + } else { + // if already open, allow swipe right to close + translate = Math.min(0, -FIRST_THRESHOLD + dx); + } + currentX = translate; + card.style.transform = `translateX(${translate}px)`; + }); + + function endDrag() { + if (!dragging) return; + dragging = false; + card.style.transition = 'transform .2s ease'; + + if (!open) { + if (currentX < -SECOND_THRESHOLD) { + card.style.transform = `translateX(-${MAX_SWIPE}px)`; + setTimeout(() => removeRow(row), 200); + } else if (currentX < -FIRST_THRESHOLD) { + card.style.transform = `translateX(-${FIRST_THRESHOLD}px)`; + open = true; + } else { + card.style.transform = 'translateX(0)'; + open = false; + } + } else { + // se è già aperto e vado a destra abbastanza → chiudo + if (currentX > -FIRST_THRESHOLD + CLOSE_THRESHOLD) { + card.style.transform = 'translateX(0)'; + open = false; + } else { + card.style.transform = `translateX(-${FIRST_THRESHOLD}px)`; + open = true; + } + } + } + + card.addEventListener('pointerup', endDrag); + card.addEventListener('pointercancel', endDrag); + + btn.addEventListener('click', () => removeRow(row)); +} + +function removeRow(row) { + const h = row.getBoundingClientRect().height; + row.style.height = h + 'px'; + row.classList.add('collapsing'); + requestAnimationFrame(() => { + row.style.opacity = '0'; + row.style.marginTop = '0'; + row.style.marginBottom = '0'; + row.style.height = '0'; + }); + setTimeout(() => row.remove(), 220); +} \ No newline at end of file From 7bfe67a97cdd9338c5ae0ce0347f593d7f8a56bd Mon Sep 17 00:00:00 2001 From: MarcoE Date: Thu, 11 Sep 2025 16:06:19 +0200 Subject: [PATCH 12/24] Gestita pagina notifiche --- ... IntegryRegisterNotificationRestClient.cs} | 6 +- .../Core/Services/ManageDataService.cs | 14 +- .../FirebaseNotificationService.cs | 4 +- .../Push/PushNotificationDelegate.cs | 10 +- salesbook.Maui/MauiProgram.cs | 1 + salesbook.Maui/salesbook.Maui.csproj | 2 +- .../Components/Layout/NavMenu.razor | 7 +- .../Components/Pages/Calendar.razor.css | 6 +- .../Components/Pages/Notifications.razor | 155 ++++++++++++++++-- .../Components/Pages/User.razor.css | 1 + .../Components/Pages/Users.razor.css | 3 +- .../Card/NotificationCard.razor | 85 +++++++++- .../Card/NotificationCard.razor.css | 61 +++++-- .../SingleElements/Modal/ActivityForm.razor | 54 ++++-- .../Core/Dto/Activity/ActivityDTO.cs | 14 +- .../Dto/Notification/PushNotificationDTO.cs | 9 - .../Core/Dto/NotificationDataDTO.cs | 12 ++ .../Core/Dto/PageState/NotificationState.cs | 8 +- .../Core/Dto/ReadNotificationRequestDTO.cs | 15 ++ .../Core/Entity/WtbDeviceNotification.cs | 15 ++ .../Core/Entity/WtbNotification.cs | 37 +++++ .../IIntegryNotificationRestClient.cs | 7 +- .../IIntegryRegisterNotificationRestClient.cs | 8 + .../NewPushNotificationMessage.cs | 4 +- .../NewPushNotificationService.cs | 4 +- .../Services/IntegryNotificationRestClient.cs | 33 ++++ salesbook.Shared/salesbook.Shared.csproj | 2 +- salesbook.Shared/wwwroot/css/app.css | 8 +- salesbook.Shared/wwwroot/js/notifications.js | 129 ++++++++++++--- salesbook.Web/salesbook.Web.csproj | 2 +- 30 files changed, 611 insertions(+), 105 deletions(-) rename salesbook.Maui/Core/RestClient/IntegryApi/{IntegryNotificationRestClient.cs => IntegryRegisterNotificationRestClient.cs} (87%) delete mode 100644 salesbook.Shared/Core/Dto/Notification/PushNotificationDTO.cs create mode 100644 salesbook.Shared/Core/Dto/NotificationDataDTO.cs create mode 100644 salesbook.Shared/Core/Dto/ReadNotificationRequestDTO.cs create mode 100644 salesbook.Shared/Core/Entity/WtbDeviceNotification.cs create mode 100644 salesbook.Shared/Core/Entity/WtbNotification.cs create mode 100644 salesbook.Shared/Core/Interface/IntegryApi/IIntegryRegisterNotificationRestClient.cs create mode 100644 salesbook.Shared/Core/Services/IntegryNotificationRestClient.cs diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryRegisterNotificationRestClient.cs similarity index 87% rename from salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs rename to salesbook.Maui/Core/RestClient/IntegryApi/IntegryRegisterNotificationRestClient.cs index 021fc6c..39fc676 100644 --- a/salesbook.Maui/Core/RestClient/IntegryApi/IntegryNotificationRestClient.cs +++ b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryRegisterNotificationRestClient.cs @@ -6,11 +6,11 @@ using salesbook.Shared.Core.Interface.IntegryApi; namespace salesbook.Maui.Core.RestClient.IntegryApi; -public class IntegryNotificationRestClient( - ILogger logger, +public class IntegryRegisterNotificationRestClient( + ILogger logger, IUserSession userSession, IIntegryApiRestClient integryApiRestClient -) : IIntegryNotificationRestClient +) : IIntegryRegisterNotificationRestClient { public async Task Register(string fcmToken, ILogger? logger1 = null) { diff --git a/salesbook.Maui/Core/Services/ManageDataService.cs b/salesbook.Maui/Core/Services/ManageDataService.cs index 9031c83..b9196a1 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -1,14 +1,15 @@ using AutoMapper; +using MudBlazor.Extensions; using salesbook.Shared.Core.Dto; using salesbook.Shared.Core.Dto.Activity; using salesbook.Shared.Core.Dto.Contact; using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Helpers.Enum; using salesbook.Shared.Core.Interface; -using Sentry.Protocol; -using System.Linq.Expressions; using salesbook.Shared.Core.Interface.IntegryApi; using salesbook.Shared.Core.Interface.System.Network; +using Sentry.Protocol; +using System.Linq.Expressions; namespace salesbook.Maui.Core.Services; @@ -211,6 +212,15 @@ public class ManageDataService( { var dto = mapper.Map(activity); + if (activity is { AlarmTime: not null, EstimatedTime: not null }) + { + var minuteBefore = activity.EstimatedTime.Value - activity.AlarmTime.Value; + dto.MinuteBefore = (int)Math.Abs(minuteBefore.TotalMinutes); + + dto.NotificationDate = dto.MinuteBefore == 0 ? + activity.EstimatedTime : activity.AlarmTime; + } + if (activity.CodJcom != null) { dto.Category = ActivityCategoryEnum.Commessa; diff --git a/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs index 2f766eb..8d0fd36 100644 --- a/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs +++ b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs @@ -8,7 +8,7 @@ namespace salesbook.Maui.Core.System.Notification; public class FirebaseNotificationService( IPushManager pushManager, - IIntegryNotificationRestClient integryNotificationRestClient, + IIntegryRegisterNotificationRestClient integryRegisterNotificationRestClient, INotificationManager notificationManager ) : IFirebaseNotificationService { @@ -19,7 +19,7 @@ public class FirebaseNotificationService( var (accessState, token) = await pushManager.RequestAccess(); if (accessState == AccessState.Denied || token is null) return; - await integryNotificationRestClient.Register(token); + await integryRegisterNotificationRestClient.Register(token); } private void CreateNotificationChannel() diff --git a/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs index 8e69c40..5f0bdc7 100644 --- a/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs +++ b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs @@ -1,5 +1,5 @@ using CommunityToolkit.Mvvm.Messaging; -using salesbook.Shared.Core.Dto.Notification; +using salesbook.Shared.Core.Entity; using salesbook.Shared.Core.Interface.IntegryApi; using salesbook.Shared.Core.Messages.Notification; using Shiny.Push; @@ -7,7 +7,7 @@ using Shiny.Push; namespace salesbook.Maui.Core.System.Notification.Push; public class PushNotificationDelegate( - IIntegryNotificationRestClient integryNotificationRestClient, + IIntegryRegisterNotificationRestClient integryRegisterNotificationRestClient, IMessenger messenger ) : IPushDelegate { @@ -20,10 +20,10 @@ public class PushNotificationDelegate( public Task OnReceived(PushNotification notification) { if (notification.Notification is null) return Task.CompletedTask; - var pushNotification = new PushNotificationDTO + var pushNotification = new WtbNotification { Title = notification.Notification.Title, - Message = notification.Notification.Message + Body = notification.Notification.Message }; messenger.Send(new NewPushNotificationMessage(pushNotification)); @@ -33,7 +33,7 @@ public class PushNotificationDelegate( public Task OnNewToken(string token) { - integryNotificationRestClient.Register(token); + integryRegisterNotificationRestClient.Register(token); return Task.CompletedTask; } diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index c9c7290..4abc81f 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -86,6 +86,7 @@ namespace salesbook.Maui //Notification builder.Services.AddNotifications(); builder.Services.AddPush(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/salesbook.Maui/salesbook.Maui.csproj b/salesbook.Maui/salesbook.Maui.csproj index fb9eb60..9b32639 100644 --- a/salesbook.Maui/salesbook.Maui.csproj +++ b/salesbook.Maui/salesbook.Maui.csproj @@ -133,7 +133,7 @@ - + diff --git a/salesbook.Shared/Components/Layout/NavMenu.razor b/salesbook.Shared/Components/Layout/NavMenu.razor index 6880a7d..f5c158d 100644 --- a/salesbook.Shared/Components/Layout/NavMenu.razor +++ b/salesbook.Shared/Components/Layout/NavMenu.razor @@ -1,7 +1,6 @@ @using CommunityToolkit.Mvvm.Messaging @using salesbook.Shared.Core.Dto @using salesbook.Shared.Core.Dto.Activity -@using salesbook.Shared.Core.Dto.Notification @using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity @using salesbook.Shared.Core.Messages.Activity.Copy @@ -35,7 +34,7 @@