fix: summarize unselected location maps

This commit is contained in:
Nico 2026-06-04 03:09:19 -07:00
parent 4fd3807ebf
commit 7f73a3aed3
4 changed files with 118 additions and 0 deletions

View File

@ -27,7 +27,9 @@ export default function LocationMapBottomSheet({
layersOpen,
setLayersOpen,
selectedObject,
mapObjects = [],
selectedZoneItems,
visibleAssignedItemCount = 0,
visibleUnmappedItems,
unmappedItemCount,
hiddenAreaCount,
@ -67,6 +69,12 @@ 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 assignedItemCount = (mapState?.items || []).filter((item) => item.zone_id).length;
const totalAreaCount = mapObjects.length;
const visibleAreaCount = filters.showZones
? Math.max(0, totalAreaCount - hiddenAreaCount)
: 0;
const showMapOverview = mode !== "edit" && !selectedObject;
const unmappedItemPreview = visibleUnmappedItems.slice(0, 8);
const overflowUnmappedItemCount = visibleUnmappedItems.length - unmappedItemPreview.length;
const hiddenUnmappedItemCount = Math.max(0, unmappedItemCount - visibleUnmappedItems.length);
@ -75,6 +83,14 @@ export default function LocationMapBottomSheet({
const hiddenAreaLabel = `Show Hidden Areas (${hiddenAreaCount})`;
const saveDraftLabel = savingAction === "save" ? "Saving..." : "Save Draft";
const publishLabel = savingAction === "publish" ? "Publishing..." : "Publish";
const mappedItemSummary = assignedItemCount === visibleAssignedItemCount
? String(assignedItemCount)
: `${visibleAssignedItemCount} shown`;
const unmappedItemSummary = !filters.showUnmapped && unmappedItemCount > 0
? `${unmappedItemCount} hidden`
: visibleUnmappedItems.length === unmappedItemCount
? String(unmappedItemCount)
: `${visibleUnmappedItems.length} shown`;
const resetLayerFilters = () => setFilters({ ...DEFAULT_MAP_FILTERS });
const showSelectedZoneItems = () => {
setFilters((current) => ({
@ -200,6 +216,23 @@ export default function LocationMapBottomSheet({
</div>
) : null}
{showMapOverview ? (
<div className="location-map-overview" aria-label="Map summary">
<div className="location-map-overview-row">
<span>Areas</span>
<strong>{filters.showZones ? `${visibleAreaCount} shown` : "Hidden"}</strong>
</div>
<div className="location-map-overview-row">
<span>Mapped items</span>
<strong>{mappedItemSummary}</strong>
</div>
<div className="location-map-overview-row">
<span>Unmapped</span>
<strong>{unmappedItemSummary}</strong>
</div>
</div>
) : null}
{mode === "edit" && editorTool === "edit" && selectedObject ? (
<div className="location-map-object-form">
<label className="location-map-field-row">

View File

@ -27,6 +27,7 @@ import {
getMapObjectDisplayLabel,
getMapStatus,
getObjectKey,
itemMatchesMapFilters,
itemsForZone,
mapForMode,
normalizeMapObject,
@ -81,6 +82,9 @@ export default function LocationMapManager() {
? itemsForZone(mapState?.items || [], selectedObject.zone_id, filters, username)
: [];
const visibleUnmappedItems = unmappedItems(mapState?.items || [], filters, username);
const visibleAssignedItemCount = (mapState?.items || []).filter((item) => (
item.zone_id && itemMatchesMapFilters(item, filters, username)
)).length;
const unmappedItemCount = (mapState?.items || []).filter((item) => !item.zone_id).length;
const hiddenAreaCount = objects.filter((object) => object.visible === false).length;
const shouldGuardLeave = canManage && hasUnsavedChanges;
@ -608,7 +612,9 @@ export default function LocationMapManager() {
layersOpen={layersOpen}
setLayersOpen={setLayersOpen}
selectedObject={selectedObject}
mapObjects={objects}
selectedZoneItems={selectedZoneItems}
visibleAssignedItemCount={visibleAssignedItemCount}
visibleUnmappedItems={visibleUnmappedItems}
unmappedItemCount={unmappedItemCount}
hiddenAreaCount={hiddenAreaCount}

View File

@ -580,6 +580,35 @@
cursor: pointer;
}
.location-map-overview {
display: grid;
gap: 0.35rem;
}
.location-map-overview-row {
min-height: 38px;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: 0.75rem;
padding: 0.35rem 0.55rem;
border: 1px solid var(--color-border-light);
border-radius: 8px;
background: rgba(15, 23, 34, 0.56);
}
.location-map-overview-row span {
color: #9fb3c8;
font-size: 0.82rem;
font-weight: 800;
}
.location-map-overview-row strong {
color: #f8fafc;
font-weight: 900;
white-space: nowrap;
}
.location-map-object-form {
display: grid;
gap: 0.45rem;

View File

@ -1478,6 +1478,56 @@ test("viewer explains when the zones layer hides the map", async ({ page }) => {
await expect(page.getByRole("button", { name: "Layers" })).toBeVisible();
});
test("viewer shows a compact map overview before selecting an area", async ({ page }) => {
await mockMapShell(page);
const mapState = publishedMapState([
{
id: 1531,
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);
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 overview = page.getByLabel("Map summary");
const areasRow = overview.locator(".location-map-overview-row", { hasText: "Areas" });
const mappedRow = overview.locator(".location-map-overview-row", { hasText: "Mapped items" });
const unmappedRow = overview.locator(".location-map-overview-row", { hasText: "Unmapped" });
await expect(overview).toBeVisible();
await expect(areasRow.locator("strong")).toHaveText("1 shown");
await expect(mappedRow.locator("strong")).toHaveText("2 shown");
await expect(unmappedRow.locator("strong")).toHaveText("1 hidden");
await page.getByRole("button", { name: "Layers" }).click();
await page.getByRole("button", { name: "Unmapped" }).click();
await expect(unmappedRow.locator("strong")).toHaveText("1");
await page.getByRole("button", { name: "Map area Bakery" }).click();
await expect(page.getByLabel("Map summary")).toHaveCount(0);
await expect(page.locator(".location-map-sheet-header strong")).toHaveText("Bakery");
});
test("viewer shows a compact overflow cue for long unmapped lists", async ({ page }) => {
await mockMapShell(page);