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 objectDragHistoryCapturedRef = useRef(false);
|
||||||
const suppressPanClickRef = useRef(false);
|
const suppressPanClickRef = useRef(false);
|
||||||
const [isPanning, setIsPanning] = useState(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 zoneItemSummary = useMemo(() => {
|
||||||
const assignedCounts = new Map();
|
const assignedCounts = new Map();
|
||||||
const visibleItemsByZone = new Map();
|
const visibleItemsByZone = new Map();
|
||||||
@ -324,13 +339,7 @@ export default function LocationMapCanvas({
|
|||||||
{mode === "edit" ? (
|
{mode === "edit" ? (
|
||||||
<rect className="location-map-grid" width={mapSize.width} height={mapSize.height} />
|
<rect className="location-map-grid" width={mapSize.width} height={mapSize.height} />
|
||||||
) : null}
|
) : null}
|
||||||
{(selectedObjectKey
|
{orderedObjects.map(renderMapObject)}
|
||||||
? [
|
|
||||||
...objects.filter((object) => getObjectKey(object) !== selectedObjectKey),
|
|
||||||
...objects.filter((object) => getObjectKey(object) === selectedObjectKey),
|
|
||||||
]
|
|
||||||
: objects
|
|
||||||
).map(renderMapObject)}
|
|
||||||
</svg>
|
</svg>
|
||||||
{emptyCanvasMessage ? (
|
{emptyCanvasMessage ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -11,13 +11,9 @@ export function LocationMapMessageState({ message, location, status = "Loading",
|
|||||||
<main className="location-map-workspace location-map-message-workspace">
|
<main className="location-map-workspace location-map-message-workspace">
|
||||||
<section className="location-map-setup location-map-message-card" aria-live="polite">
|
<section className="location-map-setup location-map-message-card" aria-live="polite">
|
||||||
<h2>{status}</h2>
|
<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">
|
<div className="location-map-setup-row">
|
||||||
<span>Status</span>
|
<span>Message</span>
|
||||||
<strong>{status}</strong>
|
|
||||||
</div>
|
|
||||||
<div className="location-map-setup-row">
|
|
||||||
<span>Detail</span>
|
|
||||||
<strong>{message}</strong>
|
<strong>{message}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,13 +30,9 @@ export function LocationMapLoadErrorState({ location, loadError, onBack, onRetry
|
|||||||
<main className="location-map-workspace">
|
<main className="location-map-workspace">
|
||||||
<section className="location-map-setup location-map-load-error" role="alert" aria-live="polite">
|
<section className="location-map-setup location-map-load-error" role="alert" aria-live="polite">
|
||||||
<h2>Map Unavailable</h2>
|
<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">
|
<div className="location-map-setup-row">
|
||||||
<span>Status</span>
|
<span>Error</span>
|
||||||
<strong>Load failed</strong>
|
|
||||||
</div>
|
|
||||||
<div className="location-map-setup-row">
|
|
||||||
<span>Detail</span>
|
|
||||||
<strong>{loadError}</strong>
|
<strong>{loadError}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -104,6 +104,7 @@ export default function LocationMapToolbar({
|
|||||||
<div
|
<div
|
||||||
className={`location-map-history-buttons ${canUseHistoryControls ? "" : "is-inactive"}`}
|
className={`location-map-history-buttons ${canUseHistoryControls ? "" : "is-inactive"}`}
|
||||||
aria-label="Edit history"
|
aria-label="Edit history"
|
||||||
|
aria-hidden={!canUseHistoryControls}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -184,6 +184,11 @@
|
|||||||
gap: 0.3rem;
|
gap: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.location-map-history-buttons.is-inactive {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.location-map-history-buttons button {
|
.location-map-history-buttons button {
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
padding-inline: 0.55rem;
|
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 page.goto("/stores/100/locations/10/map");
|
||||||
|
|
||||||
await expect(page.getByRole("heading", { name: "Select Household" })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Select Household" })).toBeVisible();
|
||||||
const messageStatus = page.getByRole("group", { name: "Map message status" });
|
const messageDetails = page.getByRole("group", { name: "Map message details" });
|
||||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Status" })).toContainText("Select Household");
|
await expect(messageDetails.locator(".location-map-setup-row")).toHaveCount(1);
|
||||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Detail" })).toContainText("Select a household to manage maps.");
|
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.locator(".location-map-message-card p")).toHaveCount(0);
|
||||||
await expect(page.getByLabel("Map controls")).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");
|
const errorCard = page.locator(".location-map-load-error");
|
||||||
await expect(errorCard.getByRole("heading", { name: "Map Unavailable" })).toBeVisible();
|
await expect(errorCard.getByRole("heading", { name: "Map Unavailable" })).toBeVisible();
|
||||||
const loadStatus = errorCard.getByRole("group", { name: "Map load status" });
|
const loadDetails = errorCard.getByRole("group", { name: "Map load details" });
|
||||||
await expect(loadStatus.locator(".location-map-setup-row", { hasText: "Status" })).toContainText("Load failed");
|
await expect(loadDetails.locator(".location-map-setup-row")).toHaveCount(1);
|
||||||
await expect(loadStatus.locator(".location-map-setup-row", { hasText: "Detail" })).toContainText("Map service unavailable");
|
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(errorCard.locator("p")).toHaveCount(0);
|
||||||
await expect(page.getByText("Load Error")).toBeVisible();
|
await expect(page.getByText("Load Error")).toBeVisible();
|
||||||
await expect(page.getByRole("heading", { name: "No Map" })).toHaveCount(0);
|
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.getByText("Eastvale")).toBeVisible();
|
||||||
await expect(page.getByRole("button", { name: "Back to grocery list" })).toBeVisible();
|
await expect(page.getByRole("button", { name: "Back to grocery list" })).toBeVisible();
|
||||||
await expect(page.locator(".location-map-status")).toHaveText("Loading");
|
await expect(page.locator(".location-map-status")).toHaveText("Loading");
|
||||||
const messageStatus = page.getByRole("group", { name: "Map message status" });
|
const messageDetails = page.getByRole("group", { name: "Map message details" });
|
||||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Status" })).toContainText("Loading");
|
await expect(messageDetails.locator(".location-map-setup-row")).toHaveCount(1);
|
||||||
await expect(messageStatus.locator(".location-map-setup-row", { hasText: "Detail" })).toContainText("Loading map...");
|
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);
|
await expect(page.locator(".location-map-message-card p")).toHaveCount(0);
|
||||||
|
|
||||||
releaseMapResponse();
|
releaseMapResponse();
|
||||||
@ -1391,15 +1394,15 @@ test("mobile keeps draft preview status compact in the topbar", async ({ page })
|
|||||||
expect(modeBox.width).toBeLessThanOrEqual(126);
|
expect(modeBox.width).toBeLessThanOrEqual(126);
|
||||||
expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(82);
|
expect(reservedHistoryBox.width).toBeGreaterThanOrEqual(82);
|
||||||
expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1);
|
expect(reservedHistoryBox.x).toBeGreaterThan(modeBox.x + modeBox.width - 1);
|
||||||
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeVisible();
|
await expect(historyActions).toHaveCSS("visibility", "hidden");
|
||||||
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeVisible();
|
await expect(historyActions.locator('button[aria-label="Undo"]')).toBeDisabled();
|
||||||
await expect(historyActions.getByRole("button", { name: "Undo" })).toBeDisabled();
|
await expect(historyActions.locator('button[aria-label="Redo"]')).toBeDisabled();
|
||||||
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeDisabled();
|
|
||||||
expect(zoomBox.y).toBeLessThan(modeBox.y + modeBox.height);
|
expect(zoomBox.y).toBeLessThan(modeBox.y + modeBox.height);
|
||||||
expect(zoomBox.x).toBeGreaterThan(reservedHistoryBox.x + reservedHistoryBox.width - 1);
|
expect(zoomBox.x).toBeGreaterThan(reservedHistoryBox.x + reservedHistoryBox.width - 1);
|
||||||
expect(zoomBox.x + zoomBox.width).toBeLessThanOrEqual(toolbarBox.x + toolbarBox.width + 1);
|
expect(zoomBox.x + zoomBox.width).toBeLessThanOrEqual(toolbarBox.x + toolbarBox.width + 1);
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Edit Draft" }).click();
|
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: "Undo" })).toBeVisible();
|
||||||
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeVisible();
|
await expect(historyActions.getByRole("button", { name: "Redo" })).toBeVisible();
|
||||||
const editToolbarBox = await toolbar.boundingBox();
|
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();
|
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 }) => {
|
test("admin cleared map area labels keep linked zone action names", async ({ page }) => {
|
||||||
await mockMapShell(page);
|
await mockMapShell(page);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user