feat: add full project - backend, frontend, docker, specs and configs
This commit is contained in:
parent
b77c7d5a01
commit
e6cb06255b
24489 changed files with 61341 additions and 36 deletions
108
frontend/src/pages/admin/AdminFavoritosPage.tsx
Normal file
108
frontend/src/pages/admin/AdminFavoritosPage.tsx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import api from '../../services/api';
|
||||
|
||||
interface Favorito {
|
||||
id: string;
|
||||
user_id: string;
|
||||
property_id?: string;
|
||||
created_at: string;
|
||||
user_name?: string;
|
||||
property_title?: string;
|
||||
}
|
||||
|
||||
export default function AdminFavoritosPage() {
|
||||
const [favoritos, setFavoritos] = useState<Favorito[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [removing, setRemoving] = useState<Favorito | null>(null);
|
||||
const [actionLoading, setActionLoading] = useState(false);
|
||||
|
||||
function fetchFavoritos() {
|
||||
setLoading(true);
|
||||
api.get('/admin/favoritos')
|
||||
.then(res => setFavoritos(res.data))
|
||||
.catch(() => setError('Erro ao carregar favoritos'))
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
useEffect(() => { fetchFavoritos(); }, []);
|
||||
|
||||
function handleRemove() {
|
||||
if (!removing) return;
|
||||
setActionLoading(true);
|
||||
api.delete(`/admin/favoritos/${removing.id}`)
|
||||
.then(() => { setRemoving(null); fetchFavoritos(); })
|
||||
.catch(() => setError('Erro ao remover favorito'))
|
||||
.finally(() => setActionLoading(false));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 md:p-8">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-bold text-textPrimary">Favoritos</h2>
|
||||
</div>
|
||||
{loading && <div className="text-textSecondary text-sm">Carregando...</div>}
|
||||
{error && <div className="text-red-400 text-sm">{error}</div>}
|
||||
<div className="overflow-x-auto rounded-xl border border-borderPrimary">
|
||||
<table className="min-w-full bg-panel text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-textSecondary border-b border-borderSubtle">
|
||||
<th className="py-3 px-4 font-medium">Cliente</th>
|
||||
<th className="py-3 px-4 font-medium hidden sm:table-cell">Imóvel</th>
|
||||
<th className="py-3 px-4 font-medium hidden md:table-cell">Adicionado em</th>
|
||||
<th className="py-3 px-4 font-medium">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{favoritos.length === 0 && !loading && (
|
||||
<tr>
|
||||
<td colSpan={4} className="py-8 text-center text-textTertiary">
|
||||
Nenhum favorito encontrado.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{favoritos.map(f => (
|
||||
<tr key={f.id} className="border-t border-borderSubtle text-textPrimary hover:bg-surface/50 transition-colors">
|
||||
<td className="py-3 px-4">{f.user_name || f.user_id || '—'}</td>
|
||||
<td className="py-3 px-4 hidden sm:table-cell text-textSecondary">{f.property_title || '—'}</td>
|
||||
<td className="py-3 px-4 hidden md:table-cell text-textSecondary">{f.created_at.slice(0, 10)}</td>
|
||||
<td className="py-3 px-4">
|
||||
<button
|
||||
className="text-red-400 hover:text-red-300 text-xs font-medium transition-colors"
|
||||
onClick={() => setRemoving(f)}
|
||||
>Remover</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Modal remoção */}
|
||||
{removing && (
|
||||
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-panel border border-borderPrimary rounded-xl shadow-lg p-6 w-full max-w-md">
|
||||
<h3 className="text-textPrimary font-semibold mb-2">Remover favorito</h3>
|
||||
<p className="text-textSecondary text-sm mb-4">
|
||||
Tem certeza que deseja remover o favorito de{' '}
|
||||
<span className="text-textPrimary font-medium">{removing.user_name || removing.user_id}</span>
|
||||
{' '}para o imóvel{' '}
|
||||
<span className="text-textPrimary font-medium">{removing.property_title || '—'}</span>?
|
||||
</p>
|
||||
<div className="flex gap-2 justify-end">
|
||||
<button
|
||||
className="px-4 py-2 rounded border border-borderPrimary text-textSecondary hover:text-textPrimary hover:border-borderSecondary text-sm transition-colors"
|
||||
onClick={() => setRemoving(null)}
|
||||
>Cancelar</button>
|
||||
<button
|
||||
className="px-4 py-2 rounded bg-red-600 hover:bg-red-500 text-white text-sm font-medium transition-colors disabled:opacity-50"
|
||||
onClick={handleRemove}
|
||||
disabled={actionLoading}
|
||||
>Remover</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue