refactor: extract map draft actions
This commit is contained in:
parent
3f6eade576
commit
03814b8b4c
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 { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import {
|
|
||||||
createBlankLocationMap,
|
|
||||||
createLocationMapFromZones,
|
|
||||||
publishLocationMapDraft,
|
|
||||||
saveLocationMapDraft,
|
|
||||||
} from "../api/locationMaps";
|
|
||||||
import { AuthContext } from "../context/AuthContext";
|
import { AuthContext } from "../context/AuthContext";
|
||||||
import { HouseholdContext } from "../context/HouseholdContext";
|
import { HouseholdContext } from "../context/HouseholdContext";
|
||||||
import { StoreContext } from "../context/StoreContext";
|
import { StoreContext } from "../context/StoreContext";
|
||||||
@ -16,9 +10,9 @@ import LocationMapToolbar from "../components/maps/LocationMapToolbar";
|
|||||||
import LocationMapTopbar from "../components/maps/LocationMapTopbar";
|
import LocationMapTopbar from "../components/maps/LocationMapTopbar";
|
||||||
import ConfirmSlideModal from "../components/modals/ConfirmSlideModal";
|
import ConfirmSlideModal from "../components/modals/ConfirmSlideModal";
|
||||||
import useActionToast from "../hooks/useActionToast";
|
import useActionToast from "../hooks/useActionToast";
|
||||||
|
import useLocationMapDraftActions from "../hooks/useLocationMapDraftActions";
|
||||||
import useLocationMapDraftState from "../hooks/useLocationMapDraftState";
|
import useLocationMapDraftState from "../hooks/useLocationMapDraftState";
|
||||||
import useUnsavedMapLeaveGuard from "../hooks/useUnsavedMapLeaveGuard";
|
import useUnsavedMapLeaveGuard from "../hooks/useUnsavedMapLeaveGuard";
|
||||||
import getApiErrorMessage from "../lib/getApiErrorMessage";
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_MAP_SIZE,
|
DEFAULT_MAP_SIZE,
|
||||||
clampMapZoom,
|
clampMapZoom,
|
||||||
@ -31,7 +25,6 @@ import {
|
|||||||
itemsForZone,
|
itemsForZone,
|
||||||
mapForMode,
|
mapForMode,
|
||||||
objectZoneId,
|
objectZoneId,
|
||||||
prepareObjectsForSave,
|
|
||||||
unmappedItems,
|
unmappedItems,
|
||||||
} from "../lib/locationMapUtils";
|
} from "../lib/locationMapUtils";
|
||||||
import "../styles/pages/LocationMapManager.css";
|
import "../styles/pages/LocationMapManager.css";
|
||||||
@ -188,101 +181,26 @@ export default function LocationMapManager() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateBlank = async () => {
|
const {
|
||||||
if (!activeHousehold?.id || !locationId) return;
|
handleCreateBlank,
|
||||||
beginSaving("create-blank");
|
handleCreateFromZones,
|
||||||
try {
|
handlePublish,
|
||||||
const response = await createBlankLocationMap(activeHousehold.id, locationId, mapDraft);
|
handleSaveDraft,
|
||||||
setMapState(response.data);
|
} = useLocationMapDraftActions({
|
||||||
setMode("edit");
|
activeHouseholdId: activeHousehold?.id,
|
||||||
setEditorTool("edit");
|
beginSaving,
|
||||||
setPreviewDraft(true);
|
endSaving,
|
||||||
syncMapDraftFromState(response.data, "edit", true);
|
hasUnsavedChanges,
|
||||||
toast.success("Created map", "Blank draft map created");
|
locationId,
|
||||||
} catch (error) {
|
mapDraft,
|
||||||
toast.error("Create map failed", getApiErrorMessage(error, "Failed to create map"));
|
objects,
|
||||||
} finally {
|
setEditorTool,
|
||||||
endSaving();
|
setMapState,
|
||||||
}
|
setMode,
|
||||||
};
|
setPreviewDraft,
|
||||||
|
syncMapDraftFromState,
|
||||||
const handleCreateFromZones = async () => {
|
toast,
|
||||||
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 handleAddObject = () => {
|
const handleAddObject = () => {
|
||||||
if (!canManage) return;
|
if (!canManage) return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user