feat(frontend): add work mode filter and randomize seeder types

This commit is contained in:
Tiago Yamamoto 2025-12-15 14:44:14 -03:00
parent 78314f2b45
commit 640eb10703
6 changed files with 51 additions and 9 deletions

View file

@ -19,6 +19,7 @@ type Job struct {
// Employment
EmploymentType *string `json:"employmentType,omitempty" db:"employment_type"` // full-time, part-time, dispatch, contract
WorkMode *string `json:"workMode,omitempty" db:"work_mode"` // onsite, hybrid, remote
WorkingHours *string `json:"workingHours,omitempty" db:"working_hours"`
// Location

View file

@ -64,10 +64,10 @@ func (s *JobService) CreateJob(req dto.CreateJobRequest) (*models.Job, error) {
}
func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) {
baseQuery := `
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,
j.employment_type, j.work_mode, 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
@ -75,7 +75,7 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany
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`
countQuery := `SELECT COUNT(*) FROM jobs j WHERE 1=1`
var args []interface{}
argId := 1

View file

@ -24,6 +24,7 @@ function JobsContent() {
const [searchTerm, setSearchTerm] = useState("")
const [locationFilter, setLocationFilter] = useState("all")
const [typeFilter, setTypeFilter] = useState("all")
const [workModeFilter, setWorkModeFilter] = useState("all")
const [sortBy, setSortBy] = useState("recent")
const [showFilters, setShowFilters] = useState(false)
@ -71,7 +72,7 @@ function JobsContent() {
// Reset page when filters change
useEffect(() => {
setCurrentPage(1)
}, [debouncedSearchTerm, locationFilter, typeFilter, sortBy])
}, [debouncedSearchTerm, locationFilter, typeFilter, workModeFilter, sortBy])
// Extrair valores únicos para os filtros
const uniqueLocations = useMemo(() => {
@ -84,6 +85,11 @@ function JobsContent() {
return Array.from(new Set(types))
}, [jobs])
const uniqueWorkModes = useMemo(() => {
const modes = jobs.map(job => job.workMode).filter(Boolean) as string[]
return Array.from(new Set(modes))
}, [jobs])
const filteredAndSortedJobs = useMemo(() => {
let filtered = jobs.filter((job) => {
const matchesSearch =
@ -92,8 +98,9 @@ function JobsContent() {
job.description.toLowerCase().includes(debouncedSearchTerm.toLowerCase())
const matchesLocation = locationFilter === "all" || job.location.includes(locationFilter)
const matchesType = typeFilter === "all" || job.type === typeFilter
const matchesWorkMode = workModeFilter === "all" || job.workMode === workModeFilter
return matchesSearch && matchesLocation && matchesType
return matchesSearch && matchesLocation && matchesType && matchesWorkMode
})
// Ordenação
@ -115,7 +122,7 @@ function JobsContent() {
}
return filtered
}, [debouncedSearchTerm, locationFilter, typeFilter, sortBy, jobs])
}, [debouncedSearchTerm, locationFilter, typeFilter, workModeFilter, sortBy, jobs])
// Pagination Logic
const totalPages = Math.ceil(filteredAndSortedJobs.length / ITEMS_PER_PAGE)
@ -124,12 +131,13 @@ function JobsContent() {
currentPage * ITEMS_PER_PAGE
)
const hasActiveFilters = searchTerm || locationFilter !== "all" || typeFilter !== "all"
const hasActiveFilters = searchTerm || locationFilter !== "all" || typeFilter !== "all" || workModeFilter !== "all"
const clearFilters = () => {
setSearchTerm("")
setLocationFilter("all")
setTypeFilter("all")
setWorkModeFilter("all")
}
return (
@ -234,6 +242,23 @@ function JobsContent() {
</SelectContent>
</Select>
<Select value={workModeFilter} onValueChange={setWorkModeFilter}>
<SelectTrigger>
<MapPin className="h-4 w-4 mr-2" />
<SelectValue placeholder="Modalidade" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Todas as modalidades</SelectItem>
{uniqueWorkModes.map((mode) => (
<SelectItem key={mode} value={mode}>
{mode === "remote" ? "Remoto" :
mode === "hybrid" ? "Híbrido" :
mode === "onsite" ? "Presencial" : mode}
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={sortBy} onValueChange={setSortBy}>
<SelectTrigger>
<ArrowUpDown className="h-4 w-4 mr-2" />
@ -297,6 +322,16 @@ function JobsContent() {
</button>
</Badge>
)}
{workModeFilter !== "all" && (
<Badge variant="secondary" className="gap-1">
{workModeFilter === "remote" ? "Remoto" :
workModeFilter === "hybrid" ? "Híbrido" :
workModeFilter === "onsite" ? "Presencial" : workModeFilter}
<button onClick={() => setWorkModeFilter("all")} className="ml-1">
<X className="h-3 w-3" />
</button>
</Badge>
)}
</div>
)}
</div>

View file

@ -107,6 +107,7 @@ export interface ApiJob {
salaryMax?: number;
salaryType?: string;
employmentType?: string;
workMode?: string;
workingHours?: string;
location?: string;
regionId?: number;
@ -182,6 +183,7 @@ export function transformApiJobToFrontend(apiJob: ApiJob): import('./types').Job
company: apiJob.companyName || 'Empresa',
location: apiJob.location || apiJob.cityName || 'Localização não informada',
type,
workMode: apiJob.workMode as any,
salary,
description: apiJob.description,
requirements: requirements.length > 0 ? requirements : ['Ver detalhes'],

View file

@ -4,6 +4,7 @@ export interface Job {
company: string;
location: string;
type: "full-time" | "part-time" | "contract" | "Remoto" | "Tempo Integral";
workMode?: "onsite" | "hybrid" | "remote";
salary?: string;
description: string;
requirements: string[];

View file

@ -89,6 +89,9 @@ export async function seedJobs() {
const salaryMin = template.salaryRange[0] + getRandomInt(-2000, 2000);
const salaryMax = template.salaryRange[1] + getRandomInt(-2000, 3000);
const employmentTypes = ['full-time', 'part-time', 'contract'];
const employmentType = employmentTypes[i % employmentTypes.length]; // Deterministic variety
await pool.query(`
INSERT INTO jobs (company_id, created_by, title, description,
salary_min, salary_max, salary_type, employment_type, working_hours,
@ -98,11 +101,11 @@ export async function seedJobs() {
company.id,
seedUserId,
title,
`We are looking for a talented ${title} to join our ${company.name} team. You will work on exciting projects and help drive our technical excellence.`,
`We are looking for a talented ${title} to join our ${company.name} team. Your role as ${level} will be crucial.`,
salaryMin,
salaryMax,
'monthly',
'full-time',
employmentType,
workMode === 'remote' ? 'Flexible' : '9:00-18:00',
workMode === 'remote' ? 'Remote (Anywhere)' : 'São Paulo - SP',
JSON.stringify(template.skills),