Speisekammer — Drag & Drop zwischen Kategorien (Parität mit Einkaufsliste) #177

Closed
opened 2026-09-07 10:06:28 +02:00 by lena · 2 comments
Collaborator

Story: Speisekammer — Drag & Drop zwischen Kategorien

As a Nutzer der Speisekammer,
I want to ein Produkt per Drag & Drop in eine andere Kategorie ziehen können, nicht nur innerhalb derselben umsortieren,
so that ich falsch einsortierte Produkte genauso einfach korrigieren kann wie auf der Einkaufsliste.

Kontext (verifiziert im Code):

  • ShoppingListPage.tsx hat das bereits: ein einziger, seitenweiter DndContext, jede Kategorie ist über DroppableCategorySection.tsx (geteilt mit TodoList.tsx) ein echtes Drop-Target auch wenn leer, handleDragEnd nutzt resolveCrossCategoryMoveTarget (ReactUi/src/utils/reorder.ts) um Kategorie-Wechsel zu erkennen und ruft moveShoppingProduct(...) mit der neuen Ziel-Kategorie auf.
  • PantryPage.tsx hat das nicht: renderCategorySection erzeugt pro Kategorie einen eigenen, isolierten DndContext (key={categoryId}), handleDragEnd(categoryId, categoryProducts) ruft movePantryProduct immer mit derselben, fest zugeordneten categoryId auf. Ein Drag kann technisch keine DndContext-Grenze überschreiten — Kategorie-Wechsel per Drag ist heute unmöglich.
  • Backend ist bereits bereit: MovePantryProductCommandHandler.cs unterstützt Kategorie-Wechsel schon vollständig (eigener Kommentar: "handles both same-category reorder and cross-category move in one command", inkl. korrekter Neu-Sortierung beider betroffener Kategorien) — das ist eine reine Frontend-Lücke, kein Backend-Change nötig.

Acceptance criteria:

  • PantryPage.tsx wird auf denselben Aufbau wie ShoppingListPage.tsx umgestellt: ein seitenweiter DndContext, Kategorien als DroppableCategorySection (Wiederverwendung der bestehenden Komponente), Kategorie-Wechsel-Erkennung über resolveCrossCategoryMoveTarget.
  • Ein Produkt kann per Drag & Drop in eine andere Kategorie gezogen werden (auch in eine leere Kategorie) und landet dort an der erwarteten Position.
  • Umsortieren innerhalb derselben Kategorie funktioniert weiterhin wie bisher.
  • Die "uncategorized"/"virtuelle" Kategorie (negative ID, siehe renderCategorySection(uncategorized, -1)) verhält sich konsistent mit dem entsprechenden Sonderfall auf der Einkaufsliste.

Out of scope for this story:

  • Änderungen am Backend (MovePantryProductCommand) — bereits vollständig, siehe Kontext oben.
  • Die parallele Story zur einheitlichen Mengen-Darstellung/Entfernung der +/- Buttons — unabhängig davon umsetzbar.
## Story: Speisekammer — Drag & Drop zwischen Kategorien **As a** Nutzer der Speisekammer, **I want to** ein Produkt per Drag & Drop in eine andere Kategorie ziehen können, nicht nur innerhalb derselben umsortieren, **so that** ich falsch einsortierte Produkte genauso einfach korrigieren kann wie auf der Einkaufsliste. **Kontext (verifiziert im Code):** - `ShoppingListPage.tsx` hat das bereits: ein einziger, seitenweiter `DndContext`, jede Kategorie ist über `DroppableCategorySection.tsx` (geteilt mit `TodoList.tsx`) ein echtes Drop-Target auch wenn leer, `handleDragEnd` nutzt `resolveCrossCategoryMoveTarget` (`ReactUi/src/utils/reorder.ts`) um Kategorie-Wechsel zu erkennen und ruft `moveShoppingProduct(...)` mit der neuen Ziel-Kategorie auf. - `PantryPage.tsx` hat das **nicht**: `renderCategorySection` erzeugt pro Kategorie einen eigenen, isolierten `DndContext` (`key={categoryId}`), `handleDragEnd(categoryId, categoryProducts)` ruft `movePantryProduct` immer mit derselben, fest zugeordneten `categoryId` auf. Ein Drag kann technisch keine `DndContext`-Grenze überschreiten — Kategorie-Wechsel per Drag ist heute unmöglich. - Backend ist bereits bereit: `MovePantryProductCommandHandler.cs` unterstützt Kategorie-Wechsel schon vollständig (eigener Kommentar: "handles both same-category reorder and cross-category move in one command", inkl. korrekter Neu-Sortierung beider betroffener Kategorien) — das ist eine reine Frontend-Lücke, kein Backend-Change nötig. **Acceptance criteria:** - [ ] `PantryPage.tsx` wird auf denselben Aufbau wie `ShoppingListPage.tsx` umgestellt: ein seitenweiter `DndContext`, Kategorien als `DroppableCategorySection` (Wiederverwendung der bestehenden Komponente), Kategorie-Wechsel-Erkennung über `resolveCrossCategoryMoveTarget`. - [ ] Ein Produkt kann per Drag & Drop in eine andere Kategorie gezogen werden (auch in eine leere Kategorie) und landet dort an der erwarteten Position. - [ ] Umsortieren innerhalb derselben Kategorie funktioniert weiterhin wie bisher. - [ ] Die "uncategorized"/"virtuelle" Kategorie (negative ID, siehe `renderCategorySection(uncategorized, -1)`) verhält sich konsistent mit dem entsprechenden Sonderfall auf der Einkaufsliste. **Out of scope for this story:** - Änderungen am Backend (`MovePantryProductCommand`) — bereits vollständig, siehe Kontext oben. - Die parallele Story zur einheitlichen Mengen-Darstellung/Entfernung der +/- Buttons — unabhängig davon umsetzbar.
lena self-assigned this 2026-09-07 12:21:59 +02:00
Author
Collaborator

