feat(agent-office): replace canvas office with 3x3 agent grid
- AgentOffice renders TopBar + AgentGrid + dynamic right panel - Right panel: SidePanel (active) / EmptyDetailPanel (initial or placeholder) - TopBar simplified to connected status only (theme/zoom dropped) - Wire AgentGrid through useAgentManager state - Remove canvas/ (9 files), useOfficeCanvas, office-map.json - New CSS for grid cards (state dot, notification badge, accent border) - Mobile: 2-column grid + bottom-sheet panel Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
// src/pages/agent-office/AgentOffice.jsx
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useAgentManager } from './hooks/useAgentManager.js';
|
||||
import { useOfficeCanvas } from './hooks/useOfficeCanvas.js';
|
||||
import { AGENT_META } from './constants.js';
|
||||
import TopBar from './components/TopBar.jsx';
|
||||
import AgentGrid from './components/AgentGrid.jsx';
|
||||
import SidePanel from './components/SidePanel.jsx';
|
||||
import EmptyDetailPanel from './components/EmptyDetailPanel.jsx';
|
||||
import './AgentOffice.css';
|
||||
|
||||
export default function AgentOffice() {
|
||||
@@ -12,85 +14,57 @@ export default function AgentOffice() {
|
||||
refreshTrigger, clearNotifications
|
||||
} = useAgentManager();
|
||||
|
||||
const {
|
||||
canvasRef, updateAgentState, setAgentNotification,
|
||||
setTheme, setZoom, hitTest, getZoom, wasDragging
|
||||
} = useOfficeCanvas();
|
||||
|
||||
// selectedAgent: null | active agent id | "placeholder-N"
|
||||
const [selectedAgent, setSelectedAgent] = useState(null);
|
||||
const [theme, setThemeState] = useState(localStorage.getItem('agent-office-theme') || 'modern');
|
||||
const [zoom, setZoomState] = useState(2);
|
||||
|
||||
// WebSocket 상태 → 캔버스 동기화
|
||||
useEffect(() => {
|
||||
for (const [id, agentState] of Object.entries(agents)) {
|
||||
updateAgentState(id, agentState.state, agentState.detail);
|
||||
}
|
||||
}, [agents, updateAgentState]);
|
||||
const handleSelectAgent = useCallback((agentId) => {
|
||||
setSelectedAgent(agentId);
|
||||
clearNotifications(agentId);
|
||||
}, [clearNotifications]);
|
||||
|
||||
// 알림 → 캔버스 동기화
|
||||
useEffect(() => {
|
||||
for (const [id, count] of Object.entries(notifications)) {
|
||||
setAgentNotification(id, count);
|
||||
}
|
||||
}, [notifications, setAgentNotification]);
|
||||
const handleSelectPlaceholder = useCallback((placeholderKey) => {
|
||||
setSelectedAgent(placeholderKey);
|
||||
}, []);
|
||||
|
||||
// 캔버스 클릭 핸들러
|
||||
const handleCanvasClick = useCallback((e) => {
|
||||
if (wasDragging()) return; // 드래그 후 발생하는 클릭 무시
|
||||
const result = hitTest(e.clientX, e.clientY);
|
||||
if (result.type === 'agent') {
|
||||
setSelectedAgent(result.id);
|
||||
clearNotifications(result.id);
|
||||
setAgentNotification(result.id, 0);
|
||||
} else {
|
||||
setSelectedAgent(null);
|
||||
}
|
||||
}, [hitTest, clearNotifications, setAgentNotification, wasDragging]);
|
||||
const handleClose = useCallback(() => {
|
||||
setSelectedAgent(null);
|
||||
}, []);
|
||||
|
||||
// 테마 변경
|
||||
const handleThemeChange = useCallback((name) => {
|
||||
setThemeState(name);
|
||||
setTheme(name);
|
||||
}, [setTheme]);
|
||||
|
||||
// 줌 변경
|
||||
const handleZoomChange = useCallback((level) => {
|
||||
setZoomState(level);
|
||||
setZoom(level);
|
||||
}, [setZoom]);
|
||||
|
||||
// 선택된 에이전트의 pending task
|
||||
const pendingTask = selectedAgent
|
||||
const pendingTask = selectedAgent && AGENT_META[selectedAgent]
|
||||
? pendingTasks.find(t => t.agent_id === selectedAgent)
|
||||
: null;
|
||||
|
||||
let rightPanel;
|
||||
if (selectedAgent === null) {
|
||||
rightPanel = <EmptyDetailPanel variant="initial" />;
|
||||
} else if (selectedAgent.startsWith('placeholder-')) {
|
||||
rightPanel = <EmptyDetailPanel variant="placeholder" onClose={handleClose} />;
|
||||
} else {
|
||||
rightPanel = (
|
||||
<SidePanel
|
||||
agentId={selectedAgent}
|
||||
agentState={agents[selectedAgent]}
|
||||
pendingTask={pendingTask}
|
||||
onClose={handleClose}
|
||||
refreshTrigger={refreshTrigger}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ao-root">
|
||||
<TopBar
|
||||
connected={connected}
|
||||
theme={theme}
|
||||
onThemeChange={handleThemeChange}
|
||||
zoom={zoom}
|
||||
onZoomChange={handleZoomChange}
|
||||
/>
|
||||
|
||||
<TopBar connected={connected} />
|
||||
<div className="ao-main">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="ao-canvas"
|
||||
onClick={handleCanvasClick}
|
||||
/>
|
||||
|
||||
{selectedAgent && (
|
||||
<SidePanel
|
||||
agentId={selectedAgent}
|
||||
agentState={agents[selectedAgent]}
|
||||
pendingTask={pendingTask}
|
||||
onClose={() => setSelectedAgent(null)}
|
||||
refreshTrigger={refreshTrigger}
|
||||
<div className="ao-grid-wrap">
|
||||
<AgentGrid
|
||||
agents={agents}
|
||||
notifications={notifications}
|
||||
selectedAgent={selectedAgent}
|
||||
onSelectAgent={handleSelectAgent}
|
||||
onSelectPlaceholder={handleSelectPlaceholder}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{rightPanel}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user