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/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/ManageDataService.cs b/salesbook.Maui/Core/Services/ManageDataService.cs index 74f2a98..9b6dc26 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -1,21 +1,131 @@ 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.ConnectionAvailable) + { + var response = await integryApiService.RetrieveAnagClie( + new CRMAnagRequestDTO + { + CodAnag = whereCond.CodAnag, + FlagStato = whereCond.FlagStato, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact + } + ); + + 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.ConnectionAvailable) + { + var response = await integryApiService.RetrieveProspect( + new CRMProspectRequestDTO + { + CodPpro = whereCond.CodAnag, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact + } + ); + + 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.ConnectionAvailable) + { + 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 +156,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.ConnectionAvailable && !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 +239,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 8178aa6..d5c55bb 100644 --- a/salesbook.Maui/Core/Services/SyncDbService.cs +++ b/salesbook.Maui/Core/Services/SyncDbService.cs @@ -5,17 +5,6 @@ namespace salesbook.Maui.Core.Services; public class SyncDbService(IIntegryApiService integryApiService, LocalDbService localDb) : ISyncDbService { - public async Task GetAndSaveActivity(string? dateFilter) - { - var allActivity = await integryApiService.RetrieveActivity(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); @@ -27,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) @@ -80,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.Maui/Core/System/Network/NetworkService.cs b/salesbook.Maui/Core/System/Network/NetworkService.cs index 7fb8b96..124578a 100644 --- a/salesbook.Maui/Core/System/Network/NetworkService.cs +++ b/salesbook.Maui/Core/System/Network/NetworkService.cs @@ -4,8 +4,11 @@ namespace salesbook.Maui.Core.System.Network; public class NetworkService : INetworkService { + public bool ConnectionAvailable { get; set; } + public bool IsNetworkAvailable() { + //return false; return Connectivity.Current.NetworkAccess == NetworkAccess.Internet; } diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index c4d0361..d565c99 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -13,6 +13,7 @@ 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.Dto.PageState; using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; using salesbook.Shared.Core.Messages.Activity.Copy; @@ -58,10 +59,16 @@ namespace salesbook.Maui builder.Services.AddScoped(provider => provider.GetRequiredService()); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); + + //SessionData + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); //Message builder.Services.AddScoped(); @@ -83,8 +90,8 @@ namespace salesbook.Maui builder.Services.AddSingleton(); 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..dc8d62b 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 const int DelaySeconds = 60; + + 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/Layout/NavMenu.razor b/salesbook.Shared/Components/Layout/NavMenu.razor index 9cf2d2e..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 @@ -67,10 +68,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/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 new file mode 100644 index 0000000..24d8b14 --- /dev/null +++ b/salesbook.Shared/Components/Pages/Commessa.razor @@ -0,0 +1,173 @@ +@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.Activity +@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 + + + +@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...)" : "")
+ } +
+
+ } +
+
+ } +
+
+ @if (ActivityIsLoading) + { + + } + else + { + @if (ActivityList is { Count: > 0 }) + { +
+ + + +
+ } + else + { + + } + } +
+
+ @if (AttachedIsLoading) + { + + } + else + { + @if (ListAttached != null) + { +
+ + + +
+ } + else + { + + } + } +
+
+ } +
+} + +@code { + [Parameter] public string CodJcom { get; set; } = ""; + [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() + { + await LoadData(); + } + + private async Task LoadData() + { + 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) + .OrderBy(x => + (x.EffectiveTime ?? x.EstimatedTime) ?? x.DataInsAct + ).ToList(); + }); + + 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 new file mode 100644 index 0000000..bc18e3a --- /dev/null +++ b/salesbook.Shared/Components/Pages/Commessa.razor.css @@ -0,0 +1,230 @@ +/* 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; +} + +.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; } + +@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/Home.razor b/salesbook.Shared/Components/Pages/Home.razor index 190c941..c39cdd5 100644 --- a/salesbook.Shared/Components/Pages/Home.razor +++ b/salesbook.Shared/Components/Pages/Home.razor @@ -2,9 +2,11 @@ @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 IFirebaseNotificationService FirebaseNotificationService +@inject PreloadService PreloadService @@ -23,12 +25,21 @@ 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; } + _ = StartSyncUser(); NavigationManager.NavigateTo("/Calendar"); } + + private Task StartSyncUser() + { + return Task.Run(() => + { + _ = PreloadService.PreloadUsersAsync(); + }); + } } 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/SyncPage.razor b/salesbook.Shared/Components/Pages/SyncPage.razor index 80484a0..1597d9f 100644 --- a/salesbook.Shared/Components/Pages/SyncPage.razor +++ b/salesbook.Shared/Components/Pages/SyncPage.razor @@ -17,10 +17,7 @@ protected override void OnInitialized() { - Elements["Attività"] = false; Elements["Commesse"] = false; - Elements["Clienti"] = false; - Elements["Prospect"] = false; Elements["Impostazioni"] = false; } @@ -36,9 +33,6 @@ } await Task.WhenAll( - RunAndTrack(SetActivity), - RunAndTrack(SetClienti), - RunAndTrack(SetProspect), RunAndTrack(SetCommesse), RunAndTrack(SetSettings) ); @@ -62,30 +56,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/User.razor b/salesbook.Shared/Components/Pages/User.razor index dedce52..0da73a8 100644 --- a/salesbook.Shared/Components/Pages/User.razor +++ b/salesbook.Shared/Components/Pages/User.razor @@ -7,20 +7,31 @@ @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 @inject IManageDataService ManageData @inject IMapper Mapper @inject IDialogService Dialog -@inject INetworkService NetworkService +@inject IIntegryApiService IntegryApiService +@inject UserPageState UserState - + @if (IsLoading) { - + } else { -
+
@@ -29,11 +40,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})") } @@ -48,9 +59,7 @@ else {
Telefono - - @Anag.Telefono - + @Anag.Telefono
} @@ -58,9 +67,7 @@ else {
P. IVA - - @Anag.PartIva - + @Anag.PartIva
}
@@ -70,9 +77,7 @@ else {
E-mail - - @Anag.EMail - + @Anag.EMail
} @@ -80,31 +85,45 @@ 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()) {
} - + }
} @@ -117,92 +136,644 @@ else Aggiungi contatto
- - - @if (Commesse.IsNullOrEmpty()) +
+ + +
+ @if (IsLoadingCommesse) { - + } - else + else if (Commesse?.Count == 0) { - - - + } - - + 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 (TotalPagesCommesse > 1) + { +
+ +
+ +
+ + 5 + 10 + 15 + +
+ } +
+ } +
+ + +
+ @if (ActivityIsLoading) + { + + } + else if (ActivityList?.Count == 0) + { + + } + else if (ActivityList != null) + { + +
+ +
+ +
+ @foreach (var activity in CurrentPageActivity) + { + + } + + @if (TotalPagesActivity > 1) + { +
+ +
+ +
+ + 5 + 15 + 30 + +
+ } +
+ } +
+
+ + + +
} @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 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 ActivityIsLoading { get; set; } = true; + private bool IsLoadingSteps { get; set; } + private readonly HashSet _loadingSteps = []; + + // Gestione tab + private int ActiveTab { get; set; } + + // 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? _searchTimerCommesse; + private Timer? _searchTimerActivity; + private const int SearchDelayMs = 300; + + #region Properties per Commesse + + private int SelectedPageCommesse + { + get => _selectedPageCommesse; + set + { + if (_selectedPageCommesse == value) return; + _selectedPageCommesse = value; + _ = LoadStepsForCurrentPageAsync(); + } + } + + private int SelectedPageSizeCommesse + { + get => _selectedPageSizeCommesse; + set + { + if (_selectedPageSizeCommesse == value) return; + _selectedPageSizeCommesse = value; + _selectedPageCommesse = 1; + ApplyFiltersCommesse(); + _ = LoadStepsForCurrentPageAsync(); + } + } + + private string SearchTermCommesse + { + get => _searchTermCommesse; + set + { + if (_searchTermCommesse == value) return; + _searchTermCommesse = value; + + _searchTimerCommesse?.Dispose(); + _searchTimerCommesse = new Timer(async _ => await InvokeAsync(ApplyFiltersCommesse), null, SearchDelayMs, Timeout.Infinite); + } + } + + private List FilteredCommesse + { + get => _filteredCommesse; + set + { + _filteredCommesse = value; + StateHasChanged(); + } + } + + private int TotalPagesCommesse => + FilteredCommesse.Count == 0 ? 1 : (int)Math.Ceiling(FilteredCommesse.Count / (double)SelectedPageSizeCommesse); + + private IEnumerable CurrentPageCommesse => + 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 + + #region Lifecycle Methods protected override async Task OnInitializedAsync() { - await LoadData(); + try + { + _loadingCts = new CancellationTokenSource(); + + if (UserState.CodUser?.Equals(CodContact) == true) + { + LoadDataFromSession(); + } + else + { + await LoadDataAsync(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Errore in OnInitializedAsync: {ex.Message}"); + } + finally + { + IsLoading = false; + StateHasChanged(); + } } - private async Task LoadData() + public async ValueTask DisposeAsync() + { + _loadingCts?.CancelAsync(); + _loadingCts?.Dispose(); + _stepsCts?.CancelAsync(); + _stepsCts?.Dispose(); + _searchTimerCommesse?.DisposeAsync(); + _searchTimerActivity?.DisposeAsync(); + } + + #endregion + + #region Data Loading Methods + + private async Task LoadDataAsync() + { + try + { + await LoadAnagAsync(); + await LoadPersRifAsync(); + _ = LoadActivity(); + + if (!string.IsNullOrEmpty(Anag.CodVage)) + { + Agente = (await ManageData.GetTable(x => x.UserCode != null && x.UserCode.Equals(Anag.CodVage))) + .LastOrDefault(); + } + + SaveDataToSession(); + + _ = Task.Run(async () => await LoadCommesseAsync()); + } + catch (Exception ex) + { + Console.WriteLine($"Errore nel caricamento dati: {ex.Message}"); + } + } + + private async Task LoadAnagAsync() { 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); } - - await LoadPersRif(); - - Commesse = await ManageData.GetTable(x => x.CodAnag != null && x.CodAnag.Equals(CodContact)); - - if (Anag.CodVage != null) - { - Agente = (await ManageData.GetTable(x => x.UserCode != null && x.UserCode.Equals(Anag.CodVage))).LastOrDefault(); - } - - IsLoading = false; - StateHasChanged(); } - private async Task LoadPersRif() + private async Task LoadPersRifAsync() { 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 + { + IsLoadingCommesse = true; + + Commesse = await ManageData.GetTable(x => x.CodAnag != null && x.CodAnag.Equals(CodContact)); + + Commesse = Commesse? + .OrderByDescending(x => x.CodJcom) + .ToList(); + + UserState.Commesse = Commesse; + + ApplyFiltersCommesse(); + + 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; + ActivityList = UserState.Activitys; + + ApplyFiltersCommesse(); + ApplyFiltersActivity(); + + IsLoadingCommesse = false; + ActivityIsLoading = false; + } + + private void SaveDataToSession() + { + UserState.CodUser = CodContact; + UserState.Anag = Anag; + UserState.PersRif = PersRif; + UserState.Agente = Agente; + UserState.Steps = Steps; + UserState.Activitys = ActivityList; + } + + #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; + + if (tabIndex == 1 && Commesse == null) + { + _ = Task.Run(async () => await LoadCommesseAsync()); + } + 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 ApplyFiltersCommesse() + { + if (Commesse == null) + { + FilteredCommesse = []; + return; + } + + var filtered = Commesse.AsEnumerable(); + if (!string.IsNullOrWhiteSpace(SearchTermCommesse)) + { + var searchLower = SearchTermCommesse.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(); + if (SelectedPageCommesse > TotalPagesCommesse && TotalPagesCommesse > 0) _selectedPageCommesse = 1; + + _ = LoadStepsForCurrentPageAsync(); + } + + private void ApplyFiltersActivity() + { + 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 + + #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 }) + { + 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 45277bc..b69a8da 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,137 @@ display: flex; gap: 1rem; flex-direction: column; +} + +.commesse-container { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.attivita-container { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +/*-------------- + 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; + width: -webkit-fill-available; +} + +.tab-content { + display: none; + flex: 1; + overflow-y: hidden; + 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), +#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); + } +} + +.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/Components/Pages/Users.razor b/salesbook.Shared/Components/Pages/Users.razor index 666fae8..610d3c7 100644 --- a/salesbook.Shared/Components/Pages/Users.razor +++ b/salesbook.Shared/Components/Pages/Users.razor @@ -6,11 +6,15 @@ @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 +@implements IDisposable @@ -67,25 +71,47 @@ private bool OpenFilter { get; set; } private string TypeUser { get; set; } = "all"; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - NewContact.OnContactCreated += async response => await OnUserCreated(response); - Console.WriteLine($"Filter HashCode: {Filter.GetHashCode()} - IsInitialized: {Filter.IsInitialized}"); - } + IsLoading = true; - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) + if (!UserState.IsLoaded) + { + UserState.OnUsersLoaded += OnUsersLoaded; + } + else { await LoadData(); + LoadFromSession(); + FilterUsers(); } + + NewContact.OnContactCreated += async response => await OnUserCreated(response); + } + + private void OnUsersLoaded() + { + InvokeAsync(async () => + { + await LoadData(); + LoadFromSession(); + FilterUsers(); + }); + } + + void IDisposable.Dispose() + { + UserState.OnUsersLoaded -= OnUsersLoaded; + } + + private void LoadFromSession() + { + GroupedUserList = UserState.GroupedUserList!; + FilteredGroupedUserList = UserState.FilteredGroupedUserList!; } private async Task LoadData() { - IsLoading = true; - StateHasChanged(); - if (!Filter.IsInitialized) { var loggedUser = (await ManageData.GetTable(x => x.UserName.Equals(UserSession.User.Username))).Last(); @@ -95,50 +121,6 @@ Filter.IsInitialized = true; } - - var users = await ManageData.GetContact(); - - 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 - }); - } - - 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); @@ -205,6 +187,9 @@ } FilteredGroupedUserList = result; + + IsLoading = false; + StateHasChanged(); } private async Task OnUserCreated(CRMCreateContactResponseDTO response) 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..7fa75f1 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 @@ -28,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}") + } }
@@ -67,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() @@ -81,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/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/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/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 b91bc5e..352cdc7 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -1,18 +1,21 @@ @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 @inject IIntegryApiService IntegryApiService @inject IMessenger Messenger @inject IDialogService Dialog +@inject IAttachedService AttachedService @@ -142,7 +145,7 @@ { foreach (var file in ActivityFileList) { - + @file.FileName } @@ -240,15 +243,15 @@ 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; } = []; 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; } @@ -273,18 +276,18 @@ { Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; - _ = LoadData(); - - LabelSave = IsNew ? "Aggiungi" : null; - - if (!Id.IsNullOrEmpty()) - ActivityModel = (await ManageData.GetActivity(x => x.ActivityId.Equals(Id))).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) @@ -294,6 +297,8 @@ ActivityModel.UserName = UserSession.User.Username; } + await LoadActivityType(); + OriginalModel = ActivityModel.Clone(); } @@ -315,7 +320,7 @@ await ManageData.InsertOrUpdate(newActivity); - await SaveAttached(newActivity.ActivityId); + await SaveAttached(newActivity.ActivityId!); SuccessAnimation = true; StateHasChanged(); @@ -353,7 +358,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); } } } @@ -378,9 +383,17 @@ Users = await ManageData.GetTable(); ActivityResult = await ManageData.GetTable(); - Clienti = await ManageData.GetTable(x => x.FlagStato.Equals("A")); - Pros = await ManageData.GetTable(); - ActivityType = await ManageData.GetTable(x => x.FlagTipologia.Equals("A")); + Clienti = await ManageData.GetClienti(new WhereCondContact {FlagStato = "A"}); + Pros = await ManageData.GetProspect(); + } + + 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() => @@ -427,6 +440,12 @@ OnAfterChangeValue(); } + private async Task OnUserChanged() + { + await LoadActivityType(); + OnAfterChangeValue(); + } + private void OnAfterChangeValue() { if (!IsNew) @@ -541,12 +560,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/ContactForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor index f070e1f..8b6ffbd 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 @@ -279,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; } @@ -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/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/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/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..4da291b --- /dev/null +++ b/salesbook.Shared/Core/Dto/Activity/CRMRetrieveActivityRequestDTO.cs @@ -0,0 +1,24 @@ +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("codAnag")] + public string? CodAnag { 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/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/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/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..c1451b0 --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/UserListState.cs @@ -0,0 +1,21 @@ +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; } + + 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/Dto/PageState/UserPageState.cs b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs new file mode 100644 index 0000000..efecc2a --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs @@ -0,0 +1,17 @@ +using salesbook.Shared.Core.Dto.Activity; +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; } + public List Activitys { get; set; } +} \ No newline at end of file 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/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/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/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/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 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/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 574a8ba..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; @@ -85,23 +86,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 3635223..a8fdc7d 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IIntegryApiService.cs @@ -1,15 +1,21 @@ 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; namespace salesbook.Shared.Core.Interface; public interface IIntegryApiService { - Task?> RetrieveActivity(string? dateFilter = null); + Task SystemOk(); + + 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); Task DeleteActivity(string activityId); @@ -21,6 +27,9 @@ 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); //Position Task SavePosition(PositionDTO position); 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/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/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 5cc6714..c138b68 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -1,21 +1,33 @@ 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; public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUserSession userSession) : IIntegryApiService { - public Task?> RetrieveActivity(string? dateFilter) + public async Task SystemOk() { - var queryParams = new Dictionary { { "dateFilter", dateFilter ?? "2020-01-01" } }; - - return integryApiRestClient.AuthorizedGet?>("crm/retrieveActivity", queryParams); + try + { + await integryApiRestClient.Get("system/ok"); + return true; + } + catch (Exception e) + { + return false; + } } + + public Task?> RetrieveActivity(CRMRetrieveActivityRequestDTO activityRequest) => + integryApiRestClient.AuthorizedPost?>("crm/retrieveActivity", activityRequest); public Task?> RetrieveAllCommesse(string? dateFilter) { @@ -29,33 +41,26 @@ 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")!; + 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 @@ -137,6 +142,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)!; @@ -146,4 +162,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/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 diff --git a/salesbook.Shared/wwwroot/css/app.css b/salesbook.Shared/wwwroot/css/app.css index e224251..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); @@ -31,11 +67,18 @@ 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; +} + +.content::-webkit-scrollbar { width: 3px; } + +.content::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 2px; } h1:focus { outline: none; } @@ -97,9 +140,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 +237,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; 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/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/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; 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(); }