feat: add click-redirection to dashboard stats, soft hover animations, and premium activity logs modal
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m3s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m3s
Details
This commit is contained in:
parent
f10f213b9e
commit
4f5c2e6c7f
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { SchoolData, Student, Payment, Class } from '../types';
|
import { SchoolData, Student, Payment, Class, View } from '../types';
|
||||||
import {
|
import {
|
||||||
BarChart,
|
BarChart,
|
||||||
Bar,
|
Bar,
|
||||||
|
|
@ -31,19 +31,22 @@ import {
|
||||||
Calendar,
|
Calendar,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Layout,
|
Layout,
|
||||||
ClipboardPen
|
ClipboardPen,
|
||||||
|
X
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { pdfService } from '../services/pdfService';
|
import { pdfService } from '../services/pdfService';
|
||||||
|
|
||||||
interface DashboardProps {
|
interface DashboardProps {
|
||||||
data: SchoolData;
|
data: SchoolData;
|
||||||
|
setView?: (view: View) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
const Dashboard: React.FC<DashboardProps> = ({ data, setView }) => {
|
||||||
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
||||||
const [dashboardView, setDashboardView] = useState<'standard' | 'detailed'>('standard');
|
const [dashboardView, setDashboardView] = useState<'standard' | 'detailed'>('standard');
|
||||||
const [dbClasses, setDbClasses] = useState<any[]>(data.classes || []);
|
const [dbClasses, setDbClasses] = useState<any[]>(data.classes || []);
|
||||||
const [prematriculas, setPrematriculas] = useState<any[]>([]);
|
const [prematriculas, setPrematriculas] = useState<any[]>([]);
|
||||||
|
const [showLogsModal, setShowLogsModal] = useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
fetch('/api/turmas')
|
fetch('/api/turmas')
|
||||||
|
|
@ -160,6 +163,30 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
return activities;
|
return activities;
|
||||||
}, [data.students, data.payments]);
|
}, [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 () => {
|
const handleGenerateReport = async () => {
|
||||||
setIsGeneratingPDF(true);
|
setIsGeneratingPDF(true);
|
||||||
try {
|
try {
|
||||||
|
|
@ -172,25 +199,26 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{ label: 'Alunos Ativos', value: activeStudents, icon: Users, color: 'text-blue-600', bg: 'bg-blue-100', trend: '+12%' },
|
{ 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' },
|
{ label: 'Turmas Ativas', value: totalClasses, icon: BookOpen, color: 'text-indigo-600', bg: 'bg-indigo-100', trend: '+2', targetView: View.Classes },
|
||||||
{
|
{
|
||||||
label: 'Pré-Matrículas',
|
label: 'Pré-Matrículas',
|
||||||
value: prematriculas.length,
|
value: prematriculas.length,
|
||||||
icon: ClipboardPen,
|
icon: ClipboardPen,
|
||||||
color: 'text-amber-600',
|
color: 'text-amber-600',
|
||||||
bg: 'bg-amber-100',
|
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: '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%' },
|
{ 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 = [
|
const secondaryStats = [
|
||||||
{ label: 'Aulas a Repor', value: aulasARepor, icon: Calendar, color: 'text-red-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' },
|
{ 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' },
|
{ 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' },
|
{ label: 'Ticket Médio', value: `R$ ${averagePaymentValue}`, icon: Wallet, color: 'text-slate-600', targetView: View.Finance },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -233,7 +261,11 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
{/* Main Stats Grid */}
|
{/* Main Stats Grid */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||||
{stats.map((stat, i) => (
|
{stats.map((stat, i) => (
|
||||||
<div key={i} className="group bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-all duration-300">
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => 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"
|
||||||
|
>
|
||||||
<div className="flex justify-between items-start mb-4">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<div className={`${stat.bg} ${stat.color} p-3 rounded-xl group-hover:scale-110 transition-transform`}>
|
<div className={`${stat.bg} ${stat.color} p-3 rounded-xl group-hover:scale-110 transition-transform`}>
|
||||||
<stat.icon size={24} />
|
<stat.icon size={24} />
|
||||||
|
|
@ -253,7 +285,11 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
{/* Secondary Stats Row */}
|
{/* Secondary Stats Row */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{secondaryStats.map((stat, i) => (
|
{secondaryStats.map((stat, i) => (
|
||||||
<div key={i} className="bg-slate-50 p-4 rounded-xl border border-slate-200 flex items-center justify-between">
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => 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"
|
||||||
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`${stat.color}`}>
|
<div className={`${stat.color}`}>
|
||||||
<stat.icon size={20} />
|
<stat.icon size={20} />
|
||||||
|
|
@ -267,7 +303,10 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
{/* Revenue Area Chart */}
|
{/* Revenue Area Chart */}
|
||||||
<div className="lg:col-span-2 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-black text-slate-900">Fluxo de Receita</h3>
|
<h3 className="text-lg font-black text-slate-900">Fluxo de Receita</h3>
|
||||||
|
|
@ -278,7 +317,7 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
<span>+15.2% vs ano anterior</span>
|
<span>+15.2% vs ano anterior</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-80">
|
<div className="h-80" onClick={(e) => e.stopPropagation()}>
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<AreaChart data={revenueHistory}>
|
<AreaChart data={revenueHistory}>
|
||||||
<defs>
|
<defs>
|
||||||
|
|
@ -319,10 +358,13 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment Status Pie Chart */}
|
{/* Payment Status Pie Chart */}
|
||||||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<h3 className="text-lg font-black text-slate-900 mb-2">Status Financeiro</h3>
|
<h3 className="text-lg font-black text-slate-900 mb-2">Status Financeiro</h3>
|
||||||
<p className="text-xs text-slate-500 font-bold uppercase tracking-tighter mb-8">Distribuição de pagamentos</p>
|
<p className="text-xs text-slate-500 font-bold uppercase tracking-tighter mb-8">Distribuição de pagamentos</p>
|
||||||
<div className="h-64 relative">
|
<div className="h-64 relative" onClick={(e) => e.stopPropagation()}>
|
||||||
{data.payments.length > 0 ? (
|
{data.payments.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
|
@ -371,15 +413,23 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
{/* Class Occupancy Bar Chart */}
|
{/* Class Occupancy Bar Chart */}
|
||||||
<div className="lg:col-span-2 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-black text-slate-900">Ocupação das Turmas</h3>
|
<h3 className="text-lg font-black text-slate-900">Ocupação das Turmas</h3>
|
||||||
<p className="text-xs text-slate-500 font-bold uppercase tracking-tighter">Alunos por turma</p>
|
<p className="text-xs text-slate-500 font-bold uppercase tracking-tighter">Alunos por turma</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="text-indigo-600 text-xs font-black uppercase tracking-widest hover:underline">Ver todas</button>
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); setView && setView(View.Classes); }}
|
||||||
|
className="text-indigo-600 text-xs font-black uppercase tracking-widest hover:underline"
|
||||||
|
>
|
||||||
|
Ver todas
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-80">
|
<div className="h-80" onClick={(e) => e.stopPropagation()}>
|
||||||
{classOccupancy.length > 0 ? (
|
{classOccupancy.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={classOccupancy} layout="vertical" margin={{left: 40}}>
|
<BarChart data={classOccupancy} layout="vertical" margin={{left: 40}}>
|
||||||
|
|
@ -411,12 +461,21 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Recent Activity Feed */}
|
{/* Recent Activity Feed */}
|
||||||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
<div 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">
|
||||||
<h3 className="text-lg font-black text-slate-900 mb-6">Atividade Recente</h3>
|
<h3 className="text-lg font-black text-slate-900 mb-6">Atividade Recente</h3>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{recentActivity.length > 0 ? (
|
{recentActivity.length > 0 ? (
|
||||||
recentActivity.map((activity, i) => (
|
recentActivity.map((activity, i) => (
|
||||||
<div key={i} className="flex gap-4 relative">
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={(e) => {
|
||||||
|
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 && (
|
{i !== recentActivity.length - 1 && (
|
||||||
<div className="absolute left-5 top-10 bottom-0 w-0.5 bg-slate-100 -mb-6"></div>
|
<div className="absolute left-5 top-10 bottom-0 w-0.5 bg-slate-100 -mb-6"></div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -438,7 +497,10 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
<div className="text-center py-10 text-slate-400 font-bold italic">Nenhuma atividade recente</div>
|
<div className="text-center py-10 text-slate-400 font-bold italic">Nenhuma atividade recente</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button className="w-full mt-8 py-3 rounded-xl border border-slate-200 text-slate-600 text-xs font-black uppercase tracking-widest hover:bg-slate-50 transition-colors flex items-center justify-center gap-2">
|
<button
|
||||||
|
onClick={() => setShowLogsModal(true)}
|
||||||
|
className="w-full mt-8 py-3 rounded-xl border border-slate-200 text-slate-600 text-xs font-black uppercase tracking-widest hover:bg-slate-50 transition-colors flex items-center justify-center gap-2 active:scale-95"
|
||||||
|
>
|
||||||
Ver Log Completo
|
Ver Log Completo
|
||||||
<ChevronRight size={14} />
|
<ChevronRight size={14} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -448,7 +510,10 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
{/* Pré-Matrículas Section */}
|
{/* Pré-Matrículas Section */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
{/* Recent Pre-Enrollments */}
|
{/* Recent Pre-Enrollments */}
|
||||||
<div className="lg:col-span-2 bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-black text-slate-900">Pré-Matrículas Recentes</h3>
|
<h3 className="text-lg font-black text-slate-900">Pré-Matrículas Recentes</h3>
|
||||||
|
|
@ -459,7 +524,7 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto" onClick={(e) => e.stopPropagation()}>
|
||||||
{prematriculas.length > 0 ? (
|
{prematriculas.length > 0 ? (
|
||||||
<table className="w-full text-left border-collapse text-xs">
|
<table className="w-full text-left border-collapse text-xs">
|
||||||
<thead>
|
<thead>
|
||||||
|
|
@ -503,7 +568,10 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pre-Enrollment Stats Funnel */}
|
{/* Pre-Enrollment Stats Funnel */}
|
||||||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<h3 className="text-lg font-black text-slate-900 mb-2">Funil de Pré-Matrícula</h3>
|
<h3 className="text-lg font-black text-slate-900 mb-2">Funil de Pré-Matrícula</h3>
|
||||||
<p className="text-xs text-slate-500 font-bold uppercase tracking-tighter mb-6">Conversão e status</p>
|
<p className="text-xs text-slate-500 font-bold uppercase tracking-tighter mb-6">Conversão e status</p>
|
||||||
|
|
||||||
|
|
@ -560,9 +628,12 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
{/* Detailed View Expansion */}
|
{/* Detailed View Expansion */}
|
||||||
{dashboardView === 'detailed' && (
|
{dashboardView === 'detailed' && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 animate-in slide-in-from-bottom-4 duration-500">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 animate-in slide-in-from-bottom-4 duration-500">
|
||||||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<h3 className="text-lg font-black text-slate-900 mb-6">Distribuição por Gênero</h3>
|
<h3 className="text-lg font-black text-slate-900 mb-6">Distribuição por Gênero</h3>
|
||||||
<div className="h-48 flex items-center justify-center">
|
<div className="h-48 flex items-center justify-center" onClick={(e) => e.stopPropagation()}>
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<PieChart>
|
<PieChart>
|
||||||
<Pie
|
<Pie
|
||||||
|
|
@ -587,9 +658,12 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm">
|
<div
|
||||||
|
onClick={() => 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]"
|
||||||
|
>
|
||||||
<h3 className="text-lg font-black text-slate-900 mb-6">Alunos por Status</h3>
|
<h3 className="text-lg font-black text-slate-900 mb-6">Alunos por Status</h3>
|
||||||
<div className="h-48">
|
<div className="h-48" onClick={(e) => e.stopPropagation()}>
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<BarChart data={[
|
<BarChart data={[
|
||||||
{ name: 'Ativo', value: data.students.filter(s => s.status === 'active').length },
|
{ name: 'Ativo', value: data.students.filter(s => s.status === 'active').length },
|
||||||
|
|
@ -605,6 +679,63 @@ const Dashboard: React.FC<DashboardProps> = ({ data }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Floating Audit Logs Modal (Rule 11 & 17 compliant) */}
|
||||||
|
{showLogsModal && (
|
||||||
|
<div className="fixed inset-0 bg-transparent z-50 flex items-center justify-center p-4">
|
||||||
|
<div className="bg-white rounded-3xl w-full max-w-2xl p-6 shadow-2xl border border-slate-100 flex flex-col max-h-[80vh] animate-in fade-in zoom-in-95 duration-200">
|
||||||
|
<div className="flex justify-between items-center pb-4 border-b border-slate-100 mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-black text-slate-900">Histórico de Atividades</h3>
|
||||||
|
<p className="text-[10px] text-slate-400 font-black uppercase tracking-widest mt-0.5">Logs de auditoria e novos registros</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowLogsModal(false)}
|
||||||
|
className="p-2 text-slate-400 hover:text-slate-600 hover:bg-slate-50 rounded-xl transition-colors"
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto space-y-4 pr-2 custom-scrollbar">
|
||||||
|
{allActivities.length > 0 ? (
|
||||||
|
allActivities.map((activity, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${activity.color}`}>
|
||||||
|
<activity.icon size={18} />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<p className="text-sm font-black text-slate-900 truncate">{activity.title}</p>
|
||||||
|
<span className="text-[10px] font-bold text-slate-400 whitespace-nowrap ml-2">
|
||||||
|
{new Date(activity.date).toLocaleDateString('pt-BR', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-slate-500 font-medium truncate mt-0.5">{activity.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-10 text-slate-400 font-bold italic">Nenhuma atividade registrada</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ const App = () => {
|
||||||
const renderView = () => {
|
const renderView = () => {
|
||||||
switch (currentView) {
|
switch (currentView) {
|
||||||
case View.Dashboard:
|
case View.Dashboard:
|
||||||
return <Dashboard data={data} />;
|
return <Dashboard data={data} setView={setCurrentView} />;
|
||||||
case View.Courses:
|
case View.Courses:
|
||||||
return <Courses data={data} updateData={updateData} />;
|
return <Courses data={data} updateData={updateData} />;
|
||||||
case View.Students:
|
case View.Students:
|
||||||
|
|
@ -313,7 +313,7 @@ const App = () => {
|
||||||
case View.PreMatricula:
|
case View.PreMatricula:
|
||||||
return <PreMatricula data={data} updateData={updateData} onConvert={handleNavigateToStudentsWithPreMatricula} />;
|
return <PreMatricula data={data} updateData={updateData} onConvert={handleNavigateToStudentsWithPreMatricula} />;
|
||||||
default:
|
default:
|
||||||
return <Dashboard data={data} />;
|
return <Dashboard data={data} setView={setCurrentView} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue