79 lines
3.1 KiB
React
79 lines
3.1 KiB
React
import StatusDot from './StatusDot.jsx'
|
|
import { kindColor } from '../../pages/task/kind.js'
|
|
|
|
const statusLabel = (s) => (s === 'done' ? '完成' : s === 'doing' ? '执行中' : '未开始')
|
|
|
|
// 列表视图的一行 (Todoist 风格, T-UI-01)
|
|
// 点击行体 → 打开详情; 长按 400ms 后左右拖动换列 (bindDrag 来自 useRowDrag)
|
|
// 子任务与普通任务同构: 标题一行 + 状态元信息一行 (字体略小), 不显示分组标签
|
|
// 复选框 / ⋯ 是独立控件: pointerdown 不冒泡, 不发起拖动,
|
|
// 避免长按行后抬手时合成的 click 落在控件上误触状态切换
|
|
export default function TaskRow({
|
|
task, onToggle, onClick, onMore, bindDrag, bindDragHandle, dragging = false, dragOver, indent = false, children,
|
|
}) {
|
|
const done = task.status === 'done'
|
|
return (
|
|
<>
|
|
<div
|
|
className={
|
|
`${indent ? 'sub-row' : 'task-row'}${done ? ' done' : ''}`
|
|
+ (dragging ? ' dragging' : '')
|
|
+ (dragOver ? ` dragover ${dragOver.after ? 'after' : 'before'}` : '')
|
|
}
|
|
data-task-id={indent ? undefined : task.id}
|
|
onClick={onClick}
|
|
{...(!indent && bindDrag ? bindDrag(task) : {})}
|
|
>
|
|
{!indent && bindDragHandle && (
|
|
<span className="grip" title="拖动排序 / 换列" {...bindDragHandle(task)}>⠿</span>
|
|
)}
|
|
<div
|
|
className="box"
|
|
onClick={(e) => { e.stopPropagation(); onToggle?.(task) }}
|
|
onPointerDown={(e) => e.stopPropagation()}
|
|
>
|
|
<svg viewBox="0 0 14 14" width={indent ? 10 : 11} height={indent ? 10 : 11}>
|
|
<path d="M2.5 7.2l3 3 6-6.4" stroke="#fff" strokeWidth="2" fill="none" strokeLinecap="round" />
|
|
</svg>
|
|
</div>
|
|
|
|
{indent ? (
|
|
<div className="sub-main">
|
|
<span className="sub-title">{task.title}</span>
|
|
<div className="task-meta sub">
|
|
<StatusDot status={task.status} />
|
|
<span>{statusLabel(task.status)}</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<div className="task-main">
|
|
<div className="task-title">{task.title}</div>
|
|
{/* 无类型/成员也保留该行, 与有标签的行等高 */}
|
|
<div className="task-meta">
|
|
{task.kind && (
|
|
<span className="kind-tag" style={{ background: kindColor(task.kind) }}>{task.kind}</span>
|
|
)}
|
|
{task.members?.length > 0 && (
|
|
<span className="members">+ {task.members.map((m) => m.name).join('、')}</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="task-right">
|
|
<span
|
|
className="more"
|
|
onClick={(e) => { e.stopPropagation(); onMore?.(task) }}
|
|
onPointerDown={(e) => e.stopPropagation()}
|
|
>⋯</span>
|
|
<span className={`assignee${task.assignee ? '' : ' none'}`}>
|
|
{task.assignee?.name || '未指派'}
|
|
</span>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
{children}
|
|
</>
|
|
)
|
|
}
|