diff --git a/src/pages/agent-office/AgentOffice.jsx b/src/pages/agent-office/AgentOffice.jsx index 90c9643..08d082b 100644 --- a/src/pages/agent-office/AgentOffice.jsx +++ b/src/pages/agent-office/AgentOffice.jsx @@ -14,7 +14,7 @@ export default function AgentOffice() { const { canvasRef, updateAgentState, setAgentNotification, - setTheme, setZoom, hitTest, getZoom + setTheme, setZoom, hitTest, getZoom, wasDragging } = useOfficeCanvas(); const [selectedAgent, setSelectedAgent] = useState(null); @@ -37,6 +37,7 @@ export default function AgentOffice() { // 캔버스 클릭 핸들러 const handleCanvasClick = useCallback((e) => { + if (wasDragging()) return; // 드래그 후 발생하는 클릭 무시 const result = hitTest(e.clientX, e.clientY); if (result.type === 'agent') { setSelectedAgent(result.id); @@ -45,7 +46,7 @@ export default function AgentOffice() { } else { setSelectedAgent(null); } - }, [hitTest, clearNotifications, setAgentNotification]); + }, [hitTest, clearNotifications, setAgentNotification, wasDragging]); // 테마 변경 const handleThemeChange = useCallback((name) => { diff --git a/src/pages/agent-office/canvas/OfficeRenderer.js b/src/pages/agent-office/canvas/OfficeRenderer.js index c63197e..a30ff1f 100644 --- a/src/pages/agent-office/canvas/OfficeRenderer.js +++ b/src/pages/agent-office/canvas/OfficeRenderer.js @@ -48,6 +48,11 @@ export class OfficeRenderer { // 게임 루프 this._lastTime = 0; this._animId = null; + this._lastDpr = window.devicePixelRatio || 1; + + // 드래그 감지 + this._mouseDownPos = { x: 0, y: 0 }; + this._wasDragging = false; // 이벤트 this._setupInputHandlers(); @@ -91,12 +96,17 @@ export class OfficeRenderer { if (e.button === 0) { this._isPanning = true; this._panStart = { x: e.clientX - this.panX, y: e.clientY - this.panY }; + this._mouseDownPos = { x: e.clientX, y: e.clientY }; + this._wasDragging = false; } }); this._onMouseMove = (e) => { if (this._isPanning) { this.panX = e.clientX - this._panStart.x; this.panY = e.clientY - this._panStart.y; + const dx = e.clientX - this._mouseDownPos.x; + const dy = e.clientY - this._mouseDownPos.y; + if (Math.abs(dx) > 5 || Math.abs(dy) > 5) this._wasDragging = true; } }; this._onMouseUp = () => { @@ -244,11 +254,13 @@ export class OfficeRenderer { // 캔버스 크기 조정 const displayW = this.canvas.clientWidth; const displayH = this.canvas.clientHeight; - if (this.canvas.width !== displayW * dpr || this.canvas.height !== displayH * dpr) { + if (this.canvas.width !== displayW * dpr || this.canvas.height !== displayH * dpr || this._lastDpr !== dpr) { this.canvas.width = displayW * dpr; this.canvas.height = displayH * dpr; - ctx.scale(dpr, dpr); + this._lastDpr = dpr; } + // setTransform 방식으로 누적 없이 항상 올바른 변환 적용 + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.imageSmoothingEnabled = false; ctx.clearRect(0, 0, displayW, displayH); @@ -287,6 +299,9 @@ export class OfficeRenderer { } } + /** 드래그 여부 반환 (클릭 이벤트 필터링용) */ + wasDragging() { return this._wasDragging; } + /** 리사이즈 처리 */ resize() { // 다음 프레임에서 자동 조정됨 (_render에서 크기 체크) diff --git a/src/pages/agent-office/canvas/Pathfinder.js b/src/pages/agent-office/canvas/Pathfinder.js index 5d93d2c..94628fe 100644 --- a/src/pages/agent-office/canvas/Pathfinder.js +++ b/src/pages/agent-office/canvas/Pathfinder.js @@ -13,7 +13,7 @@ export class Pathfinder { /** blocked 타일 세팅 (wall + furniture footprint) */ setBlocked(blockedList) { - this.blocked.clear(); + // Do NOT clear — setWalls already added wall tiles for (const [col, row] of blockedList) { this.blocked.add(`${col},${row}`); } @@ -65,9 +65,9 @@ export class Pathfinder { const nr = current.row + dr; const nk = key(nc, nr); - if (visited.has(nk) || this.isBlocked(nc, nr)) continue; + if (visited.has(nk)) continue; // 골 지점은 blocked여도 이동 가능 (에이전트가 자기 자리에 앉으려면) - if (nk !== goalKey && this.blocked.has(nk)) continue; + if (nk !== goalKey && this.isBlocked(nc, nr)) continue; visited.add(nk); parent.set(nk, key(current.col, current.row)); diff --git a/src/pages/agent-office/canvas/TileMap.js b/src/pages/agent-office/canvas/TileMap.js index 7d892ef..409fbc3 100644 --- a/src/pages/agent-office/canvas/TileMap.js +++ b/src/pages/agent-office/canvas/TileMap.js @@ -30,8 +30,8 @@ export class TileMap { const x = c * ts + offsetX; const y = r * ts + offsetY; - // 화면 밖이면 스킵 - if (x + ts < 0 || y + ts < 0 || x > ctx.canvas.width || y > ctx.canvas.height) continue; + // 화면 밖이면 스킵 (CSS 공간 기준 — DPR 변환 적용된 좌표계) + if (x + ts < 0 || y + ts < 0 || x > ctx.canvas.clientWidth || y > ctx.canvas.clientHeight) continue; if (tileType === 0) { // 벽 diff --git a/src/pages/agent-office/components/TaskTab.jsx b/src/pages/agent-office/components/TaskTab.jsx index 000dff1..3c9412f 100644 --- a/src/pages/agent-office/components/TaskTab.jsx +++ b/src/pages/agent-office/components/TaskTab.jsx @@ -45,7 +45,12 @@ export default function TaskTab({ agentId, refreshTrigger }) { {formatTime(task.created_at)} {expanded === task.id && task.result_data && ( -
{JSON.stringify(JSON.parse(task.result_data), null, 2)}
+
+ {(() => {
+ try { return JSON.stringify(JSON.parse(task.result_data), null, 2); }
+ catch { return task.result_data; }
+ })()}
+
)}
);
diff --git a/src/pages/agent-office/hooks/useOfficeCanvas.js b/src/pages/agent-office/hooks/useOfficeCanvas.js
index 6876f73..8d4d4c8 100644
--- a/src/pages/agent-office/hooks/useOfficeCanvas.js
+++ b/src/pages/agent-office/hooks/useOfficeCanvas.js
@@ -47,6 +47,10 @@ export function useOfficeCanvas() {
return rendererRef.current?.zoom || 2;
}, []);
+ const wasDragging = useCallback(() => {
+ return rendererRef.current?.wasDragging?.() || false;
+ }, []);
+
return {
canvasRef,
updateAgentState,
@@ -54,6 +58,7 @@ export function useOfficeCanvas() {
setTheme,
setZoom,
hitTest,
- getZoom
+ getZoom,
+ wasDragging
};
}