69 lines
2.9 KiB
TypeScript
69 lines
2.9 KiB
TypeScript
import { memo } from 'react';
|
|
import { LocalTask } from '../../api';
|
|
import { relativeTime, statusTone, formatStatusLabel } from '../../lib/utils';
|
|
|
|
interface LocalTaskListItemProps {
|
|
task: LocalTask;
|
|
active: boolean;
|
|
onClick: () => void;
|
|
}
|
|
|
|
export const LocalTaskListItem = memo(function LocalTaskListItem({ task, active, onClick }: LocalTaskListItemProps) {
|
|
const status = task.latestJob?.status ?? 'queued';
|
|
const tone = statusTone(status);
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`w-full text-left px-3 py-2.5 rounded-md border transition-colors ${
|
|
active
|
|
? 'border-accent/60 bg-accent-soft'
|
|
: 'border-hairline bg-canvas hover:bg-surface'
|
|
}`}
|
|
>
|
|
<div className="flex items-center justify-between gap-2 min-w-0">
|
|
<div className="flex items-center gap-1.5 min-w-0">
|
|
<span className="text-[10px] font-mono text-slate-400 tabular-nums">#{task.id}</span>
|
|
<span className="text-[13px] font-semibold text-slate-900 truncate">{task.title}</span>
|
|
</div>
|
|
<div className="flex-shrink-0 flex items-center gap-1.5">
|
|
{task.subtaskCount != null && task.subtaskCount > 0 && (
|
|
<span className="text-[10px] font-mono text-slate-400 tabular-nums">
|
|
{task.subtaskCompleted ?? 0}/{task.subtaskCount}
|
|
</span>
|
|
)}
|
|
<span
|
|
className="px-1.5 py-0.5 rounded text-[10px] font-medium border"
|
|
style={{ background: tone.bg, color: tone.fg, borderColor: 'transparent' }}
|
|
>
|
|
{formatStatusLabel(status)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="mt-1 text-2xs text-slate-500 truncate leading-snug">
|
|
{task.body.length > 80 ? `${task.body.slice(0, 80)}…` : task.body}
|
|
</div>
|
|
<div className="mt-1.5 flex items-center gap-1.5 text-[10px]">
|
|
<span className="font-mono text-slate-400 tabular-nums">{relativeTime(task.updatedAt)}</span>
|
|
<span className="text-slate-300">·</span>
|
|
{task.ownerId ? (
|
|
<span className="text-slate-600">{task.ownerName ?? 'user'}</span>
|
|
) : (
|
|
<span className="text-slate-400">system</span>
|
|
)}
|
|
{task.visibility === 'private' && (
|
|
<span className="px-1 rounded text-[10px] font-medium bg-amber-50 text-amber-700 border border-amber-100" title="Private">private</span>
|
|
)}
|
|
{task.visibility === 'org' && (
|
|
<span className="px-1 rounded text-[10px] font-medium bg-blue-50 text-blue-700 border border-blue-100" title={`Shared with ${task.visibilityScopeOrgName ?? 'org'}`}>
|
|
{task.visibilityScopeOrgName ?? 'org'}
|
|
</span>
|
|
)}
|
|
{task.visibility === 'public' && (
|
|
<span className="px-1 rounded text-[10px] font-medium bg-emerald-50 text-emerald-700 border border-emerald-100" title="Public">public</span>
|
|
)}
|
|
</div>
|
|
</button>
|
|
);
|
|
});
|