Files
iristea/src/app/dashboard/relatorios/page.tsx
2026-02-10 15:46:23 -03:00

322 lines
12 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useSession } from 'next-auth/react';
import {
ArrowLeft, FileText, Download, Calendar, TrendingUp,
BarChart3, PieChart, Loader2
} from 'lucide-react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
ResponsiveContainer, RadarChart, PolarGrid, PolarAngleAxis,
PolarRadiusAxis, Radar, AreaChart, Area
} from 'recharts';
interface ProgressData {
progressByCategory: Record<string, { dates: string[]; values: number[] }>;
currentProgress: { category: string; value: number }[];
child?: { id: string; name: string };
}
const categoryLabels: Record<string, string> = {
comunicacao: 'Comunicação',
habilidades_sociais: 'Habilidades Sociais',
autonomia: 'Autonomia',
regulacao_emocional: 'Regulação Emocional',
};
const categoryColors: Record<string, string> = {
comunicacao: '#3b82f6',
habilidades_sociais: '#22c55e',
autonomia: '#eab308',
regulacao_emocional: '#a855f7',
};
export default function RelatoriosPage() {
const { data: session } = useSession();
const [loading, setLoading] = useState(true);
const [data, setData] = useState<ProgressData | null>(null);
const [period, setPeriod] = useState<'7' | '30' | '90'>('30');
useEffect(() => {
fetchProgress();
}, [period]);
const fetchProgress = async () => {
try {
setLoading(true);
const response = await fetch(`/api/progress?days=${period}`);
if (response.ok) {
const progressData = await response.json();
setData(progressData);
}
} catch (error) {
console.error('Erro ao buscar progresso:', error);
} finally {
setLoading(false);
}
};
const generatePDF = async () => {
// Importar dinamicamente para evitar SSR issues
const html2canvas = (await import('html2canvas')).default;
const { jsPDF } = await import('jspdf');
const content = document.getElementById('report-content');
if (!content) return;
const canvas = await html2canvas(content, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210;
const pageHeight = 295;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
pdf.save(`relatorio-iris-${new Date().toISOString().split('T')[0]}.pdf`);
};
// Preparar dados para gráficos
const prepareLineChartData = () => {
if (!data) return [];
const allDates = new Set<string>();
Object.values(data.progressByCategory).forEach(cat => {
cat.dates.forEach(d => allDates.add(d));
});
const sortedDates = Array.from(allDates).sort();
return sortedDates.map(date => {
const point: Record<string, any> = { date: date.slice(5) }; // MM-DD format
Object.entries(data.progressByCategory).forEach(([cat, catData]) => {
const idx = catData.dates.indexOf(date);
point[cat] = idx !== -1 ? catData.values[idx] : null;
});
return point;
});
};
const prepareRadarData = () => {
if (!data) return [];
return data.currentProgress.map(p => ({
category: categoryLabels[p.category] || p.category,
value: p.value,
fullMark: 100,
}));
};
const lineChartData = prepareLineChartData();
const radarData = prepareRadarData();
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<header className="bg-white border-b border-gray-200 px-4 lg:px-8 py-4">
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/dashboard" className="text-gray-400 hover:text-gray-600">
<ArrowLeft className="w-6 h-6" />
</Link>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center">
<FileText className="w-5 h-5" />
</div>
<div>
<h1 className="text-xl font-bold text-gray-900">Relatórios</h1>
<p className="text-sm text-gray-500">
{data?.child ? `Progresso de ${data.child.name}` : 'Acompanhamento de progresso'}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-4">
{/* Period selector */}
<div className="flex bg-gray-100 rounded-lg p-1">
{[
{ value: '7', label: '7 dias' },
{ value: '30', label: '30 dias' },
{ value: '90', label: '90 dias' },
].map(p => (
<button
key={p.value}
onClick={() => setPeriod(p.value as any)}
className={`px-3 py-1.5 text-sm rounded-md transition ${
period === p.value
? 'bg-white shadow text-indigo-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{p.label}
</button>
))}
</div>
<button
onClick={generatePDF}
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700 transition"
>
<Download className="w-4 h-4" />
Exportar PDF
</button>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto p-4 lg:p-8" id="report-content">
{loading ? (
<div className="flex justify-center py-20">
<Loader2 className="w-12 h-12 text-indigo-500 animate-spin" />
</div>
) : (
<div className="space-y-8">
{/* Current Progress Cards */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{data?.currentProgress.map(p => (
<div key={p.category} className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-500">
{categoryLabels[p.category]}
</span>
<TrendingUp
className="w-5 h-5"
style={{ color: categoryColors[p.category] }}
/>
</div>
<div className="text-3xl font-bold text-gray-900 mb-2">
{p.value}%
</div>
<div className="h-2 bg-gray-200 rounded-full">
<div
className="h-2 rounded-full transition-all"
style={{
width: `${p.value}%`,
backgroundColor: categoryColors[p.category],
}}
/>
</div>
</div>
))}
</div>
{/* Charts */}
<div className="grid lg:grid-cols-2 gap-8">
{/* Line Chart */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<BarChart3 className="w-5 h-5 text-indigo-600" />
Evolução ao Longo do Tempo
</h3>
{lineChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={lineChartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis domain={[0, 100]} />
<Tooltip />
<Legend />
{Object.keys(categoryLabels).map(cat => (
<Area
key={cat}
type="monotone"
dataKey={cat}
name={categoryLabels[cat]}
stroke={categoryColors[cat]}
fill={categoryColors[cat]}
fillOpacity={0.1}
connectNulls
/>
))}
</AreaChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
Sem dados para o período selecionado
</div>
)}
</div>
{/* Radar Chart */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<PieChart className="w-5 h-5 text-indigo-600" />
Visão Geral das Áreas
</h3>
{radarData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<RadarChart data={radarData}>
<PolarGrid />
<PolarAngleAxis dataKey="category" />
<PolarRadiusAxis domain={[0, 100]} />
<Radar
name="Progresso"
dataKey="value"
stroke="#6366f1"
fill="#6366f1"
fillOpacity={0.5}
/>
</RadarChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
Sem dados para exibir
</div>
)}
</div>
</div>
{/* Summary */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Resumo do Período
</h3>
<div className="prose prose-sm max-w-none text-gray-600">
<p>
Nos últimos <strong>{period} dias</strong>, observamos progresso em todas as áreas de desenvolvimento.
</p>
{data?.currentProgress && data.currentProgress.length > 0 && (
<>
<p>
A área com melhor desempenho é <strong>
{categoryLabels[data.currentProgress.reduce((a, b) =>
a.value > b.value ? a : b
).category]}
</strong> com {Math.max(...data.currentProgress.map(p => p.value))}% de progresso.
</p>
<p>
Recomendamos focar em <strong>
{categoryLabels[data.currentProgress.reduce((a, b) =>
a.value < b.value ? a : b
).category]}
</strong> nas próximas sessões para equilibrar o desenvolvimento.
</p>
</>
)}
</div>
</div>
</div>
)}
</div>
</div>
);
}