// [nexus] Phase 11 — Projects list page (hero-stat cards).
//
// Replaces the old Paperclip EntityRow table with the spec §7.1
// hero-stat card grid. Cards stack full-width on <1024px and 2-up on
// >=1024px. A forest-green "+ NEW PROJECT" CTA sits top-right. When
// the project list is empty we render the 96px "NO PROJECTS YET"
// empty state canvas.
//
// Data gaps (Phase 11): the shared Project type has no
// milestoneProgress, nextGate, costBurned, lastActivity, or per-
// project agent counts. Each card renders "—" placeholders where
// data is missing. See the Phase 11 report for the full gap list.
import { useEffect, useMemo } from "react";
import { VOCAB } from "@paperclipai/branding";
import { useQuery } from "@tanstack/react-query";
import { useNavigate } from "@/lib/router";
import { projectsApi } from "../api/projects";
import { useCompany } from "../context/CompanyContext";
import { useDialog } from "../context/DialogContext";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys";
import { PageSkeleton } from "../components/PageSkeleton";
import { EmptyState } from "../components/EmptyState";
import { Hexagon } from "lucide-react";
import { projectUrl } from "../lib/utils";
import { cn } from "@/lib/utils";
import { ProjectCard, type ProjectCardStatus } from "../components/projects/ProjectCard";
function deriveStatus(status: string): ProjectCardStatus {
// Waiting > working > idle priority. For Phase 11 we only have the
// coarse ProjectStatus enum to work with; the finer-grained "has
// pending gate" / "has working agent" checks are data gaps.
if (status === "paused" || status === "awaiting_input") return "waiting";
if (status === "active" || status === "running") return "working";
return "idle";
}
export function Projects() {
const { selectedCompanyId } = useCompany();
const { openNewProject } = useDialog();
const { setBreadcrumbs } = useBreadcrumbs();
const navigate = useNavigate();
useEffect(() => {
setBreadcrumbs([{ label: "Projects" }]);
}, [setBreadcrumbs]);
const { data: allProjects, isLoading, error } = useQuery({
queryKey: queryKeys.projects.list(selectedCompanyId!),
queryFn: () => projectsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const projects = useMemo(
() => (allProjects ?? []).filter((p) => !p.archivedAt),
[allProjects],
);
if (!selectedCompanyId) {
return (
{error.message}
} {/* Card grid: 1 col <1024px, 2 cols >=1024px, 16px gap */}