Files
spoon/apps/next/src/app/(app)/dashboard/page.tsx
T
2026-06-22 10:37:26 -04:00

168 lines
5.8 KiB
TypeScript

'use client';
import Link from 'next/link';
import { MetricCard } from '@/components/dashboard/metric-card';
import { SpoonCard } from '@/components/spoons/spoon-card';
import { MaintenanceQueue } from '@/components/threads/maintenance-queue';
import { useQuery } from 'convex/react';
import { GitBranch, MessageSquare, RefreshCw, ShieldCheck } from 'lucide-react';
import { api } from '@spoon/backend/convex/_generated/api.js';
import { Button, Card, CardContent, CardHeader, CardTitle } from '@spoon/ui';
const DashboardPage = () => {
const spoons = useQuery(api.spoons.listMine, {}) ?? [];
const syncRuns = useQuery(api.syncRuns.listRecent, { limit: 5 }) ?? [];
const threads = useQuery(api.threads.listMine, { limit: 25 }) ?? [];
const activeSpoons = spoons.filter(
(spoon) => spoon.status === 'active',
).length;
const behind = spoons.filter((spoon) => spoon.syncStatus === 'behind').length;
const diverged = spoons.filter(
(spoon) => spoon.syncStatus === 'diverged',
).length;
const openPullRequests = spoons.reduce(
(total, spoon) => total + (spoon.upstreamAheadBy ?? 0),
0,
);
return (
<main className='space-y-6'>
<div className='flex flex-col justify-between gap-4 md:flex-row md:items-end'>
<div>
<h1 className='text-3xl font-semibold tracking-normal'>Dashboard</h1>
<p className='text-muted-foreground mt-2'>
Monitor managed forks, upstream activity, and open maintenance
threads.
</p>
</div>
<Button asChild>
<Link href='/spoons/new'>Create Spoon</Link>
</Button>
</div>
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
<MetricCard
label='Spoons'
value={spoons.length}
note={`${activeSpoons} active`}
icon={GitBranch}
/>
<MetricCard
label='Behind upstream'
value={behind}
note={`${diverged} diverged`}
icon={RefreshCw}
/>
<MetricCard
label='Open threads'
value={
threads.filter(
(thread) =>
!['resolved', 'ignored', 'failed', 'cancelled'].includes(
thread.status,
),
).length
}
note='Across all Spoons'
icon={MessageSquare}
/>
<MetricCard
label='Upstream commits'
value={openPullRequests}
note='Waiting across Spoons'
icon={ShieldCheck}
/>
</div>
<section className='space-y-3'>
<h2 className='text-lg font-semibold'>Maintenance queue</h2>
<MaintenanceQueue threads={threads} />
</section>
<div className='grid gap-6 xl:grid-cols-2'>
<section className='space-y-3'>
<h2 className='text-lg font-semibold'>Recent Spoons</h2>
{spoons.length ? (
spoons
.slice(0, 3)
.map((spoon) => <SpoonCard key={spoon._id} spoon={spoon} />)
) : (
<Card className='shadow-none'>
<CardContent className='p-6'>
<p className='font-medium'>No Spoons yet</p>
<p className='text-muted-foreground mt-2 text-sm'>
Create a manual Spoon record to start shaping your fork
maintenance dashboard.
</p>
</CardContent>
</Card>
)}
</section>
<section className='space-y-3'>
<h2 className='text-lg font-semibold'>Recent activity</h2>
<Card className='shadow-none'>
<CardHeader>
<CardTitle className='text-base'>Upstream checks</CardTitle>
</CardHeader>
<CardContent>
{syncRuns.length ? (
<div className='space-y-3'>
{syncRuns.map((run) => (
<div
key={run._id}
className='border-border border p-3 text-sm'
>
<p className='font-medium'>
{run.kind.replaceAll('_', ' ')}
</p>
<p className='text-muted-foreground'>
{run.status.replaceAll('_', ' ')}
</p>
</div>
))}
</div>
) : (
<p className='text-muted-foreground text-sm'>
Scheduled upstream checks will appear here once provider
automation is connected.
</p>
)}
</CardContent>
</Card>
<Card className='mt-4 shadow-none'>
<CardHeader>
<CardTitle className='text-base'>Recent threads</CardTitle>
</CardHeader>
<CardContent>
{threads.length ? (
<div className='space-y-3'>
{threads.slice(0, 5).map((thread) => (
<div
key={thread._id}
className='border-border border p-3 text-sm'
>
<p className='font-medium'>{thread.title}</p>
<p className='text-muted-foreground'>
{thread.status.replaceAll('_', ' ')} ·{' '}
{thread.source.replaceAll('_', ' ')}
</p>
</div>
))}
</div>
) : (
<p className='text-muted-foreground text-sm'>
Threads appear when you request work or upstream changes need
review.
</p>
)}
</CardContent>
</Card>
</section>
</div>
</main>
);
};
export default DashboardPage;