diff --git a/frontend/src/components/maps/LocationMapBottomSheet.jsx b/frontend/src/components/maps/LocationMapBottomSheet.jsx index a82c5c9..3d732f9 100644 --- a/frontend/src/components/maps/LocationMapBottomSheet.jsx +++ b/frontend/src/components/maps/LocationMapBottomSheet.jsx @@ -59,9 +59,10 @@ export default function LocationMapBottomSheet({ const selectedAssignedItems = selectedObject?.zone_id ? (mapState?.items || []).filter((item) => String(item.zone_id || "") === objectZoneId(selectedObject)) : []; - const hasHiddenSelectedItems = selectedAssignedItems.length > 0 && selectedZoneItems.length === 0; + const hiddenSelectedItemCount = Math.max(0, selectedAssignedItems.length - selectedZoneItems.length); + const hasHiddenSelectedItems = hiddenSelectedItemCount > 0; const selectedItemCountLabel = hasHiddenSelectedItems - ? "0 shown" + ? `${selectedZoneItems.length} shown` : `${selectedZoneItems.length} item${selectedZoneItems.length === 1 ? "" : "s"}`; const sheetTitle = mode === "edit" ? selectedObject && editorTool === "edit" @@ -383,14 +384,26 @@ export default function LocationMapBottomSheet({ ) : null} ) : ( - + <> + + {hasHiddenSelectedItems ? ( +
+

+ {hiddenSelectedItemCount} more hidden by layer filters. +

+ +
+ ) : null} + )} ) : ( diff --git a/frontend/src/components/maps/LocationMapCanvas.jsx b/frontend/src/components/maps/LocationMapCanvas.jsx index 4c9083c..dd96881 100644 --- a/frontend/src/components/maps/LocationMapCanvas.jsx +++ b/frontend/src/components/maps/LocationMapCanvas.jsx @@ -268,8 +268,9 @@ export default function LocationMapCanvas({ ).length : 0; const count = zoneItems.length; - const countLabel = assignedZoneItemCount > 0 && count === 0 - ? "0 shown" + const hasHiddenZoneItems = assignedZoneItemCount > count; + const countLabel = hasHiddenZoneItems + ? `${count} shown` : `${count} item${count === 1 ? "" : "s"}`; const labelLines = fittedLabelLines(object, "Area"); const countY = filters.showLabels && labelLines.length > 1 ? object.y + 76 : object.y + 52; diff --git a/frontend/src/styles/pages/LocationMapManager.css b/frontend/src/styles/pages/LocationMapManager.css index 5b19359..b5afe21 100644 --- a/frontend/src/styles/pages/LocationMapManager.css +++ b/frontend/src/styles/pages/LocationMapManager.css @@ -639,6 +639,28 @@ cursor: pointer; } +.location-map-zone-hidden-note { + display: grid; + gap: 0.45rem; + margin-top: 0.55rem; +} + +.location-map-zone-hidden-note p { + margin: 0; +} + +.location-map-zone-hidden-note button { + min-height: 40px; + width: fit-content; + padding: 0.45rem 0.7rem; + border: 1px solid var(--color-border-light); + border-radius: 8px; + background: #15283b; + color: #f8fafc; + font-weight: 800; + cursor: pointer; +} + .location-map-zone-items ul, .location-map-unmapped-list ul { display: grid; diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index c4dfa2f..34658b2 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -1587,6 +1587,77 @@ test("viewer explains when selected zone items are hidden by filters", async ({ await expect(page.locator(".location-map-pin")).toHaveCount(0); }); +test("viewer marks partial zone counts when filters hide some items", async ({ page }) => { + await mockMapShell(page); + + const mapState = { + ...publishedMapState([ + { + id: 1552, + location_map_id: 901, + zone_id: 501, + zone_name: "Bakery", + type: "zone", + label: "Bakery", + x: 40, + y: 40, + width: 260, + height: 160, + rotation: 0, + locked: false, + visible: true, + sort_order: 1, + }, + ], true), + items: [ + { + id: 5001, + item_name: "visible bread", + quantity: 1, + bought: false, + zone_id: 501, + zone: "Bakery", + added_by_users: ["map-user"], + }, + { + id: 5002, + item_name: "hidden baguette", + quantity: 1, + bought: false, + zone_id: 501, + zone: "Bakery", + added_by_users: ["other-user"], + }, + ], + }; + + await page.route("**/households/1/locations/10/map", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(mapState), + }); + }); + + await page.goto("/stores/100/locations/10/map"); + + const bakeryArea = page.locator('[data-object-key="1552"]'); + await expect(bakeryArea.locator(".location-map-count")).toHaveText("1 shown"); + + await bakeryArea.locator("rect").first().click(); + await expect(page.locator(".location-map-sheet-count")).toHaveText("1 shown"); + await expect(page.getByText("visible bread")).toBeVisible(); + await expect(page.getByText("hidden baguette")).toHaveCount(0); + await expect(page.getByText("1 more hidden by layer filters.")).toBeVisible(); + await expect(page.getByRole("button", { name: "Show All Zone Items" })).toBeVisible(); + + await page.getByRole("button", { name: "Show All Zone Items" }).click(); + + await expect(page.locator(".location-map-sheet-count")).toHaveText("2 items"); + await expect(page.getByText("hidden baguette")).toBeVisible(); + await expect(bakeryArea.locator(".location-map-count")).toHaveText("2 items"); +}); + test("viewer handles empty and many-item zone panels without default pins", async ({ page }) => { await mockMapShell(page);