#110 — Einkaufsliste — UI-Kleinigkeiten (Menü-Sichtbarkeit, Mengen-Anzeige, Online-Warenkorb) #113

Closed
opened 2026-08-18 13:14:11 +02:00 by lena · 1 comment
lena commented 2026-08-18 13:14:11 +02:00 (Migrated from git.butzei.de)

Story: Einkaufsliste — UI-Kleinigkeiten (Menü-Sichtbarkeit, Mengen-Anzeige, Online-Warenkorb)

As a Nutzer der Einkaufsliste,
I want to ein paar kleinere Inkonsistenzen zwischen Einkaufsliste und Todo-Liste behoben haben,
so that sich beide Listentypen gleich bedienen lassen.

Vier unabhängige, jeweils kleine Punkte, gebündelt in einer Story:

1. "..."-Menü in der Listen-Übersicht immer sichtbar

  • Verifiziert im Code: ShoppingListItem.tsxs Menü-Button nutzt opacity-0 group-hover:opacity-100,
    ListActionsMenu.tsx (Todo-Pendant) nutzt opacity-50 group-hover:opacity-100 — die Einkaufsliste ist
    also unsichtbar statt nur abgeblendet ohne Hover.
  • AC: ShoppingListItem.tsxs Menü-Button auf dasselbe opacity-50-Verhalten wie ListActionsMenu.tsx
    umstellen.

2. Mengen-Anzeige: Präfix statt Suffix, normale statt größerer Schrift

  • AC: Wo eine Produktmenge neben dem Namen angezeigt wird, steht die Zahl/Menge vor dem Produktnamen (z. B.
    "5 Dosen Kokosmilch" statt "Dosen Kokosmilch 5"), in derselben Schriftgröße wie der Produktname.

3. Online-Warenkorb — Rückweg

  • Verifiziert im Code: ShoppingCartCheckPanel ist kein eigener Screen/eine eigene Route, sondern ein
    einblendbares Panel innerhalb von ShoppingListPage.tsx mit einem funktionierenden ✕-Schließen-Button
    (onClose); die normale Produktliste bleibt technisch die ganze Zeit im DOM. Der gemeldete "kein Weg
    zurück"-Eindruck deutet auf ein Auffindbarkeits-/Layout-Problem hin (z. B. auf kleinen Viewports der
    Schließen-Button außerhalb des sichtbaren Bereichs), nicht auf eine fehlende Funktion.
  • AC: Der Rückweg aus dem Online-Warenkorb-Panel muss auf allen unterstützten Viewport-Größen (insbesondere
    mobil) sichtbar und erreichbar sein, ohne zu scrollen oder zu suchen.

