From c7fb4a28a44e415c743c41b470b793ab7ff5da03 Mon Sep 17 00:00:00 2001 From: MarcoE Date: Tue, 24 Feb 2026 11:33:16 +0100 Subject: [PATCH] Gestite immagini allegate --- SteUp.Data/LocalDb/AppDbContext.cs | 10 +- .../20260223154219_AddListImage.Designer.cs | 105 +++++++++++++++ .../Migrations/20260223154219_AddListImage.cs | 28 ++++ .../Migrations/AppDbContextModelSnapshot.cs | 5 +- SteUp.Maui/Core/Services/AttachedService.cs | 81 ++++++++++- .../Modal/ModalFormScheda.razor | 126 ++++++++++++++++-- SteUp.Shared/Core/Dto/AttachedDto.cs | 3 + SteUp.Shared/Core/Entities/Scheda.cs | 4 +- .../Core/Interface/System/IAttachedService.cs | 5 + SteUp.Shared/wwwroot/css/form.css | 6 +- 10 files changed, 355 insertions(+), 18 deletions(-) create mode 100644 SteUp.Data/Migrations/20260223154219_AddListImage.Designer.cs create mode 100644 SteUp.Data/Migrations/20260223154219_AddListImage.cs diff --git a/SteUp.Data/LocalDb/AppDbContext.cs b/SteUp.Data/LocalDb/AppDbContext.cs index a2b7389..3a2ab28 100644 --- a/SteUp.Data/LocalDb/AppDbContext.cs +++ b/SteUp.Data/LocalDb/AppDbContext.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; using SteUp.Shared.Core.Entities; namespace SteUp.Data.LocalDb; @@ -23,5 +24,12 @@ public class AppDbContext(DbContextOptions options) : DbContext(op modelBuilder.Entity() .HasIndex(x => new { x.CodMdep, x.Data, x.Rilevatore }); + + modelBuilder.Entity() + .Property(x => x.ImageNames) + .HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), + v => JsonSerializer.Deserialize>(v, (JsonSerializerOptions?)null) ?? new List() + ); } } \ No newline at end of file diff --git a/SteUp.Data/Migrations/20260223154219_AddListImage.Designer.cs b/SteUp.Data/Migrations/20260223154219_AddListImage.Designer.cs new file mode 100644 index 0000000..6973a2b --- /dev/null +++ b/SteUp.Data/Migrations/20260223154219_AddListImage.Designer.cs @@ -0,0 +1,105 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using SteUp.Data.LocalDb; + +#nullable disable + +namespace SteUp.Data.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260223154219_AddListImage")] + partial class AddListImage + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("SteUp.Shared.Core.Entities.Ispezione", b => + { + b.Property("CodMdep") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Rilevatore") + .HasColumnType("TEXT"); + + b.Property("Stato") + .HasColumnType("INTEGER"); + + b.HasKey("CodMdep", "Data", "Rilevatore"); + + b.ToTable("Ispezioni"); + }); + + modelBuilder.Entity("SteUp.Shared.Core.Entities.Scheda", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivityTypeId") + .HasColumnType("TEXT"); + + b.Property("CodJfas") + .HasColumnType("TEXT"); + + b.Property("CodMdep") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DescrizioneReparto") + .HasColumnType("TEXT"); + + b.Property("ImageNames") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Responsabile") + .HasColumnType("TEXT"); + + b.Property("Rilevatore") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Scadenza") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CodMdep", "Data", "Rilevatore"); + + b.ToTable("Schede"); + }); + + modelBuilder.Entity("SteUp.Shared.Core.Entities.Scheda", b => + { + b.HasOne("SteUp.Shared.Core.Entities.Ispezione", "Ispezione") + .WithMany("Schede") + .HasForeignKey("CodMdep", "Data", "Rilevatore") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Ispezione"); + }); + + modelBuilder.Entity("SteUp.Shared.Core.Entities.Ispezione", b => + { + b.Navigation("Schede"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SteUp.Data/Migrations/20260223154219_AddListImage.cs b/SteUp.Data/Migrations/20260223154219_AddListImage.cs new file mode 100644 index 0000000..4f5cf50 --- /dev/null +++ b/SteUp.Data/Migrations/20260223154219_AddListImage.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SteUp.Data.Migrations +{ + /// + public partial class AddListImage : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImageNames", + table: "Schede", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImageNames", + table: "Schede"); + } + } +} diff --git a/SteUp.Data/Migrations/AppDbContextModelSnapshot.cs b/SteUp.Data/Migrations/AppDbContextModelSnapshot.cs index 95bc096..98888ab 100644 --- a/SteUp.Data/Migrations/AppDbContextModelSnapshot.cs +++ b/SteUp.Data/Migrations/AppDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace SteUp.Data.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.13"); + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); modelBuilder.Entity("SteUp.Shared.Core.Entities.Ispezione", b => { @@ -58,6 +58,9 @@ namespace SteUp.Data.Migrations b.Property("DescrizioneReparto") .HasColumnType("TEXT"); + b.Property("ImageNames") + .HasColumnType("TEXT"); + b.Property("Note") .HasColumnType("TEXT"); diff --git a/SteUp.Maui/Core/Services/AttachedService.cs b/SteUp.Maui/Core/Services/AttachedService.cs index 61d6bd5..19de1a5 100644 --- a/SteUp.Maui/Core/Services/AttachedService.cs +++ b/SteUp.Maui/Core/Services/AttachedService.cs @@ -1,4 +1,6 @@ -using SteUp.Shared.Core.Dto; +using Microsoft.Extensions.Logging.Abstractions; +using SteUp.Shared.Core.Dto; +using SteUp.Shared.Core.Entities; using SteUp.Shared.Core.Helpers; using SteUp.Shared.Core.Interface.System; @@ -80,6 +82,80 @@ public class AttachedService : IAttachedService }; } + private async Task ConvertToDto(FileInfo file, AttachedDto.TypeAttached type) + { + var (origUrl, thumbUrl) = await SaveAndCreateThumbAsync( + await File.ReadAllBytesAsync(file.FullName), + file.Name + ); + + return new AttachedDto + { + Name = file.Name, + Path = file.FullName, + TempPath = origUrl, + ThumbPath = thumbUrl, + Type = type, + SavedOnAppData = true + }; + } + + public async Task?> GetInspectionFiles(Ispezione ispezione) + { + var baseDir = FileSystem.AppDataDirectory; + var inspectionDir = Path.Combine(baseDir, $"attached_{GetInspectionKey(ispezione)}"); + var directory = new DirectoryInfo(inspectionDir); + + if (!directory.Exists) return null; + + var fileList = directory.GetFiles().ToList(); + + var returnList = new List(); + foreach (var file in fileList) + { + returnList.Add(await ConvertToDto(file, AttachedDto.TypeAttached.Image)); + } + + return returnList; + } + + public async Task SaveInspectionFile(Ispezione ispezione, byte[] file, string fileName, + CancellationToken ct) + { + ArgumentNullException.ThrowIfNull(ispezione); + ArgumentNullException.ThrowIfNull(file); + ArgumentException.ThrowIfNullOrWhiteSpace(fileName); + + var baseDir = FileSystem.AppDataDirectory; + var inspectionDir = Path.Combine(baseDir, $"attached_{GetInspectionKey(ispezione)}"); + if (!Directory.Exists(inspectionDir)) Directory.CreateDirectory(inspectionDir); + + var filePath = Path.Combine(inspectionDir, fileName); + await File.WriteAllBytesAsync(filePath, file, ct); + + return filePath; + } + + public bool RemoveInspectionFile(Ispezione ispezione, string fileName) + { + var baseDir = FileSystem.AppDataDirectory; + var inspectionDir = Path.Combine(baseDir, $"attached_{GetInspectionKey(ispezione)}"); + + if (!Directory.Exists(inspectionDir)) return false; + if (string.IsNullOrWhiteSpace(fileName)) return false; + + var filePath = Path.Combine(inspectionDir, fileName); + + if (!File.Exists(filePath)) return false; + + File.Delete(filePath); + + if (!Directory.EnumerateFileSystemEntries(inspectionDir).Any()) + Directory.Delete(inspectionDir); + + return true; + } + public async Task SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(file); @@ -146,4 +222,7 @@ public class AttachedService : IAttachedService var name = Path.GetFileName(fileName); return Path.GetInvalidFileNameChars().Aggregate(name, (current, c) => current.Replace(c, '_')); } + + private static string GetInspectionKey(Ispezione ispezione) => + $"{ispezione.CodMdep}_{ispezione.Data:ddMMyyyy}_{ispezione.Rilevatore.ToLower()}"; } \ No newline at end of file diff --git a/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor b/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor index 8580eec..e380ee6 100644 --- a/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor +++ b/SteUp.Shared/Components/SingleElements/Modal/ModalFormScheda.razor @@ -49,7 +49,13 @@ - @if (!AttachedList.IsNullOrEmpty()) + @if (FileLoading) + { +
+ +
+ } + else if (!AttachedList.IsNullOrEmpty()) {
@@ -62,7 +68,8 @@ } @item.p.Name - @@ -133,13 +140,15 @@ [Parameter] public required DateOnly Data { get; set; } [Parameter] public bool IsNew { get; set; } [Parameter] public Scheda Scheda { get; set; } = new(); - + private bool IsView => !NetworkService.ConnectionAvailable; //Overlay private bool VisibleOverlay { get; set; } private bool SuccessAnimation { get; set; } + private bool FileLoading { get; set; } + private ConfirmUpdateActivity _confirmUpdateMessage = null!; private MudForm _form = null!; @@ -152,6 +161,34 @@ { _originalScheda = Scheda.Clone(); Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter; + + LoadAttached(); + } + + private void LoadAttached() + { + FileLoading = true; + StateHasChanged(); + + Task.Run(async () => + { + var fileList = await AttachedService.GetInspectionFiles( + new Ispezione + { + CodMdep = CodMdep, + Data = Data, + Rilevatore = UserSession.User.Username + } + ); + + await InvokeAsync(() => + { + AttachedList = fileList; + FileLoading = false; + + StateHasChanged(); + }); + }); } private async Task Save() @@ -159,18 +196,77 @@ VisibleOverlay = true; StateHasChanged(); - if (IsNew) - await IspezioniService.AddSchedaAsync(CodMdep, Data, UserSession.User.Username, Scheda); - else - await IspezioniService.UpdateSchedaAsync(Scheda); + if (IsNew) await NewSave(); + else await Update(); + + await AttachedService.CleanTempStorageAsync(); SuccessAnimation = true; StateHasChanged(); - + await Task.Delay(1250); MudDialog.Close(Scheda); } + private async Task NewSave() + { + if (!AttachedList.IsNullOrEmpty()) + { + foreach (var attached in AttachedList!) + { + var fileNameAdded = await AttachedService.SaveInspectionFile( + new Ispezione + { + CodMdep = CodMdep, + Data = Data, + Rilevatore = UserSession.User.Username + }, + attached.FileBytes!, + attached.Name! + ); + + Scheda.ImageNames ??= []; + if (fileNameAdded != null) + Scheda.ImageNames.Add(fileNameAdded); + } + } + + await IspezioniService.AddSchedaAsync(CodMdep, Data, UserSession.User.Username, Scheda); + } + + private async Task Update() + { + if (!AttachedList.IsNullOrEmpty()) + { + foreach (var attached in AttachedList!.Where(x => !x.SavedOnAppData)) + { + var ispezione = new Ispezione + { + CodMdep = CodMdep, + Data = Data, + Rilevatore = UserSession.User.Username + }; + + if (!attached.ToRemove) + { + var fileNameAdded = await AttachedService.SaveInspectionFile( + ispezione, + attached.FileBytes!, + attached.Name! + ); + + Scheda.ImageNames ??= []; + if (fileNameAdded != null) + Scheda.ImageNames.Add(fileNameAdded); + } + else + _ = AttachedService.RemoveInspectionFile(ispezione, attached.Name!); + } + } + + await IspezioniService.UpdateSchedaAsync(Scheda); + } + private async Task Cancel() { if (await CheckSavePreAction()) @@ -188,9 +284,9 @@ StateHasChanged(); } - private void RecalcDirty() + private void RecalcDirty(bool forceTrue = false) { - IsDirty = !ValueComparer.AreEqual(Scheda, _originalScheda); + IsDirty = forceTrue || !ValueComparer.AreEqual(Scheda, _originalScheda); if (IsDirty) LabelSave = !IsNew ? "Aggiorna" : "Salva"; else LabelSave = null; @@ -255,6 +351,8 @@ AttachedList.Add(new AttachedDto { Name = a.Name, MimeType = a.MimeType, FileBytes = a.FileBytes }); await InvokeAsync(StateHasChanged); + + RecalcDirty(true); // Processa in background e aggiorna UI man mano (o a blocchi) _ = Task.Run(async () => @@ -285,10 +383,13 @@ private void OnRemoveAttached(int index) { - if (AttachedList is null || index < 0 || index >= AttachedList.Count) - return; + if (AttachedList is null || index < 0 || index >= AttachedList.Count) return; + + if (AttachedList[index].SavedOnAppData) + AttachedList[index].ToRemove = true; AttachedList.RemoveAt(index); + RecalcDirty(true); StateHasChanged(); } @@ -318,5 +419,4 @@ await InvokeAsync(StateHasChanged); }); } - } \ No newline at end of file diff --git a/SteUp.Shared/Core/Dto/AttachedDto.cs b/SteUp.Shared/Core/Dto/AttachedDto.cs index 404c738..61b908c 100644 --- a/SteUp.Shared/Core/Dto/AttachedDto.cs +++ b/SteUp.Shared/Core/Dto/AttachedDto.cs @@ -13,6 +13,9 @@ public class AttachedDto public string? TempPath { get; set; } public string? ThumbPath { get; set; } + + public bool SavedOnAppData { get; set; } + public bool ToRemove { get; set; } public Stream? FileContent => FileBytes is null ? null : new MemoryStream(FileBytes); diff --git a/SteUp.Shared/Core/Entities/Scheda.cs b/SteUp.Shared/Core/Entities/Scheda.cs index 5f2bdd0..664ac5c 100644 --- a/SteUp.Shared/Core/Entities/Scheda.cs +++ b/SteUp.Shared/Core/Entities/Scheda.cs @@ -16,7 +16,9 @@ public class Scheda : EntityBase [Required] public string Rilevatore { get; set; } = string.Empty; public Ispezione? Ispezione { get; set; } - + + public List? ImageNames { get; set; } + public string? DescrizioneReparto { get; set; } public string? ActivityTypeId { get; set; } public string? Note { get; set; } diff --git a/SteUp.Shared/Core/Interface/System/IAttachedService.cs b/SteUp.Shared/Core/Interface/System/IAttachedService.cs index 9353902..09e32d2 100644 --- a/SteUp.Shared/Core/Interface/System/IAttachedService.cs +++ b/SteUp.Shared/Core/Interface/System/IAttachedService.cs @@ -1,4 +1,5 @@ using SteUp.Shared.Core.Dto; +using SteUp.Shared.Core.Entities; namespace SteUp.Shared.Core.Interface.System; @@ -6,6 +7,10 @@ public interface IAttachedService { Task SelectImageFromCamera(); Task?> SelectImageFromGallery(); + + Task?> GetInspectionFiles(Ispezione ispezione); + Task SaveInspectionFile(Ispezione ispezione, byte[] file, string fileName, CancellationToken ct = default); + bool RemoveInspectionFile(Ispezione ispezione, string fileName); Task SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default); Task CleanTempStorageAsync(CancellationToken ct = default); diff --git a/SteUp.Shared/wwwroot/css/form.css b/SteUp.Shared/wwwroot/css/form.css index 4790712..2dd6487 100644 --- a/SteUp.Shared/wwwroot/css/form.css +++ b/SteUp.Shared/wwwroot/css/form.css @@ -138,12 +138,16 @@ .container-button { width: 100%; - /*background: var(--mud-palette-table-striped);*/ + background: var(--mud-palette-table-striped); padding: .5rem 0; border-radius: 20px; margin-bottom: 2rem; } +.container-button.mud-elevation-1 { + background: unset !important; +} + .container-button .divider { margin: .5rem 0 .5rem 3rem; width: unset;