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}
) : (
-
- {selectedZoneItems.map((item) => (
- -
- {item.item_name}
- x{item.quantity}
-
- ))}
-
+ <>
+
+ {selectedZoneItems.map((item) => (
+ -
+ {item.item_name}
+ x{item.quantity}
+
+ ))}
+
+ {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);