- Pathfinder.setBlocked: remove blocked.clear() to preserve wall tiles set by setWalls() - Pathfinder.findPath: fix dead-code goal exception — remove redundant isBlocked check, keep goal-tile exception in single guard - OfficeRenderer: track mouseDownPos/_wasDragging; expose wasDragging() method for click-after-drag suppression - OfficeRenderer._render: track _lastDpr to detect monitor DPR changes; use setTransform instead of scale to avoid accumulation - TileMap.render: use clientWidth/clientHeight for viewport culling (CSS space, not buffer pixels) - TaskTab: wrap JSON.parse in try/catch to prevent crash on malformed result_data Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
2.2 KiB
JavaScript
61 lines
2.2 KiB
JavaScript
// src/pages/agent-office/components/TaskTab.jsx
|
|
import { useState, useEffect } from 'react';
|
|
import { getAgentTasks } from '../../../api';
|
|
|
|
const STATUS_STYLE = {
|
|
succeeded: { bg: '#065f46', fg: '#34d399' },
|
|
failed: { bg: '#7f1d1d', fg: '#fca5a5' },
|
|
working: { bg: '#1e3a5f', fg: '#60a5fa' },
|
|
pending: { bg: '#92400e', fg: '#fbbf24' },
|
|
approved: { bg: '#065f46', fg: '#34d399' },
|
|
rejected: { bg: '#7f1d1d', fg: '#fca5a5' }
|
|
};
|
|
|
|
function formatTime(ts) {
|
|
if (!ts) return '';
|
|
const d = new Date(ts);
|
|
const now = new Date();
|
|
const isToday = d.toDateString() === now.toDateString();
|
|
const time = d.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' });
|
|
return isToday ? time : `${d.getMonth() + 1}/${d.getDate()} ${time}`;
|
|
}
|
|
|
|
export default function TaskTab({ agentId, refreshTrigger }) {
|
|
const [tasks, setTasks] = useState([]);
|
|
const [expanded, setExpanded] = useState(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
getAgentTasks(agentId, 20).then(data => {
|
|
if (!cancelled) setTasks(data || []);
|
|
});
|
|
return () => { cancelled = true; };
|
|
}, [agentId, refreshTrigger]);
|
|
|
|
return (
|
|
<div className="ao-task-tab">
|
|
{tasks.length === 0 && <div className="ao-empty">No tasks yet</div>}
|
|
{tasks.map(task => {
|
|
const style = STATUS_STYLE[task.status] || STATUS_STYLE.pending;
|
|
return (
|
|
<div key={task.id} className="ao-task-item" onClick={() => setExpanded(expanded === task.id ? null : task.id)}>
|
|
<div className="ao-task-header">
|
|
<span className="ao-task-type">{task.task_type}</span>
|
|
<span className="ao-task-badge" style={{ background: style.bg, color: style.fg }}>{task.status}</span>
|
|
<span className="ao-task-time">{formatTime(task.created_at)}</span>
|
|
</div>
|
|
{expanded === task.id && task.result_data && (
|
|
<pre className="ao-task-result">
|
|
{(() => {
|
|
try { return JSON.stringify(JSON.parse(task.result_data), null, 2); }
|
|
catch { return task.result_data; }
|
|
})()}
|
|
</pre>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|