Claiming this for the current go-loop cycle. Plan: switch PantryPage.tsx from its current per-category DndContext (renderCategorySection, one DndContext per category, blocking cross-category drag) to a single page-wide DndContext with DroppableCategorySection (shared with ShoppingListPage.tsx), using resolveCrossCategoryMoveTarget the same way ShoppingListPage already does. Backend (MovePantryProductCommandHandler) already supports cross-category moves, so this is frontend-only.

Claiming this for the current go-loop cycle. Plan: switch PantryPage.tsx from its current per-category DndContext (renderCategorySection, one DndContext per category, blocking cross-category drag) to a single page-wide DndContext with DroppableCategorySection (shared with ShoppingListPage.tsx), using resolveCrossCategoryMoveTarget the same way ShoppingListPage already does. Backend (MovePantryProductCommandHandler) already supports cross-category moves, so this is frontend-only.
Author
Collaborator

Done. Switched PantryPage.tsx from a per-category DndContext (which made a drag physically unable to leave its own category, since dnd-kit collision detection cannot cross a DndContext boundary) to a single page-wide DndContext, using DroppableCategorySection + resolveCrossCategoryMoveTarget - the exact same shared pieces ShoppingListPage.tsx already uses (#106). Backend (MovePantryProductCommandHandler) already fully supported cross-category moves, so this was frontend-only.

Covered ACs:

  • Cross-category drag (including into an empty category, which Pantry always renders as a section regardless of item count, unlike Shopping) now works via the shared drop-target resolution.
  • Same-category reorder is unchanged (still goes through the same movePantryProduct call, just resolved via the shared helper now instead of a bespoke index lookup).
  • The uncategorized/virtual bucket (categoryId -1) stays non-draggable and non-droppable, consistent with the shopping-list special case.

Tests: the shared cross-category resolution logic (resolveCrossCategoryMoveTarget) is already exhaustively covered by reorder.test.ts (shared with Shopping/Todo), so no new drag-simulation test was added here - same precedent ShoppingListPage.test.tsx documents for its own Sort-mode tests. All 20 existing PantryPage.test.tsx tests still pass unchanged, confirming the refactor didn't alter any other page behavior. Full frontend suite (1211 tests) green, npm run build/tsc -b clean. No backend changes.

Commit: f47078f5.

Done. Switched `PantryPage.tsx` from a per-category `DndContext` (which made a drag physically unable to leave its own category, since dnd-kit collision detection cannot cross a `DndContext` boundary) to a single page-wide `DndContext`, using `DroppableCategorySection` + `resolveCrossCategoryMoveTarget` - the exact same shared pieces `ShoppingListPage.tsx` already uses (#106). Backend (`MovePantryProductCommandHandler`) already fully supported cross-category moves, so this was frontend-only. Covered ACs: - Cross-category drag (including into an empty category, which Pantry always renders as a section regardless of item count, unlike Shopping) now works via the shared drop-target resolution. - Same-category reorder is unchanged (still goes through the same `movePantryProduct` call, just resolved via the shared helper now instead of a bespoke index lookup). - The uncategorized/virtual bucket (`categoryId -1`) stays non-draggable and non-droppable, consistent with the shopping-list special case. Tests: the shared cross-category resolution logic (`resolveCrossCategoryMoveTarget`) is already exhaustively covered by `reorder.test.ts` (shared with Shopping/Todo), so no new drag-simulation test was added here - same precedent `ShoppingListPage.test.tsx` documents for its own Sort-mode tests. All 20 existing `PantryPage.test.tsx` tests still pass unchanged, confirming the refactor didn't alter any other page behavior. Full frontend suite (1211 tests) green, `npm run build`/`tsc -b` clean. No backend changes. Commit: f47078f5.
lena closed this issue 2026-09-07 12:27:49 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#177
No description provided.