diff --git a/frontend/src/components/maps/LocationMapBottomSheet.jsx b/frontend/src/components/maps/LocationMapBottomSheet.jsx
index 3995588..ac6090f 100644
--- a/frontend/src/components/maps/LocationMapBottomSheet.jsx
+++ b/frontend/src/components/maps/LocationMapBottomSheet.jsx
@@ -46,7 +46,13 @@ export default function LocationMapBottomSheet({
onDeleteObject,
}) {
const selectedTitle = selectedObject?.label || selectedObject?.zone_name || "Map Area";
- const selectedItemCountLabel = `${selectedZoneItems.length} item${selectedZoneItems.length === 1 ? "" : "s"}`;
+ const selectedAssignedItems = selectedObject?.zone_id
+ ? (mapState?.items || []).filter((item) => String(item.zone_id || "") === objectZoneId(selectedObject))
+ : [];
+ const hasHiddenSelectedItems = selectedAssignedItems.length > 0 && selectedZoneItems.length === 0;
+ const selectedItemCountLabel = hasHiddenSelectedItems
+ ? "0 shown"
+ : `${selectedZoneItems.length} item${selectedZoneItems.length === 1 ? "" : "s"}`;
const sheetTitle = mode === "edit"
? selectedObject && editorTool === "edit"
? "Object Settings"
@@ -58,6 +64,14 @@ export default function LocationMapBottomSheet({
: "Map Details";
const showEditorControls = mode === "edit" && canManage;
const showSelectedObjectForm = Boolean(mode === "edit" && editorTool === "edit" && selectedObject);
+ const showSelectedZoneItems = () => {
+ setFilters((current) => ({
+ ...current,
+ showMyItems: true,
+ showOtherItems: true,
+ showCompleted: true,
+ }));
+ };
const editorActions = showEditorControls ? (
@@ -218,7 +232,18 @@ export default function LocationMapBottomSheet({
{selectedItemCountLabel}
{selectedZoneItems.length === 0 ? (
- No visible items assigned to this zone.
+
+
+ {hasHiddenSelectedItems
+ ? "Items are assigned here, but hidden by layer filters."
+ : "No items assigned to this zone yet."}
+
+ {hasHiddenSelectedItems ? (
+
+ ) : null}
+
) : (
{selectedZoneItems.map((item) => (
diff --git a/frontend/src/styles/pages/LocationMapManager.css b/frontend/src/styles/pages/LocationMapManager.css
index e8c23db..a3c7ee5 100644
--- a/frontend/src/styles/pages/LocationMapManager.css
+++ b/frontend/src/styles/pages/LocationMapManager.css
@@ -477,6 +477,23 @@
text-align: center;
}
+.location-map-zone-empty {
+ display: grid;
+ gap: 0.55rem;
+}
+
+.location-map-zone-empty 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 c53f187..e7cdd44 100644
--- a/frontend/tests/location-map-manager.spec.ts
+++ b/frontend/tests/location-map-manager.spec.ts
@@ -480,6 +480,128 @@ test("viewer wraps long zone labels and keeps item counts visible", async ({ pag
await expect(produceArea.locator(".location-map-count")).toHaveAttribute("y", "92");
});
+test("viewer explains when selected zone items are hidden by filters", async ({ page }) => {
+ await mockMapShell(page);
+
+ const mapState = publishedMapState([
+ {
+ id: 1551,
+ location_map_id: 901,
+ zone_id: 502,
+ zone_name: "Frozen Foods",
+ type: "zone",
+ label: "Frozen Foods",
+ x: 40,
+ y: 40,
+ width: 260,
+ height: 160,
+ rotation: 0,
+ locked: false,
+ visible: true,
+ sort_order: 1,
+ },
+ ], true);
+
+ 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 frozenArea = page.locator('[data-object-key="1551"]');
+ await expect(frozenArea.locator(".location-map-count")).toHaveText("0 items");
+ await expect(page.locator(".location-map-pin")).toHaveCount(0);
+
+ await frozenArea.locator("rect").first().click();
+ await expect(page.locator(".location-map-zone-items-title span")).toHaveText("0 shown");
+ await expect(page.getByText("Items are assigned here, but hidden by layer filters.")).toBeVisible();
+ await expect(page.getByText("frozen salmon")).toHaveCount(0);
+
+ await page.getByRole("button", { name: "Show Zone Items" }).click();
+ await expect(page.locator(".location-map-zone-items-title span")).toHaveText("1 item");
+ await expect(page.getByText("frozen salmon")).toBeVisible();
+ await expect(frozenArea.locator(".location-map-count")).toHaveText("1 item");
+ await expect(page.locator(".location-map-pin")).toHaveCount(0);
+});
+
+test("viewer handles empty and many-item zone panels without default pins", async ({ page }) => {
+ await mockMapShell(page);
+
+ const manyItems = Array.from({ length: 14 }, (_, index) => ({
+ id: 3000 + index,
+ item_name: `bulk item ${index + 1}`,
+ quantity: index + 1,
+ bought: false,
+ zone_id: 501,
+ zone: "Bakery",
+ added_by_users: ["map-user"],
+ }));
+ const mapState = {
+ ...publishedMapState([
+ {
+ id: 1561,
+ 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,
+ },
+ {
+ id: 1562,
+ location_map_id: 901,
+ zone_id: 502,
+ zone_name: "Frozen Foods",
+ type: "zone",
+ label: "Frozen Foods",
+ x: 340,
+ y: 40,
+ width: 260,
+ height: 160,
+ rotation: 0,
+ locked: false,
+ visible: true,
+ sort_order: 2,
+ },
+ ], true),
+ items: manyItems,
+ unmapped_count: 0,
+ };
+
+ 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");
+ await expect(page.locator(".location-map-pin")).toHaveCount(0);
+
+ await page.locator('[data-object-key="1562"]').locator("rect").first().click();
+ await expect(page.locator(".location-map-zone-items-title span")).toHaveText("0 items");
+ await expect(page.getByText("No items assigned to this zone yet.")).toBeVisible();
+
+ await page.locator('[data-object-key="1561"]').locator("rect").first().click();
+ await expect(page.locator(".location-map-zone-items-title span")).toHaveText("14 items");
+ await expect(page.locator(".location-map-zone-items li")).toHaveCount(14);
+ await expect(page.locator(".location-map-zone-items li").first()).toContainText("bulk item 1");
+ await expect(page.locator(".location-map-zone-items li").last()).toContainText("bulk item 14");
+ await expect(page.locator(".location-map-pin")).toHaveCount(0);
+});
+
test("mobile editor uses bottom sheet controls instead of desktop object toolbar", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await mockMapShell(page);