Compare commits
3 Commits
85061b93d1
...
a2db8cd655
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2db8cd655 | ||
|
|
5f1eb30080 | ||
|
|
11979af190 |
@ -55,6 +55,21 @@ export default function LocationMapCanvas({
|
||||
const objectDragHistoryCapturedRef = useRef(false);
|
||||
const suppressPanClickRef = useRef(false);
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
const orderedObjects = useMemo(() => {
|
||||
if (!selectedObjectKey) return objects;
|
||||
|
||||
const selectedObjects = [];
|
||||
const remainingObjects = [];
|
||||
objects.forEach((object) => {
|
||||
if (getObjectKey(object) === selectedObjectKey) {
|
||||
selectedObjects.push(object);
|
||||
return;
|
||||
}
|
||||
remainingObjects.push(object);
|
||||
});
|
||||
|
||||
return [...remainingObjects, ...selectedObjects];
|
||||
}, [objects, selectedObjectKey]);
|
||||
const zoneItemSummary = useMemo(() => {
|
||||
const assignedCounts = new Map();
|
||||
const visibleItemsByZone = new Map();
|
||||
@ -324,13 +339,7 @@ export default function LocationMapCanvas({
|
||||
{mode === "edit" ? (
|
||||
<rect className="location-map-grid" width={mapSize.width} height={mapSize.height} />
|
||||
) : null}
|
||||
{(selectedObjectKey
|
||||
? [
|
||||
...objects.filter((object) => getObjectKey(object) !== selectedObjectKey),
|
||||
...objects.filter((object) => getObjectKey(object) === selectedObjectKey),
|
||||
]
|
||||
: objects
|
||||
).map(renderMapObject)}
|
||||
{orderedObjects.map(renderMapObject)}
|
||||
</svg>
|
||||
{emptyCanvasMessage ? (
|
||||
<div
|
||||
|
||||
@ -11,13 +11,9 @@ export function LocationMapMessageState({ message, location, status = "Loading",
|
||||
<main className="location-map-workspace location-map-message-workspace">
|
||||
<section className="location-map-setup location-map-message-card" aria-live="polite">
|
||||
<h2>{status}</h2>
|
||||
<div className="location-map-setup-rows" role="group" aria-label="Map message status">
|
||||
<div className="location-map-setup-rows" role="group" aria-label="Map message details">
|
||||
<div className="location-map-setup-row">
|
||||
<span>Status</span>
|
||||
<strong>{status}</strong>
|
||||
</div>
|
||||
<div className="location-map-setup-row">
|
||||
<span>Detail</span>
|
||||
<span>Message</span>
|
||||
<strong>{message}</strong>
|
||||
</div>
|
||||
</div>
|
||||
@ -34,13 +30,9 @@ export function LocationMapLoadErrorState({ location, loadError, onBack, onRetry
|
||||
<main className="location-map-workspace">
|
||||
<section className="location-map-setup location-map-load-error" role="alert" aria-live="polite">
|
||||
<h2>Map Unavailable</h2>
|
||||
<div className="location-map-setup-rows" role="group" aria-label="Map load status">
|
||||
<div className="location-map-setup-rows" role="group" aria-label="Map load details">
|
||||
<div className="location-map-setup-row">
|
||||
<span>Status</span>
|
||||
<strong>Load failed</strong>
|
||||
</div>
|
||||
<div className="location-map-setup-row">
|
||||
<span>Detail</span>
|
||||
<span>Error</span>
|
||||
<strong>{loadError}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -104,6 +104,7 @@ export default function LocationMapToolbar({
|
||||
<div
|
||||
className={`location-map-history-buttons ${canUseHistoryControls ? "" : "is-inactive"}`}
|
||||
aria-label="Edit history"
|
||||
aria-hidden={!canUseHistoryControls}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@ -184,6 +184,11 @@
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.location-map-history-buttons.is-inactive {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.location-map-history-buttons button {
|
||||
min-width: 40px;
|
||||
padding-inline: 0.55rem;
|
||||
|
||||
@ -465,9 +465,10 @@ test("map message prompts for a household with compact status rows", async ({ pa
|
||||
await page.goto("/stores/100/locations/10/map");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Select Household" })).toBeVisible();
|
||||
const messageStatus = page.getByRole("group", { name: "Map message status" });
|
||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Status" })).toContainText("Select Household");
|
||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Detail" })).toContainText("Select a household to manage maps.");
|
||||
const messageDetails = page.getByRole("group", { name: "Map message details" });
|
||||
await expect(messageDetails.locator(".location-map-setup-row")).toHaveCount(1);
|
||||
await expect(messageDetails.locator(".location-map-setup-row", { hasText: "Message" })).toContainText("Select a household to manage maps.");
|
||||
await expect(messageDetails.locator(".location-map-setup-row", { hasText: "Status" })).toHaveCount(0);
|
||||
await expect(page.locator(".location-map-message-card p")).toHaveCount(0);
|
||||
await expect(page.getByLabel("Map controls")).toHaveCount(0);
|
||||
});
|
||||
@ -517,9 +518,10 @@ test("load failure shows retryable error instead of no-map setup", async ({ page
|
||||
|
||||
const errorCard = page.locator(".location-map-load-error");
|
||||
await expect(errorCard.getByRole("heading", { name: "Map Unavailable" })).toBeVisible();
|
||||
const loadStatus = errorCard.getByRole("group", { name: "Map load status" });
|
||||
await expect(loadStatus.locator(".location-map-setup-row", { hasText: "Status" })).toContainText("Load failed");
|
||||
await expect(loadStatus.locator(".location-map-setup-row", { hasText: "Detail" })).toContainText("Map service unavailable");
|
||||
const loadDetails = errorCard.getByRole("group", { name: "Map load details" });
|
||||
await expect(loadDetails.locator(".location-map-setup-row")).toHaveCount(1);
|
||||
await expect(loadDetails.locator(".location-map-setup-row", { hasText: "Error" })).toContainText("Map service unavailable");
|
||||
await expect(loadDetails.locator(".location-map-setup-row", { hasText: "Status" })).toHaveCount(0);
|
||||
await expect(errorCard.locator("p")).toHaveCount(0);
|
||||
await expect(page.getByText("Load Error")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "No Map" })).toHaveCount(0);
|
||||
@ -585,9 +587,10 @@ test("loading map keeps location context visible", async ({ page }) => {
|
||||
await expect(page.getByText("Eastvale")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Back to grocery list" })).toBeVisible();
|
||||
await expect(page.locator(".location-map-status")).toHaveText("Loading");
|
||||
const messageStatus = page.getByRole("group", { name: "Map message status" });
|
||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Status" })).toContainText("Loading");
|
||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Detail" })).toContainText("Loading map...");
|
||||
const messageDetails = page.getByRole("group", { name: "Map message details" });
|
||||
await expect(messageDetails.locator(".location-map-setup-row")).toHaveCount(1);
|
||||
await expect(messageDetails.locator(".location-map-setup-row", { hasText: "Message" })).toContainText("Loading map...");
|
||||
await expect(messageDetails.locator(".location-map-setup-row", { hasText: "Status" })).toHaveCount(0);
|
||||
await expect(page.locator(".location-map-message-card p")).toHaveCount(0);
|
||||
|
||||
releaseMapResponse();
|
||||
@ -1391,15 +1394,15 @@ test("mobile keeps draft preview status compact in the topbar", async ({ page })
|
||||
expect(modeBox.width).toBeLessThanOrEqual(126);
|
||||
expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(82);
|
||||
expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1);
|
||||
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeVisible();
|
||||
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeVisible();
|
||||
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeDisabled();
|
||||
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeDisabled();
|
||||
await expect(historyActions).toHaveCSS("visibility", "hidden");
|
||||
await expect(historyActions.locator('button[aria-label="Undo"]')).toBeDisabled();
|
||||
await expect(historyActions.locator('button[aria-label="Redo"]')).toBeDisabled();
|
||||
expect(zoomBox.y).toBeLessThan(modeBox.y + modeBox.height);
|
||||
expect(zoomBox.x).toBeGreaterThan(reservedHistoryBox.x + reservedHistoryBox.width - 1);
|
||||
expect(zoomBox.x + zoomBox.width).toBeLessThanOrEqual(toolbarBox.x + toolbarBox.width + 1);
|
||||
|
||||
await page.getByRole("button", { name: "Edit Draft" }).click();
|
||||
await expect(historyActions).toHaveCSS("visibility", "visible");
|
||||
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeVisible();
|
||||
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeVisible();
|
||||
const editToolbarBox = await toolbar.boundingBox();
|
||||
@ -1778,6 +1781,68 @@ test("admin locked map areas hide resize affordances", async ({ page }) => {
|
||||
await expect(page.locator(".location-map-resize-handle")).toBeVisible();
|
||||
});
|
||||
|
||||
test("admin selected overlapping map area renders above other areas", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
const mapState = draftMapState([
|
||||
{
|
||||
id: 1311,
|
||||
location_map_id: 900,
|
||||
zone_id: 501,
|
||||
zone_name: "Bakery",
|
||||
type: "zone",
|
||||
label: "Bakery",
|
||||
x: 40,
|
||||
y: 40,
|
||||
width: 260,
|
||||
height: 180,
|
||||
rotation: 0,
|
||||
locked: false,
|
||||
visible: true,
|
||||
sort_order: 1,
|
||||
},
|
||||
{
|
||||
id: 1312,
|
||||
location_map_id: 900,
|
||||
zone_id: 502,
|
||||
zone_name: "Frozen Foods",
|
||||
type: "zone",
|
||||
label: "Frozen Foods",
|
||||
x: 120,
|
||||
y: 90,
|
||||
width: 260,
|
||||
height: 180,
|
||||
rotation: 0,
|
||||
locked: false,
|
||||
visible: true,
|
||||
sort_order: 2,
|
||||
},
|
||||
], 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");
|
||||
await page.getByRole("button", { name: "Continue Editing" }).click();
|
||||
|
||||
const bakeryArea = page.getByRole("button", { name: "Map area Bakery" });
|
||||
await bakeryArea.focus();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(bakeryArea).toHaveAttribute("aria-pressed", "true");
|
||||
|
||||
await expect.poll(async () =>
|
||||
page.locator(".location-map-object").evaluateAll((nodes) =>
|
||||
nodes.map((node) => node.getAttribute("data-object-key"))
|
||||
)
|
||||
).toEqual(["1312", "1311"]);
|
||||
await expect(page.locator('[data-object-key="1311"] .location-map-resize-handle')).toBeVisible();
|
||||
});
|
||||
|
||||
test("admin cleared map area labels keep linked zone action names", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user