4. Online-Warenkorb — Sammel-Löschen abgehakter Punkte

  • As a Nutzer, I want to im Online-Warenkorb alle bereits dort abgehakten (IsInCart = true) Punkte
    auf einmal von der Einkaufsliste entfernen können, so that ich nach dem Abschicken einer
    Online-Bestellung nicht jeden Punkt einzeln abhaken/löschen muss.
  • AC: Ein neuer Button/Aktion im Online-Warenkorb-Panel entfernt (deaktiviert, analog zum normalen
    Abhaken-Verhalten je nach Ausgang von Story #109) alle Produkte mit IsInCart = true in einem Schritt.
  • AC: Produkte, die noch nicht im Online-Warenkorb abgehakt sind, bleiben unangetastet.
  • AC: Bestätigungsdialog vor dem Sammel-Löschen (destruktive Sammelaktion, analog zu bestehenden
    Lösch-Bestätigungen in dieser Codebase).

Out of scope for this story:

  • Alles, was in den eigenen Stories #103#109 behandelt wird.
# Story: Einkaufsliste — UI-Kleinigkeiten (Menü-Sichtbarkeit, Mengen-Anzeige, Online-Warenkorb) **As a** Nutzer der Einkaufsliste, **I want to** ein paar kleinere Inkonsistenzen zwischen Einkaufsliste und Todo-Liste behoben haben, **so that** sich beide Listentypen gleich bedienen lassen. Vier unabhängige, jeweils kleine Punkte, gebündelt in einer Story: **1. "..."-Menü in der Listen-Übersicht immer sichtbar** - Verifiziert im Code: `ShoppingListItem.tsx`s Menü-Button nutzt `opacity-0 group-hover:opacity-100`, `ListActionsMenu.tsx` (Todo-Pendant) nutzt `opacity-50 group-hover:opacity-100` — die Einkaufsliste ist also *unsichtbar* statt nur *abgeblendet* ohne Hover. - AC: `ShoppingListItem.tsx`s Menü-Button auf dasselbe `opacity-50`-Verhalten wie `ListActionsMenu.tsx` umstellen. **2. Mengen-Anzeige: Präfix statt Suffix, normale statt größerer Schrift** - AC: Wo eine Produktmenge neben dem Namen angezeigt wird, steht die Zahl/Menge vor dem Produktnamen (z. B. "5 Dosen Kokosmilch" statt "Dosen Kokosmilch 5"), in derselben Schriftgröße wie der Produktname. **3. Online-Warenkorb — Rückweg** - Verifiziert im Code: `ShoppingCartCheckPanel` ist kein eigener Screen/eine eigene Route, sondern ein einblendbares Panel innerhalb von `ShoppingListPage.tsx` mit einem funktionierenden ✕-Schließen-Button (`onClose`); die normale Produktliste bleibt technisch die ganze Zeit im DOM. Der gemeldete "kein Weg zurück"-Eindruck deutet auf ein Auffindbarkeits-/Layout-Problem hin (z. B. auf kleinen Viewports der Schließen-Button außerhalb des sichtbaren Bereichs), nicht auf eine fehlende Funktion. - AC: Der Rückweg aus dem Online-Warenkorb-Panel muss auf allen unterstützten Viewport-Größen (insbesondere mobil) sichtbar und erreichbar sein, ohne zu scrollen oder zu suchen. **4. Online-Warenkorb — Sammel-Löschen abgehakter Punkte** - **As a** Nutzer, **I want to** im Online-Warenkorb alle bereits dort abgehakten (`IsInCart = true`) Punkte auf einmal von der Einkaufsliste entfernen können, **so that** ich nach dem Abschicken einer Online-Bestellung nicht jeden Punkt einzeln abhaken/löschen muss. - AC: Ein neuer Button/Aktion im Online-Warenkorb-Panel entfernt (deaktiviert, analog zum normalen Abhaken-Verhalten je nach Ausgang von Story `#109`) alle Produkte mit `IsInCart = true` in einem Schritt. - AC: Produkte, die noch nicht im Online-Warenkorb abgehakt sind, bleiben unangetastet. - AC: Bestätigungsdialog vor dem Sammel-Löschen (destruktive Sammelaktion, analog zu bestehenden Lösch-Bestätigungen in dieser Codebase). **Out of scope for this story:** - Alles, was in den eigenen Stories `#103`–`#109` behandelt wird.
lena commented 2026-08-18 13:14:11 +02:00 (Migrated from git.butzei.de)

design (110_shopping_ui_polish_bundle_design.md)

Design note — #110 Einkaufsliste UI-Kleinigkeiten

Four independent frontend-only fixes, no backend/DB changes and no new commands — every mutation
path already exists.

1. "..."-Menü immer sichtbar

ShoppingListItem.tsx's menu button switches from opacity-0 group-hover:opacity-100 to
opacity-50 group-hover:opacity-100, matching ListActionsMenu.tsx's Todo-list equivalent
verbatim.

2. Mengen-Anzeige als Präfix

ShoppingQuantity is a free-text field (e.g. "5 Dosen"), not a number — the AC's example ("5
Dosen Kokosmilch") is just quantity-text-then-name concatenation with a space, no new formatting
logic needed. Two render sites show quantity next to a product name:

  • ShoppingProductItem.tsx (the main list row)
  • RecentlyCheckedOffPanel.tsx (the restore-history panel)

Both move the {product.quantity} span before the name span and drop the text-xs size class (so
it inherits the row's text-sm, same as the name) — color (text-muted-foreground) is unchanged,
only prefix/suffix order and size were in scope per the AC.

ShoppingCartCheckPanel.tsx intentionally shows name only (no quantity) — pre-existing, unrelated
to this story, and covered by its own test ('name only (no quantity/comments)').

3. Online-Warenkorb — Rückweg immer erreichbar

Root cause confirmed: ShoppingCartCheckPanel's outer div applied max-h-64 overflow-y-auto to
both the header (title + ✕) and the product list together. With enough products, scrolling the
list scrolls the header off-screen — no separate route, no missing handler, just a layout bug that
makes a working close button unreachable without scrolling back up first (exactly the reported
symptom).

Fixed by splitting into a fixed header (shrink-0, never scrolls) and a separately-scrollable body
— the same structure RecentlyCheckedOffPanel.tsx already uses correctly one file over. Verified
at mobile width (375px) in the local dev server: header with ✕ stays pinned regardless of list
length or scroll position.

4. Sammel-Löschen abgehakter Online-Warenkorb-Punkte

No new backend command: DeactivateShoppingProductCommand already clears IsOnList, IsInCart,
and InCartSource in one call — exactly what "remove from list" needs per product, and it's the
same command the individual checkbox/quantity-dialog paths already use. Bulk removal composes it
in a sequential loop from the frontend, the same "compose existing single-item commands" tradeoff
already established for CSV import (#83) rather than adding a new batch endpoint for a low-volume,
owner-only-triggered action.

New ShoppingCartCheckPanel prop onRemoveInCartItems: (products: ShoppingProductDto[]) => Promise<void>.
ShoppingListPage implements it: loop deactivateShoppingProduct + patchProduct +
pushShoppingCheckedOff per item — literally handleCheck's existing body (renamed from
handleUncheck by #109, which landed mid-cycle and was merged into this branch before this story
was implemented), called once per in-cart product, so removed items land in "Recently checked off"
exactly like a normal manual check-off. Matches the AC's "analog zum normalen
Abhaken-Verhalten" against #109's now-delivered semantics (checking a box off removes the item from
the active list).

Button only renders when at least one visible product has isInCart; a confirm dialog (same
pattern as every other destructive action in this codebase — ShoppingListItem's delete dialog,
ShoppingProductItem's delete dialog) gates the actual removal. Products not in the cart are
untouched (the loop only ever iterates the filtered in-cart subset).

**design** (`110_shopping_ui_polish_bundle_design.md`) # Design note — `#110` Einkaufsliste UI-Kleinigkeiten Four independent frontend-only fixes, no backend/DB changes and no new commands — every mutation path already exists. ## 1. "..."-Menü immer sichtbar `ShoppingListItem.tsx`'s menu button switches from `opacity-0 group-hover:opacity-100` to `opacity-50 group-hover:opacity-100`, matching `ListActionsMenu.tsx`'s Todo-list equivalent verbatim. ## 2. Mengen-Anzeige als Präfix `ShoppingQuantity` is a free-text field (e.g. `"5 Dosen"`), not a number — the AC's example ("5 Dosen Kokosmilch") is just quantity-text-then-name concatenation with a space, no new formatting logic needed. Two render sites show quantity next to a product name: - `ShoppingProductItem.tsx` (the main list row) - `RecentlyCheckedOffPanel.tsx` (the restore-history panel) Both move the `{product.quantity}` span before the name span and drop the `text-xs` size class (so it inherits the row's `text-sm`, same as the name) — color (`text-muted-foreground`) is unchanged, only prefix/suffix order and size were in scope per the AC. `ShoppingCartCheckPanel.tsx` intentionally shows name only (no quantity) — pre-existing, unrelated to this story, and covered by its own test (`'name only (no quantity/comments)'`). ## 3. Online-Warenkorb — Rückweg immer erreichbar Root cause confirmed: `ShoppingCartCheckPanel`'s outer `div` applied `max-h-64 overflow-y-auto` to *both* the header (title + ✕) and the product list together. With enough products, scrolling the list scrolls the header off-screen — no separate route, no missing handler, just a layout bug that makes a working close button unreachable without scrolling back up first (exactly the reported symptom). Fixed by splitting into a fixed header (`shrink-0`, never scrolls) and a separately-scrollable body — the same structure `RecentlyCheckedOffPanel.tsx` already uses correctly one file over. Verified at mobile width (375px) in the local dev server: header with ✕ stays pinned regardless of list length or scroll position. ## 4. Sammel-Löschen abgehakter Online-Warenkorb-Punkte No new backend command: `DeactivateShoppingProductCommand` already clears `IsOnList`, `IsInCart`, and `InCartSource` in one call — exactly what "remove from list" needs per product, and it's the same command the individual checkbox/quantity-dialog paths already use. Bulk removal composes it in a sequential loop from the frontend, the same "compose existing single-item commands" tradeoff already established for CSV import (`#83`) rather than adding a new batch endpoint for a low-volume, owner-only-triggered action. New `ShoppingCartCheckPanel` prop `onRemoveInCartItems: (products: ShoppingProductDto[]) => Promise<void>`. `ShoppingListPage` implements it: loop `deactivateShoppingProduct` + `patchProduct` + `pushShoppingCheckedOff` per item — literally `handleCheck`'s existing body (renamed from `handleUncheck` by `#109`, which landed mid-cycle and was merged into this branch before this story was implemented), called once per in-cart product, so removed items land in "Recently checked off" exactly like a normal manual check-off. Matches the AC's "analog zum normalen Abhaken-Verhalten" against `#109`'s now-delivered semantics (checking a box off removes the item from the active list). Button only renders when at least one visible product has `isInCart`; a confirm dialog (same pattern as every other destructive action in this codebase — `ShoppingListItem`'s delete dialog, `ShoppingProductItem`'s delete dialog) gates the actual removal. Products not in the cart are untouched (the loop only ever iterates the filtered in-cart subset).
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#113
No description provided.