Skip to content

Commit 146eda9

Browse files
feat(frontend): dashboard, emprestimos, reports e sidebar
- Adiciona páginas Dashboard, Emprestimos com dados reais das APIs - Sidebar fixa com position fixed para acompanhar o scroll - Reports filtra por cobrador logado - EmprestimosService com token correto e campos camelCase - Login redireciona para /dashboard após autenticação - taxaMedia formatada corretamente em percentual Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a5404a5 commit 146eda9

9 files changed

Lines changed: 497 additions & 19 deletions

File tree

src/backend/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "http://json.schemastore.org/launchsettings.json",
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
33
"iisSettings": {
44
"windowsAuthentication": false,
55
"anonymousAuthentication": true,

src/frontend/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
"react-router-dom": "^7.14.2"
1616
},
1717
"devDependencies": {
18-
"@eslint/js": "^10.0.1",
18+
"@eslint/js": "^9.0.0",
1919
"@types/react": "^19.2.14",
2020
"@types/react-dom": "^19.2.3",
21-
"@vitejs/plugin-react": "^6.0.1",
22-
"eslint": "^10.2.1",
23-
"eslint-plugin-react-hooks": "^7.1.1",
24-
"eslint-plugin-react-refresh": "^0.5.2",
25-
"globals": "^17.5.0",
26-
"vite": "^8.0.10"
21+
"@vitejs/plugin-react": "^4.3.4",
22+
"eslint": "^9.0.0",
23+
"eslint-plugin-react-hooks": "^5.0.0",
24+
"eslint-plugin-react-refresh": "^0.4.14",
25+
"globals": "^15.0.0",
26+
"vite": "^5.4.0"
2727
}
2828
}

src/frontend/src/App.jsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Register from "./pages/Register";
77
import ForgotPassword from "./pages/ForgotPassword";
88
import ResetPassword from "./pages/ResetPassword";
99
import { isAuthenticated } from "./services/authService";
10+
import Dashboard from "./pages/Dashboard";
11+
import Emprestimos from "./pages/Emprestimos";
1012

