fix: bound map object numeric edits

This commit is contained in:
Nico 2026-06-03 17:29:57 -07:00
parent f8b3a684b8
commit bff830dd4b
3 changed files with 75 additions and 0 deletions

View File

@ -32,6 +32,7 @@ export default function LocationMapBottomSheet({
savingAction,
hasUnsavedChanges,
mapState,
mapSize,
onAddObject,
onUndo,
onRedo,
@ -75,6 +76,12 @@ export default function LocationMapBottomSheet({
const changedLayerCount = DISPLAY_CONTROLS.filter(([key]) => filters[key] !== DEFAULT_MAP_FILTERS[key]).length;
const unmappedItemPreview = visibleUnmappedItems.slice(0, 8);
const hiddenUnmappedItemCount = visibleUnmappedItems.length - unmappedItemPreview.length;
const selectedWidth = Number(selectedObject?.width) || 40;
const selectedHeight = Number(selectedObject?.height) || 40;
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));
const maxHeight = Math.max(40, Math.round(mapSize?.height || selectedHeight));
const saveDraftLabel = savingAction === "save" ? "Saving..." : "Save Draft";
const publishLabel = savingAction === "publish" ? "Publishing..." : "Publish";
const showSelectedZoneItems = () => {
@ -234,6 +241,9 @@ export default function LocationMapBottomSheet({
X
<input
type="number"
min="0"
max={maxX}
step="1"
value={Math.round(selectedObject.x)}
disabled={saving}
onChange={(event) => onObjectField("x", Number(event.target.value))}
@ -243,6 +253,9 @@ export default function LocationMapBottomSheet({
Y
<input
type="number"
min="0"
max={maxY}
step="1"
value={Math.round(selectedObject.y)}
disabled={saving}
onChange={(event) => onObjectField("y", Number(event.target.value))}
@ -252,6 +265,9 @@ export default function LocationMapBottomSheet({
W
<input
type="number"
min="40"
max={maxWidth}
step="1"
value={Math.round(selectedObject.width)}
disabled={saving}
onChange={(event) => onObjectField("width", Number(event.target.value))}
@ -261,6 +277,9 @@ export default function LocationMapBottomSheet({
H
<input
type="number"
min="40"
max={maxHeight}
step="1"
value={Math.round(selectedObject.height)}
disabled={saving}
onChange={(event) => onObjectField("height", Number(event.target.value))}

View File

@ -543,6 +543,7 @@ export default function LocationMapManager() {
savingAction={savingAction}
hasUnsavedChanges={hasUnsavedChanges}
mapState={mapState}
mapSize={mapSize}
onAddObject={handleAddObject}
onUndo={handleUndo}
onRedo={handleRedo}

View File

@ -596,6 +596,61 @@ test("admin selecting an object does not mark a draft dirty until it changes", a
await expect(page.getByRole("button", { name: "Undo" })).toBeEnabled();
});
test("admin object numeric fields expose map bounds and clamp edits", async ({ page }) => {
await mockMapShell(page);
const mapState = draftMapState([
{
id: 1352,
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.locator(".location-map-object", { hasText: "Bakery" }).locator("rect").first().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(xInput).toHaveAttribute("min", "0");
await expect(xInput).toHaveAttribute("max", "740");
await expect(yInput).toHaveAttribute("max", "540");
await expect(widthInput).toHaveAttribute("min", "40");
await expect(widthInput).toHaveAttribute("max", "1000");
await expect(heightInput).toHaveAttribute("max", "700");
await xInput.fill("9999");
await expect(xInput).toHaveValue("740");
await widthInput.fill("5000");
await expect(widthInput).toHaveValue("1000");
await expect(xInput).toHaveValue("0");
});
test("admin save progress locks draft controls", async ({ page }) => {
await mockMapShell(page);