diff --git a/salesbook.Maui/App.xaml.cs b/salesbook.Maui/App.xaml.cs index 641e04a..17778c3 100644 --- a/salesbook.Maui/App.xaml.cs +++ b/salesbook.Maui/App.xaml.cs @@ -1,20 +1,15 @@ -using CommunityToolkit.Mvvm.Messaging; - namespace salesbook.Maui { public partial class App : Application { - private readonly IMessenger _messenger; - - public App(IMessenger messenger) + public App() { InitializeComponent(); - _messenger = messenger; } protected override Window CreateWindow(IActivationState? activationState) { - return new Window(new MainPage(_messenger)); + return new Window(new MainPage()); } } } diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs new file mode 100644 index 0000000..fc7c01d --- /dev/null +++ b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/RegisterDeviceDTO.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Maui.Core.RestClient.IntegryApi.Dto; + +public class RegisterDeviceDTO +{ + [JsonPropertyName("userDeviceToken")] + public WtbUserDeviceTokenDTO UserDeviceToken { get; set; } + +} \ No newline at end of file diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs new file mode 100644 index 0000000..136bd4e --- /dev/null +++ b/salesbook.Maui/Core/RestClient/IntegryApi/Dto/WtbUserDeviceTokenDTO.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Maui.Core.RestClient.IntegryApi.Dto; + +public class WtbUserDeviceTokenDTO +{ + [JsonPropertyName("type")] + public string Type => "wtb_user_device_tokens"; + + [JsonPropertyName("deviceToken")] + public string DeviceToken { get; set; } + + [JsonPropertyName("userName")] + public string Username { get; set; } + + [JsonPropertyName("appName")] + public int AppName => 7; //salesbook + + [JsonPropertyName("platform")] + public string Platform { get; set; } + +} \ No newline at end of file diff --git a/salesbook.Maui/Core/RestClient/IntegryApi/IntegryRegisterNotificationRestClient.cs b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryRegisterNotificationRestClient.cs new file mode 100644 index 0000000..39fc676 --- /dev/null +++ b/salesbook.Maui/Core/RestClient/IntegryApi/IntegryRegisterNotificationRestClient.cs @@ -0,0 +1,38 @@ +using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account; +using IntegryApiClient.Core.Domain.RestClient.Contacts; +using Microsoft.Extensions.Logging; +using salesbook.Maui.Core.RestClient.IntegryApi.Dto; +using salesbook.Shared.Core.Interface.IntegryApi; + +namespace salesbook.Maui.Core.RestClient.IntegryApi; + +public class IntegryRegisterNotificationRestClient( + ILogger logger, + IUserSession userSession, + IIntegryApiRestClient integryApiRestClient +) : IIntegryRegisterNotificationRestClient +{ + public async Task Register(string fcmToken, ILogger? logger1 = null) + { + logger1 ??= logger; + + var userDeviceToken = new RegisterDeviceDTO() + { + UserDeviceToken = new WtbUserDeviceTokenDTO() + { + DeviceToken = fcmToken, + Platform = OperatingSystem.IsAndroid() ? "Android" : "iOS", + Username = userSession.User.Username + } + }; + try + { + await integryApiRestClient.AuthorizedPost($"device_tokens/insert", userDeviceToken, + logger: logger1); + } + catch (Exception ex) + { + SentrySdk.CaptureException(ex); + } + } +} \ No newline at end of file diff --git a/salesbook.Maui/Core/Services/AttachedService.cs b/salesbook.Maui/Core/Services/AttachedService.cs index fa432d3..0d42acc 100644 --- a/salesbook.Maui/Core/Services/AttachedService.cs +++ b/salesbook.Maui/Core/Services/AttachedService.cs @@ -5,20 +5,49 @@ namespace salesbook.Maui.Core.Services; public class AttachedService : IAttachedService { - public async Task SelectImage() + public async Task SelectImageFromCamera() { - var perm = await Permissions.RequestAsync(); - if (perm != PermissionStatus.Granted) return null; + var cameraPerm = await Permissions.RequestAsync(); + if (cameraPerm != PermissionStatus.Granted) + return null; - var result = await FilePicker.PickAsync(new PickOptions + FileResult? result = null; + + try { - PickerTitle = "Scegli un'immagine", - FileTypes = FilePickerFileType.Images - }); + result = await MediaPicker.Default.CapturePhotoAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Errore cattura foto: {ex.Message}"); + return null; + } return result is null ? null : await ConvertToDto(result, AttachedDTO.TypeAttached.Image); } + public async Task SelectImageFromGallery() + { + var storagePerm = await Permissions.RequestAsync(); + if (storagePerm != PermissionStatus.Granted) + return null; + + FileResult? result = null; + + try + { + result = await MediaPicker.Default.PickPhotoAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Errore selezione galleria: {ex.Message}"); + return null; + } + + return result is null ? null : await ConvertToDto(result, AttachedDTO.TypeAttached.Image); + } + + public async Task SelectFile() { var perm = await Permissions.RequestAsync(); @@ -57,9 +86,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..087b65a 100644 --- a/salesbook.Maui/Core/Services/ManageDataService.cs +++ b/salesbook.Maui/Core/Services/ManageDataService.cs @@ -1,21 +1,147 @@ using AutoMapper; -using System.Linq.Expressions; +using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage; 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; using salesbook.Shared.Core.Helpers.Enum; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Interface.System.Network; +using System.Linq.Expressions; +using salesbook.Shared.Core.Dto.PageState; namespace salesbook.Maui.Core.Services; -public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManageDataService +public class ManageDataService( + LocalDbService localDb, + IMapper mapper, + UserListState userListState, + 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, DateTime? lastSync) + { + List? contactList; + List? prospectList; + whereCond ??= new WhereCondContact(); + + if (networkService.ConnectionAvailable) + { + var response = new UsersSyncResponseDTO(); + + var clienti = await integryApiService.RetrieveAnagClie( + new CRMAnagRequestDTO + { + CodAnag = whereCond.CodAnag, + FlagStato = whereCond.FlagStato, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact, + FilterDate = lastSync + } + ); + + response.AnagClie = clienti.AnagClie; + response.VtbDest = clienti.VtbDest; + response.VtbCliePersRif = clienti.VtbCliePersRif; + + var prospect = await integryApiService.RetrieveProspect( + new CRMProspectRequestDTO + { + CodPpro = whereCond.CodAnag, + PartIva = whereCond.PartIva, + ReturnPersRif = !whereCond.OnlyContact, + FilterDate = lastSync + } + ); + + response.PtbPros = prospect.PtbPros; + response.PtbProsRif = prospect.PtbProsRif; + + _ = UpdateDbUsers(response); + + 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 +172,69 @@ public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManage } } - public async Task> GetActivity(Expression>? whereCond = null) + public async Task> GetActivityTryLocalDb(WhereCondActivity whereCond) { - var activities = await localDb.Get(whereCond); + List? activities; + + 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.IsNullOrEmpty() && networkService.ConnectionAvailable) + { + activities = await integryApiService.RetrieveActivity( + new CRMRetrieveActivityRequestDTO + { + StarDate = whereCond.Start, + EndDate = whereCond.End, + ActivityId = whereCond.ActivityId + } + ); + + _ = UpdateDb(activities); + } + + return await MapActivity(activities); + } + + public async Task> GetActivity(WhereCondActivity whereCond, bool useLocalDb) + { + 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)) + ); + } + + return await MapActivity(activities); + } + + public async Task> MapActivity(List? activities) + { + if (activities == null) return []; var codJcomList = activities .Select(x => x.CodJcom) @@ -56,23 +242,35 @@ public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManage .Distinct().ToList(); var jtbComtList = await localDb.Get(x => codJcomList.Contains(x.CodJcom)); - var commesseDict = jtbComtList.ToDictionary(x => x.CodJcom, x => x.Descrizione); var codAnagList = activities .Select(x => x.CodAnag) .Where(x => !string.IsNullOrEmpty(x)) .Distinct().ToList(); - var clientList = await localDb.Get(x => codAnagList.Contains(x.CodAnag)); - var distinctClient = clientList.ToDictionary(x => x.CodAnag, x => x.RagSoc); - var prospectList = await localDb.Get(x => codAnagList.Contains(x.CodPpro)); - var distinctProspect = prospectList.ToDictionary(x => x.CodPpro, x => x.RagSoc); + IDictionary? distinctUser = null; + + if (userListState.AllUsers != null) + { + distinctUser = userListState.AllUsers + .Where(x => codAnagList.Contains(x.CodContact)) + .ToDictionary(x => x.CodContact, x => x.RagSoc); + } var returnDto = activities .Select(activity => { var dto = mapper.Map(activity); + if (activity is { AlarmTime: not null, EstimatedTime: not null }) + { + var minuteBefore = activity.EstimatedTime.Value - activity.AlarmTime.Value; + dto.MinuteBefore = (int)Math.Abs(minuteBefore.TotalMinutes); + + dto.NotificationDate = dto.MinuteBefore == 0 ? + activity.EstimatedTime : activity.AlarmTime; + } + if (activity.CodJcom != null) { dto.Category = ActivityCategoryEnum.Commessa; @@ -86,16 +284,14 @@ public class ManageDataService(LocalDbService localDb, IMapper mapper) : IManage { string? ragSoc; - if (distinctClient.TryGetValue(activity.CodAnag, out ragSoc) || - distinctProspect.TryGetValue(activity.CodAnag, out ragSoc)) + if (distinctUser != null && (distinctUser.TryGetValue(activity.CodAnag, out ragSoc) || + distinctUser.TryGetValue(activity.CodAnag, out ragSoc))) { dto.Cliente = ragSoc; } } - dto.Commessa = activity.CodJcom != null && commesseDict.TryGetValue(activity.CodJcom, out var descr) - ? descr - : null; + dto.Commessa = jtbComtList.Find(x => x.CodJcom.Equals(dto.CodJcom)); return dto; }) .ToList(); @@ -103,12 +299,62 @@ 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) => localDb.InsertOrUpdate([objectToSave]); + public async Task DeleteProspect(string codPpro) + { + var persRifList = await GetTable(x => x.CodPpro!.Equals(codPpro)); + + if (!persRifList.IsNullOrEmpty()) + { + foreach (var persRif in persRifList) + { + await localDb.Delete(persRif); + } + } + + var ptbPros = (await GetTable(x => x.CodPpro!.Equals(codPpro))).FirstOrDefault(); + + if (ptbPros != null) + { + await localDb.Delete(ptbPros); + } + } + public Task Delete(T objectToDelete) => localDb.Delete(objectToDelete); diff --git a/salesbook.Maui/Core/Services/SyncDbService.cs b/salesbook.Maui/Core/Services/SyncDbService.cs index 8178aa6..795f99a 100644 --- a/salesbook.Maui/Core/Services/SyncDbService.cs +++ b/salesbook.Maui/Core/Services/SyncDbService.cs @@ -1,21 +1,11 @@ using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; 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 +17,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 +30,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/Services/NetworkService.cs b/salesbook.Maui/Core/System/Network/NetworkService.cs similarity index 55% rename from salesbook.Maui/Core/Services/NetworkService.cs rename to salesbook.Maui/Core/System/Network/NetworkService.cs index 31a45b0..af1da33 100644 --- a/salesbook.Maui/Core/Services/NetworkService.cs +++ b/salesbook.Maui/Core/System/Network/NetworkService.cs @@ -1,11 +1,15 @@ using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.System.Network; -namespace salesbook.Maui.Core.Services; +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/Core/System/Notification/FirebaseNotificationService.cs b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs new file mode 100644 index 0000000..8d0fd36 --- /dev/null +++ b/salesbook.Maui/Core/System/Notification/FirebaseNotificationService.cs @@ -0,0 +1,37 @@ +using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; +using Shiny; +using Shiny.Notifications; +using Shiny.Push; + +namespace salesbook.Maui.Core.System.Notification; + +public class FirebaseNotificationService( + IPushManager pushManager, + IIntegryRegisterNotificationRestClient integryRegisterNotificationRestClient, + INotificationManager notificationManager +) : IFirebaseNotificationService +{ + public async Task InitFirebase() + { + CreateNotificationChannel(); + + var (accessState, token) = await pushManager.RequestAccess(); + + if (accessState == AccessState.Denied || token is null) return; + await integryRegisterNotificationRestClient.Register(token); + } + + private void CreateNotificationChannel() + { + var channel = new Channel + { + Identifier = "salesbook_push", + Description = "Notifiche push di SalesBook", + Importance = ChannelImportance.High, + Actions = [] + }; + + notificationManager.AddChannel(channel); + } +} \ No newline at end of file diff --git a/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs new file mode 100644 index 0000000..d031d57 --- /dev/null +++ b/salesbook.Maui/Core/System/Notification/Push/PushNotificationDelegate.cs @@ -0,0 +1,63 @@ +using CommunityToolkit.Mvvm.Messaging; +using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Entity; +using salesbook.Shared.Core.Helpers; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Messages.Notification.NewPush; +using Shiny.Push; +using System.Text.Json; + +namespace salesbook.Maui.Core.System.Notification.Push; + +public class PushNotificationDelegate( + IIntegryRegisterNotificationRestClient integryRegisterNotificationRestClient, + IMessenger messenger +) : IPushDelegate +{ + public Task OnEntry(PushNotification notification) + { + // fires when the user taps on a push notification + return Task.CompletedTask; + } + + public Task OnReceived(PushNotification notification) + { + if (notification.Notification is null) return Task.CompletedTask; + var data = notification.Data; + + NotificationDataDTO? notificationDataDto = null; + + if (!data.IsNullOrEmpty()) + { + var json = JsonSerializer.Serialize(data); + notificationDataDto = JsonSerializer.Deserialize(json); + } + + if (notificationDataDto?.NotificationId == null) return Task.CompletedTask; + var notificationId = long.Parse(notificationDataDto.NotificationId); + + var pushNotification = new WtbNotification + { + Id = notificationId, + Title = notification.Notification.Title, + Body = notification.Notification.Message, + NotificationData = notificationDataDto + }; + + messenger.Send(new NewPushNotificationMessage(pushNotification)); + + return Task.CompletedTask; + } + + public Task OnNewToken(string token) + { + integryRegisterNotificationRestClient.Register(token); + return Task.CompletedTask; + } + + public Task OnUnRegistered(string token) + { + // fires when a push notification change is set by the operating system or provider + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs b/salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs new file mode 100644 index 0000000..ab1ce6b --- /dev/null +++ b/salesbook.Maui/Core/System/Notification/ShinyNotificationManager.cs @@ -0,0 +1,9 @@ +using salesbook.Shared.Core.Interface.System.Notification; +using Shiny.Notifications; + +namespace salesbook.Maui.Core.System.Notification; + +public class ShinyNotificationManager(INotificationManager notificationManager) : IShinyNotificationManager +{ + public Task RequestAccess() => notificationManager.RequestAccess(); +} \ No newline at end of file diff --git a/salesbook.Maui/GoogleService-Info.plist b/salesbook.Maui/GoogleService-Info.plist new file mode 100644 index 0000000..4ed039f --- /dev/null +++ b/salesbook.Maui/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyC_QtQpsVortjzgl-B7__IQZ-85lOct55E + GCM_SENDER_ID + 830771692001 + PLIST_VERSION + 1 + BUNDLE_ID + it.integry.salesbook + PROJECT_ID + salesbook-smetar + STORAGE_BUCKET + salesbook-smetar.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:830771692001:ios:59d8b1d8570ac81f3752a0 + + \ No newline at end of file diff --git a/salesbook.Maui/MainPage.xaml.cs b/salesbook.Maui/MainPage.xaml.cs index 0b723b4..034b003 100644 --- a/salesbook.Maui/MainPage.xaml.cs +++ b/salesbook.Maui/MainPage.xaml.cs @@ -1,23 +1,10 @@ -using CommunityToolkit.Mvvm.Messaging; -using salesbook.Shared.Core.Messages.Back; - namespace salesbook.Maui { public partial class MainPage : ContentPage { - private readonly IMessenger _messenger; - - public MainPage(IMessenger messenger) + public MainPage() { InitializeComponent(); - _messenger = messenger; } - - protected override bool OnBackButtonPressed() - { - _messenger.Send(new HardwareBackMessage("back")); - return true; - } - } } diff --git a/salesbook.Maui/MauiProgram.cs b/salesbook.Maui/MauiProgram.cs index 2b3aa2a..199c320 100644 --- a/salesbook.Maui/MauiProgram.cs +++ b/salesbook.Maui/MauiProgram.cs @@ -1,4 +1,3 @@ -using AutoMapper; using CommunityToolkit.Maui; using CommunityToolkit.Mvvm.Messaging; using IntegryApiClient.MAUI; @@ -6,16 +5,27 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; using MudBlazor.Services; using MudExtensions.Services; +using salesbook.Maui.Core.RestClient.IntegryApi; using salesbook.Maui.Core.Services; +using salesbook.Maui.Core.System.Network; +using salesbook.Maui.Core.System.Notification; +using salesbook.Maui.Core.System.Notification.Push; using salesbook.Shared; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.PageState; using salesbook.Shared.Core.Helpers; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Interface.System.Network; +using salesbook.Shared.Core.Interface.System.Notification; using salesbook.Shared.Core.Messages.Activity.Copy; using salesbook.Shared.Core.Messages.Activity.New; using salesbook.Shared.Core.Messages.Back; using salesbook.Shared.Core.Messages.Contact; +using salesbook.Shared.Core.Messages.Notification.Loaded; +using salesbook.Shared.Core.Messages.Notification.NewPush; using salesbook.Shared.Core.Services; +using Shiny; namespace salesbook.Maui { @@ -23,7 +33,7 @@ namespace salesbook.Maui { private const string AppToken = "f0484398-1f8b-42f5-ab79-5282c164e1d8"; - public static MauiApp CreateMauiApp() + public static MauiAppBuilder CreateMauiAppBuilder() { InteractiveRenderSettings.ConfigureBlazorHybridRenderModes(); @@ -32,6 +42,7 @@ namespace salesbook.Maui .UseMauiApp() .UseIntegry(appToken: AppToken, useLoginAzienda: true) .UseMauiCommunityToolkit() + .UseShiny() .UseSentry(options => { options.Dsn = "https://453b6b38f94fd67e40e0d5306d6caff8@o4508499810254848.ingest.de.sentry.io/4509605099667536"; @@ -53,17 +64,35 @@ 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(); + builder.Services.AddSingleton(); //Message - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + //Notification + builder.Services.AddNotifications(); + builder.Services.AddPush(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); @@ -72,10 +101,10 @@ namespace salesbook.Maui builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - return builder.Build(); + return builder; } } } \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/AndroidManifest.xml b/salesbook.Maui/Platforms/Android/AndroidManifest.xml index ddb278b..7c85307 100644 --- a/salesbook.Maui/Platforms/Android/AndroidManifest.xml +++ b/salesbook.Maui/Platforms/Android/AndroidManifest.xml @@ -1,9 +1,27 @@  - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/AndroidModule.cs b/salesbook.Maui/Platforms/Android/AndroidModule.cs new file mode 100644 index 0000000..19ab9b1 --- /dev/null +++ b/salesbook.Maui/Platforms/Android/AndroidModule.cs @@ -0,0 +1,13 @@ +using salesbook.Maui.Core; +using salesbook.Shared.Core.Interface.System.Battery; + +namespace salesbook.Maui; + +public static class AndroidModule +{ + public static MauiAppBuilder RegisterAndroidAppServices(this MauiAppBuilder mauiAppBuilder) + { + mauiAppBuilder.Services.AddSingleton(); + return mauiAppBuilder; + } +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs b/salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs new file mode 100644 index 0000000..4e1f3ff --- /dev/null +++ b/salesbook.Maui/Platforms/Android/Core/BatteryOptimizationManagerService.cs @@ -0,0 +1,28 @@ +using Android.App; +using Android.Content; +using Android.OS; +using Android.Provider; +using salesbook.Shared.Core.Interface.System.Battery; +using Application = Android.App.Application; + +namespace salesbook.Maui.Core; + +public class BatteryOptimizationManagerService : IBatteryOptimizationManagerService +{ + public bool IsBatteryOptimizationEnabled() + { + var packageName = AppInfo.PackageName; + + var pm = (PowerManager)Application.Context.GetSystemService(Context.PowerService)!; + return !pm.IsIgnoringBatteryOptimizations(packageName); + } + + public void OpenBatteryOptimizationSettings(Action onCompleted) + { + var packageName = AppInfo.PackageName; + + var intent = new Intent(Settings.ActionRequestIgnoreBatteryOptimizations); + intent.SetData(Android.Net.Uri.Parse("package:" + packageName)); + ((MainActivity)Platform.CurrentActivity!).StartActivityForResult(intent, (result, _) => { onCompleted(result == Result.Ok); }); + } +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/MainActivity.cs b/salesbook.Maui/Platforms/Android/MainActivity.cs index 1c93fe9..dbc05ea 100644 --- a/salesbook.Maui/Platforms/Android/MainActivity.cs +++ b/salesbook.Maui/Platforms/Android/MainActivity.cs @@ -1,13 +1,42 @@ using Android.App; +using Android.Content; using Android.Content.PM; namespace salesbook.Maui { - [Activity(Theme = "@style/Maui.SplashTheme", + [Activity( + Theme = "@style/Maui.SplashTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] + + [IntentFilter( + [ + Shiny.ShinyPushIntents.NotificationClickAction + ], + Categories = + [ + "android.intent.category.DEFAULT" + ] + )] public class MainActivity : MauiAppCompatActivity { + private readonly IDictionary> _onActivityResultSubscriber = + new Dictionary>(); + + public void StartActivityForResult(Intent intent, Action onResultAction) + { + var requestCode = new Random(DateTime.Now.Millisecond).Next(); + _onActivityResultSubscriber.Add(requestCode, onResultAction); + StartActivityForResult(intent, requestCode); + } + + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + if (_onActivityResultSubscriber.TryGetValue(requestCode, out var value)) + value(resultCode, data); + + base.OnActivityResult(requestCode, resultCode, data); + } } } \ No newline at end of file diff --git a/salesbook.Maui/Platforms/Android/MainApplication.cs b/salesbook.Maui/Platforms/Android/MainApplication.cs index 2820e44..ff3db19 100644 --- a/salesbook.Maui/Platforms/Android/MainApplication.cs +++ b/salesbook.Maui/Platforms/Android/MainApplication.cs @@ -1,16 +1,16 @@ using Android.App; using Android.Runtime; -namespace salesbook.Maui -{ - [Application(HardwareAccelerated = true)] - public class MainApplication : MauiApplication - { - public MainApplication(IntPtr handle, JniHandleOwnership ownership) - : base(handle, ownership) - { - } +namespace salesbook.Maui; - protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +[Application(HardwareAccelerated = true)] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { } -} + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiAppBuilder() + .RegisterAndroidAppServices().Build(); +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/iOS/AppDelegate.cs b/salesbook.Maui/Platforms/iOS/AppDelegate.cs index 57062da..0728e77 100644 --- a/salesbook.Maui/Platforms/iOS/AppDelegate.cs +++ b/salesbook.Maui/Platforms/iOS/AppDelegate.cs @@ -1,10 +1,24 @@ using Foundation; +using UIKit; namespace salesbook.Maui { [Register("AppDelegate")] public class AppDelegate : MauiUIApplicationDelegate { - protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiAppBuilder() + .RegisterIosAppServices().Build(); + + [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")] + public void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken) + => global::Shiny.Hosting.Host.Lifecycle.OnRegisteredForRemoteNotifications(deviceToken); + + [Export("application:didFailToRegisterForRemoteNotificationsWithError:")] + public void FailedToRegisterForRemoteNotifications(UIApplication application, NSError error) + => global::Shiny.Hosting.Host.Lifecycle.OnFailedToRegisterForRemoteNotifications(error); + + [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")] + public void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action completionHandler) + => global::Shiny.Hosting.Host.Lifecycle.OnDidReceiveRemoteNotification(userInfo, completionHandler); } } diff --git a/salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs b/salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs new file mode 100644 index 0000000..3f9400d --- /dev/null +++ b/salesbook.Maui/Platforms/iOS/Core/BatteryOptimizationManagerService.cs @@ -0,0 +1,12 @@ +using salesbook.Shared.Core.Interface.System.Battery; + +namespace salesbook.Maui.Core; + +public class BatteryOptimizationManagerService : IBatteryOptimizationManagerService +{ + public bool IsBatteryOptimizationEnabled() => true; + + public void OpenBatteryOptimizationSettings(Action onCompleted) + { + } +} \ No newline at end of file diff --git a/salesbook.Maui/Platforms/iOS/Info.plist b/salesbook.Maui/Platforms/iOS/Info.plist index a094b3e..103dc74 100644 --- a/salesbook.Maui/Platforms/iOS/Info.plist +++ b/salesbook.Maui/Platforms/iOS/Info.plist @@ -39,11 +39,18 @@ NSLocationWhenInUseUsageDescription L'app utilizza la tua posizione per allegarla alle attività. + NSCameraUsageDescription + Questa app necessita di accedere alla fotocamera per scattare foto. + NSPhotoLibraryUsageDescription - Consente di selezionare immagini da allegare alle attività. + Questa app necessita di accedere alla libreria foto. NSPhotoLibraryAddUsageDescription Permette all'app di salvare file o immagini nella tua libreria fotografica se necessario. + UIBackgroundModes + + remote-notification + diff --git a/salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy b/salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..fcd26e7 --- /dev/null +++ b/salesbook.Maui/Platforms/iOS/PrivacyInfo.xcprivacy @@ -0,0 +1,110 @@ + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyCollectedDataTypes + + + + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataTypeEmailAddress + NSPrivacyCollectedDataTypeLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataTypePhoneNumber + NSPrivacyCollectedDataTypeLocation + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + + + diff --git a/salesbook.Maui/Platforms/iOS/iOSModule.cs b/salesbook.Maui/Platforms/iOS/iOSModule.cs new file mode 100644 index 0000000..071675e --- /dev/null +++ b/salesbook.Maui/Platforms/iOS/iOSModule.cs @@ -0,0 +1,13 @@ +using salesbook.Maui.Core; +using salesbook.Shared.Core.Interface.System.Battery; + +namespace salesbook.Maui; + +public static class iOSModule +{ + public static MauiAppBuilder RegisterIosAppServices(this MauiAppBuilder mauiAppBuilder) + { + mauiAppBuilder.Services.AddSingleton(); + return mauiAppBuilder; + } +} \ No newline at end of file diff --git a/salesbook.Maui/google-services.json b/salesbook.Maui/google-services.json new file mode 100644 index 0000000..ef98865 --- /dev/null +++ b/salesbook.Maui/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "830771692001", + "project_id": "salesbook-smetar", + "storage_bucket": "salesbook-smetar.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:830771692001:android:06bc5a9706bc9bef3752a0", + "android_client_info": { + "package_name": "it.integry.salesbook" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyB43ai_Ph0phO_OkBC1wAOazKZUV9KsLaM" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/salesbook.Maui/salesbook.Maui.csproj b/salesbook.Maui/salesbook.Maui.csproj index 4c87907..441b42b 100644 --- a/salesbook.Maui/salesbook.Maui.csproj +++ b/salesbook.Maui/salesbook.Maui.csproj @@ -29,8 +29,8 @@ it.integry.salesbook - 1.1.0 - 5 + 2.0.0 + 6 14.2 - --> + + + @@ -118,16 +120,29 @@ + + + PreserveNewest + + + + + + + - + - - - - - - - + + + + + + + + + + diff --git a/salesbook.Maui/wwwroot/index.html b/salesbook.Maui/wwwroot/index.html index 74faadd..7f7b15f 100644 --- a/salesbook.Maui/wwwroot/index.html +++ b/salesbook.Maui/wwwroot/index.html @@ -53,6 +53,7 @@ + diff --git a/salesbook.Shared/Components/Layout/ConnectionState.razor b/salesbook.Shared/Components/Layout/ConnectionState.razor new file mode 100644 index 0000000..6ec018c --- /dev/null +++ b/salesbook.Shared/Components/Layout/ConnectionState.razor @@ -0,0 +1,26 @@ +
+ @if (IsNetworkAvailable) + { + if(ServicesIsDown) + { + + Servizi offline + } + else + { + + Online + } + } + else + { + + Nessuna connessione + } +
+ +@code{ + [Parameter] public bool IsNetworkAvailable { get; set; } + [Parameter] public bool ServicesIsDown { get; set; } + [Parameter] public bool ShowWarning { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Components/Layout/HeaderLayout.razor.css b/salesbook.Shared/Components/Layout/HeaderLayout.razor.css index 82cd2a6..c777948 100644 --- a/salesbook.Shared/Components/Layout/HeaderLayout.razor.css +++ b/salesbook.Shared/Components/Layout/HeaderLayout.razor.css @@ -14,6 +14,8 @@ position: relative; } +.header-content > .title { width: 100%; } + .header-content.with-back .page-title { position: absolute; left: 50%; diff --git a/salesbook.Shared/Components/Layout/MainLayout.razor b/salesbook.Shared/Components/Layout/MainLayout.razor index 0130251..362c8be 100644 --- a/salesbook.Shared/Components/Layout/MainLayout.razor +++ b/salesbook.Shared/Components/Layout/MainLayout.razor @@ -1,16 +1,20 @@ @using System.Globalization -@using CommunityToolkit.Mvvm.Messaging +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Messages.Back @inherits LayoutComponentBase @inject IJSRuntime JS -@inject IMessenger Messenger @inject BackNavigationService BackService +@inject INetworkService NetworkService +@inject IIntegryApiService IntegryApiService + +
@@ -19,7 +23,6 @@ @Body -
@code { @@ -27,6 +30,49 @@ private bool IsDarkMode { get; set; } private string _mainContentClass = ""; + //Connection state + private bool _isNetworkAvailable; + private bool _servicesIsDown; + private bool _showWarning; + + private DateTime _lastApiCheck = DateTime.MinValue; + private const int DelaySeconds = 90; + + 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 +127,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 +138,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..fc379bd 100644 --- a/salesbook.Shared/Components/Layout/NavMenu.razor +++ b/salesbook.Shared/Components/Layout/NavMenu.razor @@ -1,12 +1,21 @@ @using CommunityToolkit.Mvvm.Messaging @using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Dto.Activity +@using salesbook.Shared.Core.Dto.PageState @using salesbook.Shared.Core.Entity +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Messages.Activity.Copy @using salesbook.Shared.Core.Messages.Activity.New @using salesbook.Shared.Core.Messages.Contact +@using salesbook.Shared.Core.Messages.Notification.Loaded +@using salesbook.Shared.Core.Messages.Notification.NewPush @inject IDialogService Dialog @inject IMessenger Messenger @inject CopyActivityService CopyActivityService +@inject NewPushNotificationService NewPushNotificationService +@inject NotificationState Notification +@inject INetworkService NetworkService +@inject NotificationsLoadedService NotificationsLoadedService
@@ -46,8 +57,8 @@ - Nuovo contatto - Nuova attività + Nuovo contatto + Nuova attività } @@ -61,16 +72,16 @@ protected override Task OnInitializedAsync() { - CopyActivityService.OnCopyActivity += async dto => await CreateActivity(dto); + InitMessage(); - NavigationManager.LocationChanged += (_, args) => + NavigationManager.LocationChanged += (_, args) => { 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; @@ -103,4 +114,17 @@ Messenger.Send(new NewContactMessage((CRMCreateContactResponseDTO)result.Data)); } } + + private void NewNotificationReceived(WtbNotification notification) + { + Notification.ReceivedNotifications.Add(notification); + InvokeAsync(StateHasChanged); + } + + private void InitMessage() + { + CopyActivityService.OnCopyActivity += async dto => await CreateActivity(dto); + NewPushNotificationService.OnNotificationReceived += NewNotificationReceived; + NotificationsLoadedService.OnNotificationsLoaded += () => InvokeAsync(StateHasChanged); + } } \ No newline at end of file diff --git a/salesbook.Shared/Components/Layout/NavMenu.razor.css b/salesbook.Shared/Components/Layout/NavMenu.razor.css index e73f065..ecd8bcf 100644 --- a/salesbook.Shared/Components/Layout/NavMenu.razor.css +++ b/salesbook.Shared/Components/Layout/NavMenu.razor.css @@ -9,7 +9,7 @@ .animated-navbar.show-nav { transform: translateY(0); } -.animated-navbar.hide-nav { transform: translateY(100%); } +.animated-navbar.hide-nav { transform: translateY(150%); } .animated-navbar.with-plus { margin-left: 30px; } diff --git a/salesbook.Shared/Components/Layout/Spinner/OverlayLayout.razor b/salesbook.Shared/Components/Layout/Spinner/OverlayLayout.razor new file mode 100644 index 0000000..9cec0ec --- /dev/null +++ b/salesbook.Shared/Components/Layout/Spinner/OverlayLayout.razor @@ -0,0 +1,12 @@ + +
+ Caricamento + + +
+
+ +@code +{ + [Parameter] public bool Visible { get; set; } +} diff --git a/salesbook.Shared/Components/Layout/Spinner/OverlayLayout.razor.css b/salesbook.Shared/Components/Layout/Spinner/OverlayLayout.razor.css new file mode 100644 index 0000000..c693381 --- /dev/null +++ b/salesbook.Shared/Components/Layout/Spinner/OverlayLayout.razor.css @@ -0,0 +1,18 @@ +.overlay-container { + background: var(--mud-palette-background); + width: 20rem; + height: 6rem; + padding: 0 1rem; + display: flex; + gap: .5rem; + flex-direction: column; + justify-content: center; + border-radius: 20px; + box-shadow: var(--custom-box-shadow); +} + + .overlay-container > span { + text-align: center; + font-size: 20px; + font-weight: 600; + } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/Calendar.razor b/salesbook.Shared/Components/Pages/Calendar.razor index 4dac748..d988e69 100644 --- a/salesbook.Shared/Components/Pages/Calendar.razor +++ b/salesbook.Shared/Components/Pages/Calendar.razor @@ -1,14 +1,16 @@ @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.Dto.PageState +@using salesbook.Shared.Core.Interface @using salesbook.Shared.Core.Messages.Activity.New @inject IManageDataService ManageData @inject IJSRuntime JS @inject NewActivityService NewActivity +@inject UserListState UserState @code { - // Modelli per ottimizzazione rendering private record DayData(DateTime Date, string CssClass, bool HasEvents, CategoryData[] EventCategories, string DayName = ""); @@ -221,11 +222,20 @@ PrepareRenderingData(); NewActivity.OnActivityCreated += async activityId => await OnActivityCreated(activityId); + UserState.OnUsersLoaded += async () => await InvokeAsync(LoadData); } protected override async Task OnAfterRenderAsync(bool firstRender) { - if (firstRender) + if (firstRender && UserState.IsLoaded) + { + await LoadData(); + } + } + + private async Task LoadData() + { + if (!_isInitialized) { Filter.User = new HashSet { UserSession.User.Username }; @@ -471,9 +481,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 +549,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 +566,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 +591,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..4695e59 100644 --- a/salesbook.Shared/Components/Pages/Calendar.razor.css +++ b/salesbook.Shared/Components/Pages/Calendar.razor.css @@ -24,6 +24,7 @@ padding-bottom: 1rem; overflow-x: hidden; overflow-y: visible; + min-height: 7rem; } .week-slider.expanded { @@ -31,7 +32,7 @@ grid-template-columns: repeat(7, 1fr); gap: 0.4rem; padding: 1rem; - overflow-y: auto; + overflow-y: hidden; } .week-slider.expand-animation { animation: expandFromCenter 0.3s ease forwards; } @@ -79,13 +80,12 @@ } .day { - background: var(--mud-palette-surface); border-radius: 10px; text-align: center; cursor: pointer; transition: background 0.3s ease, transform 0.2s ease; font-size: 0.95rem; - box-shadow: var(--custom-box-shadow); + background: var(--mud-palette-background-gray); width: 38px; height: 38px; display: flex; @@ -93,7 +93,7 @@ justify-content: center; align-items: center; color: var(--mud-palette-text-primary); - border: 1px solid var(--mud-palette-surface); + border: 1px solid var(--mud-palette-background-gray); margin: 0 auto; } @@ -122,8 +122,7 @@ flex-direction: column; -ms-overflow-style: none; scrollbar-width: none; - padding-bottom: 70px; - height: calc(100% - 130px); + padding-bottom: 9vh; } .appointments.ah-calendar-m { height: calc(100% - 315px) !important; } diff --git a/salesbook.Shared/Components/Pages/Commessa.razor b/salesbook.Shared/Components/Pages/Commessa.razor new file mode 100644 index 0000000..b2dfc03 --- /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 +@using salesbook.Shared.Core.Interface.IntegryApi +@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 = (await ManageData.MapActivity(activities)).OrderByDescending(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..820e906 --- /dev/null +++ b/salesbook.Shared/Components/Pages/Commessa.razor.css @@ -0,0 +1,229 @@ +/* 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; + background: var(--light-card-background); + 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; +} + +/* 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 6e62825..0df4d04 100644 --- a/salesbook.Shared/Components/Pages/Home.razor +++ b/salesbook.Shared/Components/Pages/Home.razor @@ -1,9 +1,19 @@ @page "/" @attribute [Authorize] +@using CommunityToolkit.Mvvm.Messaging @using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Core.Interface.System.Network +@using salesbook.Shared.Core.Interface.System.Notification +@using salesbook.Shared.Core.Messages.Notification.Loaded +@using salesbook.Shared.Core.Services @inject IFormFactor FormFactor @inject INetworkService NetworkService +@inject IFirebaseNotificationService FirebaseNotificationService +@inject IShinyNotificationManager NotificationManager +@inject INotificationService NotificationService +@inject PreloadService PreloadService +@inject IMessenger Messenger @@ -11,14 +21,48 @@ { protected override async Task OnInitializedAsync() { + NetworkService.ConnectionAvailable = NetworkService.IsNetworkAvailable(); + + await LoadNotification(); + await CheckAndRequestPermissions(); + + try + { + await FirebaseNotificationService.InitFirebase(); + } + catch (Exception e) + { + Console.WriteLine($"Firebase init: {e.Message}"); + } + var lastSyncDate = LocalStorage.Get("last-sync"); - if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate.Equals(DateTime.MinValue)) + if (!FormFactor.IsWeb() && NetworkService.ConnectionAvailable && lastSyncDate.Equals(DateTime.MinValue)) { NavigationManager.NavigateTo("/sync"); return; } + _ = StartSyncUser(); NavigationManager.NavigateTo("/Calendar"); } + + private async Task LoadNotification() + { + await NotificationService.LoadNotification(); + Messenger.Send(new NotificationsLoadedMessage()); + } + + private async Task CheckAndRequestPermissions() + { + await NotificationManager.RequestAccess(); + } + + 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..bbf2048 100644 --- a/salesbook.Shared/Components/Pages/Login.razor +++ b/salesbook.Shared/Components/Pages/Login.razor @@ -1,8 +1,11 @@ @page "/login" @using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Services @inject IUserAccountService UserAccountService @inject AppAuthenticationStateProvider AuthenticationStateProvider +@inject INetworkService NetworkService @if (Spinner) { @@ -26,7 +29,7 @@ else - Login + Login @if (_attemptFailed) { @ErrorMessage diff --git a/salesbook.Shared/Components/Pages/Notifications.razor b/salesbook.Shared/Components/Pages/Notifications.razor index 9579556..603f3e9 100644 --- a/salesbook.Shared/Components/Pages/Notifications.razor +++ b/salesbook.Shared/Components/Pages/Notifications.razor @@ -1,14 +1,146 @@ @page "/Notifications" @attribute [Authorize] +@using CommunityToolkit.Mvvm.Messaging @using salesbook.Shared.Components.Layout +@using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Components.SingleElements +@using salesbook.Shared.Core.Dto.PageState +@using salesbook.Shared.Core.Entity +@using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Messages.Notification.Loaded +@using salesbook.Shared.Core.Messages.Notification.NewPush +@inject NotificationState Notification +@inject NewPushNotificationService NewPushNotificationService +@inject IJSRuntime JS +@inject IIntegryNotificationRestClient IntegryNotificationRestClient +@inject INotificationService NotificationService +@inject IMessenger Messenger -
- +
+ @if (Loading) + { + + + } + else + { + if (Notification.ReceivedNotifications.IsNullOrEmpty() && Notification.UnreadNotifications.IsNullOrEmpty() && Notification.NotificationsRead.IsNullOrEmpty()) + { + + } + else + { +
+ @foreach(var notification in Notification.ReceivedNotifications) + { + + } + + @foreach(var notification in Notification.UnreadNotifications) + { + + } + + @foreach (var notification in Notification.NotificationsRead) + { + + } +
+ } + }
@code { + private DotNetObjectReference? _objectReference; + private bool Loading { get; set; } + protected override Task OnInitializedAsync() + { + _objectReference = DotNetObjectReference.Create(this); + NewPushNotificationService.OnNotificationReceived += NewNotificationReceived; + return Task.CompletedTask; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await JS.InvokeVoidAsync("initNotifications", _objectReference); + } + + private void NewNotificationReceived(WtbNotification notification) + { + InvokeAsync(StateHasChanged); + } + + [JSInvokable] + public async Task Delete(string id) + { + if (!long.TryParse(id, out var notificationId)) return; + + Loading = true; + StateHasChanged(); + + var removed = false; + + if (Notification.ReceivedNotifications.RemoveAll(x => x.Id == notificationId) > 0) + removed = true; + else if (Notification.UnreadNotifications.RemoveAll(x => x.Id == notificationId) > 0) + removed = true; + else if (Notification.NotificationsRead.RemoveAll(x => x.Id == notificationId) > 0) + removed = true; + + if (!removed) + { + Loading = false; + StateHasChanged(); + return; + } + + await IntegryNotificationRestClient.Delete(notificationId); + + NotificationService.OrderNotificationList(); + Loading = false; + StateHasChanged(); + + Messenger.Send(new NotificationsLoadedMessage()); + } + + [JSInvokable] + public async Task MarkAsRead(string id) + { + Loading = true; + StateHasChanged(); + + var notificationId = long.Parse(id); + + var wtbNotification = Notification.ReceivedNotifications + .FindLast(x => x.Id == notificationId); + + if (wtbNotification == null) + { + wtbNotification = Notification.UnreadNotifications + .FindLast(x => x.Id == notificationId); + + Notification.UnreadNotifications.Remove(wtbNotification!); + } + else + { + Notification.ReceivedNotifications.Remove(wtbNotification); + } + + wtbNotification = await IntegryNotificationRestClient.MarkAsRead(notificationId); + Notification.NotificationsRead.Add(wtbNotification); + + NotificationService.OrderNotificationList(); + Messenger.Send(new NotificationsLoadedMessage()); + Loading = false; + StateHasChanged(); + } + + public void Dispose() + { + _objectReference?.Dispose(); + } } \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/Notifications.razor.css b/salesbook.Shared/Components/Pages/Notifications.razor.css index e69de29..2e695fa 100644 --- a/salesbook.Shared/Components/Pages/Notifications.razor.css +++ b/salesbook.Shared/Components/Pages/Notifications.razor.css @@ -0,0 +1,21 @@ +.container-notifications { + height: 100%; + overflow: auto; + padding: .2rem 0 75px 0; +} + +.list { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; +} + +.container-notifications::-webkit-scrollbar { + width: 6px; +} + +.container-notifications::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 3px; +} \ No newline at end of file diff --git a/salesbook.Shared/Components/Pages/PersonalInfo.razor b/salesbook.Shared/Components/Pages/PersonalInfo.razor index b3f2bd3..77d21e0 100644 --- a/salesbook.Shared/Components/Pages/PersonalInfo.razor +++ b/salesbook.Shared/Components/Pages/PersonalInfo.razor @@ -3,6 +3,7 @@ @using salesbook.Shared.Components.Layout @using salesbook.Shared.Core.Authorization.Enum @using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.System.Network @using salesbook.Shared.Core.Services @inject AppAuthenticationStateProvider AuthenticationStateProvider @inject INetworkService NetworkService @@ -12,7 +13,7 @@ @if (IsLoggedIn) { -
+
@@ -39,7 +40,7 @@
Status - @if (NetworkService.IsNetworkAvailable()) + @if (NetworkService.ConnectionAvailable) {
@@ -129,7 +130,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/PersonalInfo.razor.css b/salesbook.Shared/Components/Pages/PersonalInfo.razor.css index 5ebd0cc..0c9734a 100644 --- a/salesbook.Shared/Components/Pages/PersonalInfo.razor.css +++ b/salesbook.Shared/Components/Pages/PersonalInfo.razor.css @@ -1,5 +1,5 @@ .container-primary-info { - box-shadow: var(--custom-box-shadow); + background: var(--light-card-background); width: 100%; margin-bottom: 2rem; border-radius: 12px; 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..a312a2e 100644 --- a/salesbook.Shared/Components/Pages/User.razor +++ b/salesbook.Shared/Components/Pages/User.razor @@ -7,12 +7,24 @@ @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 +@using salesbook.Shared.Core.Interface.IntegryApi +@implements IAsyncDisposable @inject IManageDataService ManageData @inject IMapper Mapper @inject IDialogService Dialog -@inject INetworkService NetworkService +@inject IIntegryApiService IntegryApiService +@inject UserPageState UserState - + @if (IsLoading) { @@ -20,20 +32,20 @@ } else { -
+
- + @UtilityString.ExtractInitials(Anag.RagSoc)
@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})") } @@ -43,166 +55,752 @@ else
+ +
+ + + + +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ +
+ +
+ @if (PersRif?.Count > 0) + { +
+ @foreach (var person in PersRif) + { + + @if (person != PersRif.Last()) + { +
+ } + }
} - @if (!string.IsNullOrEmpty(Anag.PartIva)) +
+
+ + + Aggiungi contatto + +
+
+ + +
+ @if (IsLoadingCommesse) { -
- P. IVA - - @Anag.PartIva - + + } + 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 (!string.IsNullOrEmpty(Anag.EMail)) + +
+ @if (ActivityIsLoading) { -
- E-mail - - @Anag.EMail - -
+ } - - @if (Agente != null) + else if (ActivityList?.Count == 0) { -
- Agente - - @Agente.FullName - + + } + else if (ActivityList != null) + { + +
+ +
+ +
+ @foreach (var activity in CurrentPageActivity) + { + + } + + @if (TotalPagesActivity > 1) + { +
+ +
+ +
+ + 5 + 15 + 30 + +
+ }
}
+ + + +
- - - - @if (PersRif is { Count: > 0 }) - { -
- - @{ - var index = PersRif.IndexOf(person); - var isLast = index == PersRif.Count - 1; - } - - @if (!isLast) - { -
- } -
-
- } - -
- - Aggiungi contatto - -
-
- - @if (Commesse.IsNullOrEmpty()) - { - - } - else - { - - - - } - -
} @code { - [Parameter] public string CodContact { get; set; } + [Parameter] public string CodContact { get; set; } = string.Empty; [Parameter] public bool IsContact { get; set; } + // Dati principali private ContactDTO Anag { get; set; } = new(); private List? PersRif { get; set; } - private List Commesse { get; set; } + private 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 = (await ManageData.MapActivity(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; + + if (ActiveTab != UserState.ActiveTab) + SwitchTab(UserState.ActiveTab); + + 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; + UserState.ActiveTab = ActiveTab; + + 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, Data: not null } && result.Data.GetType() == typeof(AnagClie)) + { + var clie = (AnagClie)result.Data; + IsContact = true; + CodContact = clie.CodAnag!; + + await LoadAnagAsync(); + SaveDataToSession(); + } + else 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..c59348f 100644 --- a/salesbook.Shared/Components/Pages/User.razor.css +++ b/salesbook.Shared/Components/Pages/User.razor.css @@ -1,5 +1,11 @@ +.tab-section { + width: 100%; + border-radius: var(--mud-default-borderradius); + background: var(--light-card-background); +} + .container-primary-info { - box-shadow: var(--custom-box-shadow); + background: var(--light-card-background); width: 100%; margin-bottom: 2rem; border-radius: 16px; @@ -39,9 +45,10 @@ .section-info { display: flex; - justify-content: space-between; flex-direction: row; padding: .4rem 1.2rem .8rem; + flex-wrap: wrap; + justify-content: space-between; } .section-personal-info { @@ -84,9 +91,11 @@ .container-button { width: 100%; - box-shadow: var(--custom-box-shadow); - padding: .25rem 0; + background: var(--light-card-background); + padding: 0 !important; + padding-bottom: .5rem !important; border-radius: 16px; + margin-bottom: 0 !important; } .container-button .divider { @@ -136,15 +145,20 @@ display: flex; flex-direction: column; gap: 1rem; - margin-bottom: 1rem; - box-shadow: var(--custom-box-shadow); + background: var(--light-card-background); border-radius: 16px; max-height: 32vh; overflow: auto; scrollbar-width: none; + padding: 1rem 0; } -.container-pers-rif::-webkit-scrollbar { display: none; } +.container-pers-rif::-webkit-scrollbar { width: 3px; } + +.container-pers-rif::-webkit-scrollbar-thumb { + background: #bbb; + border-radius: 2px; +} .container-pers-rif .divider { margin: 0 0 0 3.5rem; @@ -156,4 +170,132 @@ display: flex; gap: 1rem; flex-direction: column; -} \ No newline at end of file +} + +.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; + background: var(--light-card-background); + border-radius: 20px 20px 0 0; + border-bottom: .1rem solid var(--card-border-color); + 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; +} + +/* 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/Pages/Users.razor.css b/salesbook.Shared/Components/Pages/Users.razor.css index 06a5b75..0de27f4 100644 --- a/salesbook.Shared/Components/Pages/Users.razor.css +++ b/salesbook.Shared/Components/Pages/Users.razor.css @@ -5,8 +5,7 @@ flex-direction: column; -ms-overflow-style: none; scrollbar-width: none; - padding-bottom: 70px; - height: 100%; + padding-bottom: 9vh; } .users .divider { 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/SearchAddress.razor b/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor index 44a29f7..e75c79c 100644 --- a/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor +++ b/salesbook.Shared/Components/SingleElements/BottomSheet/SearchAddress.razor @@ -1,7 +1,7 @@ @using salesbook.Shared.Components.Layout.Spinner @using salesbook.Shared.Core.Dto -@using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Overlay +@using salesbook.Shared.Core.Interface.IntegryApi @inject IIntegryApiService IntegryApiService
diff --git a/salesbook.Shared/Components/SingleElements/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..befcc7d 100644 --- a/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor @@ -1,4 +1,4 @@ -@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 @@ -11,7 +11,7 @@ @switch (Activity.Category) { case ActivityCategoryEnum.Commessa: - @Activity.Commessa + @Activity.Commessa?.Descrizione break; case ActivityCategoryEnum.Interna: @Activity.Cliente @@ -28,11 +28,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 +81,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 +97,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/ActivityCard.razor.css b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor.css index 947044b..7e5a74e 100644 --- a/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor.css +++ b/salesbook.Shared/Components/SingleElements/Card/ActivityCard.razor.css @@ -5,14 +5,23 @@ padding: .5rem .5rem; border-radius: 12px; line-height: normal; - box-shadow: var(--custom-box-shadow); + /*box-shadow: var(--custom-box-shadow);*/ } -.activity-card.memo { border-left: 5px solid var(--mud-palette-info-darken); } +.activity-card.memo { + border-left: 5px solid var(--mud-palette-info-darken); + background-color: hsl(from var(--mud-palette-info-darken) h s 98%); +} -.activity-card.interna { border-left: 5px solid var(--mud-palette-success-darken); } +.activity-card.interna { + border-left: 5px solid var(--mud-palette-success-darken); + background-color: hsl(from var(--mud-palette-success-darken) h s 98%); +} -.activity-card.commessa { border-left: 5px solid var(--mud-palette-warning); } +.activity-card.commessa { + border-left: 5px solid var(--mud-palette-warning); + background-color: hsl(from var(--mud-palette-warning) h s 98%); +} .activity-left-section { display: flex; diff --git a/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor new file mode 100644 index 0000000..3d0d085 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/AttachCard.razor @@ -0,0 +1,45 @@ +@using salesbook.Shared.Core.Dto +@using salesbook.Shared.Core.Interface +@using salesbook.Shared.Core.Interface.IntegryApi +@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..f09ce33 --- /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; + background: var(--light-card-background); +} + +.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..bcc21b1 100644 --- a/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor +++ b/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor @@ -1,24 +1,72 @@ +@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; + + // Ultimo step non skip + var lastBeforeSkip = Steps + .LastOrDefault(s => s.Status is { Skip: false }); + + if (lastBeforeSkip is not null) + Stato = lastBeforeSkip.StepName; + + // Ultima data disponibile + 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..d9f919b 100644 --- a/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor.css +++ b/salesbook.Shared/Components/SingleElements/Card/CommessaCard.razor.css @@ -5,15 +5,11 @@ padding: .5rem .5rem; border-radius: 12px; line-height: normal; - box-shadow: var(--custom-box-shadow); + + border-left: 5px solid var(--card-border-color); + background-color: hsl(from var(--card-border-color) h s 99%); } -.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; @@ -28,8 +24,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 +37,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 +57,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/ContactCard.razor.css b/salesbook.Shared/Components/SingleElements/Card/ContactCard.razor.css index 3db2239..6596dd8 100644 --- a/salesbook.Shared/Components/SingleElements/Card/ContactCard.razor.css +++ b/salesbook.Shared/Components/SingleElements/Card/ContactCard.razor.css @@ -2,8 +2,7 @@ width: 100%; display: flex; flex-direction: row; - padding: 0 .75rem; - border-radius: 16px; + padding: 0 .5rem; line-height: normal; justify-content: space-between; align-items: center; diff --git a/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor new file mode 100644 index 0000000..1ed29c3 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor @@ -0,0 +1,121 @@ +@using Java.Sql +@using salesbook.Shared.Components.Layout.Spinner +@using salesbook.Shared.Core.Dto.Activity +@using salesbook.Shared.Core.Entity +@using salesbook.Shared.Core.Interface +@inject IManageDataService ManageDataService +@inject ISnackbar Snackbar +@inject IDialogService Dialog + +
+
+ +
+
+ +
+
+ @if (Notification.NotificationData is { Type: not null }) + { + @switch (Notification.NotificationData.Type) + { + case "memo": +
+ break; + case "newPlanned": +
+ break; + } + } +
+
+
@Notification.Title
+ +
+
@GetTimeAgo(Notification.StartDate)
+ @if (Unread) + { + + } +
+
+ + @if ( + Notification.StartDate < DateTime.Today && Notification.Body != null && Notification.Body.Contains("Oggi") + ) + { +
@Notification.Body.Replace("Oggi", $"{Notification.StartDate:d}")
+ } + else + { +
@Notification.Body
+ } +
+
+
+ + + +@code { + [Parameter] public bool Unread { get; set; } + [Parameter] public WtbNotification Notification { get; set; } = new(); + + private bool VisibleOverlay { get; set; } + + private async Task OpenActivity() + { + if(Notification.NotificationData?.ActivityId == null) return; + var activityId = Notification.NotificationData.ActivityId; + + Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; + Snackbar.Clear(); + + VisibleOverlay = true; + StateHasChanged(); + + var activity = (await ManageDataService.GetActivityTryLocalDb(new WhereCondActivity { ActivityId = activityId })).LastOrDefault(); + + VisibleOverlay = false; + StateHasChanged(); + + if (activity == null) Snackbar.Add("Impossibile aprire l'attività", Severity.Error); + + _ = ModalHelpers.OpenActivityForm(Dialog, activity, null); + } + + private static string GetTimeAgo(DateTime? timestamp) + { + if (timestamp is null) return ""; + + var difference = DateTime.Now - timestamp.Value; + + if (DateTime.Now.Day != timestamp.Value.Day) + return timestamp.Value.ToString("dd/MM/yyyy"); + + switch (difference.TotalMinutes) + { + case < 1: + return "Adesso"; + case < 60: + return $"{(int)difference.TotalMinutes} minuti fa"; + default: + { + switch (difference.TotalHours) + { + case < 2: + return $"{(int)difference.TotalHours} ora fa"; + case < 24: + return $"{timestamp.Value:t}"; + default: + { + return timestamp.Value.ToString("dd/MM/yyyy"); + } + } + } + } + } +} \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css new file mode 100644 index 0000000..f3efd98 --- /dev/null +++ b/salesbook.Shared/Components/SingleElements/Card/NotificationCard.razor.css @@ -0,0 +1,109 @@ +.row { + position: relative; + overflow: hidden; + border-radius: var(--mud-default-borderradius); + width: 100%; +} + +.behind-left, .behind-right { + position: absolute; + inset: 0; + display: flex; + align-items: center; + z-index: 0; +} + +.behind-right { + justify-content: flex-end; + padding-right: 14px; + background: var(--mud-palette-error); +} + +.behind-left { + justify-content: flex-start; + padding-left: 14px; + background: var(--mud-palette-info); +} + +.read-btn, .trash-btn { + color: white; + padding: 10px 15px; + cursor: pointer; +} + +.notification-card { + position: relative; + z-index: 1; + display: flex; + align-items: center; + padding: 12px; + gap: 12px; + background: var(--light-card-background); + transition: transform .2s ease; + touch-action: pan-y; + will-change: transform; + transform: translateX(0); +} + +.avatar { + min-width: 42px; + height: 42px; + border-radius: 12px; + display: grid; + place-items: center; + background: var(--mud-palette-background); + border: 1px solid var(--mud-palette-divider); + font-weight: bold; + color: var(--mud-palette-primary); +} + +.notification-body { + width: 100% +} + +.title-row { + display: flex; + gap: 8px; + justify-items: center; + align-items: flex-start; + justify-content: space-between; +} + +.unread-dot { + width: 10px; + height: 10px; + background-color: var(--mud-palette-error); + border-radius: 50%; +} + +.title { + font-weight: 700; + margin-bottom: unset !important; + line-height: normal; +} + +.section-time { + display: flex; + gap: .65rem; + min-width: max-content; + justify-content: flex-end; + align-items: center; +} + +.notification-time { + font-size: 13px; + color: #94a3b8; + line-height: normal; +} + +.collapsing { + transition: height .22s ease, margin .22s ease, opacity .22s ease; + overflow: hidden; +} + +.notification-body ::deep > .subtitle { + font-size: 12px; + color: var(--mud-palette-drawer-text); + line-height: inherit; + margin-top: .5rem; +} \ 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 8443d96..91e5bee 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ActivityForm.razor @@ -1,19 +1,22 @@ @using System.Globalization -@using System.Text.RegularExpressions @using CommunityToolkit.Mvvm.Messaging -@using Java.Util.Jar -@using salesbook.Shared.Core.Dto @using salesbook.Shared.Components.Layout -@using salesbook.Shared.Core.Entity -@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.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @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 @@ -36,18 +39,25 @@
Commessa - @if (Commesse.IsNullOrEmpty()) + @if (ActivityModel.CodJcom != null && SelectedComessa == null) { - Nessuna commessa presente + } else { - - @foreach (var com in Commesse) - { - @($"{com.CodJcom} - {com.Descrizione}") - } - + }
@@ -70,9 +80,17 @@
- Avviso + Avviso - + + Nessuno + All'ora pianificata + 30 minuti prima + 1 ora prima + 2 ore prima + 1 giorno prima + 1 settimana prima +
@@ -80,12 +98,27 @@
Assegnata a - - @foreach (var user in Users) - { - @user.FullName - } - + @if (ActivityModel.UserName != null && SelectedUser == null) + { + + } + else + { + + }
@@ -93,12 +126,19 @@
Tipo - - @foreach (var type in ActivityType) - { - @type.ActivityTypeId - } - + @if (ActivityType.IsNullOrEmpty()) + { + + } + else + { + + @foreach (var type in ActivityType) + { + @type.ActivityTypeId + } + + }
@@ -126,65 +166,68 @@ { @if (item.p.Type == AttachedDTO.TypeAttached.Position) { - + @item.p.Description } else { - + @item.p.Name } } } - + @if (ActivityFileList != null) { foreach (var file in ActivityFileList) { - + @file.FileName } }
-
- - Aggiungi allegati - - - @if (!IsNew) - { -
- - + - Duplica + Aggiungi allegati -
+ @if (!IsNew) + { +
- - Elimina - - } -
+ + Duplica + + +
+ + + Elimina + + } +
+ }
@@ -198,30 +241,6 @@ - - - - - - - - Salva - - - - - - - Vuoi creare un promemoria? - - - - Crea - - - @@ -241,15 +260,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; } @@ -262,39 +281,47 @@ //MessageBox private MudMessageBox ConfirmDelete { get; set; } - private MudMessageBox ConfirmMemo { get; set; } - private MudMessageBox AddNamePosition { get; set; } //Attached private List? AttachedList { get; set; } - private string? NamePosition { get; set; } private bool CanAddPosition { get; set; } = true; + // cache per commesse + private string? _lastLoadedCodAnag; + + //Commessa + private JtbComt? SelectedComessa { get; set; } + + //User + private StbUser? SelectedUser { get; set; } + protected override async Task OnInitializedAsync() { 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(); + } - await LoadCommesse(); + if (Id.IsNullOrEmpty()) Id = ActivityModel.ActivityId; + IsNew = Id.IsNullOrEmpty(); + LabelSave = IsNew ? "Aggiungi" : null; if (IsNew) { - ActivityModel.EstimatedTime = DateTime.Today.Add(TimeSpan.FromHours(DateTime.Now.Hour)); - ActivityModel.EstimatedEndtime = DateTime.Today.Add(TimeSpan.FromHours(DateTime.Now.Hour) + TimeSpan.FromHours(1)); + ActivityModel.EstimatedTime = DateTime.Today.AddHours(DateTime.Now.Hour); + ActivityModel.EstimatedEndtime = ActivityModel.EstimatedTime?.AddHours(1); ActivityModel.UserName = UserSession.User.Username; } + _ = LoadData(); + + await LoadActivityType(); OriginalModel = ActivityModel.Clone(); } @@ -306,153 +333,259 @@ StateHasChanged(); await SavePosition(); - var response = await IntegryApiService.SaveActivity(ActivityModel); if (response == null) return; var newActivity = response.Last(); - await ManageData.InsertOrUpdate(newActivity); - - await SaveAttached(newActivity.ActivityId); + await SaveAttached(newActivity.ActivityId!); SuccessAnimation = true; StateHasChanged(); await Task.Delay(1250); - MudDialog.Close(newActivity); } private async Task SavePosition() { - if (AttachedList != null) - { - foreach (var attached in AttachedList) - { - if (attached.Type != AttachedDTO.TypeAttached.Position) continue; + if (AttachedList is null) return; + var positionTasks = AttachedList + .Where(a => a.Type == AttachedDTO.TypeAttached.Position) + .Select(async attached => + { var position = new PositionDTO { Description = attached.Description, Lat = attached.Lat, Lng = attached.Lng }; - ActivityModel.Position = await IntegryApiService.SavePosition(position); - } - } + }); + + await Task.WhenAll(positionTasks); } private async Task SaveAttached(string activityId) { - if (AttachedList != null) - { - foreach (var attached in AttachedList) - { - if (attached.FileContent is not null && attached.Type != AttachedDTO.TypeAttached.Position) - { - await IntegryApiService.UploadFile(activityId, attached.FileContent, attached.Name); - } - } - } + if (AttachedList is null) return; + + var uploadTasks = AttachedList + .Where(a => a.FileContent is not null && a.Type != AttachedDTO.TypeAttached.Position) + .Select(a => IntegryApiService.UploadFile(activityId, a.FileBytes, a.Name)); + + await Task.WhenAll(uploadTasks); } private bool CheckPreSave() { Snackbar.Clear(); - if (!ActivityModel.ActivityTypeId.IsNullOrEmpty()) return true; - Snackbar.Add("Tipo attività obbligatorio!", Severity.Error); + Snackbar.Add("Tipo attività obbligatorio!", Severity.Error); return false; } - private async Task LoadData() + private Task LoadData() { - if (!IsNew && Id != null) + return Task.Run(async () => { - ActivityFileList = await IntegryApiService.GetActivityFile(Id); + SelectedComessa = ActivityModel.Commessa; + + Users = await ManageData.GetTable(); + if (!ActivityModel.UserName.IsNullOrEmpty()) + SelectedUser = Users.FindLast(x => x.UserName.Equals(ActivityModel.UserName)); + + if (!IsNew && Id != null) + ActivityFileList = await IntegryApiService.GetActivityFile(Id); + + ActivityResult = await ManageData.GetTable(); + Clienti = await ManageData.GetClienti(new WhereCondContact { FlagStato = "A" }); + Pros = await ManageData.GetProspect(); + + await InvokeAsync(StateHasChanged); + }); + } + + private async Task LoadActivityType() + { + if (ActivityModel.UserName is null) + { + ActivityType = []; + return; } - 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")); + ActivityType = await ManageData.GetTable(x => x.UserName != null && x.UserName.Equals(ActivityModel.UserName) + ); } - private async Task LoadCommesse() => - Commesse = await ManageData.GetTable(x => x.CodAnag != null && x.CodAnag.Equals(ActivityModel.CodAnag)); - - private async Task?> SearchCliente(string value, CancellationToken token) + private async Task LoadCommesse() { - if (string.IsNullOrEmpty(value)) - return null; + if (_lastLoadedCodAnag == ActivityModel.CodAnag) return; - var listToReturn = new List(); - - listToReturn.AddRange( - Clienti.Where(x => x.RagSoc.Contains(value, StringComparison.OrdinalIgnoreCase)).Select(x => $"{x.CodAnag} - {x.RagSoc}") - ); - - listToReturn.AddRange( - Pros.Where(x => x.RagSoc.Contains(value, StringComparison.OrdinalIgnoreCase)).Select(x => $"{x.CodPpro} - {x.RagSoc}") - ); - - return listToReturn; + Commesse = await ManageData.GetTable(x => x.CodAnag == ActivityModel.CodAnag); + Commesse = Commesse.OrderByDescending(x => x.CodJcom).ToList(); + _lastLoadedCodAnag = ActivityModel.CodAnag; } - private async Task OnClienteChanged() + private async Task> SearchCommesseAsync(string value, CancellationToken token) + { + await LoadCommesse(); + if (Commesse.IsNullOrEmpty()) return []; + + IEnumerable list; + + if (string.IsNullOrWhiteSpace(value)) + { + list = Commesse.OrderByDescending(x => x.CodJcom).Take(10); + } + else + { + list = Commesse + .Where(x => (x.CodJcom.Contains(value, StringComparison.OrdinalIgnoreCase) + || x.Descrizione.Contains(value, StringComparison.OrdinalIgnoreCase)) + && (x.CodAnag == ActivityModel.CodAnag || ActivityModel.CodAnag == null)) + .OrderByDescending(x => x.CodJcom) + .Take(50); + } + + return token.IsCancellationRequested ? [] : list; + } + + private Task> SearchUtentiAsync(string value, CancellationToken token) + { + IEnumerable list; + + if (string.IsNullOrWhiteSpace(value)) + { + list = Users.OrderBy(u => u.FullName).Take(50); + } + else + { + list = Users + .Where(x => x.UserName.Contains(value, StringComparison.OrdinalIgnoreCase) + || x.FullName.Contains(value, StringComparison.OrdinalIgnoreCase)) + .OrderBy(u => u.FullName) + .Take(50); + } + + return Task.FromResult(token.IsCancellationRequested ? [] : list); + } + + private Task?> SearchCliente(string value, CancellationToken token) + { + if (string.IsNullOrWhiteSpace(value)) + return Task.FromResult?>(null); + + var results = new List(); + + results.AddRange(Clienti + .Where(x => x.RagSoc.Contains(value, StringComparison.OrdinalIgnoreCase)) + .Select(x => $"{x.CodAnag} - {x.RagSoc}")); + + results.AddRange(Pros + .Where(x => x.RagSoc.Contains(value, StringComparison.OrdinalIgnoreCase)) + .Select(x => $"{x.CodPpro} - {x.RagSoc}")); + + return Task.FromResult?>(results); + } + + private Task OnClienteChanged() { ActivityModel.CodJcom = null; + if (string.IsNullOrWhiteSpace(ActivityModel.Cliente)) return Task.CompletedTask; - if (ActivityModel.Cliente.IsNullOrEmpty()) return; + var parts = ActivityModel.Cliente.Split('-', 2, StringSplitOptions.TrimEntries); + if (parts.Length == 2) + { + ActivityModel.CodAnag = parts[0]; + ActivityModel.Cliente = parts[1]; + } - var match = Regex.Match(ActivityModel.Cliente!, @"^\s*(\S+)\s*-\s*(.*)$"); - if (!match.Success) - return; + OnAfterChangeValue(); + return Task.CompletedTask; + } - ActivityModel.CodAnag = match.Groups[1].Value; - ActivityModel.Cliente = match.Groups[2].Value; + private async Task OnCommessaSelectedAfter() + { + var com = SelectedComessa; + if (com != null) + { + ActivityModel.CodJcom = com.CodJcom; + ActivityModel.Commessa = com; + } + else + { + ActivityModel.CodJcom = null; + ActivityModel.Commessa = null; + } - await LoadCommesse(); OnAfterChangeValue(); } - private async Task OnCommessaChanged() + private async Task OnUserSelectedAfter() { - ActivityModel.Commessa = (await ManageData.GetTable(x => x.CodJcom.Equals(ActivityModel.CodJcom))).Last().Descrizione; + ActivityModel.UserName = SelectedUser?.UserName; + await LoadActivityType(); + OnAfterChangeValue(); + } + + private async Task OnCommessaClear() + { + ActivityModel.Commessa = null; + ActivityModel.CodJcom = null; + StateHasChanged(); + } + + private async Task OnUserClear() + { + ActivityModel.UserName = null; + ActivityType = []; + StateHasChanged(); + } + + private void OnAfterChangeTimeBefore() + { + if (ActivityModel.EstimatedTime is not null) + { + ActivityModel.NotificationDate = ActivityModel.MinuteBefore switch + { + -1 => null, + 0 => ActivityModel.EstimatedTime, + _ => ActivityModel.EstimatedTime.Value.AddMinutes(ActivityModel.MinuteBefore * -1) + }; + } + OnAfterChangeValue(); } private void OnAfterChangeValue() { if (!IsNew) - { LabelSave = !OriginalModel.Equals(ActivityModel) ? "Aggiorna" : null; - } - if (ActivityModel.EstimatedEndtime <= ActivityModel.EstimatedTime) + if (ActivityModel.EstimatedTime is not null && + (ActivityModel.EstimatedEndtime is null || ActivityModel.EstimatedEndtime <= ActivityModel.EstimatedTime)) { ActivityModel.EstimatedEndtime = ActivityModel.EstimatedTime.Value.AddHours(1); } + + StateHasChanged(); } - private async Task OnAfterChangeEsito() + private Task OnAfterChangeEsito() { OnAfterChangeValue(); - - // var result = await ConfirmMemo.ShowAsync(); - - // if (result is true) - // OpenAddMemo = !OpenAddMemo; + return Task.CompletedTask; } private void OpenSelectEsito() { + if (IsView) return; + if (!IsNew && (ActivityModel.UserName is null || !ActivityModel.UserName.Equals(UserSession.User.Username))) { Snackbar.Add("Non puoi inserire un esito per un'attività che non ti è stata assegnata.", Severity.Info); @@ -511,16 +644,8 @@ var attached = (AttachedDTO)result.Data; if (attached.Type == AttachedDTO.TypeAttached.Position) - { CanAddPosition = false; - var resultNamePosition = await AddNamePosition.ShowAsync(); - - if (resultNamePosition is true) - attached.Description = NamePosition; - attached.Name = NamePosition!; - } - AttachedList ??= []; AttachedList.Add(attached); @@ -542,4 +667,51 @@ 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 }) + { + await AttachedService.OpenFile(attached.FileContent!, attached.Name); + } + else + { + Snackbar.Clear(); + Snackbar.Add("Impossibile aprire il file", Severity.Error); + } + } + + private void OpenPosition(AttachedDTO attached) + { + if (attached is { Lat: not null, Lng: not null }) + { + const string baseUrl = "https://www.google.it/maps/place/"; + NavigationManager.NavigateTo( + $"{baseUrl}{AdjustCoordinate(attached.Lat.Value)},{AdjustCoordinate(attached.Lng.Value)}" + ); + } + else + { + Snackbar.Clear(); + Snackbar.Add("Impossibile aprire la posizione", Severity.Error); + } + } + + private static string AdjustCoordinate(double coordinate) => + coordinate.ToString(CultureInfo.InvariantCulture).Replace(",", "."); + } \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor b/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor index 16f5e69..2a27c74 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor @@ -4,27 +4,58 @@ @using salesbook.Shared.Components.Layout.Overlay @inject IAttachedService AttachedService - + -
- - - - - @if (CanAddPosition) + @if (RequireNewName) + { + + } + else + { + @if (!SelectTypePicture) { - +
+ + + + + @if (CanAddPosition) + { + + } +
} -
+ else + { +
+ + + +
+ } + }
+ + @if (RequireNewName) + { + + Salva + + } +
@@ -40,34 +71,47 @@ private AttachedDTO? Attached { get; set; } + private bool RequireNewName { get; set; } + private bool SelectTypePicture { get; set; } + + private string? _newName; + private string? NewName + { + get => _newName; + set + { + _newName = value; + StateHasChanged(); + } + } + protected override async Task OnInitializedAsync() { + SelectTypePicture = false; + RequireNewName = false; Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; - - _ = LoadData(); - } - - private async Task LoadData() - { - } - - private async Task Save() - { - VisibleOverlay = true; - StateHasChanged(); - - SuccessAnimation = true; - StateHasChanged(); - - await Task.Delay(1250); - - MudDialog.Close(); } private async Task OnImage() { - Attached = await AttachedService.SelectImage(); - MudDialog.Close(Attached); + SelectTypePicture = true; + StateHasChanged(); + } + + private async Task OnCamera() + { + Attached = await AttachedService.SelectImageFromCamera(); + + RequireNewName = true; + StateHasChanged(); + } + + private async Task OnGallery() + { + Attached = await AttachedService.SelectImageFromGallery(); + + RequireNewName = true; + StateHasChanged(); } private async Task OnFile() @@ -79,6 +123,37 @@ private async Task OnPosition() { Attached = await AttachedService.SelectPosition(); + + RequireNewName = true; + StateHasChanged(); + } + + private void OnNewName() + { + switch (Attached.Type) + { + case AttachedDTO.TypeAttached.Position: + { + CanAddPosition = false; + + Attached.Description = NewName!; + Attached.Name = NewName!; + + break; + } + case AttachedDTO.TypeAttached.Image: + { + var extension = Path.GetExtension(Attached.Name); + Attached.Name = NewName! + extension; + + break; + } + case AttachedDTO.TypeAttached.Document: + break; + default: + throw new ArgumentOutOfRangeException(); + } + MudDialog.Close(Attached); } diff --git a/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor.css b/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor.css index 6eeaddd..9e4e250 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor.css +++ b/salesbook.Shared/Components/SingleElements/Modal/AddAttached.razor.css @@ -1,7 +1,9 @@ .content.attached { display: flex; - flex-direction: column; gap: 2rem; padding: 1rem; height: unset; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; } diff --git a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor index 7985f88..1eb9ca1 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/ContactForm.razor @@ -4,6 +4,9 @@ @using salesbook.Shared.Components.Layout.Overlay @using salesbook.Shared.Core.Entity @using salesbook.Shared.Components.SingleElements.BottomSheet +@using salesbook.Shared.Core.Dto.Contact +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @inject IManageDataService ManageData @inject INetworkService NetworkService @inject IIntegryApiService IntegryApiService @@ -83,20 +86,19 @@
Tipo cliente - - @if (VtbTipi.IsNullOrEmpty()) - { - Nessun tipo cliente trovato - } - else - { - - @foreach (var tipo in VtbTipi) - { - @($"{tipo.CodVtip} - {tipo.Descrizione}") - } - - } + +
@@ -238,15 +240,19 @@
Agente - - - @foreach (var user in Users) - { - @($"{user.UserCode} - {user.FullName}") - } - + +
@@ -265,9 +271,9 @@
} -
- @if (IsNew) - { + @if (IsNew) + { +
Persona di riferimento - } - else +
+ } + else + { + @if (NetworkService.ConnectionAvailable && !ContactModel.IsContact) { - @if (NetworkService.IsNetworkAvailable() && !ContactModel.IsContact) - { +
Converti in cliente - } +
} -
+ }
@@ -331,7 +339,7 @@ private List Users { get; set; } = []; private bool IsNew => OriginalModel is null; - private bool IsView => !NetworkService.IsNetworkAvailable(); + private bool IsView => !NetworkService.ConnectionAvailable; private string? LabelSave { get; set; } @@ -348,11 +356,17 @@ private bool OpenSearchAddress { get; set; } private IndirizzoDTO Address { get; set; } + //Agente + private StbUser? SelectedUser { get; set; } + + //Type + private VtbTipi? SelectedType { get; set; } + protected override async Task OnInitializedAsync() { Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; - await LoadData(); + _ = LoadData(); LabelSave = IsNew ? "Aggiungi" : null; } @@ -400,24 +414,27 @@ MudDialog.Close(response); } - private async Task LoadData() + private Task LoadData() { - if (IsNew) + return Task.Run(async () => { - var loggedUser = (await ManageData.GetTable(x => x.UserName.Equals(UserSession.User.Username))).Last(); + if (IsNew) + { + var loggedUser = (await ManageData.GetTable(x => x.UserName.Equals(UserSession.User.Username))).Last(); - ContactModel.IsContact = false; - ContactModel.Nazione = "IT"; - ContactModel.CodVage = loggedUser.UserCode; - } - else - { - ContactModel = OriginalModel!.Clone(); - } + ContactModel.IsContact = false; + ContactModel.Nazione = "IT"; + ContactModel.CodVage = loggedUser.UserCode; + } + else + { + ContactModel = OriginalModel!.Clone(); + } - Users = await ManageData.GetTable(x => x.KeyGroup == 5); - Nazioni = await ManageData.GetTable(); - VtbTipi = await ManageData.GetTable(); + Users = await ManageData.GetTable(x => x.KeyGroup == 5); + Nazioni = await ManageData.GetTable(); + VtbTipi = await ManageData.GetTable(); + }); } private void OnAfterChangeValue() @@ -473,11 +490,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) { @@ -559,6 +576,83 @@ private async Task ConvertProspectToContact() { + VisibleOverlay = true; + StateHasChanged(); + var response = await IntegryApiService.TransferProspect(new CRMTransferProspectRequestDTO + { + CodPpro = ContactModel.CodContact + }); + + await ManageData.DeleteProspect(ContactModel.CodContact); + + if (response.AnagClie != null) + await ManageData.InsertOrUpdate(response.AnagClie); + + if (response.VtbCliePersRif != null) + await ManageData.InsertOrUpdate(response.VtbCliePersRif); + + if (response.VtbDest != null) + await ManageData.InsertOrUpdate(response.VtbDest); + + SuccessAnimation = true; + StateHasChanged(); + + await Task.Delay(1250); + MudDialog.Close(response.AnagClie); + } + + private async Task OnUserSelectedAfter() + { + ContactModel.CodVage = SelectedUser?.UserCode; + OnAfterChangeValue(); + } + + private Task> SearchUtentiAsync(string value, CancellationToken token) + { + IEnumerable list; + + if (string.IsNullOrWhiteSpace(value)) + { + list = Users.OrderBy(u => u.FullName).Take(50); + } + else + { + list = Users + .Where(x => x.UserName.Contains(value, StringComparison.OrdinalIgnoreCase) + || x.FullName.Contains(value, StringComparison.OrdinalIgnoreCase)) + .OrderBy(u => u.FullName) + .Take(50); + } + + return Task.FromResult(token.IsCancellationRequested ? [] : list); + } + + private async Task OnTypeSelectedAfter() + { + ContactModel.CodVtip = SelectedType?.CodVtip; + OnAfterChangeValue(); + } + + private Task> SearchTypeAsync(string value, CancellationToken token) + { + IEnumerable list = []; + + if (VtbTipi == null) return Task.FromResult(token.IsCancellationRequested ? [] : list); + + if (string.IsNullOrWhiteSpace(value)) + { + list = VtbTipi.OrderBy(u => u.CodVtip); + } + else + { + list = VtbTipi + .Where(x => x.CodVtip.Contains(value, StringComparison.OrdinalIgnoreCase) + || x.Descrizione.Contains(value, StringComparison.OrdinalIgnoreCase)) + .OrderBy(u => u.CodVtip) + .Take(50); + } + + return Task.FromResult(token.IsCancellationRequested ? [] : list); } } \ No newline at end of file diff --git a/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor b/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor index b95a1ac..c3813b2 100644 --- a/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor +++ b/salesbook.Shared/Components/SingleElements/Modal/PersRifForm.razor @@ -2,6 +2,8 @@ @using salesbook.Shared.Components.Layout @using salesbook.Shared.Core.Interface @using salesbook.Shared.Components.Layout.Overlay +@using salesbook.Shared.Core.Interface.IntegryApi +@using salesbook.Shared.Core.Interface.System.Network @inject IManageDataService ManageData @inject INetworkService NetworkService @inject IIntegryApiService IntegryApiService @@ -118,7 +120,7 @@ private PersRifDTO PersRifModel { get; set; } = new(); private bool IsNew => OriginalModel is null; - private bool IsView => !NetworkService.IsNetworkAvailable(); + private bool IsView => !NetworkService.ConnectionAvailable; private string? LabelSave { get; set; } diff --git a/salesbook.Shared/Core/Dto/ActivityDTO.cs b/salesbook.Shared/Core/Dto/Activity/ActivityDTO.cs similarity index 89% rename from salesbook.Shared/Core/Dto/ActivityDTO.cs rename to salesbook.Shared/Core/Dto/Activity/ActivityDTO.cs index bd5f262..f96e682 100644 --- a/salesbook.Shared/Core/Dto/ActivityDTO.cs +++ b/salesbook.Shared/Core/Dto/Activity/ActivityDTO.cs @@ -1,15 +1,21 @@ -using salesbook.Shared.Core.Entity; +using System.Text.Json.Serialization; +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 { - public string? Commessa { get; set; } + public JtbComt? Commessa { get; set; } public string? Cliente { get; set; } public ActivityCategoryEnum Category { get; set; } public bool Complete { get; set; } + //Notification + public int MinuteBefore { get; set; } = -1; + [JsonPropertyName("notificationDate")] + public DateTime? NotificationDate { get; set; } + public bool Deleted { get; set; } public PositionDTO? Position { get; set; } @@ -23,6 +29,9 @@ public class ActivityDTO : StbActivity { return Commessa == other.Commessa && Cliente == other.Cliente && + Position == other.Position && + MinuteBefore == other.MinuteBefore && + NotificationDate == other.NotificationDate && Category == other.Category && Complete == other.Complete && ActivityId == other.ActivityId && ActivityResultId == other.ActivityResultId && ActivityTypeId == other.ActivityTypeId && DataInsAct.Equals(other.DataInsAct) && ActivityDescription == other.ActivityDescription && ParentActivityId == other.ParentActivityId && TipoAnag == other.TipoAnag && CodAnag == other.CodAnag && CodJcom == other.CodJcom && CodJfas == other.CodJfas && Nullable.Equals(EstimatedDate, other.EstimatedDate) && Nullable.Equals(EstimatedTime, other.EstimatedTime) && Nullable.Equals(AlarmDate, other.AlarmDate) && Nullable.Equals(AlarmTime, other.AlarmTime) && Nullable.Equals(EffectiveDate, other.EffectiveDate) && Nullable.Equals(EffectiveTime, other.EffectiveTime) && ResultDescription == other.ResultDescription && Nullable.Equals(EstimatedEnddate, other.EstimatedEnddate) && Nullable.Equals(EstimatedEndtime, other.EstimatedEndtime) && Nullable.Equals(EffectiveEnddate, other.EffectiveEnddate) && Nullable.Equals(EffectiveEndtime, other.EffectiveEndtime) && UserCreator == other.UserCreator && UserName == other.UserName && Nullable.Equals(PercComp, other.PercComp) && Nullable.Equals(EstimatedHours, other.EstimatedHours) && CodMart == other.CodMart && PartitaMag == other.PartitaMag && Matricola == other.Matricola && Priorita == other.Priorita && Nullable.Equals(ActivityPlayCounter, other.ActivityPlayCounter) && ActivityEvent == other.ActivityEvent && Guarantee == other.Guarantee && Note == other.Note && Rfid == other.Rfid && IdLotto == other.IdLotto && PersonaRif == other.PersonaRif && HrNum == other.HrNum && Gestione == other.Gestione && Nullable.Equals(DataOrd, other.DataOrd) && NumOrd == other.NumOrd && IdStep == other.IdStep && IdRiga == other.IdRiga && Nullable.Equals(OraInsAct, other.OraInsAct) && IndiceGradimento == other.IndiceGradimento && NoteGradimento == other.NoteGradimento && FlagRisolto == other.FlagRisolto && FlagTipologia == other.FlagTipologia && OreRapportino == other.OreRapportino && UserModifier == other.UserModifier && Nullable.Equals(OraModAct, other.OraModAct) && Nullable.Equals(OraViewAct, other.OraViewAct) && CodVdes == other.CodVdes && CodCmac == other.CodCmac && WrikeId == other.WrikeId && CodMgrp == other.CodMgrp && PlanId == other.PlanId; } @@ -97,6 +106,9 @@ public class ActivityDTO : StbActivity hashCode.Add(Cliente); hashCode.Add(Category); hashCode.Add(Complete); + hashCode.Add(Position); + hashCode.Add(MinuteBefore); + hashCode.Add(NotificationDate); return hashCode.ToHashCode(); } } \ No newline at end of file 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/CRMTransferProspectResponseDTO.cs b/salesbook.Shared/Core/Dto/CRMTransferProspectResponseDTO.cs index 863124c..7ce0db3 100644 --- a/salesbook.Shared/Core/Dto/CRMTransferProspectResponseDTO.cs +++ b/salesbook.Shared/Core/Dto/CRMTransferProspectResponseDTO.cs @@ -7,4 +7,10 @@ public class CRMTransferProspectResponseDTO { [JsonPropertyName("anagClie")] public AnagClie? AnagClie { get; set; } + + [JsonPropertyName("vtbDest")] + public List? VtbDest { get; set; } + + [JsonPropertyName("vtbCliePersRif")] + public List? VtbCliePersRif { 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/NotificationDataDTO.cs b/salesbook.Shared/Core/Dto/NotificationDataDTO.cs new file mode 100644 index 0000000..4379125 --- /dev/null +++ b/salesbook.Shared/Core/Dto/NotificationDataDTO.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto; + +public class NotificationDataDTO +{ + [JsonPropertyName("notificationId")] + public string? NotificationId { get; set; } + + [JsonPropertyName("activityId")] + public string? ActivityId { get; set; } + + [JsonPropertyName("type")] + public string? Type { 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/NotificationState.cs b/salesbook.Shared/Core/Dto/PageState/NotificationState.cs new file mode 100644 index 0000000..79c61b4 --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/NotificationState.cs @@ -0,0 +1,12 @@ +using salesbook.Shared.Core.Entity; + +namespace salesbook.Shared.Core.Dto.PageState; + +public class NotificationState +{ + public List ReceivedNotifications { get; set; } = []; + public List UnreadNotifications { get; set; } = []; + public List NotificationsRead { get; set; } = []; + + public int Count => ReceivedNotifications.Count() + UnreadNotifications.Count(); +} \ 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..7088d4d --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/UserListState.cs @@ -0,0 +1,23 @@ +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 List? AllUsers { 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..f211abf --- /dev/null +++ b/salesbook.Shared/Core/Dto/PageState/UserPageState.cs @@ -0,0 +1,19 @@ +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; } + + public int ActiveTab { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Dto/ReadNotificationRequestDTO.cs b/salesbook.Shared/Core/Dto/ReadNotificationRequestDTO.cs new file mode 100644 index 0000000..d8b448e --- /dev/null +++ b/salesbook.Shared/Core/Dto/ReadNotificationRequestDTO.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Dto; + +public class ReadNotificationRequestDTO +{ + [JsonPropertyName("deviceToken")] + public string DeviceToken { get; set; } + + [JsonPropertyName("username")] + public string Username { get; set; } + + [JsonPropertyName("notificationId")] + public long NotificationId { 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/Entity/WtbDeviceNotification.cs b/salesbook.Shared/Core/Entity/WtbDeviceNotification.cs new file mode 100644 index 0000000..0d0b375 --- /dev/null +++ b/salesbook.Shared/Core/Entity/WtbDeviceNotification.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace salesbook.Shared.Core.Entity; + +public class WtbDeviceNotification +{ + [JsonPropertyName("userDeviceId")] + public long? UserDeviceId { get; set; } + + [JsonPropertyName("notificationId")] + public long? NotificationId { get; set; } + + [JsonPropertyName("readDate")] + public DateTime? ReadDate { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Entity/WtbNotification.cs b/salesbook.Shared/Core/Entity/WtbNotification.cs new file mode 100644 index 0000000..d4ed855 --- /dev/null +++ b/salesbook.Shared/Core/Entity/WtbNotification.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; +using salesbook.Shared.Core.Dto; + +namespace salesbook.Shared.Core.Entity; + +public class WtbNotification +{ + [JsonPropertyName("id")] + public long Id { get; set; } + + [JsonPropertyName("title")] + public string? Title { get; set; } + + [JsonPropertyName("body")] + public string? Body { get; set; } + + [JsonPropertyName("imageUrl")] + public string? ImageUrl { get; set; } + + [JsonPropertyName("notificationData")] + public NotificationDataDTO? NotificationData { get; set; } + + [JsonPropertyName("startDate")] + public DateTime? StartDate { get; set; } + + [JsonPropertyName("endDate")] + public DateTime? EndDate { get; set; } + + [JsonPropertyName("persistent")] + public bool? Persistent { get; set; } + + [JsonPropertyName("topics")] + public List? Topics { get; set; } + + [JsonPropertyName("wtbDeviceNotifications")] + public List? WtbDeviceNotifications { get; set; } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Helpers/IconConstants.cs b/salesbook.Shared/Core/Helpers/IconConstants.cs index 23df9cc..c7b6e97 100644 --- a/salesbook.Shared/Core/Helpers/IconConstants.cs +++ b/salesbook.Shared/Core/Helpers/IconConstants.cs @@ -7,5 +7,7 @@ class IconConstants public const string Stato = "ri-list-check-3 fa-fw fa-chip"; public const string User = "ri-user-fill fa-fw fa-chip"; public const string Time = "ri-time-line fa-fw fa-chip"; + public const string FileTextLine = "ri-file-text-line fa-fw fa-chip"; + public const string Tag = "ri-price-tag-3-fill fa-fw fa-chip"; } } \ No newline at end of file diff --git a/salesbook.Shared/Core/Helpers/MappingProfile.cs b/salesbook.Shared/Core/Helpers/MappingProfile.cs index 8ac3142..d967823 100644 --- a/salesbook.Shared/Core/Helpers/MappingProfile.cs +++ b/salesbook.Shared/Core/Helpers/MappingProfile.cs @@ -1,5 +1,6 @@ using AutoMapper; using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Dto.Activity; using salesbook.Shared.Core.Entity; namespace salesbook.Shared.Core.Helpers; diff --git a/salesbook.Shared/Core/Helpers/ModalHelpers.cs b/salesbook.Shared/Core/Helpers/ModalHelpers.cs index fa199f6..74da67e 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; @@ -79,7 +80,8 @@ public class ModalHelpers { FullScreen = false, CloseButton = false, - NoHeader = true + NoHeader = true, + BackdropClick = false } ); diff --git a/salesbook.Shared/Core/Interface/IAttachedService.cs b/salesbook.Shared/Core/Interface/IAttachedService.cs index 6897b19..bc267bc 100644 --- a/salesbook.Shared/Core/Interface/IAttachedService.cs +++ b/salesbook.Shared/Core/Interface/IAttachedService.cs @@ -4,7 +4,9 @@ namespace salesbook.Shared.Core.Interface; public interface IAttachedService { - Task SelectImage(); + Task SelectImageFromCamera(); + Task SelectImageFromGallery(); Task SelectFile(); Task SelectPosition(); + Task OpenFile(Stream file, string fileName); } \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs b/salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs new file mode 100644 index 0000000..47c6707 --- /dev/null +++ b/salesbook.Shared/Core/Interface/IFirebaseNotificationService.cs @@ -0,0 +1,6 @@ +namespace salesbook.Shared.Core.Interface; + +public interface IFirebaseNotificationService +{ + Task InitFirebase(); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IManageDataService.cs b/salesbook.Shared/Core/Interface/IManageDataService.cs index 0729793..2fbcf4d 100644 --- a/salesbook.Shared/Core/Interface/IManageDataService.cs +++ b/salesbook.Shared/Core/Interface/IManageDataService.cs @@ -1,22 +1,31 @@ -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, DateTime? lastSync = null); Task GetSpecificContact(string codAnag, bool IsContact); + Task> GetActivityTryLocalDb(WhereCondActivity whereCond); + Task> GetActivity(WhereCondActivity whereCond, bool useLocalDb = false); + Task InsertOrUpdate(T objectToSave); Task InsertOrUpdate(List listToSave); + Task DeleteProspect(string codPpro); Task Delete(T objectToDelete); Task DeleteActivity(ActivityDTO activity); + Task> MapActivity(List? activities); + Task ClearDb(); } \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/INetworkService.cs b/salesbook.Shared/Core/Interface/INetworkService.cs deleted file mode 100644 index d397c46..0000000 --- a/salesbook.Shared/Core/Interface/INetworkService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace salesbook.Shared.Core.Interface; - -public interface INetworkService -{ - public bool IsNetworkAvailable(); -} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/INotificationService.cs b/salesbook.Shared/Core/Interface/INotificationService.cs new file mode 100644 index 0000000..23c847a --- /dev/null +++ b/salesbook.Shared/Core/Interface/INotificationService.cs @@ -0,0 +1,7 @@ +namespace salesbook.Shared.Core.Interface; + +public interface INotificationService +{ + Task LoadNotification(); + void OrderNotificationList(); +} \ 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/Interface/IIntegryApiService.cs b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs similarity index 62% rename from salesbook.Shared/Core/Interface/IIntegryApiService.cs rename to salesbook.Shared/Core/Interface/IntegryApi/IIntegryApiService.cs index 3635223..1cd51af 100644 --- a/salesbook.Shared/Core/Interface/IIntegryApiService.cs +++ b/salesbook.Shared/Core/Interface/IntegryApi/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; +namespace salesbook.Shared.Core.Interface.IntegryApi; 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/IntegryApi/IIntegryNotificationRestClient.cs b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryNotificationRestClient.cs new file mode 100644 index 0000000..19c8573 --- /dev/null +++ b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryNotificationRestClient.cs @@ -0,0 +1,11 @@ +using salesbook.Shared.Core.Entity; + +namespace salesbook.Shared.Core.Interface.IntegryApi; + +public interface IIntegryNotificationRestClient +{ + Task?> Get(); + Task MarkAsRead(long id); + Task Delete(long id); + Task DeleteAll(); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/IntegryApi/IIntegryRegisterNotificationRestClient.cs b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryRegisterNotificationRestClient.cs new file mode 100644 index 0000000..e62e891 --- /dev/null +++ b/salesbook.Shared/Core/Interface/IntegryApi/IIntegryRegisterNotificationRestClient.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.Logging; + +namespace salesbook.Shared.Core.Interface.IntegryApi; + +public interface IIntegryRegisterNotificationRestClient +{ + Task Register(string fcmToken, ILogger? logger1 = null); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs b/salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs new file mode 100644 index 0000000..179f1ac --- /dev/null +++ b/salesbook.Shared/Core/Interface/System/Battery/IBatteryOptimizationManagerService.cs @@ -0,0 +1,8 @@ +namespace salesbook.Shared.Core.Interface.System.Battery; + +public interface IBatteryOptimizationManagerService +{ + bool IsBatteryOptimizationEnabled(); + + void OpenBatteryOptimizationSettings(Action onCompleted); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/System/Network/INetworkService.cs b/salesbook.Shared/Core/Interface/System/Network/INetworkService.cs new file mode 100644 index 0000000..43b6acc --- /dev/null +++ b/salesbook.Shared/Core/Interface/System/Network/INetworkService.cs @@ -0,0 +1,8 @@ +namespace salesbook.Shared.Core.Interface.System.Network; + +public interface INetworkService +{ + public bool ConnectionAvailable { get; set; } + + public bool IsNetworkAvailable(); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs b/salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs new file mode 100644 index 0000000..ec8454b --- /dev/null +++ b/salesbook.Shared/Core/Interface/System/Notification/IShinyNotificationManager.cs @@ -0,0 +1,6 @@ +namespace salesbook.Shared.Core.Interface.System.Notification; + +public interface IShinyNotificationManager +{ + Task RequestAccess(); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/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/Messages/Notification/Loaded/NotificationsLoadedMessage.cs b/salesbook.Shared/Core/Messages/Notification/Loaded/NotificationsLoadedMessage.cs new file mode 100644 index 0000000..750a3d3 --- /dev/null +++ b/salesbook.Shared/Core/Messages/Notification/Loaded/NotificationsLoadedMessage.cs @@ -0,0 +1,5 @@ +using CommunityToolkit.Mvvm.Messaging.Messages; + +namespace salesbook.Shared.Core.Messages.Notification.Loaded; + +public class NotificationsLoadedMessage(object? value = null) : ValueChangedMessage(value); \ No newline at end of file diff --git a/salesbook.Shared/Core/Messages/Notification/Loaded/NotificationsLoadedService.cs b/salesbook.Shared/Core/Messages/Notification/Loaded/NotificationsLoadedService.cs new file mode 100644 index 0000000..985cf46 --- /dev/null +++ b/salesbook.Shared/Core/Messages/Notification/Loaded/NotificationsLoadedService.cs @@ -0,0 +1,13 @@ +using CommunityToolkit.Mvvm.Messaging; + +namespace salesbook.Shared.Core.Messages.Notification.Loaded; + +public class NotificationsLoadedService +{ + public event Action? OnNotificationsLoaded; + + public NotificationsLoadedService(IMessenger messenger) + { + messenger.Register(this, (_, _) => { OnNotificationsLoaded?.Invoke(); }); + } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Messages/Notification/NewPush/NewPushNotificationMessage.cs b/salesbook.Shared/Core/Messages/Notification/NewPush/NewPushNotificationMessage.cs new file mode 100644 index 0000000..634a1f1 --- /dev/null +++ b/salesbook.Shared/Core/Messages/Notification/NewPush/NewPushNotificationMessage.cs @@ -0,0 +1,6 @@ +using CommunityToolkit.Mvvm.Messaging.Messages; +using salesbook.Shared.Core.Entity; + +namespace salesbook.Shared.Core.Messages.Notification.NewPush; + +public class NewPushNotificationMessage(WtbNotification value) : ValueChangedMessage(value); \ No newline at end of file diff --git a/salesbook.Shared/Core/Messages/Notification/NewPush/NewPushNotificationService.cs b/salesbook.Shared/Core/Messages/Notification/NewPush/NewPushNotificationService.cs new file mode 100644 index 0000000..c276873 --- /dev/null +++ b/salesbook.Shared/Core/Messages/Notification/NewPush/NewPushNotificationService.cs @@ -0,0 +1,14 @@ +using CommunityToolkit.Mvvm.Messaging; +using salesbook.Shared.Core.Entity; + +namespace salesbook.Shared.Core.Messages.Notification.NewPush; + +public class NewPushNotificationService +{ + public event Action? OnNotificationReceived; + + public NewPushNotificationService(IMessenger messenger) + { + messenger.Register(this, (_, o) => { OnNotificationReceived?.Invoke(o.Value); }); + } +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Services/IntegryApiService.cs b/salesbook.Shared/Core/Services/IntegryApiService.cs index 5cc6714..4a3d943 100644 --- a/salesbook.Shared/Core/Services/IntegryApiService.cs +++ b/salesbook.Shared/Core/Services/IntegryApiService.cs @@ -1,21 +1,34 @@ 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; +using salesbook.Shared.Core.Interface.IntegryApi; 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 +42,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 +143,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 +163,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/IntegryNotificationRestClient.cs b/salesbook.Shared/Core/Services/IntegryNotificationRestClient.cs new file mode 100644 index 0000000..a415bfc --- /dev/null +++ b/salesbook.Shared/Core/Services/IntegryNotificationRestClient.cs @@ -0,0 +1,33 @@ +using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account; +using IntegryApiClient.Core.Domain.RestClient.Contacts; +using salesbook.Shared.Core.Dto; +using salesbook.Shared.Core.Entity; +using salesbook.Shared.Core.Interface.IntegryApi; + +namespace salesbook.Shared.Core.Services; + +public class IntegryNotificationRestClient( + IUserSession userSession, + IIntegryApiRestClient integryApiRestClient) : IIntegryNotificationRestClient +{ + public Task?> Get() + { + var queryParams = new Dictionary + { + { "mode", "ENABLED" }, + { "forUser", userSession.User.Username } + }; + + return integryApiRestClient.Get>("notification", queryParams); + } + + public Task MarkAsRead(long id) => + integryApiRestClient.Post("notification/read", + new ReadNotificationRequestDTO { NotificationId = id, Username = userSession.User.Username })!; + + public Task Delete(long id) => + integryApiRestClient.Delete($"notification/{id}", null); + + public Task DeleteAll() => + integryApiRestClient.Delete($"notification/all/{userSession.User.Username}", null); +} \ No newline at end of file diff --git a/salesbook.Shared/Core/Services/NotificationService.cs b/salesbook.Shared/Core/Services/NotificationService.cs new file mode 100644 index 0000000..6d650b2 --- /dev/null +++ b/salesbook.Shared/Core/Services/NotificationService.cs @@ -0,0 +1,47 @@ +using salesbook.Shared.Core.Dto.PageState; +using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; + +namespace salesbook.Shared.Core.Services; + +public class NotificationService( + IIntegryNotificationRestClient integryNotificationRestClient, + NotificationState Notification +) : INotificationService +{ + public async Task LoadNotification() + { + var allNotifications = await integryNotificationRestClient.Get(); + if (allNotifications == null) return; + + var allIds = allNotifications.Select(n => n.Id).ToHashSet(); + + Notification.ReceivedNotifications = Notification.ReceivedNotifications + .Where(r => !allIds.Contains(r.Id)) + .ToList(); + + Notification.UnreadNotifications = allNotifications + .Where(x => + x.WtbDeviceNotifications == null || + x.WtbDeviceNotifications.Any(y => y.ReadDate == null)) + .ToList(); + + Notification.NotificationsRead = allNotifications + .Where(x => + x.WtbDeviceNotifications != null && + x.WtbDeviceNotifications.All(y => y.ReadDate != null)) + .ToList(); + } + + public void OrderNotificationList() + { + Notification.ReceivedNotifications = Notification.ReceivedNotifications + .OrderByDescending(x => x.StartDate).ToList(); + + Notification.UnreadNotifications = Notification.UnreadNotifications + .OrderByDescending(x => x.StartDate).ToList(); + + Notification.NotificationsRead = Notification.NotificationsRead + .OrderByDescending(x => x.StartDate).ToList(); + } +} \ 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..1915d5e --- /dev/null +++ b/salesbook.Shared/Core/Services/PreloadService.cs @@ -0,0 +1,56 @@ +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.AllUsers = users; + + 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/Core/Utility/UtilityString.cs b/salesbook.Shared/Core/Utility/UtilityString.cs index 1951677..e97a45c 100644 --- a/salesbook.Shared/Core/Utility/UtilityString.cs +++ b/salesbook.Shared/Core/Utility/UtilityString.cs @@ -4,8 +4,10 @@ namespace salesbook.Shared.Core.Utility; public static class UtilityString { - public static string ExtractInitials(string fullname) + public static string ExtractInitials(string? fullname) { + if (fullname == null) return ""; + return string.Concat(fullname .Split(' ', StringSplitOptions.RemoveEmptyEntries) .Take(3) diff --git a/salesbook.Shared/salesbook.Shared.csproj b/salesbook.Shared/salesbook.Shared.csproj index b9d0f7f..cc2dadb 100644 --- a/salesbook.Shared/salesbook.Shared.csproj +++ b/salesbook.Shared/salesbook.Shared.csproj @@ -24,13 +24,13 @@ - - - + + + - - - + + + diff --git a/salesbook.Shared/wwwroot/css/app.css b/salesbook.Shared/wwwroot/css/app.css index e224251..c5490f8 100644 --- a/salesbook.Shared/wwwroot/css/app.css +++ b/salesbook.Shared/wwwroot/css/app.css @@ -1,6 +1,6 @@ html { overflow: hidden; } -.page, article, main { height: 100% !important; } +.page, article, main { height: 100% !important; overflow: hidden; } #app { height: 100vh; } @@ -22,6 +22,47 @@ a, .btn-link { color: inherit; } +article { + display: flex; + flex-direction: column; +} + +/*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); +} + +.Connection.Hide ~ .page { + transition: all 0.5s ease; + transform: translateY(-35px); +} + .btn-primary { color: #fff; background-color: var(--primary-color); @@ -31,11 +72,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 +145,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 +242,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; @@ -224,8 +268,7 @@ h1:focus { outline: none; } #app { margin-top: env(safe-area-inset-top); - margin-bottom: env(safe-area-inset-bottom); - height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom)); + height: calc(100vh - env(safe-area-inset-top)); } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } diff --git a/salesbook.Shared/wwwroot/css/default-theme.css b/salesbook.Shared/wwwroot/css/default-theme.css index 9422456..e202162 100644 --- a/salesbook.Shared/wwwroot/css/default-theme.css +++ b/salesbook.Shared/wwwroot/css/default-theme.css @@ -5,7 +5,9 @@ /*Utility*/ --exception-box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.3); --custom-box-shadow: 1px 2px 5px var(--gray-for-shadow); - --mud-default-borderradius: 12px !important; + --mud-default-borderradius: 20px !important; --m-page-x: 1rem; --mh-header: 4rem; + + --light-card-background: hsl(from var(--mud-palette-background-gray) h s 97%); } diff --git a/salesbook.Shared/wwwroot/css/form.css b/salesbook.Shared/wwwroot/css/form.css index c455bc4..8d13e3a 100644 --- a/salesbook.Shared/wwwroot/css/form.css +++ b/salesbook.Shared/wwwroot/css/form.css @@ -5,6 +5,10 @@ margin-bottom: 1rem; } +.customDialog-form.disable-safe-area .mud-dialog-content { margin-top: unset !important; } + +.customDialog-form.disable-safe-area .content { height: 100% !important; } + .customDialog-form .content { height: calc(100vh - (.6rem + 40px)); overflow: auto; @@ -12,6 +16,14 @@ scrollbar-width: none; } +@supports (-webkit-touch-callout: none) { + .customDialog-form .content { height: calc(100vh - (.6rem + 40px) - env(safe-area-inset-top)) !important; } + + .customDialog-form.disable-safe-area .content { height: 100% !important; } + + .customDialog-form.disable-safe-area .mud-dialog-content { margin-top: unset !important; } +} + .customDialog-form .header { padding: 0 !important; } .customDialog-form .content::-webkit-scrollbar { display: none; } @@ -31,6 +43,8 @@ padding: .4rem 1rem !important; } +.input-card.clearButton.custom-border-bottom { border-bottom: .1rem solid var(--card-border-color); } + .input-card > .divider { margin: 0 !important; } .form-container { @@ -92,7 +106,7 @@ .container-button { width: 100%; - box-shadow: var(--custom-box-shadow); + background: var(--light-card-background); padding: .5rem 0; border-radius: 12px; margin-bottom: 2rem; 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.Shared/wwwroot/js/notifications.js b/salesbook.Shared/wwwroot/js/notifications.js new file mode 100644 index 0000000..6732fa8 --- /dev/null +++ b/salesbook.Shared/wwwroot/js/notifications.js @@ -0,0 +1,172 @@ +const FIRST_THRESHOLD = 80; +const SECOND_THRESHOLD = 160; +const CLOSE_THRESHOLD = 40; +const MAX_SWIPE = 200; + +let dotnetHelper; + +window.initNotifications = (dotnetRef) => { + dotnetHelper = dotnetRef; + document.querySelectorAll('.row').forEach(initRow); +}; + +function initRow(row) { + const card = row.querySelector('.notification-card'); + const btnTrash = row.querySelector('.trash-btn'); + const btnRead = row.querySelector('.read-btn'); + + const behindRight = row.querySelector('.behind-right'); // cestino + const behindLeft = row.querySelector('.behind-left'); // mark as read + + let startX = 0, currentX = 0, dragging = false; + let open = null; // "left", "right" oppure null + + // inizializza nascosti + behindRight.style.visibility = "hidden"; + behindLeft.style.visibility = "hidden"; + + // funzione di utilità → controlla se c'è unread-dot + function canMarkAsRead() { + return row.querySelector('.unread-dot') !== null; + } + + card.addEventListener('pointerdown', (e) => { + if (e.pointerType === 'mouse' && e.button !== 0) return; + dragging = true; + startX = e.clientX; + card.setPointerCapture(e.pointerId); + card.style.transition = 'none'; + }); + + card.addEventListener('pointermove', (e) => { + if (!dragging) return; + + const dx = e.clientX - startX; + + if (dx > 0 && !canMarkAsRead() && !open) { + currentX = 0; + return; // niente movimento + } + + let translate = dx; + + if (!open) { + translate = Math.max(-MAX_SWIPE, Math.min(MAX_SWIPE, dx)); + } else if (open === "left") { + translate = Math.min(MAX_SWIPE, FIRST_THRESHOLD + dx); + } else if (open === "right") { + translate = Math.max(-MAX_SWIPE, -FIRST_THRESHOLD + dx); + } + + currentX = translate; + card.style.transform = `translateX(${translate}px)`; + + // mostra/nascondi i behind in tempo reale + if (currentX < 0) { + behindRight.style.visibility = "visible"; // cestino + behindLeft.style.visibility = "hidden"; + } else if (currentX > 0) { + behindLeft.style.visibility = "visible"; // mark as read + behindRight.style.visibility = "hidden"; + } else { + behindLeft.style.visibility = "hidden"; + behindRight.style.visibility = "hidden"; + } + }); + + function endDrag() { + if (!dragging) return; + dragging = false; + card.style.transition = 'transform .2s ease'; + + // blocca swipe destro se non consentito + if (currentX > 0 && !canMarkAsRead() && !open) { + card.style.transform = 'translateX(0)'; + currentX = 0; + open = null; + behindRight.style.visibility = "hidden"; + behindLeft.style.visibility = "hidden"; + return; + } + + // Swipe a sinistra → elimina + if (!open && currentX < 0) { + if (currentX < -SECOND_THRESHOLD) { + card.style.transform = `translateX(-${MAX_SWIPE}px)`; + setTimeout(() => removeRow(row), 200); + } else if (currentX < -FIRST_THRESHOLD) { + card.style.transform = `translateX(-${FIRST_THRESHOLD}px)`; + open = "right"; + } else { + card.style.transform = 'translateX(0)'; + open = null; + behindRight.style.visibility = "hidden"; + behindLeft.style.visibility = "hidden"; + } + } + // Swipe a destra → mark as read SOLO se consentito + else if (!open && currentX > 0) { + if (currentX > SECOND_THRESHOLD) { + card.style.transform = `translateX(${MAX_SWIPE}px)`; + setTimeout(() => markAsRead(row), 200); + } else if (currentX > FIRST_THRESHOLD) { + card.style.transform = `translateX(${FIRST_THRESHOLD}px)`; + open = "left"; + } else { + card.style.transform = 'translateX(0)'; + open = null; + behindRight.style.visibility = "hidden"; + behindLeft.style.visibility = "hidden"; + } + } + // Se già aperta, gestisci chiusura + else { + if (open === "right" && currentX > -FIRST_THRESHOLD + CLOSE_THRESHOLD) { + card.style.transform = 'translateX(0)'; + open = null; + behindRight.style.visibility = "hidden"; + behindLeft.style.visibility = "hidden"; + } else if (open === "left" && currentX < FIRST_THRESHOLD - CLOSE_THRESHOLD) { + card.style.transform = 'translateX(0)'; + open = null; + behindRight.style.visibility = "hidden"; + behindLeft.style.visibility = "hidden"; + } else if (open) { + card.style.transform = `translateX(${open === "right" ? -FIRST_THRESHOLD : FIRST_THRESHOLD}px)`; + } + } + } + + card.addEventListener('pointerup', endDrag); + card.addEventListener('pointercancel', endDrag); + + btnTrash.addEventListener('click', () => removeRow(row)); + btnRead.addEventListener('click', () => markAsRead(row)); +} + +function removeRow(row) { + const id = row.id; + + collapseAndRemove(row); + dotnetHelper.invokeMethodAsync('Delete', id); +} + +function markAsRead(row) { + const id = row.id; + + collapseAndRemove(row); + dotnetHelper.invokeMethodAsync('MarkAsRead', id); +} + +function collapseAndRemove(row) { + const h = row.getBoundingClientRect().height; + row.style.height = h + 'px'; + row.classList.add('collapsing'); + requestAnimationFrame(() => { + row.style.opacity = '0'; + row.style.marginTop = '0'; + row.style.marginBottom = '0'; + row.style.height = '0'; + }); + setTimeout(() => row.remove(), 220); +} diff --git a/salesbook.Web/Core/Services/ManageDataService.cs b/salesbook.Web/Core/Services/ManageDataService.cs index 3775113..f5b7864 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,32 @@ public class ManageDataService : IManageDataService throw new NotImplementedException(); } - public Task> GetActivity(Expression>? whereCond = null) + public Task> GetClienti(WhereCondContact? whereCond = null) { throw new NotImplementedException(); } - public Task> GetContact() + public Task> GetProspect(WhereCondContact? whereCond = null) { throw new NotImplementedException(); } - public Task GetSpecificContact(string codAnag, bool IsContact) + public Task> GetContact(WhereCondContact whereCond, DateTime? lastSync = null) + { + throw new NotImplementedException(); + } + + public Task GetSpecificContact(string codAnag, bool IsContact) + { + throw new NotImplementedException(); + } + + public Task> GetActivityTryLocalDb(WhereCondActivity whereCond) + { + throw new NotImplementedException(); + } + + public Task> GetActivity(WhereCondActivity whereCond, bool useLocalDb = false) { throw new NotImplementedException(); } @@ -37,6 +54,11 @@ public class ManageDataService : IManageDataService throw new NotImplementedException(); } + public Task DeleteProspect(string codPpro) + { + throw new NotImplementedException(); + } + public Task Delete(T objectToDelete) { throw new NotImplementedException(); @@ -47,6 +69,11 @@ public class ManageDataService : IManageDataService throw new NotImplementedException(); } + public Task> MapActivity(List? activities) + { + throw new NotImplementedException(); + } + public Task ClearDb() { throw new NotImplementedException(); diff --git a/salesbook.Web/Core/Services/NetworkService.cs b/salesbook.Web/Core/Services/NetworkService.cs index 918d89e..16d9938 100644 --- a/salesbook.Web/Core/Services/NetworkService.cs +++ b/salesbook.Web/Core/Services/NetworkService.cs @@ -1,9 +1,12 @@ using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.System.Network; 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(); } diff --git a/salesbook.Web/Program.cs b/salesbook.Web/Program.cs index cbb65dc..fc2b24a 100644 --- a/salesbook.Web/Program.cs +++ b/salesbook.Web/Program.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; using salesbook.Shared.Components; using salesbook.Shared.Core.Interface; +using salesbook.Shared.Core.Interface.IntegryApi; +using salesbook.Shared.Core.Interface.System.Network; using salesbook.Shared.Core.Services; using salesbook.Web.Core.Services; diff --git a/salesbook.Web/salesbook.Web.csproj b/salesbook.Web/salesbook.Web.csproj index 45301e1..f23565a 100644 --- a/salesbook.Web/salesbook.Web.csproj +++ b/salesbook.Web/salesbook.Web.csproj @@ -16,10 +16,10 @@ - - - - + + + +