Files
SIG-CM/frontend/public-web/src/pages/HomePage.tsx

169 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-12-18 13:32:50 -03:00
import { useEffect, useState } from 'react';
import SearchBar from '../components/SearchBar';
import ListingCard from '../components/ListingCard';
import { publicService } from '../services/publicService';
import type { Listing, Category } from '../types';
2025-12-23 15:12:57 -03:00
import { Filter, X } from 'lucide-react';
import { processCategoriesForSelect, type FlatCategory } from '../utils/categoryTreeUtils';
2025-12-18 13:32:50 -03:00
export default function HomePage() {
const [listings, setListings] = useState<Listing[]>([]);
2025-12-23 15:12:57 -03:00
// Usamos FlatCategory para el renderizado
const [flatCategories, setFlatCategories] = useState<FlatCategory[]>([]);
const [rawCategories, setRawCategories] = useState<Category[]>([]); // Guardamos raw para los botones del home
2025-12-18 13:32:50 -03:00
const [loading, setLoading] = useState(true);
2025-12-23 15:12:57 -03:00
// Estado de Búsqueda
const [searchText, setSearchText] = useState('');
const [selectedCatId, setSelectedCatId] = useState<number | null>(null);
const [dynamicFilters, setDynamicFilters] = useState<Record<string, string>>({});
2025-12-18 13:32:50 -03:00
useEffect(() => {
loadInitialData();
}, []);
const loadInitialData = async () => {
setLoading(true);
try {
const [latestListings, cats] = await Promise.all([
publicService.getLatestListings(),
publicService.getCategories()
]);
setListings(latestListings);
2025-12-23 15:12:57 -03:00
setRawCategories(cats);
// Procesamos el árbol para el select
const processed = processCategoriesForSelect(cats);
setFlatCategories(processed);
} catch (e) { console.error(e); }
finally { setLoading(false); }
2025-12-18 13:32:50 -03:00
};
2025-12-23 15:12:57 -03:00
const performSearch = async () => {
2025-12-18 13:32:50 -03:00
setLoading(true);
try {
2025-12-23 15:12:57 -03:00
const response = await import('../services/api').then(m => m.default.post('/listings/search', {
query: searchText,
categoryId: selectedCatId,
filters: dynamicFilters
}));
setListings(response.data);
} catch (e) { console.error(e); }
finally { setLoading(false); }
};
useEffect(() => {
if (searchText || selectedCatId || Object.keys(dynamicFilters).length > 0) {
performSearch();
2025-12-18 13:32:50 -03:00
}
2025-12-23 15:12:57 -03:00
}, [selectedCatId, dynamicFilters]);
const handleSearchText = (q: string) => {
setSearchText(q);
performSearch();
2025-12-18 13:32:50 -03:00
};
2025-12-23 15:12:57 -03:00
const clearFilters = () => {
setDynamicFilters({});
setSelectedCatId(null);
setSearchText('');
publicService.getLatestListings().then(setListings); // Reset list
}
// Para los botones del Home, solo mostramos los Raíz
const rootCategories = rawCategories.filter(c => !c.parentId);
2025-12-18 13:32:50 -03:00
return (
<div className="min-h-screen bg-gray-50 pb-20">
2025-12-23 15:12:57 -03:00
{/* Hero */}
<div className="bg-primary-900 text-white py-16 px-4 relative overflow-hidden">
2025-12-18 13:32:50 -03:00
<div className="absolute inset-0 bg-gradient-to-br from-primary-900 to-gray-900 opacity-90"></div>
<div className="max-w-4xl mx-auto text-center relative z-10">
2025-12-23 15:12:57 -03:00
<h1 className="text-3xl md:text-5xl font-bold mb-6">Encuentra tu próximo objetivo</h1>
2025-12-18 13:32:50 -03:00
<div className="flex justify-center">
2025-12-23 15:12:57 -03:00
<SearchBar onSearch={handleSearchText} />
2025-12-18 13:32:50 -03:00
</div>
</div>
</div>
2025-12-23 15:12:57 -03:00
<div className="max-w-7xl mx-auto px-4 mt-8 flex flex-col lg:flex-row gap-8">
{/* SIDEBAR DE FILTROS */}
<div className="w-full lg:w-64 flex-shrink-0 space-y-6">
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
<div className="flex justify-between items-center mb-4">
<h3 className="font-bold text-gray-800 flex items-center gap-2">
<Filter size={18} /> Filtros
</h3>
{(selectedCatId || Object.keys(dynamicFilters).length > 0) && (
<button onClick={clearFilters} className="text-xs text-red-500 hover:underline flex items-center">
<X size={12} /> Limpiar
</button>
)}
</div>
{/* Filtro Categoría (MEJORADO) */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-600 mb-2">Categoría</label>
<select
className="w-full border border-gray-300 rounded p-2 text-sm focus:ring-2 focus:ring-primary-500 outline-none"
value={selectedCatId || ''}
onChange={(e) => setSelectedCatId(Number(e.target.value) || null)}
>
<option value="">Todas las categorías</option>
{flatCategories.map(cat => (
<option
key={cat.id}
value={cat.id}
className={cat.level === 0 ? "font-bold text-gray-900" : "text-gray-600"}
>
{cat.label}
</option>
))}
</select>
</div>
{/* Filtros Dinámicos */}
{selectedCatId && (
<div className="space-y-3 pt-3 border-t">
<p className="text-xs font-bold text-gray-400 uppercase">Atributos</p>
<div>
<input
type="text"
placeholder="Filtrar por Kilometraje..."
className="w-full border p-2 rounded text-sm"
onChange={(e) => setDynamicFilters({ ...dynamicFilters, 'Kilometraje': e.target.value })}
/>
</div>
2025-12-18 13:32:50 -03:00
</div>
2025-12-23 15:12:57 -03:00
)}
</div>
2025-12-18 13:32:50 -03:00
</div>
2025-12-23 15:12:57 -03:00
{/* LISTADO */}
<div className="flex-1">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
{loading ? 'Cargando...' : selectedCatId
? `${listings.length} Resultados en ${flatCategories.find(c => c.id === selectedCatId)?.name}`
: 'Resultados Recientes'}
</h2>
2025-12-18 13:32:50 -03:00
2025-12-23 15:12:57 -03:00
{!loading && listings.length === 0 && (
<div className="text-center text-gray-500 py-20 bg-white rounded-lg border border-dashed border-gray-300">
No se encontraron avisos con esos criterios.
</div>
)}
2025-12-18 13:32:50 -03:00
2025-12-23 15:12:57 -03:00
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{listings.map(listing => (
<ListingCard key={listing.id} listing={listing} />
))}
</div>
2025-12-18 13:32:50 -03:00
</div>
</div>
</div>
);
}