Files
documente/frontend/src/pages/Dashboard.tsx
Luca Sacchi Ricciardi 74cefd3366 feat(frontend): implement complete React/Vite frontend
## Features
- React 18 + TypeScript + Vite setup
- Tailwind CSS + shadcn/ui components
- Complete authentication flow (API Key)
- Dashboard with stats and recent documents
- Document upload (drag & drop) and management
- Chat interface with RAG support
- Settings page (theme, provider selection)
- Responsive design with mobile support

## Components
- Layout with sidebar navigation
- Button, Card, Input, Label, Separator (shadcn)
- Protected and public routes

## State Management
- Zustand stores: auth, chat, settings
- Persisted to localStorage

## API Integration
- Axios client with interceptors
- All API endpoints integrated
- Error handling and loading states

## Pages
- Login: API key authentication
- Dashboard: Overview and stats
- Documents: Upload, list, delete
- Chat: Conversational interface with sources
- Settings: Theme and provider config

🎨 Production-ready build (339KB gzipped)
2026-04-06 11:44:46 +02:00

196 lines
7.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { apiClient } from '@/api/client';
import type { Document, SystemConfig } from '@/types';
import {
FileText,
Brain,
TrendingUp,
Upload,
MessageCircle,
ChevronRight,
Loader2
} from 'lucide-react';
import { useNavigate } from 'react-router-dom';
export function Dashboard() {
const [documents, setDocuments] = useState<Document[]>([]);
const [config, setConfig] = useState<SystemConfig | null>(null);
const [isLoading, setIsLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
const fetchData = async () => {
try {
const [docs, cfg] = await Promise.all([
apiClient.getDocuments(),
apiClient.getConfig(),
]);
setDocuments(docs.slice(0, 5)); // Last 5 documents
setConfig(cfg);
} catch (error) {
console.error('Error fetching dashboard data:', error);
} finally {
setIsLoading(false);
}
};
fetchData();
}, []);
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
);
}
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-3xl font-bold tracking-tight">Dashboard</h1>
<p className="text-muted-foreground">
Overview of your AgenticRAG system
</p>
</div>
{/* Stats Cards */}
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Documents</CardTitle>
<FileText className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{documents.length}</div>
<p className="text-xs text-muted-foreground">
Documents in your knowledge base
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Provider</CardTitle>
<Brain className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold capitalize">
{config?.default_llm_provider || 'N/A'}
</div>
<p className="text-xs text-muted-foreground">
{config?.default_llm_model || ''}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">System Status</CardTitle>
<TrendingUp className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">Active</div>
<p className="text-xs text-muted-foreground">
All systems operational
</p>
</CardContent>
</Card>
</div>
{/* Quick Actions */}
<div className="grid gap-4 md:grid-cols-2">
<Card className="cursor-pointer hover:bg-accent/50 transition-colors" onClick={() => navigate('/documents')}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-lg">Upload Documents</CardTitle>
<Upload className="h-5 w-5 text-primary" />
</div>
<CardDescription>
Add new documents to your knowledge base
</CardDescription>
</CardHeader>
<CardContent>
<Button variant="secondary" className="w-full">
Go to Upload
<ChevronRight className="ml-2 h-4 w-4" />
</Button>
</CardContent>
</Card>
<Card className="cursor-pointer hover:bg-accent/50 transition-colors" onClick={() => navigate('/chat')}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="text-lg">Start Chat</CardTitle>
<MessageCircle className="h-5 w-5 text-primary" />
</div>
<CardDescription>
Ask questions about your documents
</CardDescription>
</CardHeader>
<CardContent>
<Button variant="secondary" className="w-full">
Open Chat
<ChevronRight className="ml-2 h-4 w-4" />
</Button>
</CardContent>
</Card>
</div>
{/* Recent Documents */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>Recent Documents</CardTitle>
<CardDescription>Recently uploaded documents</CardDescription>
</div>
<Button variant="outline" onClick={() => navigate('/documents')}>
View All
</Button>
</div>
</CardHeader>
<CardContent>
{documents.length === 0 ? (
<div className="text-center py-8 text-muted-foreground">
<FileText className="mx-auto h-12 w-12 mb-4 opacity-50" />
<p>No documents yet</p>
<p className="text-sm">Upload your first document to get started</p>
</div>
) : (
<div className="space-y-4">
{documents.map((doc) => (
<div
key={doc.id}
className="flex items-center justify-between p-4 rounded-lg border bg-card hover:bg-accent/50 transition-colors"
>
<div className="flex items-center space-x-4">
<FileText className="h-8 w-8 text-primary" />
<div>
<p className="font-medium">{doc.filename}</p>
<p className="text-sm text-muted-foreground">
{new Date(doc.created_at).toLocaleDateString()} {(doc.size / 1024).toFixed(1)} KB
</p>
</div>
</div>
<div className={`px-3 py-1 rounded-full text-xs font-medium ${
doc.status === 'ready'
? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
: doc.status === 'processing'
? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200'
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
}`}>
{doc.status}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
}