agretrospectivaweb/src/views/components/retrospective-slides.tsx

435 lines
19 KiB
TypeScript

import {
useMetricsByPortal,
useMetricsBySystems,
} from '@/app/hooks/useMetrics';
import logoAImg from '@/assets/images/a-agape.png';
import logoImg from '@/assets/images/agape-logo.png';
import {
BuildingIcon,
ChartLineUpIcon,
SpinnerIcon,
} from '@phosphor-icons/react';
import { motion } from 'framer-motion';
import * as React from 'react';
import { useSearchParams } from 'react-router-dom';
import { Navigation } from './navigation';
import ProfileCard from './ProfileCard';
import { StorySlide } from './story-slide';
export function RetrospectiveSlides() {
const [searchParams] = useSearchParams();
const cpf = searchParams.get('cpf') || '';
const ano = searchParams.get('ano')
? parseInt(searchParams.get('ano')!)
: new Date().getFullYear();
const {
data: metricsPortal,
isLoading: isLoadingPortal,
error: errorPortal,
} = useMetricsByPortal(cpf, ano);
const {
data: metricsSystems,
isLoading: isLoadingSystems,
error: errorSystems,
} = useMetricsBySystems(cpf, ano);
const [current, setCurrent] = React.useState(0);
// Calcula slides baseado nos dados disponíveis
const slides: number[] = [];
slides.push(0); // Slide inicial
if (metricsPortal && metricsPortal.length > 0) slides.push(1); // Slide com portal principal
if (
(metricsPortal && metricsPortal.length > 0) ||
(metricsSystems && metricsSystems.length > 0)
) {
slides.push(2); // Slide com podio de aplicações
}
slides.push(3); // Slide final
const total = slides.length;
const isLoading = isLoadingPortal || isLoadingSystems;
const hasError = errorPortal || errorSystems;
// Extrai total de acessos
const totalAcessos = [
...(metricsPortal || []),
...(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);
const allItems = [...(metricsPortal || []), ...(metricsSystems || [])];
const aplicacoesCount = new Set(
allItems.map((i) => (i.nomeSistema || '').trim()).filter((n) => !!n),
).size;
const topAppName = topItems[0]?.nomeSistema ?? null;
const topAppAcessos = topItems[0]?.totalAcessos ?? null;
return (
<div className="w-full h-screen overflow-hidden">
{isLoading ? (
<div className="h-screen w-full bg-black flex items-center justify-center">
<div className="text-center space-y-4">
<SpinnerIcon className="h-12 w-12 animate-spin mx-auto text-white" />
<p className="text-white text-lg">
Carregando sua retrospectiva...
</p>
</div>
</div>
) : hasError ? (
<div className="h-screen w-full bg-black flex items-center justify-center">
<div className="text-center space-y-4 max-w-md">
<p className="text-white text-lg font-semibold">
Erro ao carregar dados
</p>
<p className="text-white/70">
Verifique seu CPF e ano, e tente novamente.
</p>
</div>
</div>
) : (
<>
<Navigation total={total} current={current} setCurrent={setCurrent} />
<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">
<div className="text-center space-y-8">
<motion.div
initial={{ scale: 0.8, opacity: 0 }}
whileInView={{ scale: 1, opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}>
<img
src={logoImg}
alt="Ágape Logo"
className="mx-auto w-52 mb-6"
/>
</motion.div>
<motion.h1
className="text-6xl md:text-7xl font-medium mb-4"
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}>
Sua Retrospectiva
<br />
Ágape {ano}
</motion.h1>
<motion.p
className="text-xl md:text-2xl text-white/90 max-w-2xl mx-auto font-light"
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}>
Um ano de conquistas, eficiência e transparência na gestão
pública
</motion.p>
<motion.p
className="text-lg text-white/70"
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.5 }}>
Seus dados analisados: CPF
</motion.p>
<motion.div
className="flex items-center justify-center gap-4 text-sm text-white/70 pt-8"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.8 }}>
<span>Transparência</span>
<span></span>
<span>Eficiência</span>
<span></span>
<span>Parceria</span>
</motion.div>
</div>
</StorySlide>
{metricsPortal && metricsPortal.length > 0 && (
<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 - Ícone e números principais */}
<motion.div
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 }}>
{/* Ícone com efeito */}
<motion.div
initial={{ scale: 0 }}
whileInView={{ scale: 1 }}
viewport={{ once: true }}
transition={{ type: 'spring', duration: 0.6 }}
className="mb-6 md:mb-8">
<div className="relative">
<div className="absolute inset-0 bg-white/20 rounded-full blur-lg animate-pulse"></div>
<BuildingIcon className="h-14 w-14 md:h-16 md:w-16 text-white drop-shadow-lg relative z-10" />
</div>
</motion.div>
{/* Título */}
<motion.h2
className="text-2xl md:text-3xl lg:text-4xl font-bold text-center md:text-left mb-4"
initial={{ opacity: 0, y: -20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.1 }}>
Acessos no AgPortal
</motion.h2>
<motion.p
className="text-base md:text-lg 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 }}>
A porta de entrada para todas as aplicações
</motion.p>
{/* Número principal */}
<motion.div
className="space-y-3 mb-6"
initial={{ opacity: 0, scale: 0.8 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.8, delay: 0.4 }}>
<div className="text-5xl md:text-6xl lg:text-7xl font-black text-white tracking-tighter">
{metricsPortal
.reduce((acc, p) => acc + p.totalAcessos, 0)
.toLocaleString('pt-BR')}
</div>
<p className="text-xl md:text-2xl font-semibold text-blue-200">
acessos em {ano}
</p>
</motion.div>
{/* Estatísticas rápidas */}
<motion.div
className="mt-4 md:mt-auto w-full"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.6 }}>
<div className="grid grid-cols-3 gap-3">
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-3 border border-white/20 text-center">
<div className="text-lg font-bold text-white mb-1">
{Math.round(
metricsPortal.reduce(
(acc, p) => acc + p.totalAcessos,
0,
) / 365,
)}
</div>
<div className="text-xs text-blue-100 opacity-90">
Por dia
</div>
</div>
<div className="bg-white/10 backdrop-blur-sm rounded-lg p-3 border border-white/20 text-center">
<div className="text-lg font-bold text-white mb-1">
{Math.round(
metricsPortal.reduce(
(acc, p) => acc + p.totalAcessos,
0,
) / 12,
).toLocaleString('pt-BR')}
</div>
<div className="text-xs text-blue-100 opacity-90">
Por mês
</div>
</div>
</div>
</motion.div>
</motion.div>
{/* Coluna direita - Sistemas mais acessados (mantendo seu design) */}
<div className="md:w-3/5 h-full flex items-center">
<div className="w-full">
<motion.div
className="mb-6 text-center md:text-left"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}>
<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-2">
<ChartLineUpIcon className="h-4 w-4 text-yellow-300" />
<span className="text-sm font-semibold text-white">
Aplicativos Mais Acessados
</span>
</div>
</motion.div>
{/* Lista de sistemas */}
<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>
</div>
</div>
</div>
</StorySlide>
)}
<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="space-y-8 w-full md:w-1/2 text-center md:text-left">
<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>
<h2 className="text-4xl md:text-5xl font-bold text-[#0e233d]">
A Ágape deseja a você um {ano + 1} repleto de sucesso!
</h2>
<p className="text-xl text-muted-foreground max-w-xl">
Obrigado por fazer parte desta jornada de transformação
digital na gestão pública. Sua dedicação é essencial para
construirmos um serviço público mais eficiente e
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>
</motion.div>
</div>
<div className="w-full md:w-1/2 flex justify-center">
<div className="max-w-md w-full">
<ProfileCard
name="Usuário Ágape"
title="Gestor Público"
handle="agape"
status="Online"
contactText="Contato"
avatarUrl="https://github.com/GuilhermeSantosUI.png"
// métricas para preencher o cartão final
totalAcessos={totalAcessos}
aplicacoesCount={aplicacoesCount}
topAppName={topAppName}
topAppAcessos={topAppAcessos}
showUserInfo={true}
enableTilt={true}
enableMobileTilt={false}
onContactClick={() => console.log('Contact clicked')}
/>
</div>
</div>
</div>
</StorySlide>
</div>
</>
)}
</div>
);
}