feat: refatorar componente RetrospectiveSlides para melhorar a estrutura e a apresentação visual

main
guilherme 2025-12-11 14:14:51 -03:00
parent b8bcb00e74
commit cb01415dfa
1 changed files with 263 additions and 263 deletions

View File

@ -29,28 +29,25 @@ export function RetrospectiveSlides() {
error: errorSystems, error: errorSystems,
} = useMetricsBySystems(cpf, ano); } = useMetricsBySystems(cpf, ano);
// Calcula slides baseado nos dados disponíveis
const slides: number[] = []; const slides: number[] = [];
slides.push(0); // Slide inicial slides.push(0);
if (metricsPortal && metricsPortal.length > 0) slides.push(1); // Slide com portal principal if (metricsPortal && metricsPortal.length > 0) slides.push(1);
if ( if (
(metricsPortal && metricsPortal.length > 0) || (metricsPortal && metricsPortal.length > 0) ||
(metricsSystems && metricsSystems.length > 0) (metricsSystems && metricsSystems.length > 0)
) { ) {
slides.push(2); // Slide com podio de aplicações slides.push(2);
} }
slides.push(3); // Slide final slides.push(3);
const isLoading = isLoadingPortal || isLoadingSystems; const isLoading = isLoadingPortal || isLoadingSystems;
const hasError = errorPortal || errorSystems; const hasError = errorPortal || errorSystems;
// Extrai total de acessos
const totalAcessos = [ const totalAcessos = [
...(metricsPortal || []), ...(metricsPortal || []),
...(metricsSystems || []), ...(metricsSystems || []),
].reduce((acc, item) => acc + item.totalAcessos, 0); ].reduce((acc, item) => acc + item.totalAcessos, 0);
// Top sistemas/portais por acessos
const topItems = [...(metricsPortal || []), ...(metricsSystems || [])] const topItems = [...(metricsPortal || []), ...(metricsSystems || [])]
.filter((item) => (item.nomeSistema || '').toLowerCase() !== 'agportal') .filter((item) => (item.nomeSistema || '').toLowerCase() !== 'agportal')
.sort((a, b) => b.totalAcessos - a.totalAcessos) .sort((a, b) => b.totalAcessos - a.totalAcessos)
@ -89,247 +86,249 @@ export function RetrospectiveSlides() {
<> <>
<div className="h-full snap-y snap-mandatory overflow-y-scroll scroll-smooth"> <div className="h-full snap-y snap-mandatory overflow-y-scroll scroll-smooth">
<StorySlide className="bg-linear-to-br from-[#0e233d] via-[#173b63] to-[#145190] text-white"> <StorySlide className="bg-linear-to-br from-[#0e233d] via-[#173b63] to-[#145190] text-white">
<div className="text-center space-y-8"> <div className="max-w-7xl mx-auto w-full px-6">
<motion.div <div className="text-center space-y-8">
initial={{ scale: 0.8, opacity: 0 }} <motion.div
whileInView={{ scale: 1, opacity: 1 }} initial={{ scale: 0.8, opacity: 0 }}
viewport={{ once: true }} whileInView={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.6 }}> viewport={{ once: true }}
<img transition={{ duration: 0.6 }}>
src={logoImg} <img
alt="Ágape Logo" src={logoImg}
className="mx-auto w-52 mb-6" alt="Ágape Logo"
/> className="mx-auto w-52 mb-6"
</motion.div> />
</motion.div>
<motion.h1 <motion.h1
className="text-6xl md:text-7xl font-medium mb-4" className="text-6xl md:text-7xl font-medium mb-4"
initial={{ y: 20, opacity: 0 }} initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }} whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}> transition={{ duration: 0.6, delay: 0.2 }}>
Sua Retrospectiva Sua Retrospectiva
<br /> <br />
Ágape {ano} Ágape {ano}
</motion.h1> </motion.h1>
<motion.p <motion.p
className="text-xl md:text-2xl text-white/90 max-w-2xl mx-auto font-light" className="text-xl md:text-2xl text-white/90 max-w-2xl mx-auto font-light"
initial={{ y: 20, opacity: 0 }} initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }} whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}> transition={{ duration: 0.6, delay: 0.4 }}>
Um ano de conquistas, eficiência e transparência na gestão Um ano de conquistas, eficiência e transparência na gestão
pública pública
</motion.p> </motion.p>
<motion.div <motion.div
className="flex items-center justify-center gap-4 text-sm text-white/70 pt-8" className="flex items-center justify-center gap-4 text-sm text-white/70 pt-8"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }} whileInView={{ opacity: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.8 }}> transition={{ duration: 0.6, delay: 0.8 }}>
<span>Transparência</span> <span>Transparência</span>
<span></span> <span></span>
<span>Eficiência</span> <span>Eficiência</span>
<span></span> <span></span>
<span>Parceria</span> <span>Parceria</span>
</motion.div> </motion.div>
</div>
</div> </div>
</StorySlide> </StorySlide>
{metricsPortal && metricsPortal.length > 0 && ( {metricsPortal && metricsPortal.length > 0 && (
<> <>
{/* Slide 1: Acessos no AgPortal */}
<StorySlide className="bg-white text-gray-900 overflow-hidden relative"> <StorySlide className="bg-white text-gray-900 overflow-hidden relative">
{/* Decoração de fundo sutil */}
<div className="absolute inset-0 overflow-hidden"> <div className="absolute inset-0 overflow-hidden">
<div className="absolute -top-24 -right-24 w-96 h-96 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-full opacity-70"></div> <div className="absolute -top-24 -right-24 w-96 h-96 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-full opacity-70"></div>
<div className="absolute -bottom-32 -left-32 w-80 h-80 bg-gradient-to-tr from-blue-50 to-cyan-50 rounded-full opacity-60"></div> <div className="absolute -bottom-32 -left-32 w-80 h-80 bg-gradient-to-tr from-blue-50 to-cyan-50 rounded-full opacity-60"></div>
</div> </div>
<div className="flex flex-col lg:flex-row items-center justify-between h-full px-6 py-8 md:py-16 md:px-12 relative z-10"> <div className="max-w-7xl mx-auto w-full h-full px-6 py-8 md:py-16 relative z-10">
<motion.div <div className="flex flex-col lg:flex-row items-center justify-between h-full">
className="lg:w-1/2 flex flex-col items-start mb-10 lg:mb-0 space-y-8 lg:pr-12"
initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}>
<motion.div <motion.div
initial={{ scale: 0 }} className="lg:w-1/2 flex flex-col items-start mb-10 lg:mb-0 space-y-8 lg:pr-12"
whileInView={{ scale: 1 }} initial={{ opacity: 0, x: -30 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ type: 'spring', duration: 0.6 }}> transition={{ duration: 0.6 }}>
<img src={logoAImg} className="w-10" /> <motion.div
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
viewport={{ once: true }}
transition={{ type: 'spring', duration: 0.6 }}>
<img src={logoAImg} className="w-10" />
</motion.div>
<motion.h2
className="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-4"
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.1 }}>
<span className="text-[#0e233d mt-2">
AgPortal <br />
Sua Porta de Entrada Digital
</span>
</motion.h2>
<motion.p
className="text-lg md:text-xl text-gray-600 mb-8 max-w-2xl"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}>
O AgPortal é o hub central que conecta todos os
colaboradores às ferramentas, sistemas e informações
necessárias para o dia a dia de trabalho.
</motion.p>
</motion.div> </motion.div>
<motion.h2 <div className="lg:w-1/2 h-full flex flex-col items-center justify-center">
className="text-3xl md:text-4xl lg:text-5xl font-bold text-gray-900 mb-4" <motion.div
initial={{ opacity: 0, y: -20 }} className="space-y-4 mb-8"
whileInView={{ opacity: 1, y: 0 }} initial={{ opacity: 0, scale: 0.95 }}
viewport={{ once: true }} whileInView={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, delay: 0.1 }}> viewport={{ once: true }}
<span className="text-[#0e233d mt-2"> transition={{ duration: 0.8, delay: 0.3 }}>
AgPortal <br /> <div className="flex flex-col items-center justify-center gap-3">
Sua Porta de Entrada Digital <div className="text-9xl font-black text-gray-900 tracking-tight">
</span> {metricsPortal
</motion.h2> .reduce((acc, p) => acc + p.totalAcessos, 0)
.toLocaleString('pt-BR')}
</div>
{/* Descrição */} <div className="flex flex-col">
<motion.p <span className="text-lg md:text-xl text-muted-foreground">
className="text-lg md:text-xl text-gray-600 mb-8 max-w-2xl" Acessos em {ano}
initial={{ opacity: 0 }} </span>
whileInView={{ opacity: 1 }} </div>
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}>
O AgPortal é o hub central que conecta todos os
colaboradores às ferramentas, sistemas e informações
necessárias para o dia a dia de trabalho.
</motion.p>
</motion.div>
<div className="lg:w-1/2 h-full flex flex-col items-center justify-center">
<motion.div
className="space-y-4 mb-8"
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.3 }}>
<div className="flex flex-col items-center justify-center gap-3">
<div className="text-9xl font-black text-gray-900 tracking-tight">
{metricsPortal
.reduce((acc, p) => acc + p.totalAcessos, 0)
.toLocaleString('pt-BR')}
</div> </div>
</motion.div>
<div className="flex flex-col"> </div>
<span className="text-lg md:text-xl text-muted-foreground">
Acessos em {ano}
</span>
</div>
</div>
</motion.div>
</div> </div>
</div> </div>
</StorySlide> </StorySlide>
{/* Slide 2: Top 5 Aplicativos com Horas */}
<StorySlide className="bg-linear-to-br from-[#0e233d] via-[#173b63] to-[#145190] text-white overflow-hidden"> <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-start md:items-center justify-between h-full px-6 py-8 md:py-12 md:px-8"> <div className="max-w-7xl mx-auto w-full h-full px-6 py-8 md:py-12">
<div className="space-y-8 w-full md:w-1/2 text-center md:text-left"> <div className="flex flex-col md:flex-row items-start md:items-center justify-between h-full">
<motion.div <div className="space-y-8 w-full md:w-1/2 text-center md:text-left">
className="space-y-8" <motion.div
initial={{ scale: 0.9, opacity: 0 }} className="space-y-8"
whileInView={{ scale: 1, opacity: 1 }} initial={{ scale: 0.9, opacity: 0 }}
viewport={{ once: true }} whileInView={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.6 }}> viewport={{ once: true }}
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 mb-4"> transition={{ duration: 0.6 }}>
<ChartLineUpIcon className="h-4 w-4 text-yellow-300" /> <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 mb-4">
<span className="text-sm font-semibold text-white"> <ChartLineUpIcon className="h-4 w-4 text-yellow-300" />
Top 5 Aplicativos <span className="text-sm font-semibold text-white">
</span> Top 5 Aplicativos
</div> </span>
</div>
<h2 className="text-4xl md:text-5xl font-bold text-white/80"> <h2 className="text-4xl md:text-5xl font-bold text-white/80">
Aplicativos mais acessados Aplicativos mais acessados
</h2> </h2>
<p className="text-xl text-white/80 max-w-xl"> <p className="text-xl text-white/80 max-w-xl">
Aqui estão as 5 aplicações com mais acessos e o total Aqui estão as 5 aplicações com mais acessos e o
de horas registradas nelas durante o ano. total de horas registradas nelas durante o ano.
</p> </p>
</motion.div> </motion.div>
</div> </div>
<div className="md:w-3/5 w-full"> <div className="md:w-3/5 w-full mt-8 md:mt-0">
<div className="w-full grid grid-cols-1 gap-3 md:gap-4 max-h-full"> <div className="w-full grid grid-cols-1 gap-3 md:gap-4">
{topItems.slice(0, 5).map((item, index) => { {topItems.slice(0, 5).map((item, index) => {
const medalColors = [ const medalColors = [
'bg-yellow-400 text-yellow-900', 'bg-yellow-400 text-yellow-900',
'bg-gray-300 text-gray-900', 'bg-gray-300 text-gray-900',
'bg-orange-400 text-orange-900', 'bg-orange-400 text-orange-900',
'bg-blue-400/20 text-blue-300', 'bg-blue-400/20 text-blue-300',
'bg-purple-400/20 text-purple-300', 'bg-purple-400/20 text-purple-300',
]; ];
const isMedal = index < 3; const isMedal = index < 3;
const medalColor = const medalColor =
medalColors[index] || 'bg-white/20'; 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 {' '}
{Math.round(
item.horasLogadas || 0,
).toLocaleString('pt-BR')}{' '}
h
</p>
</div>
return (
<motion.div <motion.div
className="ml-4 flex flex-col items-end" key={index}
initial={{ scale: 0 }} className={`flex items-center p-4 md:p-5 rounded-xl backdrop-blur-sm border border-white/20 ${
whileInView={{ scale: 1 }} 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 }} viewport={{ once: true }}
transition={{ transition={{
type: 'spring', duration: 0.5,
delay: index * 0.1 + 0.2, delay: index * 0.1,
}}> }}>
<p className="text-xl md:text-2xl font-bold text-yellow-300 mb-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}`}>
(item.totalAcessos / (totalAcessos || 1)) * {index === 0 && '🥇'}
100 {index === 1 && '🥈'}
).toFixed(0)} {index === 2 && '🥉'}
% {index >= 3 && `#${index + 1}`}
</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 /
(totalAcessos || 1)) *
100
}%`,
}}
viewport={{ once: true }}
transition={{
duration: 1,
delay: index * 0.1 + 0.3,
}}
/>
</div> </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 {' '}
{Math.round(
item.horasLogadas || 0,
).toLocaleString('pt-BR')}{' '}
h
</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 / (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 /
(totalAcessos || 1)) *
100
}%`,
}}
viewport={{ once: true }}
transition={{
duration: 1,
delay: index * 0.1 + 0.3,
}}
/>
</div>
</motion.div>
</motion.div> </motion.div>
</motion.div> );
); })}
})} </div>
</div> </div>
</div> </div>
</div> </div>
@ -338,59 +337,60 @@ export function RetrospectiveSlides() {
)} )}
<StorySlide className="bg-linear-to-br from-slate-50 to-blue-50"> <StorySlide className="bg-linear-to-br from-slate-50 to-blue-50">
<div className="h-full flex flex-col md:flex-row items-center justify-center px-6 gap-12 py-12"> <div className="max-w-7xl mx-auto w-full h-full px-6 py-12">
<div className="space-y-8 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 gap-12">
<motion.div <div className="space-y-8 w-full md:w-1/2 text-center md:text-left">
initial={{ scale: 0 }} <motion.div
whileInView={{ scale: 1 }} initial={{ scale: 0 }}
viewport={{ once: true }} whileInView={{ scale: 1 }}
transition={{ type: 'spring', duration: 0.6 }}> viewport={{ once: true }}
<img src={logoAImg} className="w-10" /> transition={{ type: 'spring', duration: 0.6 }}>
</motion.div> <img src={logoAImg} className="w-10" />
</motion.div>
<h2 className="text-4xl md:text-5xl font-bold text-[#0e233d]"> <h2 className="text-4xl md:text-5xl font-bold text-[#0e233d]">
A Ágape deseja a você um {ano + 1} repleto de sucesso! A Ágape deseja a você um {ano + 1} repleto de sucesso!
</h2> </h2>
<p className="text-xl text-muted-foreground max-w-xl"> <p className="text-xl text-muted-foreground max-w-xl">
Obrigado por fazer parte desta jornada de transformação Obrigado por fazer parte desta jornada de transformação
digital na gestão pública. Sua dedicação é essencial para digital na gestão pública. Sua dedicação é essencial para
construirmos um serviço público mais eficiente e construirmos um serviço público mais eficiente e
transparente. transparente.
</p>
<motion.div
className="text-sm text-muted-foreground"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}>
<p>&copy; {ano} Ágape Sistemas e Tecnologia</p>
<p className="mt-2">
O Futuro da Gestão Pública começa aqui!
</p> </p>
</motion.div>
</div>
<div className="w-full md:w-1/2 flex justify-center"> <motion.div
<div className="max-w-md w-full"> className="text-sm text-muted-foreground"
<ProfileCard initial={{ opacity: 0 }}
name="Usuário Ágape" whileInView={{ opacity: 1 }}
title="Gestor Público" viewport={{ once: true }}
handle="agape" transition={{ duration: 0.6, delay: 0.4 }}>
status="Online" <p>&copy; {ano} Ágape Sistemas e Tecnologia</p>
contactText="Contato" <p className="mt-2">
avatarUrl="https://github.com/GuilhermeSantosUI.png" O Futuro da Gestão Pública começa aqui!
// métricas para preencher o cartão final </p>
totalAcessos={totalAcessos} </motion.div>
aplicacoesCount={aplicacoesCount} </div>
topAppName={topAppName}
topAppAcessos={topAppAcessos} <div className="w-full md:w-1/2 flex justify-center">
showUserInfo={true} <div className="max-w-md w-full">
enableTilt={true} <ProfileCard
enableMobileTilt={false} name="Usuário Ágape"
onContactClick={() => console.log('Contact clicked')} title="Gestor Público"
/> handle="agape"
status="Online"
contactText="Contato"
avatarUrl="https://github.com/GuilhermeSantosUI.png"
totalAcessos={totalAcessos}
aplicacoesCount={aplicacoesCount}
topAppName={topAppName}
topAppAcessos={topAppAcessos}
showUserInfo={true}
enableTilt={true}
enableMobileTilt={false}
onContactClick={() => console.log('Contact clicked')}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -400,4 +400,4 @@ export function RetrospectiveSlides() {
)} )}
</div> </div>
); );
} }