diff --git a/frontend/src/components/maps/LocationMapBottomSheet.jsx b/frontend/src/components/maps/LocationMapBottomSheet.jsx
index 94faa24..ab8aa90 100644
--- a/frontend/src/components/maps/LocationMapBottomSheet.jsx
+++ b/frontend/src/components/maps/LocationMapBottomSheet.jsx
@@ -85,6 +85,7 @@ export default function LocationMapBottomSheet({
const selectedWidth = Number(selectedObject?.width) || 40;
const selectedHeight = Number(selectedObject?.height) || 40;
const hiddenAreaLabel = `Show Hidden Areas (${hiddenAreaCount})`;
+ const geometryLocked = Boolean(selectedObject?.locked);
const maxX = Math.max(0, Math.round((mapSize?.width || selectedWidth) - selectedWidth));
const maxY = Math.max(0, Math.round((mapSize?.height || selectedHeight) - selectedHeight));
const maxWidth = Math.max(40, Math.round(mapSize?.width || selectedWidth));
@@ -294,7 +295,7 @@ export default function LocationMapBottomSheet({
max={maxX}
step="1"
value={Math.round(selectedObject.x)}
- disabled={saving}
+ disabled={saving || geometryLocked}
onChange={(event) => onObjectField("x", Number(event.target.value))}
/>
@@ -306,7 +307,7 @@ export default function LocationMapBottomSheet({
max={maxY}
step="1"
value={Math.round(selectedObject.y)}
- disabled={saving}
+ disabled={saving || geometryLocked}
onChange={(event) => onObjectField("y", Number(event.target.value))}
/>
@@ -318,7 +319,7 @@ export default function LocationMapBottomSheet({
max={maxWidth}
step="1"
value={Math.round(selectedObject.width)}
- disabled={saving}
+ disabled={saving || geometryLocked}
onChange={(event) => onObjectField("width", Number(event.target.value))}
/>
@@ -330,11 +331,16 @@ export default function LocationMapBottomSheet({
max={maxHeight}
step="1"
value={Math.round(selectedObject.height)}
- disabled={saving}
+ disabled={saving || geometryLocked}
onChange={(event) => onObjectField("height", Number(event.target.value))}
/>
+ {geometryLocked ? (
+
+ Unlock this area before moving or resizing it.
+
+ ) : null}
diff --git a/frontend/src/components/maps/LocationMapCanvas.jsx b/frontend/src/components/maps/LocationMapCanvas.jsx
index 29b84ec..93c1293 100644
--- a/frontend/src/components/maps/LocationMapCanvas.jsx
+++ b/frontend/src/components/maps/LocationMapCanvas.jsx
@@ -318,7 +318,7 @@ export default function LocationMapCanvas({
) : null}
{pinDots}
- {canEditObjects && isSelected ? (
+ {canEditObjects && isSelected && !object.locked ? (
{
+ await mockMapShell(page);
+
+ const mapState = draftMapState([
+ {
+ id: 1355,
+ location_map_id: 900,
+ 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");
+
+ await page.getByRole("button", { name: "Continue Editing" }).click();
+ await page.getByRole("button", { name: "Edit Objects" }).click();
+ await page.getByRole("button", { name: "Map area Bakery" }).click();
+
+ const xInput = page.getByRole("spinbutton", { name: "X" });
+ const yInput = page.getByRole("spinbutton", { name: "Y" });
+ const widthInput = page.getByRole("spinbutton", { name: "W" });
+ const heightInput = page.getByRole("spinbutton", { name: "H" });
+ await expect(page.locator(".location-map-resize-handle")).toBeVisible();
+ await expect(xInput).toBeEnabled();
+ await expect(page.getByLabel("Locked")).not.toBeChecked();
+
+ await page.getByLabel("Locked").click();
+ await expect(page.getByLabel("Locked")).toBeChecked();
+ await expect(page.getByRole("textbox", { name: "Label" })).toBeEnabled();
+ await expect(page.getByLabel("Visible")).toBeEnabled();
+ await expect(xInput).toBeDisabled();
+ await expect(yInput).toBeDisabled();
+ await expect(widthInput).toBeDisabled();
+ await expect(heightInput).toBeDisabled();
+ await expect(page.locator(".location-map-resize-handle")).toHaveCount(0);
+ await expect(page.getByText("Unlock this area before moving or resizing it.")).toBeVisible();
+ await expect(page.locator(".location-map-status")).toHaveText("Unsaved Draft");
+
+ await page.getByRole("button", { name: "Map area Bakery" }).focus();
+ await page.keyboard.press("ArrowRight");
+ await expect(xInput).toHaveValue("40");
+
+ await page.getByLabel("Locked").click();
+ await expect(page.getByLabel("Locked")).not.toBeChecked();
+ await expect(xInput).toBeEnabled();
+ await expect(page.locator(".location-map-resize-handle")).toBeVisible();
+});
+
test("admin cleared map area labels keep linked zone action names", async ({ page }) => {
await mockMapShell(page);