Add store location map manager #20
115
frontend/src/hooks/useLocationMapDraftActions.js
Normal file
115
frontend/src/hooks/useLocationMapDraftActions.js
Normal 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 };
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user