auto-sync: 2026-05-19 13:45:03

This commit is contained in:
cfdaily
2026-05-19 13:45:03 +08:00
parent 77ccec13c3
commit 8a93603f3c
+48 -9
View File
@@ -409,26 +409,65 @@ export default function EdictBoard() {
</div>
</div>
{/* 筛选 + 搜索 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 0', flexWrap: 'wrap' }}>
{/* 第 1 行:归档控制 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 8 }}>
{(['active', 'archived', 'all'] as const).map(f => (
<button key={f} onClick={() => setArchiveFilter(f)} style={{
padding: '3px 10px', borderRadius: 6, fontSize: 11,
border: `1px solid ${archiveFilter === f ? 'var(--acc)' : '#2a3550'}`,
background: archiveFilter === f ? 'var(--acc)22' : '#161b2e',
color: archiveFilter === f ? 'var(--acc)' : '#8899aa',
cursor: 'pointer', transition: 'all .15s',
}}>
{f === 'active' ? '活跃' : f === 'archived' ? '归档' : '全部'}
</button>
))}
<span style={{ fontSize: 10, color: 'var(--muted)', marginLeft: 4 }}>{activeCount} · {archivedCount} · {topLevelTasks.length}</span>
<button onClick={async () => {
// 一键归档已完成
const pid = selectedProjectId;
if (!pid) return;
const doneTasks = topLevelTasks.filter(t => t.status === 'done' && !t.archived);
if (doneTasks.length === 0) { toast('没有可归档的任务'); return; }
try {
let count = 0;
for (const t of doneTasks) {
const res = await fetch(`/api/projects/${pid}/tasks/${t.id}`, {
method: 'PATCH', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ archived: 1 }),
});
if (res.ok) count++;
}
toast(`📦 已归档 ${count} 个任务`);
loadV2Tasks();
} catch { toast('归档失败', 'err'); }
}} style={{
marginLeft: 'auto', padding: '3px 10px', borderRadius: 6, fontSize: 11,
border: '1px solid #6b728044', background: '#161b2e', color: '#6b7280',
cursor: 'pointer', transition: 'all .15s',
}}>📦 </button>
</div>
{/* 第 2 行:状态筛选 + 搜索 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 4, padding: '0 0 6px', flexWrap: 'wrap' }}>
{filters.map(f => (
<button key={f.key} onClick={() => setStatusFilter(f.key)} style={{
padding: '3px 10px', borderRadius: 6, fontSize: 11,
padding: '2px 8px', borderRadius: 5, fontSize: 10,
border: `1px solid ${statusFilter === f.key ? 'var(--acc)' : '#2a3550'}`,
background: statusFilter === f.key ? 'var(--acc)22' : '#161b2e',
color: statusFilter === f.key ? 'var(--acc)' : '#8899aa',
cursor: 'pointer', transition: 'all .15s',
display: 'flex', alignItems: 'center', gap: 3,
display: 'flex', alignItems: 'center', gap: 2,
}}>
<span>{f.icon}</span><span>{f.label}</span>
{(counts[f.key] || 0) > 0 && (
<span style={{ fontSize: 10, background: statusFilter === f.key ? 'var(--acc)' : '#2a3550', color: statusFilter === f.key ? '#000' : '#8899aa', borderRadius: 8, padding: '0 4px', minWidth: 16, textAlign: 'center' }}>{counts[f.key]}</span>
<span style={{ fontSize: 9, background: statusFilter === f.key ? 'var(--acc)' : '#2a3550', color: statusFilter === f.key ? '#000' : '#8899aa', borderRadius: 8, padding: '0 4px', minWidth: 14, textAlign: 'center' }}>{counts[f.key]}</span>
)}
</button>
))}
<div style={{ marginLeft: 'auto', position: 'relative' }}>
<input type="text" placeholder="搜索任务..." value={searchQuery} onChange={e => setSearchQuery(e.target.value)}
style={{ padding: '4px 10px 4px 28px', borderRadius: 6, fontSize: 12, border: '1px solid #2a3550', background: '#161b2e', color: '#c0ccdd', width: 200, outline: 'none' }}
style={{ padding: '4px 10px 4px 28px', borderRadius: 6, fontSize: 11, border: '1px solid #2a3550', background: '#161b2e', color: '#c0ccdd', width: 180, outline: 'none' }}
/>
<span style={{ position: 'absolute', left: 8, top: '50%', transform: 'translateY(-50%)', fontSize: 12, color: '#556' }}>🔍</span>
</div>
@@ -438,14 +477,14 @@ export default function EdictBoard() {
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))', gap: 12, marginTop: 12 }}>
{filtered.map(t => {
const sub = subtaskIndex[t.id] || { total: 0, done: 0, activeStage: null };
return <TaskCard key={t.id} task={t} subtaskCount={sub.total} subtaskDone={sub.done} activeStage={sub.activeStage} onOpen={() => setModalTaskId(t.id)} />;
})}
return <TaskCard key={t.id} task={t} subtaskCount={sub.total} subtaskDone={sub.done} activeStage={sub.activeStage} onOpen={() => setModalTaskId(t.id)} onAction={(a) => handleCardAction(t.id, a)} />;
})}}
{filtered.length === 0 && tasks.length > 0 && (
<div style={{ gridColumn: '1/-1', textAlign: 'center', padding: 40, color: 'var(--muted)' }}></div>
)}
</div>
{tasks.length === 0 && <EmptyState hasProject={true} />}
{archiveFiltered.length === 0 && <EmptyState hasProject={true} />}
</div>
);
}