feat(HAI-012): complete Steps 1-2 — add activity timeline to TaskDetailModal with styling
This commit is contained in:
@@ -5,6 +5,21 @@ import type { Task, TaskDetail, Column, MergeResult } from "@hai/core";
|
||||
import { COLUMN_LABELS, VALID_TRANSITIONS } from "@hai/core";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
|
||||
function formatTimestamp(iso: string): string {
|
||||
const date = new Date(iso);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMin = Math.floor(diffMs / 60000);
|
||||
const diffHr = Math.floor(diffMin / 60);
|
||||
const diffDay = Math.floor(diffHr / 24);
|
||||
|
||||
if (diffMin < 1) return "just now";
|
||||
if (diffMin < 60) return `${diffMin}m ago`;
|
||||
if (diffHr < 24) return `${diffHr}h ago`;
|
||||
if (diffDay < 7) return `${diffDay}d ago`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
interface TaskDetailModalProps {
|
||||
task: TaskDetail;
|
||||
onClose: () => void;
|
||||
@@ -121,6 +136,28 @@ export function TaskDetailModal({
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className="detail-section detail-activity">
|
||||
<h4>Activity</h4>
|
||||
{task.log && task.log.length > 0 ? (
|
||||
<div className="detail-activity-list">
|
||||
{[...task.log].reverse().map((entry, i) => (
|
||||
<div key={i} className="detail-log-entry">
|
||||
<div className="detail-log-header">
|
||||
<span className="detail-log-timestamp">
|
||||
{formatTimestamp(entry.timestamp)}
|
||||
</span>
|
||||
<span className="detail-log-action">{entry.action}</span>
|
||||
</div>
|
||||
{entry.outcome && (
|
||||
<div className="detail-log-outcome">{entry.outcome}</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="detail-log-empty">(no activity)</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-actions">
|
||||
<button className="btn btn-danger btn-sm" onClick={handleDelete}>
|
||||
|
||||
@@ -527,6 +527,81 @@ html, body {
|
||||
font-family: "SF Mono", Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* === Activity Timeline === */
|
||||
.detail-activity {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.detail-activity-list {
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.detail-activity-list::-webkit-scrollbar { width: 4px; }
|
||||
.detail-activity-list::-webkit-scrollbar-track { background: transparent; }
|
||||
.detail-activity-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||
|
||||
.detail-log-entry {
|
||||
padding: 8px 0 8px 14px;
|
||||
border-left: 2px solid var(--border);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detail-log-entry::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -4px;
|
||||
top: 13px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-dim);
|
||||
}
|
||||
|
||||
.detail-log-entry:first-child::before {
|
||||
background: var(--todo);
|
||||
}
|
||||
|
||||
.detail-log-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.detail-log-timestamp {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
font-family: "SF Mono", Monaco, Consolas, monospace;
|
||||
}
|
||||
|
||||
.detail-log-action {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.detail-log-outcome {
|
||||
margin-top: 6px;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg);
|
||||
border-left: 3px solid var(--border);
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.detail-log-empty {
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
/* === Toasts === */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user