Fix: Dashboard history and recent tasks visibility

- Recent Tasks widget now shows missions instead of tasks (fixing broken links)
- History page now shows Recent Runs at the top, before Missions
- This ensures completed runs are visible without scrolling past 50+ missions
This commit is contained in:
Thomas Marchand
2025-12-18 22:53:30 +00:00
parent 2c1b280240
commit c49801466e
2 changed files with 104 additions and 94 deletions

View File

@@ -248,6 +248,81 @@ export default function HistoryPage() {
</div>
) : (
<div className="space-y-6">
{/* Archived Runs - shown first for visibility */}
{filteredRuns.length > 0 && (
<div>
<h2 className="mb-3 text-xs font-medium uppercase tracking-wider text-white/40">
Recent Runs ({filteredRuns.length})
</h2>
<div className="rounded-xl bg-white/[0.02] border border-white/[0.04] overflow-hidden">
<table className="w-full">
<thead>
<tr className="border-b border-white/[0.04]">
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Status
</th>
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Input
</th>
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Created
</th>
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Cost
</th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.04]">
{filteredRuns.map((run) => {
const status = run.status as keyof typeof statusIcons;
const Icon = statusIcons[status] || Clock;
const config =
statusConfig[status] || statusConfig.pending;
return (
<tr
key={run.id}
className="group hover:bg-white/[0.02] transition-colors"
>
<td className="px-4 py-3">
<span
className={cn(
"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium",
config.bg,
config.color
)}
>
<Icon className="h-3 w-3" />
{run.status}
</span>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<p className="max-w-md truncate text-sm text-white/80">
{run.input_text}
</p>
<CopyButton text={run.input_text} showOnHover label="Copied input" />
</div>
</td>
<td className="px-4 py-3">
<RelativeTime
date={run.created_at}
className="text-xs text-white/40"
/>
</td>
<td className="px-4 py-3">
<span className="text-sm text-emerald-400 tabular-nums">
${(run.total_cost_cents / 100).toFixed(2)}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
{/* Missions */}
{filteredMissions.length > 0 && (
<div>
@@ -449,80 +524,6 @@ export default function HistoryPage() {
</div>
)}
{/* Archived Runs */}
{filteredRuns.length > 0 && (
<div>
<h2 className="mb-3 text-xs font-medium uppercase tracking-wider text-white/40">
Archived Runs ({filteredRuns.length})
</h2>
<div className="rounded-xl bg-white/[0.02] border border-white/[0.04] overflow-hidden">
<table className="w-full">
<thead>
<tr className="border-b border-white/[0.04]">
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Status
</th>
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Input
</th>
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Created
</th>
<th className="px-4 py-3 text-left text-[10px] font-medium uppercase tracking-wider text-white/40">
Cost
</th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.04]">
{filteredRuns.map((run) => {
const status = run.status as keyof typeof statusIcons;
const Icon = statusIcons[status] || Clock;
const config =
statusConfig[status] || statusConfig.pending;
return (
<tr
key={run.id}
className="group hover:bg-white/[0.02] transition-colors"
>
<td className="px-4 py-3">
<span
className={cn(
"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[10px] font-medium",
config.bg,
config.color
)}
>
<Icon className="h-3 w-3" />
{run.status}
</span>
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<p className="max-w-md truncate text-sm text-white/80">
{run.input_text}
</p>
<CopyButton text={run.input_text} showOnHover label="Copied input" />
</div>
</td>
<td className="px-4 py-3">
<RelativeTime
date={run.created_at}
className="text-xs text-white/40"
/>
</td>
<td className="px-4 py-3">
<span className="text-sm text-emerald-400 tabular-nums">
${(run.total_cost_cents / 100).toFixed(2)}
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
</div>
)}
</div>

View File

@@ -3,7 +3,7 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { cn } from "@/lib/utils";
import { listTasks, TaskState } from "@/lib/api";
import { listMissions, Mission } from "@/lib/api";
import {
ArrowRight,
CheckCircle,
@@ -11,18 +11,21 @@ import {
Loader,
Clock,
Ban,
Target,
} from "lucide-react";
const statusIcons = {
const statusIcons: Record<string, typeof Clock> = {
pending: Clock,
active: Loader,
running: Loader,
completed: CheckCircle,
failed: XCircle,
cancelled: Ban,
};
const statusColors = {
const statusColors: Record<string, string> = {
pending: "text-amber-400",
active: "text-indigo-400",
running: "text-indigo-400",
completed: "text-emerald-400",
failed: "text-red-400",
@@ -30,30 +33,34 @@ const statusColors = {
};
export function RecentTasks() {
const [tasks, setTasks] = useState<TaskState[]>([]);
const [missions, setMissions] = useState<Mission[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchTasks = async () => {
const fetchMissions = async () => {
try {
const data = await listTasks();
setTasks(data.slice(0, 5));
const data = await listMissions();
// Sort by updated_at descending and take top 5
const sorted = data
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
.slice(0, 5);
setMissions(sorted);
} catch (error) {
console.error("Failed to fetch tasks:", error);
console.error("Failed to fetch missions:", error);
} finally {
setLoading(false);
}
};
fetchTasks();
const interval = setInterval(fetchTasks, 3000);
fetchMissions();
const interval = setInterval(fetchMissions, 5000);
return () => clearInterval(interval);
}, []);
return (
<div>
<div className="mb-4 flex items-center justify-between">
<h3 className="text-sm font-medium text-white">Recent Tasks</h3>
<h3 className="text-sm font-medium text-white">Recent Missions</h3>
<span className="flex items-center gap-1.5 rounded-md bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
LIVE
@@ -62,28 +69,30 @@ export function RecentTasks() {
{loading ? (
<p className="text-xs text-white/40">Loading...</p>
) : tasks.length === 0 ? (
<p className="text-xs text-white/40">No tasks yet</p>
) : missions.length === 0 ? (
<p className="text-xs text-white/40">No missions yet</p>
) : (
<div className="space-y-2">
{tasks.map((task) => {
const Icon = statusIcons[task.status];
{missions.map((mission) => {
const Icon = statusIcons[mission.status] || Clock;
const color = statusColors[mission.status] || "text-white/40";
const title = mission.title || "Untitled Mission";
return (
<Link
key={task.id}
href={`/control?task=${task.id}`}
key={mission.id}
href={`/control?mission=${mission.id}`}
className="flex items-center justify-between rounded-lg bg-white/[0.02] hover:bg-white/[0.04] border border-white/[0.04] hover:border-white/[0.08] p-3 transition-colors"
>
<div className="flex items-center gap-3">
<Icon
className={cn(
"h-4 w-4",
statusColors[task.status],
task.status === "running" && "animate-spin"
color,
(mission.status === "running" || mission.status === "active") && "animate-spin"
)}
/>
<span className="max-w-[180px] truncate text-sm text-white/80">
{task.task}
{title}
</span>
</div>
<ArrowRight className="h-4 w-4 text-white/30" />