1113
export default function App() {
1214
const location = useLocation();
@@ -20,10 +22,10 @@ export default function App() {
2022

2123
<div style={styles.content}>
2224
<Routes>
23-
<Route path="/" element={<Navigate to="/login" replace />} />
25+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
2426
<Route
2527
path="/login"
26-
element={authenticated ? <Navigate to="/clientes" replace /> : <Login />}
28+
element={authenticated ? <Navigate to="/dashboard" replace /> : <Login />}
2729
/>
2830
<Route
2931
path="/register"
@@ -46,9 +48,14 @@ export default function App() {
4648
element={authenticated ? <Reports /> : <Navigate to="/login" replace />}
4749
/>
4850

49-
{/* Rotas futuras — descomentar conforme as páginas forem criadas */}
50-
{/* <Route path="/dashboard" element={<Dashboard />} /> */}
51-
{/* <Route path="/emprestimos" element={<Emprestimos />} /> */}
51+
<Route
52+
path="/dashboard"
53+
element={authenticated ? <Dashboard /> : <Navigate to="/login" replace />}
54+
/>
55+
<Route
56+
path="/emprestimos"
57+
element={authenticated ? <Emprestimos /> : <Navigate to="/login" replace />}
58+
/>
5259
{/* <Route path="/configuracoes" element={<Configuracoes />} /> */}
5360
</Routes>
5461
</div>
@@ -60,11 +67,11 @@ const styles = {
6067
container: {
6168
display: "flex",
6269
minHeight: "100vh",
63-
overflow: "hidden",
6470
},
6571
content: {
6672
flex: 1,
6773
minHeight: "100vh",
74+
marginLeft: "256px",
6875
},
6976
};
7077

src/frontend/src/components/Sidebar.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ const styles = {
8282
display: "flex",
8383
flexDirection: "column",
8484
flexShrink: 0,
85-
position: "sticky",
85+
position: "fixed",
8686
top: 0,
87+
left: 0,
8788
overflowY: "auto",
89+
zIndex: 100,
8890
},
8991
logoWrapper: {
9092
display: "flex",
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useEffect, useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { getUsuarioLogado, getToken } from "../services/authService";
4+
import { getClientes } from "../services/clientesService";
5+
import { getCarteira } from "../services/EmprestimosService";
6+
7+
const acoes = [
8+
{ icon: "👥", label: "Clientes", path: "/clientes", desc: "Gerenciar devedores" },
9+
{ icon: "💳", label: "Empréstimos", path: "/emprestimos", desc: "Controle de empréstimos" },
10+
{ icon: "📈", label: "Relatórios", path: "/relatorios", desc: "Visualizar relatórios" },
11+
{ icon: "⚙️", label: "Configurações", path: "/configuracoes", desc: "Ajustes do sistema" },
12+
];
13+
14+
function calcularStatus(e) {
15+
if (e.pago) return "pago";
16+
const diff = Math.ceil((new Date(e.dataVencimento) - new Date()) / 86400000);
17+
return diff < 0 ? "atraso" : "emDia";
18+
}
19+
20+
export default function Dashboard() {
21+
const navigate = useNavigate();
22+
const usuario = getUsuarioLogado();
23+
const cobrador = usuario?.nome ?? "";
24+
const nome = cobrador.split(" ")[0] || "Usuário";
25+
26+
const [hora, setHora] = useState("");
27+
const [stats, setStats] = useState({ clientes: "—", emprestimos: "—", emDia: "—", atraso: "—" });
28+
const [carregando, setCarregando] = useState(true);
29+
30+
useEffect(() => {
31+
const h = new Date().getHours();
32+
if (h < 12) setHora("Bom dia");
33+
else if (h < 18) setHora("Boa tarde");
34+
else setHora("Boa noite");
35+
}, []);
36+
37+
useEffect(() => {
38+
if (!cobrador) return;
39+
async function carregarStats() {
40+
try {
41+
const [clientes, carteira] = await Promise.all([
42+
getClientes(getToken()),
43+
getCarteira(cobrador),
44+
]);
45+
46+
const totalClientes = Array.isArray(clientes) ? clientes.length : 0;
47+
const listaCarteira = Array.isArray(carteira) ? carteira : [];
48+
const totalEmprestimos = listaCarteira.length;
49+
const emDia = listaCarteira.filter((e) => calcularStatus(e) === "emDia").length;
50+
const atraso = listaCarteira.filter((e) => calcularStatus(e) === "atraso").length;
51+
52+
setStats({ clientes: totalClientes, emprestimos: totalEmprestimos, emDia, atraso });
53+
} catch {
54+
// mantém "—" se falhar
55+
} finally {
56+
setCarregando(false);
57+
}
58+
}
59+
carregarStats();
60+
}, [cobrador]);
61+
62+
return (
63+
<div style={s.page}>
64+
<div style={s.header}>
65+
<div>
66+
<p style={s.saudacao}>{hora}, {nome} 👋</p>
67+
<h1 style={s.titulo}>Painel</h1>
68+
</div>
69+
</div>
70+
71+
<div style={s.statsGrid}>
72+
<CardResumo icon="👥" label="Clientes" valor={carregando ? "..." : stats.clientes} cor="#7C3AED" />
73+
<CardResumo icon="💳" label="Empréstimos" valor={carregando ? "..." : stats.emprestimos} cor="#2563EB" />
74+
<CardResumo icon="✅" label="Em dia" valor={carregando ? "..." : stats.emDia} cor="#16A34A" />
75+
<CardResumo icon="⚠️" label="Em atraso" valor={carregando ? "..." : stats.atraso} cor="#DC2626" />
76+
</div>
77+
78+
<h2 style={s.secaoTitulo}>Acesso rápido</h2>
79+
<div style={s.acoesGrid}>
80+
{acoes.map((a) => (
81+
<button key={a.path} style={s.card} onClick={() => navigate(a.path)}>
82+
<span style={s.cardIcon}>{a.icon}</span>
83+
<span style={s.cardLabel}>{a.label}</span>
84+
<span style={s.cardDesc}>{a.desc}</span>
85+
</button>
86+
))}
87+
</div>
88+
</div>
89+
);
90+
}
91+
92+
function CardResumo({ icon, label, valor, cor }) {
93+
return (
94+
<div style={{ ...s.statCard, borderTop: `4px solid ${cor}` }}>
95+
<span style={{ ...s.statIcon, color: cor }}>{icon}</span>
96+
<div>
97+
<p style={s.statValor}>{valor}</p>
98+
<p style={s.statLabel}>{label}</p>
99+
</div>
100+
</div>
101+
);
102+
}
103+
104+
const s = {
105+
page: { padding: "2rem", background: "#F5F3FF", minHeight: "100vh" },
106+
header: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "2rem" },
107+
saudacao: { margin: 0, fontSize: "0.95rem", color: "#6B7280" },
108+
titulo: { margin: "0.25rem 0 0", fontSize: "1.8rem", fontWeight: 700, color: "#1F2937" },
109+
statsGrid: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: "1rem", marginBottom: "2rem" },
110+
statCard: { background: "#fff", borderRadius: "12px", padding: "1.25rem", display: "flex", alignItems: "center", gap: "1rem", boxShadow: "0 1px 4px rgba(0,0,0,0.07)" },
111+
statIcon: { fontSize: "2rem" },
112+
statValor: { margin: 0, fontSize: "1.5rem", fontWeight: 700, color: "#1F2937" },
113+
statLabel: { margin: 0, fontSize: "0.8rem", color: "#6B7280" },
114+
secaoTitulo:{ fontSize: "1.1rem", fontWeight: 600, color: "#374151", marginBottom: "1rem" },
115+
acoesGrid: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "1rem" },
116+
card: { background: "#fff", border: "none", borderRadius: "12px", padding: "1.5rem", display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "0.4rem", cursor: "pointer", boxShadow: "0 1px 4px rgba(0,0,0,0.07)", textAlign: "left" },
117+
cardIcon: { fontSize: "1.8rem" },
118+
cardLabel: { fontWeight: 600, fontSize: "1rem", color: "#1F2937" },
119+
cardDesc: { fontSize: "0.8rem", color: "#6B7280" },
120+
};

0 commit comments

Comments
 (0)