From a82559377aaabe8740ab9651a41da9b9a73ab077 Mon Sep 17 00:00:00 2001 From: Frederico Castro Date: Sat, 13 Dec 2025 09:02:08 -0300 Subject: [PATCH] Adiciona recurso de comparacao de consultores - Permite selecionar ate 2 consultores na lista via checkbox - Exibe barra flutuante com consultores selecionados - Modal de comparacao lado a lado com todos os componentes (A, B, C, D) - Destaque visual para valores maiores/menores entre os consultores - Layout responsivo para mobile --- frontend/src/App.css | 109 ++++++++ frontend/src/App.jsx | 52 ++++ frontend/src/components/CompararModal.css | 326 ++++++++++++++++++++++ frontend/src/components/CompararModal.jsx | 145 ++++++++++ frontend/src/components/ConsultorCard.css | 84 +++++- frontend/src/components/ConsultorCard.jsx | 17 +- 6 files changed, 729 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/CompararModal.css create mode 100644 frontend/src/components/CompararModal.jsx diff --git a/frontend/src/App.css b/frontend/src/App.css index 18fdfd5..7240858 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -169,3 +169,112 @@ footer p + p { margin-top: 0.5rem; font-size: 0.8rem; } + +.selecao-flutuante { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + background: linear-gradient(155deg, rgba(15, 23, 42, 0.98), rgba(30, 41, 59, 0.95)); + border: 1px solid var(--accent); + border-radius: 16px; + padding: 0.85rem 1.25rem; + display: flex; + align-items: center; + gap: 1.5rem; + box-shadow: 0 15px 50px rgba(0, 0, 0, 0.5), 0 0 30px rgba(79, 70, 229, 0.2); + z-index: 999; + backdrop-filter: blur(12px); + animation: slideUp 300ms ease; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateX(-50%) translateY(20px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } +} + +.selecao-info { + display: flex; + align-items: center; + gap: 0.75rem; + color: var(--muted); + font-size: 0.9rem; +} + +.selecao-nome { + background: rgba(79, 70, 229, 0.2); + color: var(--accent-2); + padding: 0.3rem 0.6rem; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 600; +} + +.selecao-acoes { + display: flex; + gap: 0.6rem; +} + +.btn-limpar { + padding: 0.55rem 1rem; + background: rgba(255, 255, 255, 0.08); + border: 1px solid var(--stroke); + color: var(--text); + border-radius: 8px; + cursor: pointer; + font-weight: 500; + transition: all 150ms ease; +} + +.btn-limpar:hover { + background: rgba(255, 59, 48, 0.2); + border-color: rgba(255, 59, 48, 0.4); +} + +.btn-comparar { + padding: 0.55rem 1.25rem; + background: linear-gradient(145deg, var(--accent), var(--accent-2)); + border: none; + color: white; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: all 150ms ease; +} + +.btn-comparar:hover:not(:disabled) { + filter: brightness(1.1); + box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4); +} + +.btn-comparar:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +@media (max-width: 600px) { + .selecao-flutuante { + left: 1rem; + right: 1rem; + transform: none; + flex-direction: column; + gap: 0.75rem; + padding: 1rem; + } + + .selecao-info { + flex-wrap: wrap; + justify-content: center; + } + + .selecao-acoes { + width: 100%; + justify-content: center; + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d3f2f41..c483936 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import Header from './components/Header'; import ConsultorCard from './components/ConsultorCard'; +import CompararModal from './components/CompararModal'; import { rankingService } from './services/api'; import './App.css'; @@ -15,6 +16,26 @@ function App() { const [highlightId, setHighlightId] = useState(null); const [busca, setBusca] = useState(''); const [buscando, setBuscando] = useState(false); + const [selecionados, setSelecionados] = useState([]); + const [modalAberto, setModalAberto] = useState(false); + + const toggleSelecionado = (consultor) => { + setSelecionados((prev) => { + const existe = prev.find((c) => c.id_pessoa === consultor.id_pessoa); + if (existe) { + return prev.filter((c) => c.id_pessoa !== consultor.id_pessoa); + } + if (prev.length >= 2) { + return [prev[1], consultor]; + } + return [...prev, consultor]; + }); + }; + + const limparSelecao = () => { + setSelecionados([]); + setModalAberto(false); + }; useEffect(() => { loadRanking(); @@ -123,10 +144,41 @@ function App() { key={consultor.id_pessoa} consultor={consultor} highlight={consultor.id_pessoa === highlightId} + selecionado={selecionados.some((c) => c.id_pessoa === consultor.id_pessoa)} + onToggleSelecionado={toggleSelecionado} /> ))} + {selecionados.length > 0 && ( +
+
+ {selecionados.length}/2 selecionados + {selecionados.map((c) => ( + {c.nome.split(' ')[0]} + ))} +
+
+ + +
+
+ )} + + {modalAberto && ( + setModalAberto(false)} + /> + )} +