feat: refactor RetrospectiveSlides component for improved performance and UI enhancements
parent
5a8a97e9b4
commit
ad6edc1094
|
|
@ -4,12 +4,7 @@ import {
|
|||
} from '@/app/hooks/useMetrics';
|
||||
import logoAImg from '@/assets/images/a-agape.png';
|
||||
import logoImg from '@/assets/images/agape-logo.png';
|
||||
import {
|
||||
BuildingIcon,
|
||||
CalendarIcon,
|
||||
ClockIcon,
|
||||
SpinnerIcon,
|
||||
} from '@phosphor-icons/react';
|
||||
import { BuildingIcon, ClockIcon, SpinnerIcon } from '@phosphor-icons/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import * as React from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
|
@ -17,7 +12,6 @@ import { Navigation } from './navigation';
|
|||
import ProfileCard from './ProfileCard';
|
||||
import { StorySlide } from './story-slide';
|
||||
|
||||
|
||||
export function RetrospectiveSlides() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const cpf = searchParams.get('cpf') || '';
|
||||
|
|
@ -42,15 +36,14 @@ export function RetrospectiveSlides() {
|
|||
// Calcula slides baseado nos dados disponíveis
|
||||
const slides: number[] = [];
|
||||
slides.push(0); // Slide inicial
|
||||
if (metricsPortal && metricsPortal.length > 0) slides.push(1); // Slide portais
|
||||
if (metricsSystems && metricsSystems.length > 0) slides.push(2); // Slide sistemas
|
||||
if (metricsPortal && metricsPortal.length > 0) slides.push(1); // Slide com portal principal
|
||||
if (
|
||||
(metricsPortal && metricsPortal.length > 0) ||
|
||||
(metricsSystems && metricsSystems.length > 0)
|
||||
) {
|
||||
slides.push(3); // Slide com informações gerais
|
||||
slides.push(2); // Slide com podio de aplicações
|
||||
}
|
||||
slides.push(4); // Slide final
|
||||
slides.push(3); // Slide final
|
||||
|
||||
const total = slides.length;
|
||||
|
||||
|
|
@ -63,9 +56,9 @@ export function RetrospectiveSlides() {
|
|||
...(metricsSystems || []),
|
||||
].reduce((acc, item) => acc + item.totalAcessos, 0);
|
||||
|
||||
|
||||
// Top sistemas/portais por acessos
|
||||
const topItems = [...(metricsPortal || []), ...(metricsSystems || [])]
|
||||
.filter((item) => (item.nomeSistema || '').toLowerCase() !== 'agportal')
|
||||
.sort((a, b) => b.totalAcessos - a.totalAcessos)
|
||||
.slice(0, 5);
|
||||
|
||||
|
|
@ -157,193 +150,237 @@ export function RetrospectiveSlides() {
|
|||
|
||||
{metricsPortal && metricsPortal.length > 0 && (
|
||||
<StorySlide className="bg-linear-to-br from-slate-50 to-blue-50">
|
||||
<div className="space-y-8">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="flex flex-col items-center justify-center h-full space-y-16">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ type: 'spring', duration: 0.6 }}>
|
||||
<BuildingIcon className="h-20 w-20 text-[#145190]" />
|
||||
</motion.div>
|
||||
|
||||
<div className="text-center space-y-8 max-w-4xl px-6">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ type: 'spring', duration: 0.6 }}>
|
||||
<BuildingIcon className="h-16 w-16 mx-auto text-[#0e233d]" />
|
||||
transition={{ duration: 0.6 }}>
|
||||
<p className="text-2xl md:text-3xl font-light text-[#0e233d]">
|
||||
Você acessou
|
||||
</p>
|
||||
</motion.div>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-[#0e233d]">
|
||||
Seus Portais
|
||||
</h2>
|
||||
|
||||
<motion.div
|
||||
className="text-7xl md:text-8xl font-bold text-[#145190]"
|
||||
initial={{ scale: 0.5, opacity: 0 }}
|
||||
whileInView={{ scale: 1, opacity: 1 }}
|
||||
className="space-y-4"
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
duration: 0.8,
|
||||
delay: 0.2,
|
||||
}}>
|
||||
{metricsPortal.length}
|
||||
<div className="text-6xl md:text-8xl font-bold text-[#145190]">
|
||||
{metricsPortal
|
||||
.reduce((acc, p) => acc + p.totalAcessos, 0)
|
||||
.toLocaleString('pt-BR')}
|
||||
</div>
|
||||
<p className="text-3xl md:text-4xl font-semibold text-[#0e233d]">
|
||||
vezes
|
||||
</p>
|
||||
</motion.div>
|
||||
<p className="text-2xl text-muted-foreground">
|
||||
portais acessados neste ano
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 mt-12">
|
||||
{metricsPortal.map((portal, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="p-6 bg-white rounded-lg shadow hover:shadow-md transition-shadow"
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}>
|
||||
<h3 className="text-xl font-semibold text-[#0e233d] truncate">
|
||||
{portal.nomeSistema}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Acessos
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#145190] mt-2">
|
||||
{portal.totalAcessos.toLocaleString('pt-BR')}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</StorySlide>
|
||||
)}
|
||||
|
||||
{metricsSystems && metricsSystems.length > 0 && (
|
||||
<StorySlide className="bg-linear-to-br from-slate-50 to-blue-50">
|
||||
<div className="space-y-8">
|
||||
<div className="text-center space-y-4">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ type: 'spring', duration: 0.6 }}>
|
||||
<CalendarIcon className="h-16 w-16 mx-auto text-[#0e233d]" />
|
||||
transition={{ duration: 0.6, delay: 0.4 }}>
|
||||
<p className="text-2xl md:text-3xl font-light text-[#0e233d]">
|
||||
o{' '}
|
||||
<span className="font-bold text-[#145190]">
|
||||
{metricsPortal[0]?.nomeSistema || 'Portal'}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xl md:text-2xl font-light text-[#0e233d] mt-2">
|
||||
durante o ano de {ano}
|
||||
</p>
|
||||
</motion.div>
|
||||
<h2 className="text-4xl md:text-5xl font-bold text-[#0e233d]">
|
||||
Seus Sistemas
|
||||
</h2>
|
||||
<motion.div
|
||||
className="text-7xl md:text-8xl font-bold text-[#145190]"
|
||||
initial={{ scale: 0.5, opacity: 0 }}
|
||||
whileInView={{ scale: 1, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
duration: 0.8,
|
||||
delay: 0.2,
|
||||
}}>
|
||||
{metricsSystems.length}
|
||||
</motion.div>
|
||||
<p className="text-2xl text-muted-foreground">
|
||||
sistemas utilizados neste ano
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 mt-12">
|
||||
{metricsSystems.map((sistema, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="p-6 bg-white rounded-lg shadow hover:shadow-md transition-shadow"
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
whileInView={{ y: 0, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}>
|
||||
<h3 className="text-xl font-semibold text-[#0e233d] truncate">
|
||||
{sistema.nomeSistema}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Acessos
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#145190] mt-2">
|
||||
{sistema.totalAcessos.toLocaleString('pt-BR')}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
<motion.div
|
||||
className="pt-8"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<motion.span
|
||||
key={i}
|
||||
className="h-2 w-2 bg-[#145190] rounded-full"
|
||||
animate={{ scale: [1, 1.2, 1] }}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
delay: i * 0.2,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</StorySlide>
|
||||
)}
|
||||
|
||||
{topItems.length > 0 && (
|
||||
<StorySlide className="bg-linear-to-br from-[#0e233d] via-[#173b63] to-[#145190] text-white">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center min-h-screen p-8">
|
||||
<StorySlide className="bg-linear-to-br from-[#0e233d] via-[#173b63] to-[#145190] text-white overflow-hidden">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between h-full px-6 py-8 md:py-12 md:px-8">
|
||||
{/* Coluna esquerda - Título e informações */}
|
||||
<motion.div
|
||||
className="space-y-8 text-center lg:text-left"
|
||||
initial={{ x: -50, opacity: 0 }}
|
||||
whileInView={{ x: 0, opacity: 1 }}
|
||||
className="md:w-2/5 flex flex-col items-center md:items-start mb-8 md:mb-0 md:pr-8"
|
||||
initial={{ opacity: 0, x: -30 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6 }}>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ type: 'spring', duration: 0.6 }}>
|
||||
<ClockIcon className="h-24 w-24 mb-8 text-yellow-300 lg:mx-0" />
|
||||
transition={{ type: 'spring', duration: 0.6 }}
|
||||
className="mb-6 md:mb-8">
|
||||
<ClockIcon className="h-14 w-14 md:h-16 md:w-16 text-yellow-300" />
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-4xl md:text-5xl font-bold">
|
||||
Seu Engajamento
|
||||
</h2>
|
||||
<motion.h2
|
||||
className="text-3xl md:text-4xl lg:text-5xl font-bold text-center md:text-left mb-6 md:mb-8"
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}>
|
||||
Seus Aplicativos Favoritos
|
||||
</motion.h2>
|
||||
|
||||
<motion.div
|
||||
className="text-6xl md:text-7xl font-bold text-yellow-300"
|
||||
initial={{ scale: 0.5, opacity: 0 }}
|
||||
whileInView={{ scale: 1, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
duration: 0.8,
|
||||
delay: 0.2,
|
||||
}}>
|
||||
{totalAcessos.toLocaleString('pt-BR')}
|
||||
</motion.div>
|
||||
<p className="text-xl text-white/90 max-w-lg">
|
||||
Total de acessos ao longo do ano
|
||||
</p>
|
||||
<motion.p
|
||||
className="text-lg md:text-xl text-white/80 text-center md:text-left mb-6"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}>
|
||||
Os sistemas mais acessados em {ano}
|
||||
</motion.p>
|
||||
|
||||
<motion.div
|
||||
className="mt-6 md:mt-auto p-6 rounded-xl backdrop-blur-sm bg-white/10 border border-white/20 w-full"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.8 }}>
|
||||
<div className="text-center md:text-left">
|
||||
<p className="text-4xl md:text-5xl font-bold text-yellow-300 mb-2">
|
||||
{totalAcessos.toLocaleString('pt-BR')}
|
||||
</p>
|
||||
<p className="text-white/70 text-sm md:text-base">
|
||||
acessos totais no ano
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
{/* Coluna direita - Lista vertical compacta */}
|
||||
<div className="md:w-3/5 h-full flex items-center">
|
||||
<div className="w-full grid grid-cols-1 gap-3 md:gap-4 max-h-full">
|
||||
{topItems.slice(0, 6).map((item, index) => {
|
||||
const medalColors = [
|
||||
'bg-yellow-400 text-yellow-900',
|
||||
'bg-gray-300 text-gray-900',
|
||||
'bg-orange-400 text-orange-900',
|
||||
'bg-blue-400/20 text-blue-300',
|
||||
'bg-purple-400/20 text-purple-300',
|
||||
'bg-green-400/20 text-green-300',
|
||||
];
|
||||
const isMedal = index < 3;
|
||||
const medalColor = medalColors[index] || 'bg-white/20';
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={index}
|
||||
className={`flex items-center p-4 md:p-5 rounded-xl backdrop-blur-sm border border-white/20 ${
|
||||
isMedal
|
||||
? 'bg-white/15'
|
||||
: 'bg-white/10 hover:bg-white/15'
|
||||
} transition-all`}
|
||||
initial={{ x: 50, opacity: 0 }}
|
||||
whileInView={{ x: 0, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 0.5,
|
||||
delay: index * 0.1,
|
||||
}}>
|
||||
<div
|
||||
className={`w-10 h-10 md:w-12 md:h-12 rounded-full flex items-center justify-center font-bold text-base md:text-lg mr-4 ${medalColor}`}>
|
||||
{index === 0 && '🥇'}
|
||||
{index === 1 && '🥈'}
|
||||
{index === 2 && '🥉'}
|
||||
{index >= 3 && `#${index + 1}`}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-base md:text-lg font-semibold truncate">
|
||||
{item.nomeSistema}
|
||||
</p>
|
||||
<p className="text-sm text-white/70">
|
||||
{item.totalAcessos.toLocaleString('pt-BR')}{' '}
|
||||
acessos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="ml-4 flex flex-col items-end"
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
delay: index * 0.1 + 0.2,
|
||||
}}>
|
||||
<p className="text-xl md:text-2xl font-bold text-yellow-300 mb-1">
|
||||
{(
|
||||
(item.totalAcessos /
|
||||
(topItems[0]?.totalAcessos || 1)) *
|
||||
100
|
||||
).toFixed(0)}
|
||||
%
|
||||
</p>
|
||||
<div className="w-24 md:w-32 h-1.5 bg-white/20 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
className="h-full bg-yellow-400 rounded-full"
|
||||
initial={{ width: 0 }}
|
||||
whileInView={{
|
||||
width: `${
|
||||
(item.totalAcessos /
|
||||
(topItems[0]?.totalAcessos || 1)) *
|
||||
100
|
||||
}%`,
|
||||
}}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
duration: 1,
|
||||
delay: index * 0.1 + 0.3,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
className="space-y-4"
|
||||
initial={{ x: 50, opacity: 0 }}
|
||||
whileInView={{ x: 0, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}>
|
||||
<h3 className="text-2xl font-bold text-white mb-6">
|
||||
Principais Recursos
|
||||
</h3>
|
||||
{topItems.slice(0, 3).map((item, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
className="bg-white/10 backdrop-blur border-white/20 text-white p-4 rounded-lg flex items-center justify-between"
|
||||
initial={{ x: 50, opacity: 0 }}
|
||||
whileInView={{ x: 0, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5, delay: index * 0.1 }}>
|
||||
<div>
|
||||
<p className="font-semibold">{item.nomeSistema}</p>
|
||||
<p className="text-sm text-white/70">
|
||||
{item.totalAcessos.toLocaleString('pt-BR')} acessos
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-2xl font-bold text-yellow-300">
|
||||
#{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</StorySlide>
|
||||
)}
|
||||
<StorySlide className="bg-linear-to-br from-slate-50 to-blue-50">
|
||||
<div className="h-screen max-h-screen overflow-y-auto flex flex-col md:flex-row items-center justify-center px-6 gap-12">
|
||||
<div className="space-y-12 w-full md:w-1/2 text-center md:text-left">
|
||||
<div className="h-full flex flex-col md:flex-row items-center justify-center px-6 gap-12 py-12">
|
||||
<div className="space-y-8 w-full md:w-1/2 text-center md:text-left">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
|
|
|
|||
Loading…
Reference in New Issue