Compare commits
No commits in common. "c945a71f452e580eedbf1a8c69d31f2b0906cd45" and "2a3e028281aad185d5b90beb80e97ea3cdd58224" have entirely different histories.
c945a71f45
...
2a3e028281
@ -24,10 +24,7 @@ export default function LocationMapToolbar({
|
|||||||
const showHistoryControls = mode === "edit" && showHistorySlot;
|
const showHistoryControls = mode === "edit" && showHistorySlot;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section className="location-map-toolbar" aria-label="Map controls">
|
||||||
className={`location-map-toolbar ${showHistorySlot ? "has-history-slot" : ""}`}
|
|
||||||
aria-label="Map controls"
|
|
||||||
>
|
|
||||||
<div className="location-map-mode-group">
|
<div className="location-map-mode-group">
|
||||||
<div className="location-map-mode-buttons" aria-label="Map mode">
|
<div className="location-map-mode-buttons" aria-label="Map mode">
|
||||||
<button
|
<button
|
||||||
@ -53,55 +50,55 @@ export default function LocationMapToolbar({
|
|||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{showHistorySlot ? (
|
||||||
|
<div
|
||||||
|
className={`location-map-history-buttons ${showHistoryControls ? "" : "is-reserved"}`}
|
||||||
|
aria-label={showHistoryControls ? "Edit history" : undefined}
|
||||||
|
aria-hidden={showHistoryControls ? undefined : "true"}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onUndo}
|
||||||
|
disabled={!showHistoryControls || saving || history.length === 0}
|
||||||
|
aria-label="Undo"
|
||||||
|
tabIndex={showHistoryControls ? undefined : -1}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">↶</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onRedo}
|
||||||
|
disabled={!showHistoryControls || saving || future.length === 0}
|
||||||
|
aria-label="Redo"
|
||||||
|
tabIndex={showHistoryControls ? undefined : -1}
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">↷</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{mode === "edit" && canManage ? (
|
||||||
|
<div className="location-map-tool-buttons" aria-label="Edit tool">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={editorTool === "pan" ? "active is-pan" : ""}
|
||||||
|
onClick={onPanMode}
|
||||||
|
disabled={saving}
|
||||||
|
aria-pressed={editorTool === "pan"}
|
||||||
|
>
|
||||||
|
Move Map
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={editorTool === "edit" ? "active is-edit" : ""}
|
||||||
|
onClick={onEditObjects}
|
||||||
|
disabled={saving}
|
||||||
|
aria-pressed={editorTool === "edit"}
|
||||||
|
>
|
||||||
|
Edit Areas
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{showHistorySlot ? (
|
|
||||||
<div
|
|
||||||
className={`location-map-history-buttons ${showHistoryControls ? "" : "is-reserved"}`}
|
|
||||||
aria-label={showHistoryControls ? "Edit history" : undefined}
|
|
||||||
aria-hidden={showHistoryControls ? undefined : "true"}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onUndo}
|
|
||||||
disabled={!showHistoryControls || saving || history.length === 0}
|
|
||||||
aria-label="Undo"
|
|
||||||
tabIndex={showHistoryControls ? undefined : -1}
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">↶</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onRedo}
|
|
||||||
disabled={!showHistoryControls || saving || future.length === 0}
|
|
||||||
aria-label="Redo"
|
|
||||||
tabIndex={showHistoryControls ? undefined : -1}
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">↷</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{mode === "edit" && canManage ? (
|
|
||||||
<div className="location-map-tool-buttons" aria-label="Edit tool">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={editorTool === "pan" ? "active is-pan" : ""}
|
|
||||||
onClick={onPanMode}
|
|
||||||
disabled={saving}
|
|
||||||
aria-pressed={editorTool === "pan"}
|
|
||||||
>
|
|
||||||
Move Map
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={editorTool === "edit" ? "active is-edit" : ""}
|
|
||||||
onClick={onEditObjects}
|
|
||||||
disabled={saving}
|
|
||||||
aria-pressed={editorTool === "edit"}
|
|
||||||
>
|
|
||||||
Edit Areas
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="location-map-zoom-controls" aria-label="Zoom controls">
|
<div className="location-map-zoom-controls" aria-label="Zoom controls">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { useBeforeUnload, useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useBeforeUnload, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
createBlankLocationMap,
|
createBlankLocationMap,
|
||||||
@ -38,8 +38,6 @@ import {
|
|||||||
} from "../lib/locationMapUtils";
|
} from "../lib/locationMapUtils";
|
||||||
import "../styles/pages/LocationMapManager.css";
|
import "../styles/pages/LocationMapManager.css";
|
||||||
|
|
||||||
const EMPTY_MAP_ITEMS = [];
|
|
||||||
|
|
||||||
export default function LocationMapManager() {
|
export default function LocationMapManager() {
|
||||||
const { storeId, locationId } = useParams();
|
const { storeId, locationId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -79,44 +77,16 @@ export default function LocationMapManager() {
|
|||||||
const location = mapState?.location || stores.find((store) => String(store.id) === String(locationId));
|
const location = mapState?.location || stores.find((store) => String(store.id) === String(locationId));
|
||||||
const canManage = Boolean(mapState?.can_manage ?? ["owner", "admin"].includes(activeHousehold?.role));
|
const canManage = Boolean(mapState?.can_manage ?? ["owner", "admin"].includes(activeHousehold?.role));
|
||||||
const activeMap = mapForMode(mapState, mode, previewDraft);
|
const activeMap = mapForMode(mapState, mode, previewDraft);
|
||||||
const mapItems = mapState?.items || EMPTY_MAP_ITEMS;
|
const selectedObject = objects.find((object) => getObjectKey(object) === selectedObjectKey) || null;
|
||||||
const selectedObject = useMemo(
|
const selectedZoneItems = selectedObject?.zone_id
|
||||||
() => objects.find((object) => getObjectKey(object) === selectedObjectKey) || null,
|
? itemsForZone(mapState?.items || [], selectedObject.zone_id, filters, username)
|
||||||
[objects, selectedObjectKey]
|
: [];
|
||||||
);
|
const visibleUnmappedItems = unmappedItems(mapState?.items || [], filters, username);
|
||||||
const selectedZoneItems = useMemo(
|
const visibleAssignedItemCount = (mapState?.items || []).filter((item) => (
|
||||||
() => selectedObject?.zone_id
|
item.zone_id && itemMatchesMapFilters(item, filters, username)
|
||||||
? itemsForZone(mapItems, selectedObject.zone_id, filters, username)
|
)).length;
|
||||||
: EMPTY_MAP_ITEMS,
|
const unmappedItemCount = (mapState?.items || []).filter((item) => !item.zone_id).length;
|
||||||
[filters, mapItems, selectedObject?.zone_id, username]
|
const hiddenAreaCount = objects.filter((object) => object.visible === false).length;
|
||||||
);
|
|
||||||
const visibleUnmappedItems = useMemo(
|
|
||||||
() => unmappedItems(mapItems, filters, username),
|
|
||||||
[filters, mapItems, username]
|
|
||||||
);
|
|
||||||
const { visibleAssignedItemCount, unmappedItemCount } = useMemo(() => {
|
|
||||||
let nextVisibleAssignedItemCount = 0;
|
|
||||||
let nextUnmappedItemCount = 0;
|
|
||||||
|
|
||||||
mapItems.forEach((item) => {
|
|
||||||
if (!item.zone_id) {
|
|
||||||
nextUnmappedItemCount += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (itemMatchesMapFilters(item, filters, username)) {
|
|
||||||
nextVisibleAssignedItemCount += 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
visibleAssignedItemCount: nextVisibleAssignedItemCount,
|
|
||||||
unmappedItemCount: nextUnmappedItemCount,
|
|
||||||
};
|
|
||||||
}, [filters, mapItems, username]);
|
|
||||||
const hiddenAreaCount = useMemo(
|
|
||||||
() => objects.filter((object) => object.visible === false).length,
|
|
||||||
[objects]
|
|
||||||
);
|
|
||||||
const shouldGuardLeave = canManage && hasUnsavedChanges;
|
const shouldGuardLeave = canManage && hasUnsavedChanges;
|
||||||
|
|
||||||
const mapSize = {
|
const mapSize = {
|
||||||
@ -614,7 +584,7 @@ export default function LocationMapManager() {
|
|||||||
mapSize={mapSize}
|
mapSize={mapSize}
|
||||||
zoom={zoom}
|
zoom={zoom}
|
||||||
selectedObjectKey={selectedObjectKey}
|
selectedObjectKey={selectedObjectKey}
|
||||||
mapItems={mapItems}
|
mapItems={mapState?.items || []}
|
||||||
username={username}
|
username={username}
|
||||||
svgRef={svgRef}
|
svgRef={svgRef}
|
||||||
dragState={dragState}
|
dragState={dragState}
|
||||||
|
|||||||
@ -107,6 +107,7 @@
|
|||||||
|
|
||||||
.location-map-toolbar {
|
.location-map-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
@ -118,6 +119,8 @@
|
|||||||
.location-map-mode-group {
|
.location-map-mode-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.55rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,10 +225,6 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-map-zoom-controls {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-map-label-short {
|
.location-map-label-short {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -840,24 +839,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.location-map-toolbar {
|
.location-map-toolbar {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
gap: 0.15rem;
|
gap: 0.15rem;
|
||||||
padding: 0.35rem 0.25rem;
|
padding: 0.35rem 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-map-toolbar.has-history-slot {
|
|
||||||
grid-template-columns: minmax(0, 1fr) 82px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-map-mode-group {
|
.location-map-mode-group {
|
||||||
grid-column: 1;
|
flex: 0 0 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 0.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-map-mode-buttons {
|
.location-map-mode-buttons {
|
||||||
width: 100%;
|
flex: 0 0 120px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0.18rem;
|
padding: 0.18rem;
|
||||||
}
|
}
|
||||||
@ -878,7 +874,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.location-map-history-buttons {
|
.location-map-history-buttons {
|
||||||
grid-column: 2;
|
flex: 0 0 auto;
|
||||||
gap: 0.12rem;
|
gap: 0.12rem;
|
||||||
width: 82px;
|
width: 82px;
|
||||||
}
|
}
|
||||||
@ -896,14 +892,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.location-map-zoom-controls {
|
.location-map-zoom-controls {
|
||||||
grid-column: 2;
|
flex: 0 0 auto;
|
||||||
gap: 0.12rem;
|
gap: 0.12rem;
|
||||||
margin-left: 0;
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-map-toolbar.has-history-slot .location-map-zoom-controls {
|
|
||||||
grid-column: 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-map-zoom-controls button {
|
.location-map-zoom-controls button {
|
||||||
|
|||||||
@ -608,20 +608,9 @@ test("mobile keeps draft preview status compact in the topbar", async ({ page })
|
|||||||
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 editModeGroupBox = await modeGroup.boundingBox();
|
const editModeGroupBox = await modeGroup.boundingBox();
|
||||||
const editModeBox = await modeButtons.boundingBox();
|
|
||||||
const editHistoryBox = await historyActions.boundingBox();
|
|
||||||
const editZoomBox = await zoomControls.boundingBox();
|
|
||||||
expect(editModeGroupBox).not.toBeNull();
|
expect(editModeGroupBox).not.toBeNull();
|
||||||
expect(editModeBox).not.toBeNull();
|
if (!editModeGroupBox) throw new Error("Mobile edit controls layout was not measurable");
|
||||||
expect(editHistoryBox).not.toBeNull();
|
|
||||||
expect(editZoomBox).not.toBeNull();
|
|
||||||
if (!editModeGroupBox || !editModeBox || !editHistoryBox || !editZoomBox) {
|
|
||||||
throw new Error("Mobile edit controls layout was not measurable");
|
|
||||||
}
|
|
||||||
expect(Math.abs(editModeGroupBox.width - modeGroupBox.width)).toBeLessThanOrEqual(1);
|
expect(Math.abs(editModeGroupBox.width - modeGroupBox.width)).toBeLessThanOrEqual(1);
|
||||||
expect(Math.abs(editModeBox.width - modeBox.width)).toBeLessThanOrEqual(1);
|
|
||||||
expect(editHistoryBox.x).toBeGreaterThan(editModeBox.x + editModeBox.width);
|
|
||||||
expect(editZoomBox.x).toBeGreaterThan(editHistoryBox.x + editHistoryBox.width);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("admin selecting an object does not mark a draft dirty until it changes", async ({ page }) => {
|
test("admin selecting an object does not mark a draft dirty until it changes", async ({ page }) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user