diff --git a/backend/internal/services/job_service.go b/backend/internal/services/job_service.go index 08e3dc5..cac7696 100644 --- a/backend/internal/services/job_service.go +++ b/backend/internal/services/job_service.go @@ -64,18 +64,18 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest) (*models.Job, error) { } func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) { - baseQuery := ` - SELECT - j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type, - j.employment_type, j.location, j.status, j.is_featured, j.created_at, j.updated_at, - c.name as company_name, c.logo_url as company_logo_url, - p.name as region_name, ci.name as city_name - FROM jobs j - LEFT JOIN companies c ON j.company_id = c.id - LEFT JOIN prefectures p ON j.region_id = p.id - LEFT JOIN cities ci ON j.city_id = ci.id - WHERE 1=1` - countQuery := `SELECT COUNT(*) FROM jobs j WHERE 1=1` + baseQuery := ` + SELECT + j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type, + j.employment_type, j.location, j.status, j.is_featured, j.created_at, j.updated_at, + c.name as company_name, c.logo_url as company_logo_url, + r.name as region_name, ci.name as city_name + FROM jobs j + LEFT JOIN companies c ON j.company_id = c.id + LEFT JOIN regions r ON j.region_id = r.id + LEFT JOIN cities ci ON j.city_id = ci.id + WHERE 1=1` + countQuery := `SELECT COUNT(*) FROM jobs j WHERE 1=1` var args []interface{} argId := 1 diff --git a/frontend/src/app/vagas/page.tsx b/frontend/src/app/vagas/page.tsx index cd20de7..a204b90 100644 --- a/frontend/src/app/vagas/page.tsx +++ b/frontend/src/app/vagas/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useMemo, Suspense } from "react" +import { useEffect, useState, useMemo, Suspense } from "react" import { Navbar } from "@/components/navbar" import { Footer } from "@/components/footer" import { JobCard } from "@/components/job-card" @@ -11,33 +11,72 @@ import { Badge } from "@/components/ui/badge" import { Card, CardContent } from "@/components/ui/card" import { PageSkeleton } from "@/components/loading-skeletons" import { mockJobs } from "@/lib/mock-data" +import { jobsApi, transformApiJobToFrontend } from "@/lib/api" import { useDebounce } from "@/hooks/use-utils" import { Search, MapPin, Briefcase, SlidersHorizontal, X, ArrowUpDown } from "lucide-react" import { motion, AnimatePresence } from "framer-motion" +import type { Job } from "@/lib/types" function JobsContent() { + const [jobs, setJobs] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) const [searchTerm, setSearchTerm] = useState("") const [locationFilter, setLocationFilter] = useState("all") const [typeFilter, setTypeFilter] = useState("all") const [sortBy, setSortBy] = useState("recent") const [showFilters, setShowFilters] = useState(false) + useEffect(() => { + let isMounted = true + + const fetchJobs = async () => { + setLoading(true) + setError(null) + + try { + const response = await jobsApi.list({ limit: 50, page: 1 }) + const mappedJobs = response.data.map(transformApiJobToFrontend) + + if (isMounted) { + setJobs(mappedJobs) + } + } catch (err) { + console.error("Erro ao buscar vagas", err) + if (isMounted) { + setError("Não foi possível carregar as vagas agora. Exibindo exemplos.") + setJobs(mockJobs) + } + } finally { + if (isMounted) { + setLoading(false) + } + } + } + + fetchJobs() + + return () => { + isMounted = false + } + }, []) + // Debounce search term para otimizar performance const debouncedSearchTerm = useDebounce(searchTerm, 300) // Extrair valores únicos para os filtros const uniqueLocations = useMemo(() => { - const locations = mockJobs.map(job => job.location) + const locations = jobs.map(job => job.location) return Array.from(new Set(locations)) - }, []) + }, [jobs]) const uniqueTypes = useMemo(() => { - const types = mockJobs.map(job => job.type) + const types = jobs.map(job => job.type) return Array.from(new Set(types)) - }, []) + }, [jobs]) const filteredAndSortedJobs = useMemo(() => { - let filtered = mockJobs.filter((job) => { + let filtered = jobs.filter((job) => { const matchesSearch = job.title.toLowerCase().includes(debouncedSearchTerm.toLowerCase()) || job.company.toLowerCase().includes(debouncedSearchTerm.toLowerCase()) || @@ -67,7 +106,7 @@ function JobsContent() { } return filtered - }, [debouncedSearchTerm, locationFilter, typeFilter, sortBy]) + }, [debouncedSearchTerm, locationFilter, typeFilter, sortBy, jobs]) const hasActiveFilters = searchTerm || locationFilter !== "all" || typeFilter !== "all" @@ -96,7 +135,7 @@ function JobsContent() { transition={{ delay: 0.1 }} className="text-lg text-muted-foreground text-pretty" > - {mockJobs.length} vagas disponíveis nas melhores empresas + {loading ? "Carregando vagas..." : `${jobs.length} vagas disponíveis nas melhores empresas`} @@ -252,11 +291,16 @@ function JobsContent() {
- {filteredAndSortedJobs.length > 0 ? ( - + {error && ( +
+ {error} +
+ )} + + {loading ? ( +
Carregando vagas...
+ ) : filteredAndSortedJobs.length > 0 ? ( + {filteredAndSortedJobs.map((job, index) => (