Add per-store sections to group list items by walk order

Each store gets a StoreSection catalog (Produce, Dairy, etc.). Default
sections are seeded on store creation; admins can rename, reorder, add,
or delete. ShoppingListItem.SectionId is a nullable FK that sets to
null when the section is deleted, so items survive section churn.

The list detail view groups items by section in walk order, with
Uncategorized appended last. Section dropdowns on each row (and the
add-item form) let users assign or reassign on the fly. SignalR
broadcasts include sectionId on adds and a new ItemSectionChanged
event for live re-grouping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Josh Rogers
2026-05-08 22:05:57 -05:00
parent de5c18f3e6
commit fa1d4b4f62
14 changed files with 1413 additions and 82 deletions
@@ -229,6 +229,9 @@ namespace YesChef.Api.Migrations
b.Property<int?>("RemovedByUserId")
.HasColumnType("integer");
b.Property<int?>("SectionId")
.HasColumnType("integer");
b.Property<int>("ShoppingListId")
.HasColumnType("integer");
@@ -245,6 +248,8 @@ namespace YesChef.Api.Migrations
b.HasIndex("RemovedByUserId");
b.HasIndex("SectionId");
b.HasIndex("ShoppingListId");
b.ToTable("ShoppingListItems");
@@ -280,6 +285,38 @@ namespace YesChef.Api.Migrations
b.ToTable("Stores");
});
modelBuilder.Entity("YesChef.Api.Entities.StoreSection", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("FamilyId")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
b.Property<int>("SortOrder")
.HasColumnType("integer");
b.Property<int>("StoreId")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("FamilyId");
b.HasIndex("StoreId", "Name")
.IsUnique();
b.ToTable("StoreSections");
});
modelBuilder.Entity("YesChef.Api.Entities.User", b =>
{
b.Property<int>("Id")
@@ -415,6 +452,11 @@ namespace YesChef.Api.Migrations
.HasForeignKey("RemovedByUserId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("YesChef.Api.Entities.StoreSection", "Section")
.WithMany()
.HasForeignKey("SectionId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("YesChef.Api.Entities.ShoppingList", "ShoppingList")
.WithMany("Items")
.HasForeignKey("ShoppingListId")
@@ -429,6 +471,8 @@ namespace YesChef.Api.Migrations
b.Navigation("RemovedByUser");
b.Navigation("Section");
b.Navigation("ShoppingList");
});
@@ -443,6 +487,25 @@ namespace YesChef.Api.Migrations
b.Navigation("Family");
});
modelBuilder.Entity("YesChef.Api.Entities.StoreSection", b =>
{
b.HasOne("YesChef.Api.Entities.Family", "Family")
.WithMany()
.HasForeignKey("FamilyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("YesChef.Api.Entities.Store", "Store")
.WithMany()
.HasForeignKey("StoreId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Family");
b.Navigation("Store");
});
modelBuilder.Entity("YesChef.Api.Entities.Recipe", b =>
{
b.Navigation("Ingredients");