feat(dashboard): rewrite jobs/new with all backend fields and location autocomplete

- Add workMode, languageLevel, visaSupport, salaryNegotiable to form
- Add all 10 currency options and all 8 employment types (including dispatch/voluntary)
- Add status selector (draft/review/open/paused/closed)
- Add location autocomplete with country dropdown and city/region search
- Add application channel (email/url/phone) with conditional inputs
- Add resumeRequirement selector and requirements map in payload
- Load companies via adminCompaniesApi (no registration — user is already logged in)
- Extend CreateJobPayload type with workMode, cityId, regionId, languageLevel,
  visaSupport, requirements, expanded currency and status options

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tiago Yamamoto 2026-02-22 12:58:03 -06:00
parent edc1b76cfd
commit 2b98552658
2 changed files with 463 additions and 144 deletions

View file

@ -1,14 +1,14 @@
"use client"
import { useEffect, useState } from "react"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { Loader2, PlusCircle } from "lucide-react"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import {
Select,
SelectContent,
@ -16,55 +16,129 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { adminCompaniesApi, jobsApi, type AdminCompany, type CreateJobPayload } from "@/lib/api"
type ApplicationChannel = "email" | "url" | "phone"
type LocationResult = {
id: number
name: string
type: "city" | "state"
country_id: number
state_id?: number
region_name?: string
}
type ApiCountry = {
id: number
name: string
iso2: string
}
export default function DashboardNewJobPage() {
const [loading, setLoading] = useState(false)
const [loadingCompanies, setLoadingCompanies] = useState(true)
const [companies, setCompanies] = useState<AdminCompany[]>([])
const [formData, setFormData] = useState({
companyId: "",
title: "",
description: "",
location: "",
country: "",
employmentType: "",
workMode: "",
workingHours: "",
salaryMin: "",
salaryMax: "",
salaryType: "monthly",
currency: "BRL",
employmentType: "",
workingHours: "",
companyId: "",
salaryNegotiable: false,
languageLevel: "",
applicationChannel: "email" as ApplicationChannel,
applicationEmail: "",
applicationUrl: "",
applicationPhone: "",
resumeRequirement: "optional",
visaSupport: false,
status: "draft",
})
// Location autocomplete state
const [apiCountries, setApiCountries] = useState<ApiCountry[]>([])
const [locationIds, setLocationIds] = useState<{ cityId: number | null; regionId: number | null }>({ cityId: null, regionId: null })
const [locationResults, setLocationResults] = useState<LocationResult[]>([])
const [locationSearching, setLocationSearching] = useState(false)
const [showLocationDropdown, setShowLocationDropdown] = useState(false)
const locationRef = useRef<HTMLDivElement>(null)
// Load companies
useEffect(() => {
const loadCompanies = async () => {
try {
setLoadingCompanies(true)
const data = await adminCompaniesApi.list(undefined, 1, 100)
setCompanies(data.data ?? [])
} catch (error) {
console.error("Falha ao carregar empresas:", error)
toast.error("Falha ao carregar empresas")
} finally {
setLoadingCompanies(false)
adminCompaniesApi.list(undefined, 1, 100)
.then((data) => setCompanies(data.data ?? []))
.catch(() => toast.error("Falha ao carregar empresas"))
.finally(() => setLoadingCompanies(false))
}, [])
// Load countries for location autocomplete
useEffect(() => {
const apiBase = process.env.NEXT_PUBLIC_API_URL || ""
fetch(`${apiBase}/api/v1/locations/countries`)
.then((r) => r.json())
.then((data: ApiCountry[]) => setApiCountries(data))
.catch(() => {})
}, [])
const selectedCountryId = useMemo(
() => apiCountries.find((c) => c.iso2 === formData.country)?.id ?? null,
[apiCountries, formData.country]
)
const searchLocation = useCallback((query: string, countryId: number) => {
const apiBase = process.env.NEXT_PUBLIC_API_URL || ""
setLocationSearching(true)
fetch(`${apiBase}/api/v1/locations/search?q=${encodeURIComponent(query)}&country_id=${countryId}`)
.then((r) => r.json())
.then((data) => {
setLocationResults(Array.isArray(data) ? data : [])
setShowLocationDropdown(true)
})
.catch(() => setLocationResults([]))
.finally(() => setLocationSearching(false))
}, [])
useEffect(() => {
const query = formData.location.trim()
if (query.length < 2 || !selectedCountryId) {
setLocationResults([])
setShowLocationDropdown(false)
return
}
const timer = setTimeout(() => searchLocation(query, selectedCountryId), 350)
return () => clearTimeout(timer)
}, [formData.location, selectedCountryId, searchLocation])
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (locationRef.current && !locationRef.current.contains(e.target as Node)) {
setShowLocationDropdown(false)
}
}
loadCompanies()
document.addEventListener("mousedown", handleClickOutside)
return () => document.removeEventListener("mousedown", handleClickOutside)
}, [])
const set = (field: string, value: string | boolean) =>
setFormData((prev) => ({ ...prev, [field]: value }))
const canSubmit =
formData.title.trim().length >= 5 &&
formData.description.trim().length >= 20 &&
formData.companyId !== ""
const handleInputChange = (field: string, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }))
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!canSubmit) {
toast.error("Preencha os campos obrigatórios")
return
@ -77,32 +151,55 @@ export default function DashboardNewJobPage() {
title: formData.title,
description: formData.description,
location: formData.location || undefined,
employmentType:
(formData.employmentType as CreateJobPayload["employmentType"]) || undefined,
...(locationIds.cityId && { cityId: locationIds.cityId }),
...(locationIds.regionId && { regionId: locationIds.regionId }),
employmentType: (formData.employmentType as CreateJobPayload["employmentType"]) || undefined,
workMode: (formData.workMode as CreateJobPayload["workMode"]) || undefined,
workingHours: formData.workingHours || undefined,
salaryMin: formData.salaryMin ? parseFloat(formData.salaryMin) : undefined,
salaryMax: formData.salaryMax ? parseFloat(formData.salaryMax) : undefined,
salaryType: (formData.salaryType as CreateJobPayload["salaryType"]) || undefined,
currency: (formData.currency as CreateJobPayload["currency"]) || undefined,
workingHours: formData.workingHours || undefined,
status: "draft",
salaryNegotiable: formData.salaryNegotiable,
languageLevel: formData.languageLevel || undefined,
visaSupport: formData.visaSupport,
requirements: {
resumeRequirement: formData.resumeRequirement,
applicationChannel: formData.applicationChannel,
applicationEmail: formData.applicationChannel === "email" ? formData.applicationEmail : null,
applicationUrl: formData.applicationChannel === "url" ? formData.applicationUrl : null,
applicationPhone: formData.applicationChannel === "phone" ? formData.applicationPhone : null,
},
status: formData.status as CreateJobPayload["status"],
}
await jobsApi.create(payload)
toast.success("Vaga cadastrada no dashboard com sucesso")
toast.success("Vaga cadastrada com sucesso!")
setFormData({
companyId: "",
title: "",
description: "",
location: "",
country: "",
employmentType: "",
workMode: "",
workingHours: "",
salaryMin: "",
salaryMax: "",
salaryType: "monthly",
currency: "BRL",
employmentType: "",
workingHours: "",
companyId: "",
salaryNegotiable: false,
languageLevel: "",
applicationChannel: "email",
applicationEmail: "",
applicationUrl: "",
applicationPhone: "",
resumeRequirement: "optional",
visaSupport: false,
status: "draft",
})
setLocationIds({ cityId: null, regionId: null })
} catch (error: any) {
console.error("Falha ao cadastrar vaga:", error)
toast.error(error.message || "Falha ao cadastrar vaga")
} finally {
setLoading(false)
@ -111,34 +208,76 @@ export default function DashboardNewJobPage() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-foreground">Nova vaga</h1>
<p className="text-muted-foreground mt-1">
Cadastre vagas usando o padrão visual do dashboard.
</p>
</div>
<div>
<h1 className="text-3xl font-bold text-foreground">Nova vaga</h1>
<p className="text-muted-foreground mt-1">Preencha os dados da vaga. Os campos marcados com * são obrigatórios.</p>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<PlusCircle className="h-5 w-5" /> Dados da vaga
</CardTitle>
<CardDescription>
Informe os detalhes obrigatórios para criar a vaga em rascunho.
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-5">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Empresa e Status */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<PlusCircle className="h-5 w-5" /> Empresa e status
</CardTitle>
</CardHeader>
<CardContent className="grid md:grid-cols-2 gap-4">
<div>
<Label>Empresa *</Label>
{loadingCompanies ? (
<div className="h-10 px-3 flex items-center text-sm text-muted-foreground border rounded-md">
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> Carregando...
</div>
) : (
<Select value={formData.companyId} onValueChange={(v) => set("companyId", v)}>
<SelectTrigger>
<SelectValue placeholder="Selecione uma empresa" />
</SelectTrigger>
<SelectContent>
{companies.length === 0 ? (
<SelectItem value="__none" disabled>Nenhuma empresa disponível</SelectItem>
) : (
companies.map((c) => (
<SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
))
)}
</SelectContent>
</Select>
)}
</div>
<div>
<Label>Status</Label>
<Select value={formData.status} onValueChange={(v) => set("status", v)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="draft">Rascunho</SelectItem>
<SelectItem value="review">Em revisão</SelectItem>
<SelectItem value="open">Aberta</SelectItem>
<SelectItem value="paused">Pausada</SelectItem>
<SelectItem value="closed">Encerrada</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* Dados da vaga */}
<Card>
<CardHeader>
<CardTitle>Dados da vaga</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<Label htmlFor="title">Título da vaga *</Label>
<Input
id="title"
maxLength={255}
placeholder="Ex: Desenvolvedor(a) Full Stack Sênior"
value={formData.title}
onChange={(e) => handleInputChange("title", e.target.value)}
onChange={(e) => set("title", e.target.value)}
/>
<p className="text-xs text-muted-foreground mt-1">{formData.title.length}/255 caracteres</p>
</div>
<div>
@ -148,90 +287,191 @@ export default function DashboardNewJobPage() {
rows={6}
placeholder="Descreva responsabilidades, requisitos e diferenciais..."
value={formData.description}
onChange={(e) => handleInputChange("description", e.target.value)}
onChange={(e) => set("description", e.target.value)}
/>
</div>
<div>
<Label>Idioma da descrição</Label>
<Select value={formData.languageLevel} onValueChange={(v) => set("languageLevel", v)}>
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
<SelectContent>
<SelectItem value="pt">Português</SelectItem>
<SelectItem value="en">English</SelectItem>
<SelectItem value="es">Español</SelectItem>
<SelectItem value="ja"></SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
{/* Localização */}
<Card>
<CardHeader>
<CardTitle>Localização</CardTitle>
</CardHeader>
<CardContent className="grid md:grid-cols-2 gap-4">
<div>
<Label>País</Label>
<Select value={formData.country} onValueChange={(v) => {
set("country", v)
set("location", "")
setLocationIds({ cityId: null, regionId: null })
}}>
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
<SelectContent>
{apiCountries.length > 0
? apiCountries.map((c) => (
<SelectItem key={c.id} value={c.iso2}>{c.name}</SelectItem>
))
: ["US","BR","PT","ES","GB","DE","FR","JP"].map((iso) => (
<SelectItem key={iso} value={iso}>{iso}</SelectItem>
))
}
</SelectContent>
</Select>
</div>
<div>
<Label>Cidade / Estado</Label>
<div ref={locationRef} className="relative">
<Input
placeholder={formData.country ? "Digite para buscar..." : "Selecione um país primeiro"}
value={formData.location}
disabled={!formData.country}
autoComplete="off"
onChange={(e) => {
set("location", e.target.value)
setLocationIds({ cityId: null, regionId: null })
}}
onFocus={() => {
if (locationResults.length > 0) setShowLocationDropdown(true)
}}
/>
{locationSearching && (
<div className="absolute right-3 top-2.5">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
)}
{showLocationDropdown && locationResults.length > 0 && (
<div className="absolute z-50 mt-1 w-full rounded-md border bg-white shadow-md max-h-60 overflow-y-auto">
{locationResults.map((result) => (
<button
key={`${result.type}-${result.id}`}
type="button"
className="flex w-full items-center justify-between px-3 py-2 text-left hover:bg-gray-50"
onMouseDown={(e) => {
e.preventDefault()
const label = result.region_name
? `${result.name}, ${result.region_name}`
: result.name
set("location", label)
setLocationIds({
cityId: result.type === "city" ? result.id : null,
regionId: result.type === "state" ? result.id : (result.state_id ?? null),
})
setShowLocationDropdown(false)
}}
>
<span>
<span className="text-sm font-medium">{result.name}</span>
{result.region_name && (
<span className="ml-1 text-xs text-muted-foreground"> {result.region_name}</span>
)}
</span>
<span className={`rounded px-1.5 py-0.5 text-xs ${result.type === "city" ? "bg-blue-50 text-blue-600" : "bg-emerald-50 text-emerald-600"}`}>
{result.type}
</span>
</button>
))}
</div>
)}
</div>
</div>
<div>
<Label>Modo de trabalho</Label>
<Select value={formData.workMode} onValueChange={(v) => set("workMode", v)}>
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
<SelectContent>
<SelectItem value="onsite">Presencial</SelectItem>
<SelectItem value="hybrid">Híbrido</SelectItem>
<SelectItem value="remote">Remoto</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2 pt-6">
<Checkbox
id="visaSupport"
checked={formData.visaSupport}
onCheckedChange={(v) => set("visaSupport", v === true)}
/>
<Label htmlFor="visaSupport">Oferece suporte de visto</Label>
</div>
</CardContent>
</Card>
{/* Contrato e Salário */}
<Card>
<CardHeader>
<CardTitle>Contrato e salário</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="location">Localização</Label>
<Input
id="location"
placeholder="Ex: São Paulo/SP ou Remoto"
value={formData.location}
onChange={(e) => handleInputChange("location", e.target.value)}
/>
</div>
<div>
<Label>Tipo de contrato</Label>
<Select
value={formData.employmentType}
onValueChange={(v) => handleInputChange("employmentType", v)}
>
<SelectTrigger>
<SelectValue placeholder="Selecione" />
</SelectTrigger>
<Select value={formData.employmentType} onValueChange={(v) => set("employmentType", v)}>
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
<SelectContent>
<SelectItem value="permanent">Permanente</SelectItem>
<SelectItem value="full-time">Tempo integral</SelectItem>
<SelectItem value="part-time">Meio período</SelectItem>
<SelectItem value="contract">Contrato</SelectItem>
<SelectItem value="contract">Contrato (PJ)</SelectItem>
<SelectItem value="dispatch">Terceirizado</SelectItem>
<SelectItem value="temporary">Temporário</SelectItem>
<SelectItem value="training">Estágio/Trainee</SelectItem>
<SelectItem value="voluntary">Voluntário</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label htmlFor="salaryMin">Salário mínimo</Label>
<Input
id="salaryMin"
type="number"
min="0"
placeholder="Ex: 3000"
value={formData.salaryMin}
onChange={(e) => handleInputChange("salaryMin", e.target.value)}
/>
</div>
<div>
<Label htmlFor="salaryMax">Salário máximo</Label>
<Label htmlFor="workingHours">Jornada de trabalho</Label>
<Input
id="salaryMax"
type="number"
min="0"
placeholder="Ex: 6000"
value={formData.salaryMax}
onChange={(e) => handleInputChange("salaryMax", e.target.value)}
id="workingHours"
placeholder="Ex: 9h às 18h, 40h/semana"
value={formData.workingHours}
onChange={(e) => set("workingHours", e.target.value)}
/>
</div>
</div>
<div className="grid md:grid-cols-3 gap-4">
<div className="grid md:grid-cols-4 gap-4">
<div>
<Label>Moeda</Label>
<Select value={formData.currency} onValueChange={(v) => handleInputChange("currency", v)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<Select value={formData.currency} onValueChange={(v) => set("currency", v)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="BRL">BRL - R$</SelectItem>
<SelectItem value="USD">USD - $</SelectItem>
<SelectItem value="EUR">EUR - </SelectItem>
<SelectItem value="GBP">GBP - £</SelectItem>
<SelectItem value="BRL">BRL R$</SelectItem>
<SelectItem value="USD">USD $</SelectItem>
<SelectItem value="EUR">EUR </SelectItem>
<SelectItem value="GBP">GBP £</SelectItem>
<SelectItem value="JPY">JPY ¥</SelectItem>
<SelectItem value="CNY">CNY ¥</SelectItem>
<SelectItem value="AED">AED د.إ</SelectItem>
<SelectItem value="CAD">CAD $</SelectItem>
<SelectItem value="AUD">AUD $</SelectItem>
<SelectItem value="CHF">CHF Fr</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label>Período do salário</Label>
<Select value={formData.salaryType} onValueChange={(v) => handleInputChange("salaryType", v)}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<Label>Período</Label>
<Select value={formData.salaryType} onValueChange={(v) => set("salaryType", v)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="hourly">Por hora</SelectItem>
<SelectItem value="daily">Por dia</SelectItem>
@ -243,51 +483,118 @@ export default function DashboardNewJobPage() {
</div>
<div>
<Label htmlFor="workingHours">Jornada de trabalho</Label>
<Label htmlFor="salaryMin">Salário mínimo</Label>
<Input
id="workingHours"
placeholder="Ex: 9h às 18h"
value={formData.workingHours}
onChange={(e) => handleInputChange("workingHours", e.target.value)}
id="salaryMin"
type="number"
min="0"
placeholder="3000"
value={formData.salaryMin}
onChange={(e) => set("salaryMin", e.target.value)}
/>
</div>
<div>
<Label htmlFor="salaryMax">Salário máximo</Label>
<Input
id="salaryMax"
type="number"
min="0"
placeholder="6000"
value={formData.salaryMax}
onChange={(e) => set("salaryMax", e.target.value)}
/>
</div>
</div>
<div>
<Label>Empresa *</Label>
{loadingCompanies ? (
<div className="h-10 px-3 flex items-center text-sm text-muted-foreground border rounded-md">
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> Carregando empresas...
</div>
) : (
<Select value={formData.companyId} onValueChange={(v) => handleInputChange("companyId", v)}>
<SelectTrigger>
<SelectValue placeholder="Selecione uma empresa" />
</SelectTrigger>
<div className="flex items-center gap-2">
<Checkbox
id="salaryNegotiable"
checked={formData.salaryNegotiable}
onCheckedChange={(v) => set("salaryNegotiable", v === true)}
/>
<Label htmlFor="salaryNegotiable">Salário negociável</Label>
</div>
</CardContent>
</Card>
{/* Candidaturas */}
<Card>
<CardHeader>
<CardTitle>Candidaturas</CardTitle>
<CardDescription>Como os candidatos devem se candidatar à vaga.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid md:grid-cols-2 gap-4">
<div>
<Label>Canal de candidatura</Label>
<Select value={formData.applicationChannel} onValueChange={(v) => set("applicationChannel", v as ApplicationChannel)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{companies.length === 0 ? (
<SelectItem value="__none" disabled>
Nenhuma empresa disponível
</SelectItem>
) : (
companies.map((company) => (
<SelectItem key={company.id} value={company.id}>
{company.name}
</SelectItem>
))
)}
<SelectItem value="email">E-mail</SelectItem>
<SelectItem value="url">Link externo</SelectItem>
<SelectItem value="phone">Telefone</SelectItem>
</SelectContent>
</Select>
)}
</div>
<div>
<Label>Currículo</Label>
<Select value={formData.resumeRequirement} onValueChange={(v) => set("resumeRequirement", v)}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="required">Obrigatório</SelectItem>
<SelectItem value="optional">Opcional</SelectItem>
<SelectItem value="none">Não solicitado</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<Button type="submit" disabled={loading || !canSubmit}>
{loading ? <Loader2 className="h-4 w-4 mr-2 animate-spin" /> : null}
Criar vaga
</Button>
</form>
</CardContent>
</Card>
{formData.applicationChannel === "email" && (
<div>
<Label htmlFor="appEmail">E-mail para candidatura</Label>
<Input
id="appEmail"
type="email"
placeholder="jobs@empresa.com"
value={formData.applicationEmail}
onChange={(e) => set("applicationEmail", e.target.value)}
/>
</div>
)}
{formData.applicationChannel === "url" && (
<div>
<Label htmlFor="appUrl">Link externo (HTTPS)</Label>
<Input
id="appUrl"
placeholder="https://empresa.com/carreiras"
value={formData.applicationUrl}
onChange={(e) => set("applicationUrl", e.target.value)}
/>
</div>
)}
{formData.applicationChannel === "phone" && (
<div>
<Label htmlFor="appPhone">Telefone (com DDI)</Label>
<Input
id="appPhone"
placeholder="+55 11 99999-8888"
value={formData.applicationPhone}
onChange={(e) => set("applicationPhone", e.target.value)}
/>
</div>
)}
</CardContent>
</Card>
<Button type="submit" disabled={loading || !canSubmit} size="lg">
{loading && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
Criar vaga
</Button>
</form>
</div>
)
}

View file

@ -364,15 +364,27 @@ export interface CreateJobPayload {
companyId: string;
title: string;
description: string;
location?: string;
cityId?: number;
regionId?: number;
employmentType?: 'full-time' | 'part-time' | 'dispatch' | 'contract' | 'temporary' | 'training' | 'voluntary' | 'permanent';
workMode?: 'onsite' | 'hybrid' | 'remote';
workingHours?: string;
salaryMin?: number;
salaryMax?: number;
salaryType?: 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly';
currency?: 'BRL' | 'USD' | 'EUR' | 'GBP' | 'JPY';
currency?: 'BRL' | 'USD' | 'EUR' | 'GBP' | 'JPY' | 'CNY' | 'AED' | 'CAD' | 'AUD' | 'CHF';
salaryNegotiable?: boolean;
employmentType?: 'full-time' | 'part-time' | 'dispatch' | 'contract' | 'temporary' | 'training' | 'voluntary' | 'permanent';
workingHours?: string;
location?: string;
status: 'draft' | 'published' | 'open';
languageLevel?: string;
visaSupport?: boolean;
requirements?: {
resumeRequirement?: string;
applicationChannel?: string;
applicationEmail?: string | null;
applicationUrl?: string | null;
applicationPhone?: string | null;
};
status: 'draft' | 'review' | 'open' | 'paused' | 'closed' | 'published';
}
export const jobsApi = {