Files
codedump/ai-tools-dashboard/client/src/pages/Dashboard.tsx
T
2026-04-22 21:25:42 -05:00

132 lines
5.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { FolderKanban, Wrench, TrendingUp, Sparkles, ArrowRight, CheckCircle2 } from 'lucide-react';
import type { Project, Tool } from '../types';
import { getProjects, getTools } from '../api';
import ProjectCard from '../components/projects/ProjectCard';
import ToolCard from '../components/tools/ToolCard';
import ProgressBar from '../components/ui/ProgressBar';
import { useSettings } from '../hooks/useSettings';
function StatCard({ label, value, icon: Icon, sub }: { label: string; value: string | number; icon: any; sub?: string }) {
return (
<div className="bg-card border border-border rounded-xl p-5">
<div className="flex items-start justify-between mb-3">
<p className="text-sm text-muted font-medium">{label}</p>
<div className="w-8 h-8 rounded-lg bg-[var(--accent-dim)] border border-[var(--accent)]/20 flex items-center justify-center">
<Icon size={15} style={{ color: 'var(--accent)' }} />
</div>
</div>
<p className="text-3xl font-bold text-white tabular-nums">{value}</p>
{sub && <p className="text-xs text-muted mt-1">{sub}</p>}
</div>
);
}
export default function Dashboard() {
const { settings } = useSettings();
const [projects, setProjects] = useState<Project[]>([]);
const [tools, setTools] = useState<Tool[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
Promise.all([getProjects(), getTools()])
.then(([p, t]) => { setProjects(p); setTools(t); })
.finally(() => setLoading(false));
}, []);
const active = projects.filter((p) => p.status === 'active');
const complete = projects.filter((p) => p.status === 'complete');
const newTools = tools.filter((t) => t.is_new);
const avgCompletion = projects.length
? Math.round(projects.reduce((s, p) => s + p.completion, 0) / projects.length)
: 0;
const recent = [...projects].slice(0, 6);
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="w-6 h-6 border-2 border-[var(--accent)] border-t-transparent rounded-full animate-spin" />
</div>
);
}
return (
<div className="p-8 max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-2xl font-bold text-white mb-1">{settings.app_title}</h1>
<p className="text-muted text-sm">High-level overview of tools and projects.</p>
</div>
{/* Stats */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<StatCard label="Total Projects" value={projects.length} icon={FolderKanban} sub={`${active.length} active`} />
<StatCard label="Completed" value={complete.length} icon={CheckCircle2} sub="finished projects" />
<StatCard label="Avg Completion" value={`${avgCompletion}%`} icon={TrendingUp} sub="across all projects" />
<StatCard label="Available Tools" value={tools.length} icon={Wrench} sub={`${newTools.length} new`} />
</div>
{/* Overall progress */}
{projects.length > 0 && (
<div className="bg-card border border-border rounded-xl p-5 mb-8">
<div className="flex items-center justify-between mb-3">
<p className="text-sm font-medium text-white">Overall Portfolio Progress</p>
<span className="text-sm font-mono text-white">{avgCompletion}%</span>
</div>
<ProgressBar value={avgCompletion} size="md" />
</div>
)}
{/* New Tools Spotlight */}
{newTools.length > 0 && (
<section className="mb-8">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<Sparkles size={16} style={{ color: 'var(--accent)' }} />
<h2 className="text-base font-semibold text-white">New Tools Available</h2>
</div>
<Link to="/tools" className="text-xs text-muted hover:text-white flex items-center gap-1 transition-colors">
View all <ArrowRight size={12} />
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{newTools.slice(0, 3).map((tool) => (
<ToolCard key={tool.id} tool={tool} />
))}
</div>
</section>
)}
{/* Recent Projects */}
<section>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<FolderKanban size={16} style={{ color: 'var(--accent)' }} />
<h2 className="text-base font-semibold text-white">Recent Projects</h2>
</div>
<Link to="/projects" className="text-xs text-muted hover:text-white flex items-center gap-1 transition-colors">
View all <ArrowRight size={12} />
</Link>
</div>
{recent.length === 0 ? (
<div className="bg-card border border-border rounded-xl p-12 text-center">
<FolderKanban size={32} className="mx-auto mb-3 text-muted/40" />
<p className="text-muted text-sm">No projects yet.</p>
<Link to="/projects" className="text-xs mt-2 inline-block" style={{ color: 'var(--accent)' }}>
Create your first project
</Link>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{recent.map((project) => (
<ProjectCard key={project.id} project={project} />
))}
</div>
)}
</section>
</div>
);
}