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 [showFilters, setShowFilters] = useState(false)
const [currentPage, setCurrentPage] = useState(1)
const ITEMS_PER_PAGE = 10
useEffect(() => {
let isMounted = true
@ -35,7 +38,8 @@ function JobsContent() {
setError(null)
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)
if (isMounted) {
@ -64,6 +68,11 @@ function JobsContent() {
// Debounce search term para otimizar performance
const debouncedSearchTerm = useDebounce(searchTerm, 300)
// Reset page when filters change
useEffect(() => {
setCurrentPage(1)
}, [debouncedSearchTerm, locationFilter, typeFilter, sortBy])
// Extrair valores únicos para os filtros
const uniqueLocations = useMemo(() => {
const locations = jobs.map(job => job.location)
@ -108,6 +117,13 @@ function JobsContent() {
return filtered
}, [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 clearFilters = () => {
@ -156,7 +172,7 @@ function JobsContent() {
className="pl-10 h-12"
/>
</div>
<div className="flex gap-2">
<Button
variant="outline"
@ -210,9 +226,9 @@ function JobsContent() {
<SelectItem value="all">Todos os tipos</SelectItem>
{uniqueTypes.map((type) => (
<SelectItem key={type} value={type}>
{type === "full-time" ? "Tempo integral" :
type === "part-time" ? "Meio período" :
type === "contract" ? "Contrato" : type}
{type === "full-time" ? "Tempo integral" :
type === "part-time" ? "Meio período" :
type === "contract" ? "Contrato" : type}
</SelectItem>
))}
</SelectContent>
@ -252,6 +268,7 @@ function JobsContent() {
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>
{filteredAndSortedJobs.length} vaga{filteredAndSortedJobs.length !== 1 ? 's' : ''} encontrada{filteredAndSortedJobs.length !== 1 ? 's' : ''}
{totalPages > 1 && ` (Página ${currentPage} de ${totalPages})`}
</span>
{hasActiveFilters && (
<div className="flex items-center gap-2">
@ -299,23 +316,48 @@ function JobsContent() {
{loading ? (
<div className="text-center text-muted-foreground">Carregando vagas...</div>
) : filteredAndSortedJobs.length > 0 ? (
<motion.div layout className="grid gap-6">
<AnimatePresence>
{filteredAndSortedJobs.map((job, index) => (
<motion.div
key={job.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ delay: index * 0.05 }}
layout
) : paginatedJobs.length > 0 ? (
<div className="space-y-8">
<motion.div layout className="grid gap-6">
<AnimatePresence mode="popLayout">
{paginatedJobs.map((job, index) => (
<motion.div
key={job.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ delay: index * 0.05 }}
layout
>
<JobCard job={job} />
</motion.div>
))}
</AnimatePresence>
</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}
>
<JobCard job={job} />
</motion.div>
))}
</AnimatePresence>
</motion.div>
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
initial={{ opacity: 0 }}

View file

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