feat(frontend): add work mode filter and randomize seeder types
This commit is contained in:
parent
78314f2b45
commit
640eb10703
6 changed files with 51 additions and 9 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue