// Page 6 — Tasks History (timeline)
function TasksPage() {
const [moduleFilter, setModuleFilter] = useState("all");
const [agentFilter, setAgentFilter] = useState("all");
const [periodFilter, setPeriodFilter] = useState("week");
const [problemsOnly, setProblemsOnly] = useState(false);
const [selected, setSelected] = useState(49); // PB-49 selected by default
const modules = useMemo(() => {
const s = new Set(TASKS.map(t => t.module));
return Array.from(s);
}, []);
const filtered = useMemo(() => {
const now = NOW.getTime();
const cutoff =
periodFilter === "day" ? now - 24 * 3600 * 1000 :
periodFilter === "week" ? now - 7 * 24 * 3600 * 1000 :
periodFilter === "month" ? now - 30 * 24 * 3600 * 1000 :
0;
return TASKS.filter(t => {
if (cutoff && new Date(t.createdAt).getTime() < cutoff) return false;
if (moduleFilter !== "all" && t.module !== moduleFilter) return false;
if (agentFilter !== "all" && !t.agents.includes(agentFilter)) return false;
if (problemsOnly && t.status === "ok") return false;
return true;
});
}, [moduleFilter, agentFilter, periodFilter, problemsOnly]);
const sortedByCreated = [...filtered].sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
const selectedTask = TASKS.find(t => t.seq === selected) || filtered[0];
return (
История задач
Жизненный цикл задач Plane. Кружки — задачи, цвет — наличие алертов.
{/* Filters */}
{/* Timeline strip */}
Timeline · {periodFilter === "day" ? "за сутки" : periodFilter === "week" ? "за неделю" : periodFilter === "month" ? "за месяц" : "за всё время"}
{/* Detail */}
Задачи в выборке
{filtered.slice().reverse().map(t => (
setSelected(t.seq)} style={{
background: selected === t.seq ? "var(--surface-hi)" : "transparent",
}}>
PB-{t.seq}
{t.title}
{fmtDate(t.createdAt)}
{t.state}
))}
{selectedTask &&
}
);
}
function FilterGroup({ label, children }) {
return (
);
}
function Sep() { return
; }
function Legend({ color, label }) {
return (
{label}
);
}
function statusColor(s) {
return s === "ok" ? "var(--ok)" :
s === "info" ? "var(--info)" :
s === "warn" ? "var(--warn)" :
s === "crit" ? "var(--crit)" :
"#52525b";
}
function TaskTimeline({ tasks, selected, onSelect }) {
if (tasks.length === 0) return Нет задач в выборке.
;
// Group by date
const groups = {};
tasks.forEach(t => {
const day = t.createdAt.slice(0, 10);
(groups[day] = groups[day] || []).push(t);
});
const days = Object.keys(groups).sort();
return (
{days.map((d) => (
{groups[d].map((t) => (
onSelect(t.seq)} />
))}
{new Date(d).toLocaleDateString("ru-RU", { day: "2-digit", month: "short" })}
))}
);
}
function TimelineBubble({ task, selected, onSelect }) {
const color = statusColor(task.status === "open" ? "open" : task.status);
const size = task.status === "crit" ? 28 : task.status === "warn" ? 24 : 20;
return (
{task.seq}
);
}
function TaskDetail({ task }) {
// Mock state transitions
const transitions = useMemo(() => {
const created = new Date(task.createdAt);
const closed = task.closedAt ? new Date(task.closedAt) : null;
const span = closed ? closed - created : NOW - created;
const t = (ratio) => new Date(created.getTime() + span * ratio).toISOString();
if (task.state === "Done") {
return [
{ state: "Backlog", at: task.createdAt, by: task.agents[0] },
{ state: "Todo", at: t(0.1), by: task.agents[0] },
{ state: "In Progress", at: t(0.25), by: task.agents[1] || task.agents[0] },
{ state: "In Review", at: t(0.7), by: task.agents[2] || task.agents[0] },
{ state: "Done", at: task.closedAt, by: task.agents[2] || task.agents[0] },
];
}
return [
{ state: "Backlog", at: task.createdAt, by: task.agents[0] },
{ state: "Todo", at: t(0.3), by: task.agents[0] },
{ state: "In Progress", at: t(0.7), by: task.agents[1] || task.agents[0] },
];
}, [task.seq]);
const taskAlerts = ALERTS.filter(a => a.seq === task.seq);
const taskEvents = EVENTS.filter(e => e.seq === task.seq);
return (
PB-{task.seq} · {task.title}
Открыть в Plane ↗
Создана: {fmtDateTime(task.createdAt)}
{task.closedAt && Закрыта: {fmtDateTime(task.closedAt)} }
state: {task.state}
Переходы состояний
{transitions.map((tr, i) => (
{tr.state}
{fmtTime(tr.at)}
{i < transitions.length - 1 && (
›
)}
))}
Кто работал
{task.agents.map(aid => {
const a = teamById(aid);
if (!a) return null;
return (
);
})}
Сработавшие алерты
{taskAlerts.length === 0 ? (
● Без инцидентов
) : (
{taskAlerts.map(a => (
{a.ruleTitle}
{fmtTime(a.ts)}
))}
)}
{taskEvents.length > 0 && (
События
{taskEvents.map((ev, i) => (
{fmtTime(ev.ts)}
{ev.text}
{kindLabel(ev.kind)}
))}
)}
);
}
window.TasksPage = TasksPage;