Add store location map manager #20

Open
nalalangan wants to merge 206 commits from feature/location-map-manager into feature/store-selector-modal
2 changed files with 89 additions and 1 deletions
Showing only changes of commit 4a866763ac - Show all commits

View File

@ -9,6 +9,9 @@ export default function useUnsavedMapLeaveGuard({
const [pendingLeaveTarget, setPendingLeaveTarget] = useState(null);
const guardedClickTargetRef = useRef(null);
const allowGuardedClickRef = useRef(false);
const confirmedLeaveRef = useRef(false);
const historyGuardActiveRef = useRef(false);
const guardedUrlRef = useRef("");
useBeforeUnload(
useCallback((event) => {
@ -23,9 +26,21 @@ export default function useUnsavedMapLeaveGuard({
if (!shouldGuardLeave) {
setPendingLeaveTarget(null);
guardedClickTargetRef.current = null;
confirmedLeaveRef.current = false;
historyGuardActiveRef.current = false;
return undefined;
}
if (!historyGuardActiveRef.current) {
guardedUrlRef.current = window.location.href;
window.history.pushState(
{ ...(window.history.state || {}), unsavedMapGuard: true },
"",
guardedUrlRef.current
);
historyGuardActiveRef.current = true;
}
const handleDocumentClick = (event) => {
if (allowGuardedClickRef.current) return;
@ -71,9 +86,23 @@ export default function useUnsavedMapLeaveGuard({
event.preventDefault();
setPendingLeaveTarget({ type: "path", path: nextPath });
};
const handleBrowserHistoryNavigation = () => {
if (confirmedLeaveRef.current) return;
window.history.pushState(
{ ...(window.history.state || {}), unsavedMapGuard: true },
"",
guardedUrlRef.current || window.location.href
);
setPendingLeaveTarget({ type: "history" });
};
document.addEventListener("click", handleDocumentClick, true);
return () => document.removeEventListener("click", handleDocumentClick, true);
window.addEventListener("popstate", handleBrowserHistoryNavigation, true);
return () => {
document.removeEventListener("click", handleDocumentClick, true);
window.removeEventListener("popstate", handleBrowserHistoryNavigation, true);
};
}, [shouldGuardLeave]);
const requestBackNavigation = useCallback(() => {
@ -92,6 +121,12 @@ export default function useUnsavedMapLeaveGuard({
const confirmPendingLeave = useCallback(() => {
const target = pendingLeaveTarget;
setPendingLeaveTarget(null);
confirmedLeaveRef.current = true;
if (target?.type === "history") {
historyGuardActiveRef.current = false;
window.history.go(-2);
return;
}
if (target?.type === "click") {
const guardedClickTarget = guardedClickTargetRef.current;
guardedClickTargetRef.current = null;

View File

@ -2618,6 +2618,59 @@ test("admin app navigation warns when draft edits are unsaved", async ({ page })
await expect(page).toHaveURL(/\/$/);
});
test("admin browser back warns when draft edits are unsaved", async ({ page }) => {
await mockMapShell(page);
const mapState = draftMapState([
{
id: 1375,
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.evaluate(() => {
window.history.replaceState(window.history.state, "", "/");
window.history.pushState({}, "", "/stores/100/locations/10/map");
});
await expect(page).toHaveURL(/\/stores\/100\/locations\/10\/map$/);
await page.getByRole("button", { name: "Continue Editing" }).click();
await page.locator(".location-map-object", { hasText: "Bakery" }).locator("rect").first().click();
await page.getByRole("textbox", { name: "Label" }).fill("Unsaved Back Bakery");
await page.evaluate(() => window.history.back());
await expect(page.getByRole("heading", { name: "Leave with unsaved changes?" })).toBeVisible();
await expect(page).toHaveURL(/\/stores\/100\/locations\/10\/map$/);
await page.getByRole("button", { name: "Cancel" }).click();
await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Unsaved Back Bakery");
await page.evaluate(() => window.history.back());
await expect(page.getByRole("heading", { name: "Leave with unsaved changes?" })).toBeVisible();
await confirmSlide(page);
await expect(page).toHaveURL(/\/$/);
});
test("admin logout warns when draft edits are unsaved", async ({ page }) => {
await mockMapShell(page);