Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
import { api } from '@/lib/api';
|
|
import type { Project } from '@/lib/types';
|
|
import { ProjectCard } from '@/components/projects/project-card';
|
|
|
|
export default function ProjectsPage(): React.ReactElement {
|
|
const [projects, setProjects] = useState<Project[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
api<Project[]>('/api/projects')
|
|
.then(setProjects)
|
|
.catch(() => {})
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
const handleProjectClick = useCallback((project: Project) => {
|
|
console.log('Project clicked:', project.id);
|
|
}, []);
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-6 flex items-center justify-between">
|
|
<h1 className="text-2xl font-semibold">Projects</h1>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<p className="py-8 text-center text-sm text-text-muted">Loading projects...</p>
|
|
) : projects.length === 0 ? (
|
|
<div className="py-12 text-center">
|
|
<h2 className="text-lg font-medium text-text-secondary">No projects yet</h2>
|
|
<p className="mt-1 text-sm text-text-muted">
|
|
Projects will appear here when created via the gateway API
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
{projects.map((project) => (
|
|
<ProjectCard key={project.id} project={project} onClick={handleProjectClick} />
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Mission status section */}
|
|
<MissionStatus />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MissionStatus(): React.ReactElement {
|
|
const [mission, setMission] = useState<Record<string, unknown> | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
api<Record<string, unknown>>('/api/coord/status')
|
|
.then(setMission)
|
|
.catch(() => setMission(null))
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
return (
|
|
<section className="mt-8">
|
|
<h2 className="mb-4 text-lg font-semibold">Active Mission</h2>
|
|
{loading ? (
|
|
<p className="text-sm text-text-muted">Loading mission status...</p>
|
|
) : !mission ? (
|
|
<div className="rounded-lg border border-surface-border bg-surface-card p-6 text-center">
|
|
<p className="text-sm text-text-muted">No active mission detected</p>
|
|
</div>
|
|
) : (
|
|
<div className="rounded-lg border border-surface-border bg-surface-card p-4">
|
|
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
<StatCard label="Mission" value={String(mission['missionId'] ?? 'Unknown')} />
|
|
<StatCard label="Phase" value={String(mission['currentPhase'] ?? '—')} />
|
|
<StatCard
|
|
label="Tasks"
|
|
value={`${mission['completedTasks'] ?? 0} / ${mission['totalTasks'] ?? 0}`}
|
|
/>
|
|
<StatCard label="Status" value={String(mission['status'] ?? '—')} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function StatCard({ label, value }: { label: string; value: string }): React.ReactElement {
|
|
return (
|
|
<div className="rounded-lg bg-surface-elevated p-3">
|
|
<p className="text-xs text-text-muted">{label}</p>
|
|
<p className="mt-1 text-sm font-medium text-text-primary">{value}</p>
|
|
</div>
|
|
);
|
|
}
|