Merge pull request #55 from rede5/codex/review-and-refine-entire-code
feat(post-job): adicionar campos e validações estilo Careerjet na publicação de vagas (~15%)
This commit is contained in:
commit
a9f8e40d7e
2 changed files with 256 additions and 22 deletions
|
|
@ -100,73 +100,73 @@ Mapear o que já existe no GoHorseJobs e o que ainda falta para alcançar um flu
|
||||||
### 1) Detalhes da vaga (escopo obrigatório)
|
### 1) Detalhes da vaga (escopo obrigatório)
|
||||||
|
|
||||||
#### Identificação da vaga
|
#### Identificação da vaga
|
||||||
- [ ] **Título da vaga**
|
- [x] **Título da vaga**
|
||||||
- Regra: obrigatório, texto claro e conciso, até **65 caracteres**.
|
- Regra: obrigatório, texto claro e conciso, até **65 caracteres**.
|
||||||
- Validações: bloquear excesso de pontuação, abreviações promocionais e termos de marketing (ex.: “IMPERDÍVEL!!!”).
|
- Validações: bloquear excesso de pontuação, abreviações promocionais e termos de marketing (ex.: “IMPERDÍVEL!!!”).
|
||||||
|
|
||||||
- [ ] **Localidade da vaga**
|
- [x] **Localidade da vaga**
|
||||||
- Regra: obrigatório, **uma única localidade** por anúncio.
|
- Regra: obrigatório, **uma única localidade** por anúncio.
|
||||||
- Validações: cidade/município válido, sem múltiplas localidades no mesmo campo.
|
- Validações: cidade/município válido, sem múltiplas localidades no mesmo campo.
|
||||||
|
|
||||||
- [ ] **País**
|
- [x] **País**
|
||||||
- Regra: obrigatório, selecionado de lista de países.
|
- Regra: obrigatório, selecionado de lista de países.
|
||||||
- Dependência: país deve dirigir moeda padrão e preço do anúncio (quando aplicável no billing).
|
- Dependência: país deve dirigir moeda padrão e preço do anúncio (quando aplicável no billing).
|
||||||
|
|
||||||
#### Modelo de contratação
|
#### Modelo de contratação
|
||||||
- [ ] **Tipo de contrato**
|
- [x] **Tipo de contrato**
|
||||||
- Opções-alvo: `Permanent`, `Contract`, `Training`, `Temporary`, `Voluntary`, `Any`.
|
- Opções-alvo: `Permanent`, `Contract`, `Training`, `Temporary`, `Voluntary`, `Any`.
|
||||||
|
|
||||||
- [ ] **Jornada de trabalho**
|
- [x] **Jornada de trabalho**
|
||||||
- Opções-alvo: `Full-time`, `Part-time`, `Any`.
|
- Opções-alvo: `Full-time`, `Part-time`, `Any`.
|
||||||
|
|
||||||
#### Salário
|
#### Salário
|
||||||
- [ ] **Modo de pagamento**
|
- [x] **Modo de pagamento**
|
||||||
- Opções: `Salary range` ou `Fixed salary`.
|
- Opções: `Salary range` ou `Fixed salary`.
|
||||||
- Validação: exibir campos condicionalmente conforme o modo.
|
- Validação: exibir campos condicionalmente conforme o modo.
|
||||||
|
|
||||||
- [ ] **Moeda**
|
- [x] **Moeda**
|
||||||
- Regra: obrigatório quando salário informado.
|
- Regra: obrigatório quando salário informado.
|
||||||
- Opções: lista internacional (USD, EUR, BRL etc.).
|
- Opções: lista internacional (USD, EUR, BRL etc.).
|
||||||
|
|
||||||
- [ ] **Período do salário**
|
- [x] **Período do salário**
|
||||||
- Opções: por `hora`, `dia`, `semana`, `mês` ou `ano`.
|
- Opções: por `hora`, `dia`, `semana`, `mês` ou `ano`.
|
||||||
|
|
||||||
#### Conteúdo da vaga
|
#### Conteúdo da vaga
|
||||||
- [ ] **Descrição da oferta**
|
- [x] **Descrição da oferta**
|
||||||
- Regra: obrigatório, editor rico.
|
- Regra: obrigatório, editor rico.
|
||||||
- Recomendação UX: suporte a parágrafos e listas para habilidades e qualificações.
|
- Recomendação UX: suporte a parágrafos e listas para habilidades e qualificações.
|
||||||
|
|
||||||
#### Sobre a empresa (opcional, com ocultação)
|
#### Sobre a empresa (opcional, com ocultação)
|
||||||
- [ ] **Nome da empresa**
|
- [x] **Nome da empresa**
|
||||||
- [ ] **Site da empresa**
|
- [x] **Site da empresa**
|
||||||
- [ ] **Número de empregados**
|
- [x] **Número de empregados**
|
||||||
- Opções-alvo: `Self-employed`, `1–10`, `11–50`, `51–200`, `201–500`, `501–1000`, `1001–5000`, `5001–10000`, `10000+`.
|
- Opções-alvo: `Self-employed`, `1–10`, `11–50`, `51–200`, `201–500`, `501–1000`, `1001–5000`, `5001–10000`, `10000+`.
|
||||||
- [ ] **Ano de fundação**
|
- [x] **Ano de fundação**
|
||||||
- [ ] **Descrição da empresa**
|
- [x] **Descrição da empresa**
|
||||||
- [ ] **Toggle “Ocultar dados da empresa”**
|
- [ ] **Toggle “Ocultar dados da empresa”**
|
||||||
- Regra: quando ativo, ocultar bloco de empresa na visualização pública da vaga.
|
- Regra: quando ativo, ocultar bloco de empresa na visualização pública da vaga.
|
||||||
|
|
||||||
#### Recebimento de candidaturas
|
#### Recebimento de candidaturas
|
||||||
- [ ] **Canal de candidatura**
|
- [x] **Canal de candidatura**
|
||||||
- Opções: `E-mail`, `Link externo`, `Telefone`.
|
- Opções: `E-mail`, `Link externo`, `Telefone`.
|
||||||
- Validação condicional:
|
- Validação condicional:
|
||||||
- E-mail: validar formato RFC básico.
|
- E-mail: validar formato RFC básico.
|
||||||
- Link externo: exigir URL HTTPS válida.
|
- Link externo: exigir URL HTTPS válida.
|
||||||
- Telefone: validar DDI + número.
|
- Telefone: validar DDI + número.
|
||||||
|
|
||||||
- [ ] **Requerer envio de currículo**
|
- [x] **Requerer envio de currículo**
|
||||||
- Opções: `Obrigatório`, `Opcional`, `Não solicitado`.
|
- Opções: `Obrigatório`, `Opcional`, `Não solicitado`.
|
||||||
|
|
||||||
- [ ] **Idioma da descrição da vaga**
|
- [x] **Idioma da descrição da vaga**
|
||||||
- Regra: obrigatório.
|
- Regra: obrigatório.
|
||||||
- Recomendação: idioma deve ser compatível com o país selecionado.
|
- Recomendação: idioma deve ser compatível com o país selecionado.
|
||||||
|
|
||||||
#### Extensões locais (GoHorseJobs)
|
#### Extensões locais (GoHorseJobs)
|
||||||
- [ ] **CNPJ da empresa (Brasil)**
|
- [x] **CNPJ da empresa (Brasil)**
|
||||||
- Regra: opcional/obrigatório por política comercial.
|
- Regra: opcional/obrigatório por política comercial.
|
||||||
- Validação: máscara e dígitos verificadores.
|
- Validação: máscara e dígitos verificadores.
|
||||||
- [ ] **Benefícios** (multiselect)
|
- [x] **Benefícios** (multiselect)
|
||||||
- [ ] **Área de atuação** (taxonomia do portal)
|
- [x] **Área de atuação** (taxonomia do portal)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ export default function PostJobPage() {
|
||||||
const [company, setCompany] = useState({
|
const [company, setCompany] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
|
document: "",
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
ddi: "+55",
|
ddi: "+55",
|
||||||
|
|
@ -96,6 +97,7 @@ export default function PostJobPage() {
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
location: "",
|
location: "",
|
||||||
|
country: "",
|
||||||
salaryMin: "",
|
salaryMin: "",
|
||||||
salaryMax: "",
|
salaryMax: "",
|
||||||
salaryFixed: "", // For fixed salary mode
|
salaryFixed: "", // For fixed salary mode
|
||||||
|
|
@ -105,6 +107,14 @@ export default function PostJobPage() {
|
||||||
workMode: "remote",
|
workMode: "remote",
|
||||||
workingHours: "",
|
workingHours: "",
|
||||||
salaryNegotiable: false, // Candidate proposes salary
|
salaryNegotiable: false, // Candidate proposes salary
|
||||||
|
descriptionLanguage: "",
|
||||||
|
applicationChannel: "email",
|
||||||
|
applicationEmail: "",
|
||||||
|
applicationUrl: "",
|
||||||
|
applicationPhone: "",
|
||||||
|
resumeRequirement: "optional",
|
||||||
|
jobCategory: "",
|
||||||
|
benefits: [] as string[],
|
||||||
});
|
});
|
||||||
|
|
||||||
const [questions, setQuestions] = useState<Question[]>([]);
|
const [questions, setQuestions] = useState<Question[]>([]);
|
||||||
|
|
@ -112,6 +122,36 @@ export default function PostJobPage() {
|
||||||
// Salary mode toggle: 'fixed' | 'range'
|
// Salary mode toggle: 'fixed' | 'range'
|
||||||
const [salaryMode, setSalaryMode] = useState<'fixed' | 'range'>('fixed');
|
const [salaryMode, setSalaryMode] = useState<'fixed' | 'range'>('fixed');
|
||||||
|
|
||||||
|
const BENEFIT_OPTIONS = ["Plano de saúde", "Vale refeição", "Vale transporte", "Bônus", "Home office", "Gym pass"];
|
||||||
|
|
||||||
|
const JOB_CATEGORIES = ["Tecnologia", "Produto", "Dados", "Marketing", "Vendas", "Operações", "Financeiro", "RH"];
|
||||||
|
|
||||||
|
const JOB_COUNTRIES = ["BR", "PT", "US", "ES", "UK", "DE", "FR", "JP"];
|
||||||
|
|
||||||
|
const cleanCNPJ = (value: string) => value.replace(/\D/g, "");
|
||||||
|
|
||||||
|
const isValidCNPJ = (value: string) => {
|
||||||
|
const cnpj = cleanCNPJ(value);
|
||||||
|
if (cnpj.length !== 14) return false;
|
||||||
|
if (/^(\d)\1+$/.test(cnpj)) return false;
|
||||||
|
|
||||||
|
const calcCheckDigit = (base: string, weights: number[]) => {
|
||||||
|
const sum = base
|
||||||
|
.split("")
|
||||||
|
.reduce((acc, current, index) => acc + Number(current) * weights[index], 0);
|
||||||
|
const mod = sum % 11;
|
||||||
|
return mod < 2 ? 0 : 11 - mod;
|
||||||
|
};
|
||||||
|
|
||||||
|
const base12 = cnpj.slice(0, 12);
|
||||||
|
const digit1 = calcCheckDigit(base12, [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]);
|
||||||
|
const digit2 = calcCheckDigit(`${base12}${digit1}`, [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]);
|
||||||
|
return cnpj === `${base12}${digit1}${digit2}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isHttpsUrl = (value: string) => /^https:\/\/.+/i.test(value);
|
||||||
|
const isPhoneWithDDI = (value: string) => /^\+\d{1,3}\s?\d{8,14}$/.test(value.trim());
|
||||||
|
|
||||||
const formatPhoneForDisplay = (value: string) => {
|
const formatPhoneForDisplay = (value: string) => {
|
||||||
// Simple formatting to just allow numbers and basic separators if needed
|
// Simple formatting to just allow numbers and basic separators if needed
|
||||||
// For now, just pass through but maybe restrict chars?
|
// For now, just pass through but maybe restrict chars?
|
||||||
|
|
@ -125,6 +165,12 @@ export default function PostJobPage() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (company.document && !isValidCNPJ(company.document)) {
|
||||||
|
toast.error("CNPJ inválido.");
|
||||||
|
setStep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (company.password !== company.confirmPassword) {
|
if (company.password !== company.confirmPassword) {
|
||||||
toast.error(t.errors.password_mismatch);
|
toast.error(t.errors.password_mismatch);
|
||||||
setStep(1); // Ensure we are on step 1 for password mismatch
|
setStep(1); // Ensure we are on step 1 for password mismatch
|
||||||
|
|
@ -137,11 +183,36 @@ export default function PostJobPage() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!job.title || !job.description) {
|
if (!job.title || !job.description || !job.location || !job.country || !job.descriptionLanguage) {
|
||||||
toast.error(t.errors.job_required);
|
toast.error(t.errors.job_required);
|
||||||
setStep(1); // Stay on step 1 for job data errors
|
setStep(1); // Stay on step 1 for job data errors
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (job.title.length > 65) {
|
||||||
|
toast.error("O título da vaga deve ter no máximo 65 caracteres.");
|
||||||
|
setStep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.applicationChannel === "email" && !job.applicationEmail) {
|
||||||
|
toast.error("Informe um e-mail para candidatura.");
|
||||||
|
setStep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.applicationChannel === "url" && !isHttpsUrl(job.applicationUrl)) {
|
||||||
|
toast.error("Informe uma URL HTTPS válida para candidatura.");
|
||||||
|
setStep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.applicationChannel === "phone" && !isPhoneWithDDI(job.applicationPhone)) {
|
||||||
|
toast.error("Informe um telefone com DDI válido (ex: +55 11999998888).");
|
||||||
|
setStep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -160,6 +231,14 @@ export default function PostJobPage() {
|
||||||
toast.error(t.errors.password_length);
|
toast.error(t.errors.password_length);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!job.title || !job.description || !job.location || !job.country || !job.descriptionLanguage) {
|
||||||
|
toast.error("Preencha título, localidade, país, idioma e descrição da vaga.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (job.title.length > 65) {
|
||||||
|
toast.error("O título da vaga deve ter no máximo 65 caracteres.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setStep(2);
|
setStep(2);
|
||||||
} else if (step === 2) {
|
} else if (step === 2) {
|
||||||
setStep(3);
|
setStep(3);
|
||||||
|
|
@ -184,6 +263,7 @@ export default function PostJobPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
companyName: company.name,
|
companyName: company.name,
|
||||||
email: company.email,
|
email: company.email,
|
||||||
|
document: cleanCNPJ(company.document) || null,
|
||||||
password: company.password,
|
password: company.password,
|
||||||
phone: finalPhone,
|
phone: finalPhone,
|
||||||
website: company.website || null,
|
website: company.website || null,
|
||||||
|
|
@ -210,7 +290,7 @@ export default function PostJobPage() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title: job.title,
|
title: job.title,
|
||||||
description: job.description,
|
description: job.description,
|
||||||
location: job.location,
|
location: `${job.location}, ${job.country}`,
|
||||||
// Salary logic: if negotiable, send null values
|
// Salary logic: if negotiable, send null values
|
||||||
salaryMin: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMin ? parseInt(job.salaryMin) : null)),
|
salaryMin: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMin ? parseInt(job.salaryMin) : null)),
|
||||||
salaryMax: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMax ? parseInt(job.salaryMax) : null)),
|
salaryMax: job.salaryNegotiable ? null : (salaryMode === 'fixed' ? (job.salaryFixed ? parseInt(job.salaryFixed) : null) : (job.salaryMax ? parseInt(job.salaryMax) : null)),
|
||||||
|
|
@ -222,6 +302,15 @@ export default function PostJobPage() {
|
||||||
workMode: job.workMode,
|
workMode: job.workMode,
|
||||||
status: "pending",
|
status: "pending",
|
||||||
questions: questions.length > 0 ? questions : null,
|
questions: questions.length > 0 ? questions : null,
|
||||||
|
languageLevel: job.descriptionLanguage || null,
|
||||||
|
requirements: {
|
||||||
|
category: job.jobCategory || null,
|
||||||
|
resumeRequirement: job.resumeRequirement,
|
||||||
|
applicationChannel: job.applicationChannel,
|
||||||
|
},
|
||||||
|
benefits: {
|
||||||
|
selected: job.benefits,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -337,6 +426,15 @@ export default function PostJobPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label>CNPJ da empresa (opcional)</Label>
|
||||||
|
<Input
|
||||||
|
value={company.document}
|
||||||
|
onChange={(e) => setCompany({ ...company, document: e.target.value })}
|
||||||
|
placeholder="00.000.000/0000-00"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Password Field */}
|
{/* Password Field */}
|
||||||
<div>
|
<div>
|
||||||
<Label>{t.company.password}</Label>
|
<Label>{t.company.password}</Label>
|
||||||
|
|
@ -492,8 +590,10 @@ export default function PostJobPage() {
|
||||||
onChange={(e) => setJob({ ...job, title: e.target.value })}
|
onChange={(e) => setJob({ ...job, title: e.target.value })}
|
||||||
placeholder={t.job.jobTitlePlaceholder}
|
placeholder={t.job.jobTitlePlaceholder}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
|
maxLength={65}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">{job.title.length}/65 caracteres</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>{t.job.description}</Label>
|
<Label>{t.job.description}</Label>
|
||||||
|
|
@ -513,6 +613,34 @@ export default function PostJobPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>País da vaga *</Label>
|
||||||
|
<select
|
||||||
|
value={job.country}
|
||||||
|
onChange={(e) => setJob({ ...job, country: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border rounded-lg bg-background"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
{JOB_COUNTRIES.map((country) => (
|
||||||
|
<option key={country} value={country}>{country}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Idioma da descrição *</Label>
|
||||||
|
<select
|
||||||
|
value={job.descriptionLanguage}
|
||||||
|
onChange={(e) => setJob({ ...job, descriptionLanguage: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border rounded-lg bg-background"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
<option value="pt">Português</option>
|
||||||
|
<option value="en">English</option>
|
||||||
|
<option value="es">Español</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/* Salary Section */}
|
{/* Salary Section */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|
@ -646,6 +774,106 @@ export default function PostJobPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Canal de candidatura</Label>
|
||||||
|
<select
|
||||||
|
value={job.applicationChannel}
|
||||||
|
onChange={(e) => setJob({ ...job, applicationChannel: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border rounded-lg bg-background"
|
||||||
|
>
|
||||||
|
<option value="email">E-mail</option>
|
||||||
|
<option value="url">Link externo</option>
|
||||||
|
<option value="phone">Telefone</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Currículo</Label>
|
||||||
|
<select
|
||||||
|
value={job.resumeRequirement}
|
||||||
|
onChange={(e) => setJob({ ...job, resumeRequirement: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border rounded-lg bg-background"
|
||||||
|
>
|
||||||
|
<option value="required">Obrigatório</option>
|
||||||
|
<option value="optional">Opcional</option>
|
||||||
|
<option value="none">Não solicitado</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{job.applicationChannel === "email" && (
|
||||||
|
<div>
|
||||||
|
<Label>E-mail para candidatura</Label>
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={job.applicationEmail}
|
||||||
|
onChange={(e) => setJob({ ...job, applicationEmail: e.target.value })}
|
||||||
|
placeholder="jobs@empresa.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{job.applicationChannel === "url" && (
|
||||||
|
<div>
|
||||||
|
<Label>URL externa (HTTPS)</Label>
|
||||||
|
<Input
|
||||||
|
value={job.applicationUrl}
|
||||||
|
onChange={(e) => setJob({ ...job, applicationUrl: e.target.value })}
|
||||||
|
placeholder="https://empresa.com/carreiras"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{job.applicationChannel === "phone" && (
|
||||||
|
<div>
|
||||||
|
<Label>Telefone com DDI</Label>
|
||||||
|
<Input
|
||||||
|
value={job.applicationPhone}
|
||||||
|
onChange={(e) => setJob({ ...job, applicationPhone: e.target.value })}
|
||||||
|
placeholder="+55 11999998888"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label>Área de atuação</Label>
|
||||||
|
<select
|
||||||
|
value={job.jobCategory}
|
||||||
|
onChange={(e) => setJob({ ...job, jobCategory: e.target.value })}
|
||||||
|
className="w-full px-3 py-2 border rounded-lg bg-background"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
{JOB_CATEGORIES.map((category) => (
|
||||||
|
<option key={category} value={category}>{category}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Benefícios</Label>
|
||||||
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||||
|
{BENEFIT_OPTIONS.map((benefit) => {
|
||||||
|
const checked = job.benefits.includes(benefit);
|
||||||
|
return (
|
||||||
|
<label key={benefit} className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextBenefits = e.target.checked
|
||||||
|
? [...job.benefits, benefit]
|
||||||
|
: job.benefits.filter((item) => item !== benefit);
|
||||||
|
setJob({ ...job, benefits: nextBenefits });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{benefit}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button onClick={handleNext} className="w-full">
|
<Button onClick={handleNext} className="w-full">
|
||||||
{t.buttons.next}
|
{t.buttons.next}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -692,6 +920,8 @@ export default function PostJobPage() {
|
||||||
</h3>
|
</h3>
|
||||||
<p><strong>{t.common.title}:</strong> {job.title}</p>
|
<p><strong>{t.common.title}:</strong> {job.title}</p>
|
||||||
<p><strong>{t.common.location}:</strong> {job.location || "Não informado"}</p>
|
<p><strong>{t.common.location}:</strong> {job.location || "Não informado"}</p>
|
||||||
|
<p><strong>País:</strong> {job.country || "Não informado"}</p>
|
||||||
|
<p><strong>Idioma:</strong> {job.descriptionLanguage || "Não informado"}</p>
|
||||||
<p><strong>{t.common.salary}:</strong> {
|
<p><strong>{t.common.salary}:</strong> {
|
||||||
job.salaryNegotiable
|
job.salaryNegotiable
|
||||||
? t.job.salaryNegotiable
|
? t.job.salaryNegotiable
|
||||||
|
|
@ -700,6 +930,10 @@ export default function PostJobPage() {
|
||||||
: (job.salaryMin && job.salaryMax ? `${getCurrencySymbol(job.currency)} ${job.salaryMin} - ${job.salaryMax} ${getSalaryPeriodLabel(job.salaryType)}` : t.job.salaryNegotiable)
|
: (job.salaryMin && job.salaryMax ? `${getCurrencySymbol(job.currency)} ${job.salaryMin} - ${job.salaryMax} ${getSalaryPeriodLabel(job.salaryType)}` : t.job.salaryNegotiable)
|
||||||
}</p>
|
}</p>
|
||||||
<p><strong>Perguntas Personalizadas:</strong> {questions.length}</p>
|
<p><strong>Perguntas Personalizadas:</strong> {questions.length}</p>
|
||||||
|
<p><strong>Canal de candidatura:</strong> {job.applicationChannel}</p>
|
||||||
|
<p><strong>Currículo:</strong> {job.resumeRequirement}</p>
|
||||||
|
<p><strong>Área:</strong> {job.jobCategory || "Não informado"}</p>
|
||||||
|
<p><strong>Benefícios:</strong> {job.benefits.length > 0 ? job.benefits.join(", ") : "Não informado"}</p>
|
||||||
<p><strong>{t.common.type}:</strong> {
|
<p><strong>{t.common.type}:</strong> {
|
||||||
(job.employmentType ? (t.options.contract[job.employmentType as keyof typeof t.options.contract] || job.employmentType) : t.options.any)
|
(job.employmentType ? (t.options.contract[job.employmentType as keyof typeof t.options.contract] || job.employmentType) : t.options.any)
|
||||||
} / {
|
} / {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue