From 5eb8b4b37727b679d86b013f8cdee98d55f8983b Mon Sep 17 00:00:00 2001 From: Andre Beging Date: Mon, 31 Mar 2025 08:21:23 +0200 Subject: [PATCH] Add 'Modified' tracking for Prospect entity Introduce a 'Modified' field to track the last modification date of prospects. Automatically update this field on changes and integrate it in UI display for better transparency. --- .../Entity/Prospect.cs | 5 + .../Controls/ProspectContainer.razor | 8 +- .../Controls/ProspectContainer.razor.cs | 2 +- .../Controls/ProspectContainer.razor.css | 4 +- .../Data/Service/ProspectService.cs | 16 +- ...250330182300_Proposal-Modified.Designer.cs | 208 ++++++++++++++++++ .../20250330182300_Proposal-Modified.cs | 40 ++++ .../Migrations/FsContextModelSnapshot.cs | 3 + 8 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.Designer.cs create mode 100644 FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.cs diff --git a/FoodsharingSiegen.Contracts/Entity/Prospect.cs b/FoodsharingSiegen.Contracts/Entity/Prospect.cs index c51eb9f..2485022 100644 --- a/FoodsharingSiegen.Contracts/Entity/Prospect.cs +++ b/FoodsharingSiegen.Contracts/Entity/Prospect.cs @@ -42,6 +42,11 @@ namespace FoodsharingSiegen.Contracts.Entity /// public string? Memo { get; set; } + /// + /// Gets or sets the value indicating the last modification date and time. + /// + public DateTime? Modified { get; set; } + /// /// Gets or sets the value of the name (ab) /// diff --git a/FoodsharingSiegen.Server/Controls/ProspectContainer.razor b/FoodsharingSiegen.Server/Controls/ProspectContainer.razor index 3d1e9de..6d7f263 100644 --- a/FoodsharingSiegen.Server/Controls/ProspectContainer.razor +++ b/FoodsharingSiegen.Server/Controls/ProspectContainer.razor @@ -45,7 +45,7 @@ } - +
- - + +
+ + Zuletzt geändert: @Prospect?.Modified?.ToLocalTime() \ No newline at end of file diff --git a/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.cs b/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.cs index b591559..5d4b399 100644 --- a/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.cs +++ b/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.cs @@ -72,7 +72,7 @@ namespace FoodsharingSiegen.Server.Controls private async Task EditProspectAsync() { - await EditProspectDialog.ShowAsync(ModalService, () => InvokeAsync(StateHasChanged), Prospect); + await EditProspectDialog.ShowAsync(ModalService, OnDataChanged ?? (async () => await Task.CompletedTask), Prospect); } #endregion diff --git a/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.css b/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.css index 0e089da..386d98d 100644 --- a/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.css +++ b/FoodsharingSiegen.Server/Controls/ProspectContainer.razor.css @@ -14,13 +14,15 @@ } .pc-main { + display: flex; + flex-direction: column; flex-basis: 0; flex-grow: 1; max-width: 480px; border: 1px solid #533a20; border-radius: 3px; margin: 5px; - padding: 16px; + padding: .5rem .5rem 0 .5rem; } .pc-main.warning { diff --git a/FoodsharingSiegen.Server/Data/Service/ProspectService.cs b/FoodsharingSiegen.Server/Data/Service/ProspectService.cs index 79c2932..6ebd250 100644 --- a/FoodsharingSiegen.Server/Data/Service/ProspectService.cs +++ b/FoodsharingSiegen.Server/Data/Service/ProspectService.cs @@ -44,6 +44,7 @@ namespace FoodsharingSiegen.Server.Data.Service interaction.Created = DateTime.UtcNow; targetProspect.Interactions.Add(interaction); + targetProspect.Modified = DateTime.UtcNow; await Context.SaveChangesAsync(); @@ -77,6 +78,7 @@ namespace FoodsharingSiegen.Server.Data.Service if (prospect == null) return new(new Exception("Cannot be empty")); prospect.Created = DateTime.UtcNow; + prospect.Modified = DateTime.UtcNow; prospect.Id = Guid.Empty; await Context.Prospects!.AddAsync(prospect); @@ -142,8 +144,19 @@ namespace FoodsharingSiegen.Server.Data.Service { try { - Context.Interactions!.Remove(new() { Id = interactionId }); + var interaction = await Context.Interactions!.AsNoTracking().FirstOrDefaultAsync(x => x.Id == interactionId); + if(interaction == null) return new(new Exception("Interaction not found")); + + Context.Interactions!.Remove(new() { Id = interaction.Id }); await Context.SaveChangesAsync(); + + // Update prospect modified date + var prospect = await Context.Prospects!.FirstOrDefaultAsync(x => x.Id == interaction.ProspectID); + if (prospect != null) + { + prospect.Modified = DateTime.UtcNow; + await Context.SaveChangesAsync(); + } await AuditService.Insert(AuditType.RemoveInteraction, "?"); @@ -176,6 +189,7 @@ namespace FoodsharingSiegen.Server.Data.Service entityProspect.FsId = prospect.FsId; entityProspect.Warning = prospect.Warning; entityProspect.RecordState = prospect.RecordState; + entityProspect.Modified = DateTime.UtcNow; var saveR = await Context.SaveChangesAsync(); diff --git a/FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.Designer.cs b/FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.Designer.cs new file mode 100644 index 0000000..027eba7 --- /dev/null +++ b/FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.Designer.cs @@ -0,0 +1,208 @@ +// +using System; +using FoodsharingSiegen.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace FoodsharingSiegen.Server.Migrations +{ + [DbContext(typeof(FsContext))] + [Migration("20250330182300_Proposal-Modified")] + partial class ProposalModified + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Data1") + .HasColumnType("TEXT"); + + b.Property("Data2") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserID") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserID"); + + b.ToTable("Audits"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.Interaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Alert") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Info") + .HasColumnType("TEXT"); + + b.Property("NotNeeded") + .HasColumnType("INTEGER"); + + b.Property("ProspectID") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserID") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProspectID"); + + b.HasIndex("UserID"); + + b.ToTable("Interactions"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.Prospect", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("FsId") + .HasColumnType("INTEGER"); + + b.Property("Memo") + .HasColumnType("TEXT"); + + b.Property("Modified") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RecordState") + .HasColumnType("INTEGER"); + + b.Property("Warning") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Prospects"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ForceLogout") + .HasColumnType("INTEGER"); + + b.Property("Groups") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Mail") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Memo") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Network") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("Verified") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.Audit", b => + { + b.HasOne("FoodsharingSiegen.Contracts.Entity.User", "User") + .WithMany() + .HasForeignKey("UserID"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.Interaction", b => + { + b.HasOne("FoodsharingSiegen.Contracts.Entity.Prospect", "Prospect") + .WithMany("Interactions") + .HasForeignKey("ProspectID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("FoodsharingSiegen.Contracts.Entity.User", "User") + .WithMany("Interactions") + .HasForeignKey("UserID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Prospect"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.Prospect", b => + { + b.Navigation("Interactions"); + }); + + modelBuilder.Entity("FoodsharingSiegen.Contracts.Entity.User", b => + { + b.Navigation("Interactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.cs b/FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.cs new file mode 100644 index 0000000..d12a797 --- /dev/null +++ b/FoodsharingSiegen.Server/Migrations/20250330182300_Proposal-Modified.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FoodsharingSiegen.Server.Migrations +{ + /// + public partial class ProposalModified : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Modified", + table: "Prospects", + type: "TEXT", + nullable: true); + + // Fill Modified column of existing rows + migrationBuilder.Sql(@" + UPDATE Prospects + SET Modified = COALESCE( + (SELECT MAX(Created) + FROM Interactions + WHERE Interactions.ProspectID = Prospects.Id), + Prospects.Created + ); + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Modified", + table: "Prospects"); + } + } +} diff --git a/FoodsharingSiegen.Server/Migrations/FsContextModelSnapshot.cs b/FoodsharingSiegen.Server/Migrations/FsContextModelSnapshot.cs index 9cc9d2e..cf9b3a3 100644 --- a/FoodsharingSiegen.Server/Migrations/FsContextModelSnapshot.cs +++ b/FoodsharingSiegen.Server/Migrations/FsContextModelSnapshot.cs @@ -99,6 +99,9 @@ namespace FoodsharingSiegen.Server.Migrations b.Property("Memo") .HasColumnType("TEXT"); + b.Property("Modified") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT");