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 136 additions and 103 deletions
Showing only changes of commit 03814b8b4c - Show all commits

View File

@ -0,0 +1,115 @@
import { useCallback } from "react";
import {
createBlankLocationMap,
createLocationMapFromZones,
publishLocationMapDraft,
saveLocationMapDraft,
} from "../api/locationMaps";
import getApiErrorMessage from "../lib/getApiErrorMessage";
import { prepareObjectsForSave } from "../lib/locationMapUtils";
export default function useLocationMapDraftActions({
activeHouseholdId,
beginSaving,
endSaving,
hasUnsavedChanges,
locationId,
mapDraft,
objects,
setEditorTool,
setMapState,
setMode,
setPreviewDraft,
syncMapDraftFromState,
toast,
}) {
const applyDraftResponse = useCallback((response, nextMode = "edit", nextPreviewDraft = true) => {
setMapState(response.data);
setMode(nextMode);
setPreviewDraft(nextPreviewDraft);
syncMapDraftFromState(response.data, nextMode, nextPreviewDraft);
}, [setMapState, setMode, setPreviewDraft, syncMapDraftFromState]);
const handleCreateBlank = useCallback(async () => {
if (!activeHouseholdId || !locationId) return;
beginSaving("create-blank");
try {
const response = await createBlankLocationMap(activeHouseholdId, locationId, mapDraft);
applyDraftResponse(response);
setEditorTool("edit");
toast.success("Created map", "Blank draft map created");
} catch (error) {
toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map"));
} finally {
endSaving();
}
}, [activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId, mapDraft, setEditorTool, toast]);
const handleCreateFromZones = useCallback(async () => {
if (!activeHouseholdId || !locationId) return;
beginSaving("create-zones");
try {
const response = await createLocationMapFromZones(activeHouseholdId, locationId, mapDraft);
applyDraftResponse(response);
setEditorTool("edit");
toast.success("Created starter map", "Zones were added as editable rectangles");
} catch (error) {
toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map from zones"));
} finally {
endSaving();
}
}, [activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId, mapDraft, setEditorTool, toast]);
const handleSaveDraft = useCallback(async () => {
if (!activeHouseholdId || !locationId) return;
beginSaving("save");
try {
const response = await saveLocationMapDraft(activeHouseholdId, locationId, {
map: mapDraft,
objects: prepareObjectsForSave(objects),
});
applyDraftResponse(response);
toast.success("Saved draft", "Map draft saved");
} catch (error) {
toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
} finally {
endSaving();
}
}, [activeHouseholdId, applyDraftResponse, beginSaving, endSaving, locationId, mapDraft, objects, toast]);
const handlePublish = useCallback(async () => {
if (!activeHouseholdId || !locationId) return;
beginSaving("publish");
let savedPendingDraft = false;
try {
if (hasUnsavedChanges) {
const saveResponse = await saveLocationMapDraft(activeHouseholdId, locationId, {
map: mapDraft,
objects: prepareObjectsForSave(objects),
});
savedPendingDraft = true;
applyDraftResponse(saveResponse);
}
const response = await publishLocationMapDraft(activeHouseholdId, locationId);
applyDraftResponse(response, "view", false);
setEditorTool("pan");
toast.success(
"Published map",
hasUnsavedChanges
? "Latest edits were saved and published"
: "Map is now visible to household members"
);
} catch (error) {
const message = getApiErrorMessage(error, "Failed to publish map");
toast.error("Publish failed", savedPendingDraft ? `${message}. Draft changes were saved.` : message);
} finally {
endSaving();
}
}, [
activeHouseholdId, applyDraftResponse, beginSaving, endSaving, hasUnsavedChanges,
locationId, mapDraft, objects, setEditorTool, toast,
]);
return { handleCreateBlank, handleCreateFromZones, handlePublish, handleSaveDraft };
}

View File

