Gestite immagini allegate

This commit is contained in:
2026-02-24 11:33:16 +01:00
parent a4dece511f
commit c7fb4a28a4
10 changed files with 355 additions and 18 deletions

View File

@@ -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<AppDbContext> options) : DbContext(op
modelBuilder.Entity<Scheda>()
.HasIndex(x => new { x.CodMdep, x.Data, x.Rilevatore });
modelBuilder.Entity<Scheda>()
.Property(x => x.ImageNames)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<List<string>>(v, (JsonSerializerOptions?)null) ?? new List<string>()
);
}
}

View File

@@ -0,0 +1,105 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("CodMdep")
.HasColumnType("TEXT");
b.Property<DateOnly>("Data")
.HasColumnType("TEXT");
b.Property<string>("Rilevatore")
.HasColumnType("TEXT");
b.Property<int>("Stato")
.HasColumnType("INTEGER");
b.HasKey("CodMdep", "Data", "Rilevatore");
b.ToTable("Ispezioni");
});
modelBuilder.Entity("SteUp.Shared.Core.Entities.Scheda", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ActivityTypeId")
.HasColumnType("TEXT");
b.Property<string>("CodJfas")
.HasColumnType("TEXT");
b.Property<string>("CodMdep")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateOnly>("Data")
.HasColumnType("TEXT");
b.Property<string>("DescrizioneReparto")
.HasColumnType("TEXT");
b.Property<string>("ImageNames")
.HasColumnType("TEXT");
b.Property<string>("Note")
.HasColumnType("TEXT");
b.Property<string>("Responsabile")
.HasColumnType("TEXT");
b.Property<string>("Rilevatore")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("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
}
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SteUp.Data.Migrations
{
/// <inheritdoc />
public partial class AddListImage : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ImageNames",
table: "Schede",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ImageNames",
table: "Schede");
}
}
}

View File

@@ -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<string>("DescrizioneReparto")
.HasColumnType("TEXT");
b.Property<string>("ImageNames")
.HasColumnType("TEXT");
b.Property<string>("Note")
.HasColumnType("TEXT");

View File

@@ -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<AttachedDto> 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<List<AttachedDto>?> 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<AttachedDto>();
foreach (var file in fileList)
{
returnList.Add(await ConvertToDto(file, AttachedDto.TypeAttached.Image));
}
return returnList;
}
public async Task<string?> 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<string> 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()}";
}

View File

@@ -49,7 +49,13 @@
</MudSelectExtended>
</CardFormModal>
@if (!AttachedList.IsNullOrEmpty())
@if (FileLoading)
{
<div class="container-attached">
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-3"/>
</div>
}
else if (!AttachedList.IsNullOrEmpty())
{
<div class="container-attached">
<div class="scroll-attached">
@@ -62,7 +68,8 @@
}
<MudCardContent Class="image_card">
<MudText Typo="Typo.subtitle1"><b>@item.p.Name</b></MudText>
<MudIconButton Variant="Variant.Outlined" Icon="@Icons.Material.Rounded.Close"
<MudIconButton Variant="Variant.Outlined"
Icon="@Icons.Material.Rounded.Close"
Size="Size.Small" Color="Color.Error"
OnClick="@(() => OnRemoveAttached(item.index))"/>
</MudCardContent>
@@ -140,6 +147,8 @@
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,10 +196,10 @@
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();
@@ -171,6 +208,65 @@
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;
@@ -256,6 +352,8 @@
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);
});
}
}

View File

@@ -14,6 +14,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);

View File

@@ -17,6 +17,8 @@ public class Scheda : EntityBase<Scheda>
public string Rilevatore { get; set; } = string.Empty;
public Ispezione? Ispezione { get; set; }
public List<string>? ImageNames { get; set; }
public string? DescrizioneReparto { get; set; }
public string? ActivityTypeId { get; set; }
public string? Note { get; set; }

View File

@@ -1,4 +1,5 @@
using SteUp.Shared.Core.Dto;
using SteUp.Shared.Core.Entities;
namespace SteUp.Shared.Core.Interface.System;
@@ -7,6 +8,10 @@ public interface IAttachedService
Task<AttachedDto?> SelectImageFromCamera();
Task<List<AttachedDto>?> SelectImageFromGallery();
Task<List<AttachedDto>?> GetInspectionFiles(Ispezione ispezione);
Task<string?> SaveInspectionFile(Ispezione ispezione, byte[] file, string fileName, CancellationToken ct = default);
bool RemoveInspectionFile(Ispezione ispezione, string fileName);
Task<string> SaveToTempStorage(Stream file, string fileName, CancellationToken ct = default);
Task CleanTempStorageAsync(CancellationToken ct = default);
Task OpenFile(string fileName, string filePath);

View File

@@ -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;