assistant/renderer/components/Sidebar.tsx

61 lines
2.3 KiB
TypeScript

import { Plus, Settings } from './Icons';
import { STATUS_LABEL, type TaskSummary } from '../types';
function shortTime(value: string): string {
const date = new Date(value.replace(' ', 'T'));
if (Number.isNaN(date.getTime())) return value;
const now = new Date();
const hm = date.toTimeString().slice(0, 5);
return date.toDateString() === now.toDateString() ? hm : `${date.getMonth() + 1}/${date.getDate()} ${hm}`;
}
export function Sidebar({ tasks, activeId, backendLabel, backendOk, onSelect, onNew, onSettings }: {
tasks: TaskSummary[];
activeId: string | null;
backendLabel: string;
backendOk: boolean;
onSelect: (id: string) => void;
onNew: () => void;
onSettings: () => void;
}) {
return (
<aside className="sidebar" aria-label="任务列表">
<div className="sidebar-head">
<div className="brand">智能办事助手</div>
<button type="button" className="new-task" onClick={onNew} data-testid="new-task">
<Plus size={16} />新任务
</button>
</div>
<div className="section-label">最近任务</div>
<ul className="task-list">
{tasks.length === 0 && <li className="list-empty">还没有任务</li>}
{tasks.map(task => (
<li key={task.id}>
<button
type="button"
className={`task-item${task.id === activeId ? ' active' : ''}`}
onClick={() => onSelect(task.id)}
title={task.goal}
data-status={task.status}
>
<span className={`status-dot ${task.status}`} aria-hidden="true" />
<span className="task-item-body">
<span className="task-item-title">{task.goal || task.business_name || task.id}</span>
<span className="task-item-meta">
{task.business_name ? `${task.business_name} · ` : ''}{STATUS_LABEL[task.status] ?? task.status} · {shortTime(task.updated_at)}
</span>
</span>
</button>
</li>
))}
</ul>
<div className="sidebar-foot">
<span className={`backend-badge${backendOk ? ' ok' : ' err'}`} role="status">{backendLabel}</span>
<button type="button" className="icon-button" aria-label="设置" title="设置" onClick={onSettings}>
<Settings size={16} />
</button>
</div>
</aside>
);
}