diff --git a/frontend/src/components/maps/LocationMapBottomSheet.jsx b/frontend/src/components/maps/LocationMapBottomSheet.jsx
index 1fe49c2..39ce0c6 100644
--- a/frontend/src/components/maps/LocationMapBottomSheet.jsx
+++ b/frontend/src/components/maps/LocationMapBottomSheet.jsx
@@ -72,6 +72,8 @@ export default function LocationMapBottomSheet({
const showEditorControls = mode === "edit" && canManage;
const showSelectedObjectForm = Boolean(mode === "edit" && editorTool === "edit" && selectedObject);
const changedLayerCount = DISPLAY_CONTROLS.filter(([key]) => filters[key] !== DEFAULT_MAP_FILTERS[key]).length;
+ const unmappedItemPreview = visibleUnmappedItems.slice(0, 8);
+ const hiddenUnmappedItemCount = visibleUnmappedItems.length - unmappedItemPreview.length;
const showSelectedZoneItems = () => {
setFilters((current) => ({
...current,
@@ -303,9 +305,17 @@ export default function LocationMapBottomSheet({
Unmapped Items
- {visibleUnmappedItems.slice(0, 8).map((item) => (
+ {unmappedItemPreview.map((item) => (
- {item.item_name}
))}
+ {hiddenUnmappedItemCount > 0 ? (
+ -
+ +{hiddenUnmappedItemCount} more
+
+ ) : null}
) : null}
diff --git a/frontend/src/styles/pages/LocationMapManager.css b/frontend/src/styles/pages/LocationMapManager.css
index 3e57f3b..835b68c 100644
--- a/frontend/src/styles/pages/LocationMapManager.css
+++ b/frontend/src/styles/pages/LocationMapManager.css
@@ -581,6 +581,13 @@
margin-bottom: 0.45rem;
}
+.location-map-unmapped-list .location-map-unmapped-more {
+ justify-content: center;
+ border-style: dashed;
+ color: #bfdbfe;
+ font-weight: 800;
+}
+
.location-map-setup {
align-self: center;
width: min(100% - 1.5rem, 560px);
diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts
index ba4cc29..7c02395 100644
--- a/frontend/tests/location-map-manager.spec.ts
+++ b/frontend/tests/location-map-manager.spec.ts
@@ -732,6 +732,42 @@ test("viewer wraps long zone labels and keeps item counts visible", async ({ pag
await expect(produceArea.locator(".location-map-count")).toHaveAttribute("y", "116");
});
+test("viewer shows a compact overflow cue for long unmapped lists", async ({ page }) => {
+ await mockMapShell(page);
+
+ const unmappedItems = Array.from({ length: 11 }, (_, index) => ({
+ id: 4000 + index,
+ item_name: `unmapped item ${index + 1}`,
+ quantity: 1,
+ bought: false,
+ zone_id: null,
+ zone: null,
+ added_by_users: ["map-user"],
+ }));
+ const mapState = {
+ ...publishedMapState([], true),
+ items: unmappedItems,
+ unmapped_count: unmappedItems.length,
+ };
+
+ 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 page.getByRole("button", { name: "Layers" }).click();
+ await page.getByLabel("Unmapped").check();
+
+ await expect(page.getByText("unmapped item 1")).toBeVisible();
+ await expect(page.getByText("unmapped item 8")).toBeVisible();
+ await expect(page.getByText("unmapped item 9")).toHaveCount(0);
+ await expect(page.getByLabel("3 more unmapped items")).toHaveText("+3 more");
+});
+
test("viewer explains when selected zone items are hidden by filters", async ({ page }) => {
await mockMapShell(page);