fix: clarify map zone item empty states

This commit is contained in:
Nico 2026-06-03 13:21:26 -07:00
parent 607f74e679
commit 138c3e3aa8
3 changed files with 166 additions and 2 deletions

View File

@ -46,7 +46,13 @@ export default function LocationMapBottomSheet({
onDeleteObject, onDeleteObject,
}) { }) {
const selectedTitle = selectedObject?.label || selectedObject?.zone_name || "Map Area"; 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" const sheetTitle = mode === "edit"
? selectedObject && editorTool === "edit" ? selectedObject && editorTool === "edit"
? "Object Settings" ? "Object Settings"
@ -58,6 +64,14 @@ export default function LocationMapBottomSheet({
: "Map Details"; : "Map Details";
const showEditorControls = mode === "edit" && canManage; const showEditorControls = mode === "edit" && canManage;
const showSelectedObjectForm = Boolean(mode === "edit" && editorTool === "edit" && selectedObject); const showSelectedObjectForm = Boolean(mode === "edit" && editorTool === "edit" && selectedObject);
const showSelectedZoneItems = () => {
setFilters((current) => ({
...current,
showMyItems: true,
showOtherItems: true,
showCompleted: true,
}));
};
const editorActions = showEditorControls ? ( const editorActions = showEditorControls ? (
<div className="location-map-editor-actions location-map-primary-actions"> <div className="location-map-editor-actions location-map-primary-actions">
<button type="button" onClick={onAddObject}>Add Area</button> <button type="button" onClick={onAddObject}>Add Area</button>
@ -218,7 +232,18 @@ export default function LocationMapBottomSheet({
<span>{selectedItemCountLabel}</span> <span>{selectedItemCountLabel}</span>
</div> </div>
{selectedZoneItems.length === 0 ? ( {selectedZoneItems.length === 0 ? (
<p className="location-map-muted">No visible items assigned to this zone.</p> <div className="location-map-zone-empty">
<p className="location-map-muted">
{hasHiddenSelectedItems
? "Items are assigned here, but hidden by layer filters."
: "No items assigned to this zone yet."}
</p>
{hasHiddenSelectedItems ? (
<button type="button" onClick={showSelectedZoneItems}>
Show Zone Items
</button>
) : null}
</div>
) : ( ) : (
<ul> <ul>
{selectedZoneItems.map((item) => ( {selectedZoneItems.map((item) => (

View File

@ -477,6 +477,23 @@
text-align: center; 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-zone-items ul,
.location-map-unmapped-list ul { .location-map-unmapped-list ul {
display: grid; display: grid;

View File

@ -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"); 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 }) => { test("mobile editor uses bottom sheet controls instead of desktop object toolbar", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 }); await page.setViewportSize({ width: 390, height: 844 });
await mockMapShell(page); await mockMapShell(page);