feat(frontend): implement pagination and adjust seeder jobs count

This commit is contained in:
Tiago Yamamoto 2025-12-15 14:00:42 -03:00
parent d369835999
commit 78314f2b45
2 changed files with 66 additions and 24 deletions

View file

@ -27,6 +27,9 @@ function JobsContent() {
const [sortBy, setSortBy] = useState("recent") const [sortBy, setSortBy] = useState("recent")
const [showFilters, setShowFilters] = useState(false) const [showFilters, setShowFilters] = useState(false)
const [currentPage, setCurrentPage] = useState(1)
const ITEMS_PER_PAGE = 10
useEffect(() => { useEffect(() => {
let isMounted = true let isMounted = true
@ -35,7 +38,8 @@ function JobsContent() {
setError(null) setError(null)
try { try {
const response = await jobsApi.list({ limit: 50, page: 1 }) // Fetch many jobs to allow client-side filtering and pagination
const response = await jobsApi.list({ limit: 1000, page: 1 })
const mappedJobs = response.data.map(transformApiJobToFrontend) const mappedJobs = response.data.map(transformApiJobToFrontend)
if (isMounted) { if (isMounted) {
@ -64,6 +68,11 @@ function JobsContent() {
// Debounce search term para otimizar performance // Debounce search term para otimizar performance
const debouncedSearchTerm = useDebounce(searchTerm, 300) const debouncedSearchTerm = useDebounce(searchTerm, 300)
// Reset page when filters change
useEffect(() => {
setCurrentPage(1)
}, [debouncedSearchTerm, locationFilter, typeFilter, sortBy])
// Extrair valores únicos para os filtros // Extrair valores únicos para os filtros
const uniqueLocations = useMemo(() => { const uniqueLocations = useMemo(() => {
const locations = jobs.map(job => job.location) const locations = jobs.map(job => job.location)
@ -108,6 +117,13 @@ function JobsContent() {
return filtered return filtered
}, [debouncedSearchTerm, locationFilter, typeFilter, sortBy, jobs]) }, [debouncedSearchTerm, locationFilter, typeFilter, sortBy, jobs])
// Pagination Logic
const totalPages = Math.ceil(filteredAndSortedJobs.length / ITEMS_PER_PAGE)
const paginatedJobs = filteredAndSortedJobs.slice(
(currentPage - 1) * ITEMS_PER_PAGE,
currentPage * ITEMS_PER_PAGE
)
const hasActiveFilters = searchTerm || locationFilter !== "all" || typeFilter !== "all" const hasActiveFilters = searchTerm || locationFilter !== "all" || typeFilter !== "all"
const clearFilters = () => { const clearFilters = () => {
@ -252,6 +268,7 @@ function JobsContent() {
<div className="flex items-center justify-between text-sm text-muted-foreground"> <div className="flex items-center justify-between text-sm text-muted-foreground">
<span> <span>
{filteredAndSortedJobs.length} vaga{filteredAndSortedJobs.length !== 1 ? 's' : ''} encontrada{filteredAndSortedJobs.length !== 1 ? 's' : ''} {filteredAndSortedJobs.length} vaga{filteredAndSortedJobs.length !== 1 ? 's' : ''} encontrada{filteredAndSortedJobs.length !== 1 ? 's' : ''}
{totalPages > 1 && ` (Página ${currentPage} de ${totalPages})`}
</span> </span>
{hasActiveFilters && ( {hasActiveFilters && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -299,10 +316,11 @@ function JobsContent() {
{loading ? ( {loading ? (
<div className="text-center text-muted-foreground">Carregando vagas...</div> <div className="text-center text-muted-foreground">Carregando vagas...</div>
) : filteredAndSortedJobs.length > 0 ? ( ) : paginatedJobs.length > 0 ? (
<div className="space-y-8">
<motion.div layout className="grid gap-6"> <motion.div layout className="grid gap-6">
<AnimatePresence> <AnimatePresence mode="popLayout">
{filteredAndSortedJobs.map((job, index) => ( {paginatedJobs.map((job, index) => (
<motion.div <motion.div
key={job.id} key={job.id}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
@ -316,6 +334,30 @@ function JobsContent() {
))} ))}
</AnimatePresence> </AnimatePresence>
</motion.div> </motion.div>
{/* Pagination Controls */}
{totalPages > 1 && (
<div className="flex justify-center items-center gap-2 mt-8">
<Button
variant="outline"
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
disabled={currentPage === 1}
>
Anterior
</Button>
<div className="text-sm text-muted-foreground px-4">
Página {currentPage} de {totalPages}
</div>
<Button
variant="outline"
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
>
Próxima
</Button>
</div>
)}
</div>
) : ( ) : (
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}

View file

@ -55,7 +55,7 @@ function getRandomInt(min, max) {
} }
export async function seedJobs() { export async function seedJobs() {
console.log('💼 Seeding jobs (33 per company = 990 total)...'); console.log('💼 Seeding jobs (25 per company)...');
// Get company IDs // Get company IDs
const companiesRes = await pool.query('SELECT id, name FROM companies ORDER BY id'); const companiesRes = await pool.query('SELECT id, name FROM companies ORDER BY id');
@ -79,8 +79,8 @@ export async function seedJobs() {
try { try {
for (const company of companies) { for (const company of companies) {
// Generate 33 jobs per company // Generate 25 jobs per company
for (let i = 0; i < 33; i++) { for (let i = 0; i < 25; i++) {
const template = jobTemplates[i % jobTemplates.length]; const template = jobTemplates[i % jobTemplates.length];
const level = levels[i % levels.length]; const level = levels[i % levels.length];
const workMode = workModes[i % 3]; // Even distribution: onsite, hybrid, remote const workMode = workModes[i % 3]; // Even distribution: onsite, hybrid, remote