@ -1,11 +1,5 @@
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import {
createBlankLocationMap,
createLocationMapFromZones,
publishLocationMapDraft,
saveLocationMapDraft,
} from "../api/locationMaps";
import { AuthContext } from "../context/AuthContext";
import { HouseholdContext } from "../context/HouseholdContext";
import { StoreContext } from "../context/StoreContext";
@ -16,9 +10,9 @@ import LocationMapToolbar from "../components/maps/LocationMapToolbar";
import LocationMapTopbar from "../components/maps/LocationMapTopbar";
import ConfirmSlideModal from "../components/modals/ConfirmSlideModal";
import useActionToast from "../hooks/useActionToast";
import useLocationMapDraftActions from "../hooks/useLocationMapDraftActions";
import useLocationMapDraftState from "../hooks/useLocationMapDraftState";
import useUnsavedMapLeaveGuard from "../hooks/useUnsavedMapLeaveGuard";
import getApiErrorMessage from "../lib/getApiErrorMessage";
import {
DEFAULT_MAP_SIZE,
clampMapZoom,
@ -31,7 +25,6 @@ import {
itemsForZone,
mapForMode,
objectZoneId,
prepareObjectsForSave,
unmappedItems,
} from "../lib/locationMapUtils";
import "../styles/pages/LocationMapManager.css";
@ -188,101 +181,26 @@ export default function LocationMapManager() {
});
};
const handleCreateBlank = async () => {
if (!activeHousehold?.id || !locationId) return;
beginSaving("create-blank");
try {
const response = await createBlankLocationMap(activeHousehold.id, locationId, mapDraft);
setMapState(response.data);
setMode("edit");
setEditorTool("edit");
setPreviewDraft(true);
syncMapDraftFromState(response.data, "edit", true);
toast.success("Created map", "Blank draft map created");
} catch (error) {
toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map"));
} finally {
endSaving();
}
};
const handleCreateFromZones = async () => {
if (!activeHousehold?.id || !locationId) return;
beginSaving("create-zones");
try {
const response = await createLocationMapFromZones(activeHousehold.id, locationId, mapDraft);
setMapState(response.data);
setMode("edit");
setEditorTool("edit");
setPreviewDraft(true);
syncMapDraftFromState(response.data, "edit", true);
toast.success("Created starter map", "Zones were added as editable rectangles");
} catch (error) {
toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map from zones"));
} finally {
endSaving();
}
};
const handleSaveDraft = async () => {
if (!activeHousehold?.id || !locationId) return;
beginSaving("save");
try {
const response = await saveLocationMapDraft(activeHousehold.id, locationId, {
map: mapDraft,
objects: prepareObjectsForSave(objects),
});
setMapState(response.data);
setMode("edit");
setPreviewDraft(true);
syncMapDraftFromState(response.data, "edit", true);
toast.success("Saved draft", "Map draft saved");
} catch (error) {
toast.error("Save draft failed", getApiErrorMessage(error, "Failed to save map draft"));
} finally {
endSaving();
}
};
const handlePublish = async () => {
if (!activeHousehold?.id || !locationId) return;
beginSaving("publish");
let savedPendingDraft = false;
try {
if (hasUnsavedChanges) {
const saveResponse = await saveLocationMapDraft(activeHousehold.id, locationId, {
map: mapDraft,
objects: prepareObjectsForSave(objects),
});
savedPendingDraft = true;
setMapState(saveResponse.data);
setMode("edit");
setPreviewDraft(true);
syncMapDraftFromState(saveResponse.data, "edit", true);
}
const response = await publishLocationMapDraft(activeHousehold.id, locationId);
setMapState(response.data);
setMode("view");
setEditorTool("pan");
setPreviewDraft(false);
syncMapDraftFromState(response.data, "view", false);
toast.success(
"Published map",
hasUnsavedChanges
? "Latest edits were saved and published"
: "Map is now visible to household members"
);
} catch (error) {
const message = getApiErrorMessage(error, "Failed to publish map");
toast.error(
"Publish failed",
savedPendingDraft ? `${message}. Draft changes were saved.` : message
);
} finally {
endSaving();
}
};
const {
handleCreateBlank,
handleCreateFromZones,
handlePublish,
handleSaveDraft,
} = useLocationMapDraftActions({
activeHouseholdId: activeHousehold?.id,
beginSaving,
endSaving,
hasUnsavedChanges,
locationId,
mapDraft,
objects,
setEditorTool,
setMapState,
setMode,
setPreviewDraft,
syncMapDraftFromState,
toast,
});
const handleAddObject = () => {
if (!canManage) return;