Skip to content

Commit 445e148

Browse files
Samuel Ferreiraclaude
andcommitted
Fix pagination clamp, fetch error handling, catch-all route, README honesty
- Users: clamp page to valid range when totalPages changes (snaps to new last page after deleting the last item on the last page) - Users/Dashboard: add .catch to api fetches with an error state, message and retry button instead of an infinite skeleton - App: add path="*" catch-all route redirecting to home - README: label the auth as a client-side mock/demo login (not production security) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bcea10b commit 445e148

4 files changed

Lines changed: 46 additions & 10 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Painel administrativo de nível profissional: autenticação, CRUD completo, tab
1414

1515
<p align="center">
1616
<a href="https://samuelf27.github.io/admin-dashboard/"><b>🔗 Ver demo ao vivo</b></a>
17-
&nbsp;·&nbsp; 🔑 <code>admin@empresa.com</code> / <code>123456</code>
17+
&nbsp;·&nbsp; 🔑 Login de demonstração — <code>admin@empresa.com</code> / <code>123456</code> (ou qualquer e-mail válido + senha de 4+ caracteres)
1818
</p>
1919

2020
<p align="center"><img src="https://raw.githubusercontent.com/Samuelf27/admin-dashboard/main/preview.png" alt="Preview do painel" width="820" /></p>
@@ -27,7 +27,7 @@ Um **painel administrativo completo** como os usados em produtos corporativos
2727

2828
## ✨ Funcionalidades
2929

30-
- 🔐 **Autenticação** com rota protegida e sessão persistida
30+
- 🔐 **Login de demonstração (mock)** com rota protegida e sessão persistida — validação apenas no cliente (qualquer e-mail com `@` e senha de 4+ caracteres entra; o token é fictício e descartado). **Não é segurança de produção.**
3131
- 👥 **CRUD completo de usuários** — criar, editar e remover com **modal e validação**
3232
- 🔎 **Tabela de dados** com **busca**, **ordenação por coluna** e **paginação**
3333
- 📈 **Dashboard** com KPIs e gráficos (área e pizza) via Recharts

src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Routes, Route } from 'react-router-dom';
1+
import { Routes, Route, Navigate } from 'react-router-dom';
22
import Layout from './components/Layout';
33
import ProtectedRoute from './components/ProtectedRoute';
44
import Login from './pages/Login';
@@ -21,6 +21,7 @@ export default function App() {
2121
<Route path="/usuarios" element={<Users />} />
2222
<Route path="/config" element={<Settings />} />
2323
</Route>
24+
<Route path="*" element={<Navigate to="/" replace />} />
2425
</Routes>
2526
);
2627
}

src/pages/Dashboard.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,19 @@ export default function Dashboard() {
1313
const [metrics, setMetrics] = useState<Metric[] | null>(null);
1414
const [sales, setSales] = useState<SalesPoint[] | null>(null);
1515
const [cats, setCats] = useState<CategorySlice[] | null>(null);
16+
const [error, setError] = useState(false);
1617

17-
useEffect(() => {
18-
api.getMetrics().then(setMetrics);
19-
api.getSales().then(setSales);
20-
api.getCategories().then(setCats);
21-
}, []);
18+
function load() {
19+
setError(false);
20+
setMetrics(null); setSales(null); setCats(null);
21+
Promise.all([
22+
api.getMetrics().then(setMetrics),
23+
api.getSales().then(setSales),
24+
api.getCategories().then(setCats),
25+
]).catch(() => setError(true));
26+
}
27+
28+
useEffect(() => { load(); }, []);
2229

2330
return (
2431
<div className="space-y-6">
@@ -27,6 +34,13 @@ export default function Dashboard() {
2734
<p className="text-sm text-slate-500">Acompanhe os principais indicadores do negócio.</p>
2835
</div>
2936

37+
{error && (
38+
<div className="flex items-center justify-between gap-3 rounded-2xl border border-rose-200 bg-rose-50 px-5 py-4 text-sm text-rose-700 dark:border-rose-500/30 dark:bg-rose-500/10 dark:text-rose-400">
39+
<span>Erro ao carregar os dados do dashboard.</span>
40+
<button onClick={load} className="font-semibold hover:underline">Tentar novamente</button>
41+
</div>
42+
)}
43+
3044
{/* KPIs */}
3145
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
3246
{metrics

src/pages/Users.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const EMPTY: FormState = { name: '', email: '', role: 'Viewer', status: 'Ativo'
2424
export default function Users() {
2525
const { notify } = useToast();
2626
const [users, setUsers] = useState<User[] | null>(null);
27+
const [error, setError] = useState(false);
2728
const [search, setSearch] = useState('');
2829
const [sort, setSort] = useState<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'name', dir: 'asc' });
2930
const [page, setPage] = useState(1);
@@ -35,7 +36,13 @@ export default function Users() {
3536
const [errors, setErrors] = useState<Partial<Record<keyof FormState, string>>>({});
3637
const [saving, setSaving] = useState(false);
3738

38-
useEffect(() => { api.listUsers().then(setUsers); }, []);
39+
function loadUsers() {
40+
setError(false);
41+
setUsers(null);
42+
api.listUsers().then(setUsers).catch(() => setError(true));
43+
}
44+
45+
useEffect(() => { loadUsers(); }, []);
3946

4047
const filtered = useMemo(() => {
4148
if (!users) return [];
@@ -51,6 +58,11 @@ export default function Users() {
5158
const totalPages = Math.max(1, Math.ceil(filtered.length / PER_PAGE));
5259
const pageItems = filtered.slice((page - 1) * PER_PAGE, page * PER_PAGE);
5360

61+
// Mantém a página dentro do intervalo válido após remoções/filtros.
62+
useEffect(() => {
63+
setPage((p) => Math.min(Math.max(1, p), totalPages));
64+
}, [totalPages]);
65+
5466
function toggleSort(key: SortKey) {
5567
setSort((s) => ({ key, dir: s.key === key && s.dir === 'asc' ? 'desc' : 'asc' }));
5668
}
@@ -147,7 +159,16 @@ export default function Users() {
147159
</tr>
148160
</thead>
149161
<tbody>
150-
{users === null ? (
162+
{error ? (
163+
<tr>
164+
<td colSpan={6} className="px-5 py-10 text-center text-slate-400">
165+
Erro ao carregar usuários.
166+
<button onClick={loadUsers} className="ml-2 font-medium text-brand-600 hover:underline">
167+
Tentar novamente
168+
</button>
169+
</td>
170+
</tr>
171+
) : users === null ? (
151172
Array.from({ length: 6 }).map((_, i) => (
152173
<tr key={i} className="border-b border-slate-100 dark:border-slate-800">
153174
<td colSpan={6} className="px-5 py-4"><div className="h-5 animate-pulse rounded bg-slate-200 dark:bg-slate-800" /></td>

0 commit comments

Comments
 (0)