← Назад
import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card'; import { Button } from './ui/button'; import { RefreshCw, TrendingUp, Activity, CheckCircle2, AlertTriangle, XCircle } from 'lucide-react'; interface DailyReportData { generated: string; period: { from: string; to: string; }; summary: string; data: { services: { healthy: number; warning: number; error: number; downtimes: Array<{ project: string; status: string; timestamp: string; details: any; }>; }; tasks: { new: number; completed: number; inProgress: number; byProject: Record<string, { new: number; completed: number; inProgress: number; total: number; }>; }; system: { cpu: { usage: string; cores: number; }; memory: { used: string; total: string; percent: string; }; disk: Array<{ mount: string; used: string; total: string; percent: string; }>; }; }; } async function fetchDailyReport(): Promise<DailyReportData> { const response = await fetch('/api/reports/daily'); if (!response.ok) { throw new Error('Failed to fetch daily report'); } return response.json(); } export function DailyReport() { const [isRefreshing, setIsRefreshing] = useState(false); const { data, isLoading, error, refetch } = useQuery({ queryKey: ['dailyReport'], queryFn: fetchDailyReport, refetchInterval: 5 * 60 * 1000, // Auto-refresh every 5 minutes }); const handleRefresh = async () => { setIsRefreshing(true); await refetch(); setTimeout(() => setIsRefreshing(false), 1000); }; if (isLoading) { return ( <Card className="bg-slate-800/50 border-slate-700"> <CardHeader> <CardTitle className="text-white flex items-center gap-2"> <Activity className="h-5 w-5" /> AI Ежедневный отчёт </CardTitle> </CardHeader> <CardContent> <div className="flex items-center justify-center py-8"> <RefreshCw className="h-6 w-6 animate-spin text-blue-400" /> </div> </CardContent> </Card> ); } if (error) { return ( <Card className="bg-slate-800/50 border-slate-700"> <CardHeader> <CardTitle className="text-white flex items-center gap-2"> <Activity className="h-5 w-5" /> AI Ежедневный отчёт </CardTitle> </CardHeader> <CardContent> <div className="text-red-400"> Ошибка загрузки отчёта: {error instanceof Error ? error.message : 'Unknown error'} </div> </CardContent> </Card> ); } if (!data) { return null; } const { services, tasks, system } = data.data; const generatedDate = new Date(data.generated); return ( <Card className="bg-slate-800/50 border-slate-700"> <CardHeader> <div className="flex items-center justify-between"> <div> <CardTitle className="text-white flex items-center gap-2"> <Activity className="h-5 w-5" /> AI Ежедневный отчёт </CardTitle> <CardDescription className="text-slate-400 mt-1"> Последние 24 часа • Обновлено: {generatedDate.toLocaleTimeString('ru-RU')} </CardDescription> </div> <Button variant="ghost" size="sm" onClick={handleRefresh} disabled={isRefreshing} className="border-slate-600 hover:bg-slate-700" > <RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} /> </Button> </div> </CardHeader> <CardContent className="space-y-6"> {/* Summary Cards */} <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> {/* Services Status */} <div className="bg-slate-900/50 rounded-lg p-4 border border-slate-700"> <div className="flex items-center justify-between mb-2"> <span className="text-sm text-slate-400">Сервисы</span> {services.error > 0 ? ( <XCircle className="h-5 w-5 text-red-400" /> ) : services.warning > 0 ? ( <AlertTriangle className="h-5 w-5 text-yellow-400" /> ) : ( <CheckCircle2 className="h-5 w-5 text-green-400" /> )} </div> <div className="space-y-1"> <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">Здоровы</span> <span className="text-sm font-semibold text-green-400">{services.healthy}</span> </div> {services.warning > 0 && ( <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">Предупреждения</span> <span className="text-sm font-semibold text-yellow-400">{services.warning}</span> </div> )} {services.error > 0 && ( <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">Ошибки</span> <span className="text-sm font-semibold text-red-400">{services.error}</span> </div> )} </div> </div> {/* Tasks Activity */} <div className="bg-slate-900/50 rounded-lg p-4 border border-slate-700"> <div className="flex items-center justify-between mb-2"> <span className="text-sm text-slate-400">Задачи</span> <Activity className="h-5 w-5 text-blue-400" /> </div> <div className="space-y-1"> <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">Новых</span> <span className="text-sm font-semibold text-blue-400 flex items-center gap-1"> {tasks.new > 0 && <TrendingUp className="h-3 w-3" />} {tasks.new} </span> </div> <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">Завершено</span> <span className="text-sm font-semibold text-green-400 flex items-center gap-1"> {tasks.completed > 0 && <CheckCircle2 className="h-3 w-3" />} {tasks.completed} </span> </div> <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">В работе</span> <span className="text-sm font-semibold text-orange-400">{tasks.inProgress}</span> </div> </div> </div> {/* System Health */} <div className="bg-slate-900/50 rounded-lg p-4 border border-slate-700"> <div className="flex items-center justify-between mb-2"> <span className="text-sm text-slate-400">Система</span> <Activity className="h-5 w-5 text-purple-400" /> </div> <div className="space-y-1"> <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">CPU</span> <span className="text-sm font-semibold text-purple-400">{system.cpu.usage}</span> </div> <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">RAM</span> <span className="text-sm font-semibold text-purple-400">{system.memory.percent}</span> </div> {system.disk.length > 0 && ( <div className="flex items-center justify-between"> <span className="text-xs text-slate-500">Диск</span> <span className="text-sm font-semibold text-purple-400">{system.disk[0].percent}</span> </div> )} </div> </div> </div> {/* Notable Events */} {services.downtimes.length > 0 && ( <div className="bg-slate-900/50 rounded-lg p-4 border border-slate-700"> <h4 className="text-sm font-semibold text-white mb-3 flex items-center gap-2"> <AlertTriangle className="h-4 w-4 text-yellow-400" /> Проблемы за последние 24 часа </h4> <div className="space-y-2"> {services.downtimes.slice(0, 5).map((downtime, idx) => ( <div key={idx} className="flex items-center gap-2 text-xs"> <span className={`px-2 py-1 rounded ${ downtime.status === 'error' ? 'bg-red-900/30 text-red-400' : 'bg-yellow-900/30 text-yellow-400' }`}> {downtime.project} </span> <span className="text-slate-500"> {new Date(downtime.timestamp).toLocaleTimeString('ru-RU')} </span> </div> ))} </div> </div> )} {/* Active Projects */} {Object.keys(tasks.byProject).length > 0 && ( <div className="bg-slate-900/50 rounded-lg p-4 border border-slate-700"> <h4 className="text-sm font-semibold text-white mb-3">Активность по проектам</h4> <div className="grid grid-cols-2 md:grid-cols-3 gap-3"> {Object.entries(tasks.byProject) .filter(([_, stats]) => stats.new > 0 || stats.completed > 0) .slice(0, 6) .map(([project, stats]) => ( <div key={project} className="space-y-1"> <div className="text-xs font-medium text-slate-300">{project}</div> <div className="flex items-center gap-2 text-xs text-slate-500"> {stats.new > 0 && ( <span className="text-blue-400">+{stats.new}</span> )} {stats.completed > 0 && ( <span className="text-green-400">✓{stats.completed}</span> )} </div> </div> ))} </div> </div> )} </CardContent> </Card> ); }