import { useQueueStatus, useWorkspaces } from "@/api/queries";
import type { components } from "@/api/schema.d.ts";
import { ErrorAlert } from "@/components/shared/ErrorAlert";
import { PageLoader } from "@/components/shared/LoadingSpinner";
import { Body, Muted, PageTitle, SectionHeading } from "@/components/ui/typography";
import { COLOR } from "@/lib/constants";
import { formatCount } from "@/lib/utils";
import { Link } from "@tanstack/react-router";
import { motion } from "framer-motion";
import { Activity, Boxes, ChevronRight, CircleDot, LayoutDashboard } from "lucide-react";
import { useState } from "react";
type QueueStatus = components["schemas"]["QueueStatus"];
// ─── Per-workspace queue row ─────────────────────────────────────────────────
function WorkspaceQueueRow({ workspaceId }: { workspaceId: string }) {
const { data, isLoading } = useQueueStatus(workspaceId);
const pending = data?.pending_work_units ?? 0;
const active = data?.in_progress_work_units ?? 0;
const done = data?.completed_work_units ?? 0;
const total = data?.total_work_units ?? 0;
const isActive = active > 0 || pending > 0;
return (
|
{workspaceId}
|
{isLoading ? (
…
) : (
{isActive ? (
) : (
)}
{isActive ? `${formatCount(pending + active)} pending` : "Idle"}
)}
|
{(
[
{ val: total, color: "var(--text-2)" },
{ val: done, color: COLOR.success },
{ val: active, color: COLOR.warning },
{ val: pending, color: "var(--text-3)" },
] as Array<{ val: number; color: string }>
).map(({ val, color }, i) => (
{isLoading ? "—" : formatCount(val)}
|
))}
);
}
// ─── Aggregate banner ─────────────────────────────────────────────────────────
// Each workspace row already called useQueueStatus — TanStack Query deduplicates
// the fetches so calling the same hooks here just reads from cache.
function GlobalQueueBanner({ workspaces }: { workspaces: Array<{ id: string }> }) {
const statuses = workspaces.map((ws) => {
const { data } = useQueueStatus(ws.id);
return data as QueueStatus | undefined;
});
const totalPending = statuses.reduce((s, d) => s + (d?.pending_work_units ?? 0), 0);
const totalActive = statuses.reduce((s, d) => s + (d?.in_progress_work_units ?? 0), 0);
const totalDone = statuses.reduce((s, d) => s + (d?.completed_work_units ?? 0), 0);
const allLoaded = statuses.every((d) => d !== undefined);
return (
{(
[
{ label: "Workspaces", value: workspaces.length, color: "var(--text-1)", always: true },
{ label: "Total done", value: totalDone, color: COLOR.success, always: false },
{ label: "Active", value: totalActive, color: COLOR.warning, always: false },
{
label: "Pending",
value: totalPending,
color: totalPending > 0 ? COLOR.warning : "var(--text-3)",
always: false,
},
] as Array<{ label: string; value: number; color: string; always: boolean }>
).map(({ label, value, color, always }) => (
{allLoaded || always ? formatCount(value) : "—"}
{label}
))}
);
}
// ─── Main dashboard ───────────────────────────────────────────────────────────
export function Dashboard() {
const [page] = useState(1);
const { data, isLoading, error } = useWorkspaces(page, 50);
const workspaces =
(data as { items?: Array<{ id: string; created_at?: string }> } | undefined)?.items ?? [];
const total = (data as { total?: number } | undefined)?.total ?? 0;
return (
Dashboard
{total > 0 && (
{total} workspace{total !== 1 ? "s" : ""}
)}
Overview of your Honcho instance
{isLoading &&
}
{!isLoading && workspaces.length > 0 && (
{/* Aggregate stat row */}
{/* Per-workspace queue table */}
Queue Status
all workspaces · updates every 10s
{["Workspace", "Status", "Total", "Done", "Active", "Pending"].map((h) => (
|
{h}
|
))}
{workspaces.map((ws) => (
))}
{total > workspaces.length && (
Showing {workspaces.length} of {total} workspaces.{" "}
View all →
)}
)}
{!isLoading && workspaces.length === 0 && (
No workspaces found.
)}
);
}