diff --git a/manager/components/Dashboard.tsx b/manager/components/Dashboard.tsx index 1320ae8..fc59408 100644 --- a/manager/components/Dashboard.tsx +++ b/manager/components/Dashboard.tsx @@ -1,5 +1,5 @@ import React, { useState, useMemo } from 'react'; -import { SchoolData, Student, Payment, Class } from '../types'; +import { SchoolData, Student, Payment, Class, View } from '../types'; import { BarChart, Bar, @@ -31,19 +31,22 @@ import { Calendar, ChevronRight, Layout, - ClipboardPen + ClipboardPen, + X } from 'lucide-react'; import { pdfService } from '../services/pdfService'; interface DashboardProps { data: SchoolData; + setView?: (view: View) => void; } -const Dashboard: React.FC = ({ data }) => { +const Dashboard: React.FC = ({ data, setView }) => { const [isGeneratingPDF, setIsGeneratingPDF] = useState(false); const [dashboardView, setDashboardView] = useState<'standard' | 'detailed'>('standard'); const [dbClasses, setDbClasses] = useState(data.classes || []); const [prematriculas, setPrematriculas] = useState([]); + const [showLogsModal, setShowLogsModal] = useState(false); React.useEffect(() => { fetch('/api/turmas') @@ -160,6 +163,30 @@ const Dashboard: React.FC = ({ data }) => { return activities; }, [data.students, data.payments]); + // All activities for modal log auditing + const allActivities = useMemo(() => { + const activities = [ + ...data.students.map(s => ({ + type: 'student', + title: 'Novo Aluno', + desc: s.name, + date: s.registrationDate, + icon: UserPlus, + color: 'bg-blue-100 text-blue-600' + })), + ...data.payments.filter(p => p.status === 'paid').map(p => ({ + type: 'payment', + title: 'Pagamento Recebido', + desc: `R$ ${(Number((p as any).valor_pago) || (Number(p.amount) - (Number(p.discount) || 0))).toLocaleString()}`, + date: p.paidDate || p.dueDate, + icon: CheckCircle2, + color: 'bg-emerald-100 text-emerald-600' + })) + ].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + + return activities; + }, [data.students, data.payments]); + const handleGenerateReport = async () => { setIsGeneratingPDF(true); try { @@ -172,25 +199,26 @@ const Dashboard: React.FC = ({ data }) => { }; const stats = [ - { label: 'Alunos Ativos', value: activeStudents, icon: Users, color: 'text-blue-600', bg: 'bg-blue-100', trend: '+12%' }, - { label: 'Turmas Ativas', value: totalClasses, icon: BookOpen, color: 'text-indigo-600', bg: 'bg-indigo-100', trend: '+2' }, + { label: 'Alunos Ativos', value: activeStudents, icon: Users, color: 'text-blue-600', bg: 'bg-blue-100', trend: '+12%', targetView: View.Students }, + { label: 'Turmas Ativas', value: totalClasses, icon: BookOpen, color: 'text-indigo-600', bg: 'bg-indigo-100', trend: '+2', targetView: View.Classes }, { label: 'Pré-Matrículas', value: prematriculas.length, icon: ClipboardPen, color: 'text-amber-600', bg: 'bg-amber-100', - trend: `${prematriculas.filter(p => p.status === 'pendente').length} pendentes` + trend: `${prematriculas.filter(p => p.status === 'pendente').length} pendentes`, + targetView: View.PreMatricula }, - { label: 'Receita Total', value: `R$ ${revenue.toLocaleString()}`, icon: Wallet, color: 'text-emerald-600', bg: 'bg-emerald-100', trend: '+8.4%' }, - { label: 'Taxa de Presença', value: `${attendanceRate}%`, icon: TrendingUp, color: 'text-purple-600', bg: 'bg-purple-100', trend: '+2.1%' }, + { label: 'Receita Total', value: `R$ ${revenue.toLocaleString()}`, icon: Wallet, color: 'text-emerald-600', bg: 'bg-emerald-100', trend: '+8.4%', targetView: View.Finance }, + { label: 'Taxa de Presença', value: `${attendanceRate}%`, icon: TrendingUp, color: 'text-purple-600', bg: 'bg-purple-100', trend: '+2.1%', targetView: View.AttendanceQuery }, ]; const secondaryStats = [ - { label: 'Aulas a Repor', value: aulasARepor, icon: Calendar, color: 'text-red-600' }, - { label: 'Novos Alunos (Mês)', value: newStudentsThisMonth, icon: UserPlus, color: 'text-sky-600' }, - { label: 'Pagamentos Pendentes', value: pendingPayments, icon: Clock, color: 'text-amber-600' }, - { label: 'Ticket Médio', value: `R$ ${averagePaymentValue}`, icon: Wallet, color: 'text-slate-600' }, + { label: 'Aulas a Repor', value: aulasARepor, icon: Calendar, color: 'text-red-600', targetView: View.Classes }, + { label: 'Novos Alunos (Mês)', value: newStudentsThisMonth, icon: UserPlus, color: 'text-sky-600', targetView: View.Students }, + { label: 'Pagamentos Pendentes', value: pendingPayments, icon: Clock, color: 'text-amber-600', targetView: View.Finance }, + { label: 'Ticket Médio', value: `R$ ${averagePaymentValue}`, icon: Wallet, color: 'text-slate-600', targetView: View.Finance }, ]; return ( @@ -233,7 +261,11 @@ const Dashboard: React.FC = ({ data }) => { {/* Main Stats Grid */}
{stats.map((stat, i) => ( -
+
setView && setView(stat.targetView)} + className="group bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-xl hover:-translate-y-1 active:scale-95 cursor-pointer transition-all duration-300 ease-out" + >
@@ -253,7 +285,11 @@ const Dashboard: React.FC = ({ data }) => { {/* Secondary Stats Row */}
{secondaryStats.map((stat, i) => ( -
+
setView && setView(stat.targetView)} + className="bg-slate-50 p-4 rounded-xl border border-slate-200 flex items-center justify-between hover:bg-slate-100 hover:shadow-md hover:-translate-y-0.5 active:scale-95 cursor-pointer transition-all duration-300 ease-out" + >
@@ -267,7 +303,10 @@ const Dashboard: React.FC = ({ data }) => {
{/* Revenue Area Chart */} -
+
setView && setView(View.Finance)} + className="lg:col-span-2 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm cursor-pointer hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out active:scale-[0.99]" + >

Fluxo de Receita

@@ -278,7 +317,7 @@ const Dashboard: React.FC = ({ data }) => { +15.2% vs ano anterior
-
+
e.stopPropagation()}> @@ -319,10 +358,13 @@ const Dashboard: React.FC = ({ data }) => {
{/* Payment Status Pie Chart */} -
+
setView && setView(View.Finance)} + className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm cursor-pointer hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out active:scale-[0.99]" + >

Status Financeiro

Distribuição de pagamentos

-
+
e.stopPropagation()}> {data.payments.length > 0 ? ( <> @@ -371,15 +413,23 @@ const Dashboard: React.FC = ({ data }) => {
{/* Class Occupancy Bar Chart */} -
+
setView && setView(View.Classes)} + className="lg:col-span-2 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm cursor-pointer hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out active:scale-[0.99]" + >

Ocupação das Turmas

Alunos por turma

- +
-
+
e.stopPropagation()}> {classOccupancy.length > 0 ? ( @@ -411,12 +461,21 @@ const Dashboard: React.FC = ({ data }) => {
{/* Recent Activity Feed */} -
+

Atividade Recente

{recentActivity.length > 0 ? ( recentActivity.map((activity, i) => ( -
+
{ + e.stopPropagation(); + if (setView) { + setView(activity.type === 'student' ? View.Students : View.Finance); + } + }} + className="flex gap-4 relative cursor-pointer hover:bg-slate-50 p-2 -m-2 rounded-xl transition-all duration-200 active:scale-98" + > {i !== recentActivity.length - 1 && (
)} @@ -438,7 +497,10 @@ const Dashboard: React.FC = ({ data }) => {
Nenhuma atividade recente
)}
- @@ -448,7 +510,10 @@ const Dashboard: React.FC = ({ data }) => { {/* Pré-Matrículas Section */}
{/* Recent Pre-Enrollments */} -
+
setView && setView(View.PreMatricula)} + className="lg:col-span-2 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out cursor-pointer active:scale-[0.99]" + >

Pré-Matrículas Recentes

@@ -459,7 +524,7 @@ const Dashboard: React.FC = ({ data }) => {
-
+
e.stopPropagation()}> {prematriculas.length > 0 ? ( @@ -503,7 +568,10 @@ const Dashboard: React.FC = ({ data }) => { {/* Pre-Enrollment Stats Funnel */} -
+
setView && setView(View.PreMatricula)} + className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out cursor-pointer active:scale-[0.99]" + >

Funil de Pré-Matrícula

Conversão e status

@@ -560,9 +628,12 @@ const Dashboard: React.FC = ({ data }) => { {/* Detailed View Expansion */} {dashboardView === 'detailed' && (
-
+
setView && setView(View.Students)} + className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out cursor-pointer active:scale-[0.99]" + >

Distribuição por Gênero

-
+
e.stopPropagation()}> = ({ data }) => {
-
+
setView && setView(View.Students)} + className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300 ease-out cursor-pointer active:scale-[0.99]" + >

Alunos por Status

-
+
e.stopPropagation()}> s.status === 'active').length }, @@ -605,6 +679,63 @@ const Dashboard: React.FC = ({ data }) => {
)} + + {/* Floating Audit Logs Modal (Rule 11 & 17 compliant) */} + {showLogsModal && ( +
+
+
+
+

Histórico de Atividades

+

Logs de auditoria e novos registros

+
+ +
+ +
+ {allActivities.length > 0 ? ( + allActivities.map((activity, i) => ( +
{ + if (setView) { + setView(activity.type === 'student' ? View.Students : View.Finance); + setShowLogsModal(false); + } + }} + className="flex gap-4 p-3 rounded-2xl hover:bg-slate-50 border border-transparent hover:border-slate-100 transition-all duration-200 cursor-pointer active:scale-98" + > +
+ +
+
+
+

{activity.title}

+ + {new Date(activity.date).toLocaleDateString('pt-BR', { + day: '2-digit', + month: 'short', + hour: '2-digit', + minute: '2-digit' + })} + +
+

{activity.desc}

+
+
+ )) + ) : ( +
Nenhuma atividade registrada
+ )} +
+
+
+ )}
); }; diff --git a/manager/index.tsx b/manager/index.tsx index 675c939..103a2f9 100644 --- a/manager/index.tsx +++ b/manager/index.tsx @@ -266,7 +266,7 @@ const App = () => { const renderView = () => { switch (currentView) { case View.Dashboard: - return ; + return ; case View.Courses: return ; case View.Students: @@ -313,7 +313,7 @@ const App = () => { case View.PreMatricula: return ; default: - return ; + return ; } };