chore: remove scripts moved to infracloud
This commit is contained in:
parent
2d10101394
commit
a40c0d8016
8 changed files with 500 additions and 525 deletions
|
|
@ -19,6 +19,7 @@ import {
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { adminCompaniesApi, jobsApi, type AdminCompany, type CreateJobPayload } from "@/lib/api"
|
import { adminCompaniesApi, jobsApi, type AdminCompany, type CreateJobPayload } from "@/lib/api"
|
||||||
|
import { useTranslation } from "@/lib/i18n"
|
||||||
|
|
||||||
type ApplicationChannel = "email" | "url" | "phone"
|
type ApplicationChannel = "email" | "url" | "phone"
|
||||||
|
|
||||||
|
|
@ -41,6 +42,7 @@ const DESCRIPTION_MIN_LENGTH = 20
|
||||||
|
|
||||||
export default function DashboardNewJobPage() {
|
export default function DashboardNewJobPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { t } = useTranslation()
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [loadingCompanies, setLoadingCompanies] = useState(true)
|
const [loadingCompanies, setLoadingCompanies] = useState(true)
|
||||||
const [companies, setCompanies] = useState<AdminCompany[]>([])
|
const [companies, setCompanies] = useState<AdminCompany[]>([])
|
||||||
|
|
@ -69,7 +71,6 @@ export default function DashboardNewJobPage() {
|
||||||
status: "draft",
|
status: "draft",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Location autocomplete state
|
|
||||||
const [apiCountries, setApiCountries] = useState<ApiCountry[]>([])
|
const [apiCountries, setApiCountries] = useState<ApiCountry[]>([])
|
||||||
const [locationIds, setLocationIds] = useState<{ cityId: number | null; regionId: number | null }>({ cityId: null, regionId: null })
|
const [locationIds, setLocationIds] = useState<{ cityId: number | null; regionId: number | null }>({ cityId: null, regionId: null })
|
||||||
const [locationResults, setLocationResults] = useState<LocationResult[]>([])
|
const [locationResults, setLocationResults] = useState<LocationResult[]>([])
|
||||||
|
|
@ -77,15 +78,13 @@ export default function DashboardNewJobPage() {
|
||||||
const [showLocationDropdown, setShowLocationDropdown] = useState(false)
|
const [showLocationDropdown, setShowLocationDropdown] = useState(false)
|
||||||
const locationRef = useRef<HTMLDivElement>(null)
|
const locationRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
// Load companies
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
adminCompaniesApi.list(undefined, 1, 100)
|
adminCompaniesApi.list(undefined, 1, 100)
|
||||||
.then((data) => setCompanies(data.data ?? []))
|
.then((data) => setCompanies(data.data ?? []))
|
||||||
.catch(() => toast.error("Falha ao carregar empresas"))
|
.catch(() => toast.error(t("admin.jobs.create.messages.loadCompaniesError")))
|
||||||
.finally(() => setLoadingCompanies(false))
|
.finally(() => setLoadingCompanies(false))
|
||||||
}, [])
|
}, [t])
|
||||||
|
|
||||||
// Load countries for location autocomplete
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const apiBase = process.env.NEXT_PUBLIC_API_URL || ""
|
const apiBase = process.env.NEXT_PUBLIC_API_URL || ""
|
||||||
fetch(`${apiBase}/api/v1/locations/countries`)
|
fetch(`${apiBase}/api/v1/locations/countries`)
|
||||||
|
|
@ -146,9 +145,9 @@ export default function DashboardNewJobPage() {
|
||||||
if (!canSubmit) {
|
if (!canSubmit) {
|
||||||
const descriptionLength = formData.description.trim().length
|
const descriptionLength = formData.description.trim().length
|
||||||
if (descriptionLength < DESCRIPTION_MIN_LENGTH) {
|
if (descriptionLength < DESCRIPTION_MIN_LENGTH) {
|
||||||
toast.error(`Descricao da vaga deve ter no minimo ${DESCRIPTION_MIN_LENGTH} caracteres`)
|
toast.error(t("admin.jobs.create.validation.descriptionMin", { min: DESCRIPTION_MIN_LENGTH }))
|
||||||
} else {
|
} else {
|
||||||
toast.error("Preencha os campos obrigatorios")
|
toast.error(t("admin.jobs.create.validation.requiredFields"))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -183,12 +182,12 @@ export default function DashboardNewJobPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
await jobsApi.create(payload)
|
await jobsApi.create(payload)
|
||||||
toast.success("Vaga cadastrada com sucesso!")
|
toast.success(t("admin.jobs.create.messages.createSuccess"))
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push("/dashboard/jobs")
|
router.push("/dashboard/jobs")
|
||||||
}, 700)
|
}, 700)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.message || "Falha ao cadastrar vaga")
|
toast.error(error.message || t("admin.jobs.create.messages.createError"))
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
|
@ -197,36 +196,37 @@ export default function DashboardNewJobPage() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-foreground">Nova vaga</h1>
|
<h1 className="text-3xl font-bold text-foreground">{t("admin.jobs.create.title")}</h1>
|
||||||
<p className="text-muted-foreground mt-1">Preencha os dados da vaga. Os campos marcados com * são obrigatórios.</p>
|
<p className="mt-1 text-muted-foreground">
|
||||||
|
{t("admin.jobs.create.subtitle")}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-8">
|
<form onSubmit={handleSubmit} className="space-y-8">
|
||||||
{/* Empresa e Status */}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<PlusCircle className="h-5 w-5" /> Empresa e status
|
<PlusCircle className="h-5 w-5" /> {t("admin.jobs.create.sections.companyStatus")}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid md:grid-cols-4 gap-6">
|
<CardContent className="grid gap-6 md:grid-cols-4">
|
||||||
<div className="md:col-span-3 space-y-1.5">
|
<div className="space-y-1.5 md:col-span-3">
|
||||||
<Label>Empresa *</Label>
|
<Label>{t("admin.jobs.create.fields.company")} *</Label>
|
||||||
{loadingCompanies ? (
|
{loadingCompanies ? (
|
||||||
<div className="h-10 px-3 flex items-center text-sm text-muted-foreground border rounded-md">
|
<div className="flex h-10 items-center rounded-md border px-3 text-sm text-muted-foreground">
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> Carregando...
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> {t("admin.jobs.create.common.loading")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Select value={formData.companyId} onValueChange={(v) => set("companyId", v)}>
|
<Select value={formData.companyId} onValueChange={(v) => set("companyId", v)}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Selecione uma empresa" />
|
<SelectValue placeholder={t("admin.jobs.create.placeholders.company")} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{companies.length === 0 ? (
|
{companies.length === 0 ? (
|
||||||
<SelectItem value="__none" disabled>Nenhuma empresa disponível</SelectItem>
|
<SelectItem value="__none" disabled>{t("admin.jobs.create.empty.companies")}</SelectItem>
|
||||||
) : (
|
) : (
|
||||||
companies.map((c) => (
|
companies.map((company) => (
|
||||||
<SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
|
<SelectItem key={company.id} value={company.id}>{company.name}</SelectItem>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
@ -235,104 +235,111 @@ export default function DashboardNewJobPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Status</Label>
|
<Label>{t("admin.jobs.create.fields.status")}</Label>
|
||||||
|
|
||||||
<Select value={formData.status} onValueChange={(v) => set("status", v)}>
|
<Select value={formData.status} onValueChange={(v) => set("status", v)}>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("admin.jobs.create.placeholders.select")} />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="draft">Rascunho</SelectItem>
|
<SelectItem value="draft">{t("admin.jobs.create.options.status.draft")}</SelectItem>
|
||||||
<SelectItem value="review">Em revisão</SelectItem>
|
<SelectItem value="review">{t("admin.jobs.create.options.status.review")}</SelectItem>
|
||||||
<SelectItem value="open">Aberta</SelectItem>
|
<SelectItem value="open">{t("admin.jobs.create.options.status.open")}</SelectItem>
|
||||||
<SelectItem value="paused">Pausada</SelectItem>
|
<SelectItem value="paused">{t("admin.jobs.create.options.status.paused")}</SelectItem>
|
||||||
<SelectItem value="closed">Encerrada</SelectItem>
|
<SelectItem value="closed">{t("admin.jobs.create.options.status.closed")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Dados da vaga */}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Dados da vaga</CardTitle>
|
<CardTitle>{t("admin.jobs.create.sections.jobData")}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="title">Título da vaga *</Label>
|
<Label htmlFor="title">{t("admin.jobs.create.fields.jobTitle")} *</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
maxLength={255}
|
maxLength={255}
|
||||||
placeholder="Ex: Desenvolvedor(a) Full Stack Sênior"
|
placeholder={t("admin.jobs.create.placeholders.jobTitle")}
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChange={(e) => set("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>
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
{t("admin.jobs.create.help.titleLength", { current: formData.title.length, max: 255 })}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="description">Descrição da vaga *</Label>
|
<Label htmlFor="description">{t("admin.jobs.create.fields.description")} *</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="description"
|
id="description"
|
||||||
rows={4}
|
rows={4}
|
||||||
className="resize-y"
|
className="resize-y"
|
||||||
placeholder="Descreva responsabilidades, requisitos e diferenciais..."
|
placeholder={t("admin.jobs.create.placeholders.description")}
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => set("description", e.target.value)}
|
onChange={(e) => set("description", e.target.value)}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
Minimo de {DESCRIPTION_MIN_LENGTH} caracteres ({formData.description.trim().length}/{DESCRIPTION_MIN_LENGTH})
|
{t("admin.jobs.create.help.descriptionLength", {
|
||||||
|
min: DESCRIPTION_MIN_LENGTH,
|
||||||
|
current: formData.description.trim().length,
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:w-1/3 space-y-1.5">
|
<div className="space-y-1.5 md:w-1/3">
|
||||||
<Label>Idioma da descrição</Label>
|
<Label>{t("admin.jobs.create.fields.descriptionLanguage")}</Label>
|
||||||
|
|
||||||
<Select value={formData.languageLevel} onValueChange={(v) => set("languageLevel", v)}>
|
<Select value={formData.languageLevel} onValueChange={(v) => set("languageLevel", v)}>
|
||||||
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("admin.jobs.create.placeholders.select")} />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="pt">Português</SelectItem>
|
<SelectItem value="pt">{t("admin.jobs.create.options.descriptionLanguage.pt")}</SelectItem>
|
||||||
<SelectItem value="en">English</SelectItem>
|
<SelectItem value="en">{t("admin.jobs.create.options.descriptionLanguage.en")}</SelectItem>
|
||||||
<SelectItem value="es">Español</SelectItem>
|
<SelectItem value="es">{t("admin.jobs.create.options.descriptionLanguage.es")}</SelectItem>
|
||||||
<SelectItem value="ja">日本語</SelectItem>
|
<SelectItem value="ja">{t("admin.jobs.create.options.descriptionLanguage.ja")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Localização */}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Localização</CardTitle>
|
<CardTitle>{t("admin.jobs.create.sections.location")}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid md:grid-cols-4 gap-6 items-end">
|
<CardContent className="grid items-end gap-6 md:grid-cols-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>País</Label>
|
<Label>{t("admin.jobs.create.fields.country")}</Label>
|
||||||
<Select value={formData.country} onValueChange={(v) => {
|
<Select value={formData.country} onValueChange={(v) => {
|
||||||
set("country", v)
|
set("country", v)
|
||||||
set("location", "")
|
set("location", "")
|
||||||
setLocationIds({ cityId: null, regionId: null })
|
setLocationIds({ cityId: null, regionId: null })
|
||||||
}}>
|
}}>
|
||||||
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("admin.jobs.create.placeholders.select")} />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{apiCountries.length > 0
|
{apiCountries.length > 0
|
||||||
? apiCountries.map((c) => (
|
? apiCountries.map((country) => (
|
||||||
<SelectItem key={c.id} value={c.iso2}>{c.name}</SelectItem>
|
<SelectItem key={country.id} value={country.iso2}>{country.name}</SelectItem>
|
||||||
))
|
))
|
||||||
: ["US", "BR", "PT", "ES", "GB", "DE", "FR", "JP"].map((iso) => (
|
: ["US", "BR", "PT", "ES", "GB", "DE", "FR", "JP"].map((iso) => (
|
||||||
<SelectItem key={iso} value={iso}>{iso}</SelectItem>
|
<SelectItem key={iso} value={iso}>{iso}</SelectItem>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="md:col-span-2 space-y-1.5">
|
<div className="space-y-1.5 md:col-span-2">
|
||||||
<Label>Cidade / Estado</Label>
|
<Label>{t("admin.jobs.create.fields.cityState")}</Label>
|
||||||
|
|
||||||
<div ref={locationRef} className="relative">
|
<div ref={locationRef} className="relative">
|
||||||
<Input
|
<Input
|
||||||
placeholder={formData.country ? "Digite para buscar..." : "Selecione um país primeiro"}
|
placeholder={formData.country
|
||||||
|
? t("admin.jobs.create.placeholders.locationSearch")
|
||||||
|
: t("admin.jobs.create.placeholders.selectCountryFirst")}
|
||||||
value={formData.location}
|
value={formData.location}
|
||||||
disabled={!formData.country}
|
disabled={!formData.country}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
|
@ -350,7 +357,7 @@ export default function DashboardNewJobPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showLocationDropdown && locationResults.length > 0 && (
|
{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">
|
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-y-auto rounded-md border bg-white shadow-md">
|
||||||
{locationResults.map((result) => (
|
{locationResults.map((result) => (
|
||||||
<button
|
<button
|
||||||
key={`${result.type}-${result.id}`}
|
key={`${result.type}-${result.id}`}
|
||||||
|
|
@ -372,11 +379,11 @@ export default function DashboardNewJobPage() {
|
||||||
<span>
|
<span>
|
||||||
<span className="text-sm font-medium">{result.name}</span>
|
<span className="text-sm font-medium">{result.name}</span>
|
||||||
{result.region_name && (
|
{result.region_name && (
|
||||||
<span className="ml-1 text-xs text-muted-foreground">— {result.region_name}</span>
|
<span className="ml-1 text-xs text-muted-foreground"> - {result.region_name}</span>
|
||||||
)}
|
)}
|
||||||
</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"}`}>
|
<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}
|
{t(`admin.jobs.create.location.resultType.${result.type}`)}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
@ -386,108 +393,106 @@ export default function DashboardNewJobPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Modo de trabalho</Label>
|
<Label>{t("admin.jobs.create.fields.workMode")}</Label>
|
||||||
<Select value={formData.workMode} onValueChange={(v) => set("workMode", v)}>
|
<Select value={formData.workMode} onValueChange={(v) => set("workMode", v)}>
|
||||||
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("admin.jobs.create.placeholders.select")} />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="onsite">Presencial</SelectItem>
|
<SelectItem value="onsite">{t("admin.jobs.create.options.workMode.onsite")}</SelectItem>
|
||||||
<SelectItem value="hybrid">Híbrido</SelectItem>
|
<SelectItem value="hybrid">{t("admin.jobs.create.options.workMode.hybrid")}</SelectItem>
|
||||||
<SelectItem value="remote">Remoto</SelectItem>
|
<SelectItem value="remote">{t("admin.jobs.create.options.workMode.remote")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Empty div for grid alignment to push checkbox down */}
|
<div className="hidden md:block md:col-span-1" />
|
||||||
<div className="md:col-span-1 hidden md:block"></div>
|
|
||||||
|
|
||||||
<div className="md:col-span-3 flex items-center gap-2 pt-2 md:pt-0 pb-2">
|
<div className="flex items-center gap-2 pb-2 pt-2 md:col-span-3 md:pt-0">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="visaSupport"
|
id="visaSupport"
|
||||||
checked={formData.visaSupport}
|
checked={formData.visaSupport}
|
||||||
onCheckedChange={(v) => set("visaSupport", v === true)}
|
onCheckedChange={(v) => set("visaSupport", v === true)}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="visaSupport" className="font-normal cursor-pointer">Oferece suporte de visto</Label>
|
<Label htmlFor="visaSupport" className="cursor-pointer font-normal">
|
||||||
|
{t("admin.jobs.create.fields.visaSupport")}
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Contrato e Salário */}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Contrato e salário</CardTitle>
|
<CardTitle>{t("admin.jobs.create.sections.contractSalary")}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Tipo de contrato</Label>
|
<Label>{t("admin.jobs.create.fields.employmentType")}</Label>
|
||||||
|
|
||||||
<Select value={formData.employmentType} onValueChange={(v) => set("employmentType", v)}>
|
<Select value={formData.employmentType} onValueChange={(v) => set("employmentType", v)}>
|
||||||
<SelectTrigger><SelectValue placeholder="Selecione" /></SelectTrigger>
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t("admin.jobs.create.placeholders.select")} />
|
||||||
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="permanent">Permanente</SelectItem>
|
<SelectItem value="permanent">{t("admin.jobs.create.options.employmentType.permanent")}</SelectItem>
|
||||||
<SelectItem value="full-time">Tempo integral</SelectItem>
|
<SelectItem value="full-time">{t("admin.jobs.create.options.employmentType.fullTime")}</SelectItem>
|
||||||
<SelectItem value="part-time">Meio período</SelectItem>
|
<SelectItem value="part-time">{t("admin.jobs.create.options.employmentType.partTime")}</SelectItem>
|
||||||
<SelectItem value="contract">Contrato (PJ)</SelectItem>
|
<SelectItem value="contract">{t("admin.jobs.create.options.employmentType.contract")}</SelectItem>
|
||||||
<SelectItem value="dispatch">Terceirizado</SelectItem>
|
<SelectItem value="dispatch">{t("admin.jobs.create.options.employmentType.dispatch")}</SelectItem>
|
||||||
<SelectItem value="temporary">Temporário</SelectItem>
|
<SelectItem value="temporary">{t("admin.jobs.create.options.employmentType.temporary")}</SelectItem>
|
||||||
<SelectItem value="training">Estágio/Trainee</SelectItem>
|
<SelectItem value="training">{t("admin.jobs.create.options.employmentType.training")}</SelectItem>
|
||||||
<SelectItem value="voluntary">Voluntário</SelectItem>
|
<SelectItem value="voluntary">{t("admin.jobs.create.options.employmentType.voluntary")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="workingHours">Jornada de trabalho</Label>
|
<Label htmlFor="workingHours">{t("admin.jobs.create.fields.workingHours")}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="workingHours"
|
id="workingHours"
|
||||||
placeholder="Ex: 9h às 18h, 40h/semana"
|
placeholder={t("admin.jobs.create.placeholders.workingHours")}
|
||||||
value={formData.workingHours}
|
value={formData.workingHours}
|
||||||
onChange={(e) => set("workingHours", e.target.value)}
|
onChange={(e) => set("workingHours", e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-4 gap-6">
|
<div className="grid gap-6 md:grid-cols-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Moeda</Label>
|
<Label>{t("admin.jobs.create.fields.currency")}</Label>
|
||||||
|
|
||||||
<Select value={formData.currency} onValueChange={(v) => set("currency", v)}>
|
<Select value={formData.currency} onValueChange={(v) => set("currency", v)}>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="BRL">BRL — R$</SelectItem>
|
<SelectItem value="BRL">BRL - R$</SelectItem>
|
||||||
<SelectItem value="USD">USD — $</SelectItem>
|
<SelectItem value="USD">USD - $</SelectItem>
|
||||||
<SelectItem value="EUR">EUR — €</SelectItem>
|
<SelectItem value="EUR">EUR - EUR</SelectItem>
|
||||||
<SelectItem value="GBP">GBP — £</SelectItem>
|
<SelectItem value="GBP">GBP - GBP</SelectItem>
|
||||||
<SelectItem value="JPY">JPY — ¥</SelectItem>
|
<SelectItem value="JPY">JPY - JPY</SelectItem>
|
||||||
<SelectItem value="CNY">CNY — ¥</SelectItem>
|
<SelectItem value="CNY">CNY - CNY</SelectItem>
|
||||||
<SelectItem value="AED">AED — د.إ</SelectItem>
|
<SelectItem value="AED">AED - AED</SelectItem>
|
||||||
<SelectItem value="CAD">CAD — $</SelectItem>
|
<SelectItem value="CAD">CAD - $</SelectItem>
|
||||||
<SelectItem value="AUD">AUD — $</SelectItem>
|
<SelectItem value="AUD">AUD - $</SelectItem>
|
||||||
<SelectItem value="CHF">CHF — Fr</SelectItem>
|
<SelectItem value="CHF">CHF - CHF</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Período</Label>
|
<Label>{t("admin.jobs.create.fields.salaryPeriod")}</Label>
|
||||||
|
|
||||||
<Select value={formData.salaryType} onValueChange={(v) => set("salaryType", v)}>
|
<Select value={formData.salaryType} onValueChange={(v) => set("salaryType", v)}>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="hourly">Por hora</SelectItem>
|
<SelectItem value="hourly">{t("admin.jobs.create.options.salaryType.hourly")}</SelectItem>
|
||||||
<SelectItem value="daily">Por dia</SelectItem>
|
<SelectItem value="daily">{t("admin.jobs.create.options.salaryType.daily")}</SelectItem>
|
||||||
<SelectItem value="weekly">Por semana</SelectItem>
|
<SelectItem value="weekly">{t("admin.jobs.create.options.salaryType.weekly")}</SelectItem>
|
||||||
<SelectItem value="monthly">Por mês</SelectItem>
|
<SelectItem value="monthly">{t("admin.jobs.create.options.salaryType.monthly")}</SelectItem>
|
||||||
<SelectItem value="yearly">Por ano</SelectItem>
|
<SelectItem value="yearly">{t("admin.jobs.create.options.salaryType.yearly")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="salaryMin">Salário mínimo</Label>
|
<Label htmlFor="salaryMin">{t("admin.jobs.create.fields.salaryMin")}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="salaryMin"
|
id="salaryMin"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -499,8 +504,7 @@ export default function DashboardNewJobPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="salaryMax">Salário máximo</Label>
|
<Label htmlFor="salaryMax">{t("admin.jobs.create.fields.salaryMax")}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="salaryMax"
|
id="salaryMax"
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -518,50 +522,48 @@ export default function DashboardNewJobPage() {
|
||||||
checked={formData.salaryNegotiable}
|
checked={formData.salaryNegotiable}
|
||||||
onCheckedChange={(v) => set("salaryNegotiable", v === true)}
|
onCheckedChange={(v) => set("salaryNegotiable", v === true)}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="salaryNegotiable" className="font-normal cursor-pointer">Salário negociável</Label>
|
<Label htmlFor="salaryNegotiable" className="cursor-pointer font-normal">
|
||||||
|
{t("admin.jobs.create.fields.salaryNegotiable")}
|
||||||
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Candidaturas */}
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Candidaturas</CardTitle>
|
<CardTitle>{t("admin.jobs.create.sections.applications")}</CardTitle>
|
||||||
<CardDescription>Como os candidatos devem se candidatar à vaga.</CardDescription>
|
<CardDescription>{t("admin.jobs.create.application.description")}</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Canal de candidatura</Label>
|
<Label>{t("admin.jobs.create.fields.applicationChannel")}</Label>
|
||||||
|
|
||||||
<Select value={formData.applicationChannel} onValueChange={(v) => set("applicationChannel", v as ApplicationChannel)}>
|
<Select value={formData.applicationChannel} onValueChange={(v) => set("applicationChannel", v as ApplicationChannel)}>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="email">E-mail</SelectItem>
|
<SelectItem value="email">{t("admin.jobs.create.options.applicationChannel.email")}</SelectItem>
|
||||||
<SelectItem value="url">Link externo</SelectItem>
|
<SelectItem value="url">{t("admin.jobs.create.options.applicationChannel.url")}</SelectItem>
|
||||||
<SelectItem value="phone">Telefone</SelectItem>
|
<SelectItem value="phone">{t("admin.jobs.create.options.applicationChannel.phone")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label>Currículo</Label>
|
<Label>{t("admin.jobs.create.fields.resumeRequirement")}</Label>
|
||||||
|
|
||||||
<Select value={formData.resumeRequirement} onValueChange={(v) => set("resumeRequirement", v)}>
|
<Select value={formData.resumeRequirement} onValueChange={(v) => set("resumeRequirement", v)}>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="required">Obrigatório</SelectItem>
|
<SelectItem value="required">{t("admin.jobs.create.options.resumeRequirement.required")}</SelectItem>
|
||||||
<SelectItem value="optional">Opcional</SelectItem>
|
<SelectItem value="optional">{t("admin.jobs.create.options.resumeRequirement.optional")}</SelectItem>
|
||||||
<SelectItem value="none">Não solicitado</SelectItem>
|
<SelectItem value="none">{t("admin.jobs.create.options.resumeRequirement.none")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{formData.applicationChannel === "email" && (
|
{formData.applicationChannel === "email" && (
|
||||||
<div className="space-y-1.5 mt-2">
|
<div className="mt-2 space-y-1.5">
|
||||||
<Label htmlFor="appEmail">E-mail para candidatura</Label>
|
<Label htmlFor="appEmail">{t("admin.jobs.create.fields.applicationEmail")}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="appEmail"
|
id="appEmail"
|
||||||
type="email"
|
type="email"
|
||||||
|
|
@ -573,9 +575,8 @@ export default function DashboardNewJobPage() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{formData.applicationChannel === "url" && (
|
{formData.applicationChannel === "url" && (
|
||||||
<div className="space-y-1.5 mt-2">
|
<div className="mt-2 space-y-1.5">
|
||||||
<Label htmlFor="appUrl">Link externo (HTTPS)</Label>
|
<Label htmlFor="appUrl">{t("admin.jobs.create.fields.applicationUrl")}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="appUrl"
|
id="appUrl"
|
||||||
placeholder="https://empresa.com/carreiras"
|
placeholder="https://empresa.com/carreiras"
|
||||||
|
|
@ -586,9 +587,8 @@ export default function DashboardNewJobPage() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{formData.applicationChannel === "phone" && (
|
{formData.applicationChannel === "phone" && (
|
||||||
<div className="space-y-1.5 mt-2">
|
<div className="mt-2 space-y-1.5">
|
||||||
<Label htmlFor="appPhone">Telefone (com DDI)</Label>
|
<Label htmlFor="appPhone">{t("admin.jobs.create.fields.applicationPhone")}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
id="appPhone"
|
id="appPhone"
|
||||||
placeholder="+55 11 99999-8888"
|
placeholder="+55 11 99999-8888"
|
||||||
|
|
@ -601,8 +601,8 @@ export default function DashboardNewJobPage() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Button type="submit" disabled={loading || !canSubmit} size="lg">
|
<Button type="submit" disabled={loading || !canSubmit} size="lg">
|
||||||
{loading && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
Criar vaga
|
{t("admin.jobs.create.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1142,6 +1142,125 @@
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"save": "Save Changes"
|
"save": "Save Changes"
|
||||||
},
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "New job",
|
||||||
|
"subtitle": "Fill in the job details. Fields marked with * are required.",
|
||||||
|
"sections": {
|
||||||
|
"companyStatus": "Company and status",
|
||||||
|
"jobData": "Job details",
|
||||||
|
"location": "Location",
|
||||||
|
"contractSalary": "Contract and salary",
|
||||||
|
"applications": "Applications"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "Loading..."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"company": "Company",
|
||||||
|
"status": "Status",
|
||||||
|
"jobTitle": "Job title",
|
||||||
|
"description": "Job description",
|
||||||
|
"descriptionLanguage": "Description language",
|
||||||
|
"country": "Country",
|
||||||
|
"cityState": "City / State",
|
||||||
|
"workMode": "Work mode",
|
||||||
|
"visaSupport": "Offers visa support",
|
||||||
|
"employmentType": "Employment type",
|
||||||
|
"workingHours": "Working hours",
|
||||||
|
"currency": "Currency",
|
||||||
|
"salaryPeriod": "Salary period",
|
||||||
|
"salaryMin": "Minimum salary",
|
||||||
|
"salaryMax": "Maximum salary",
|
||||||
|
"salaryNegotiable": "Negotiable salary",
|
||||||
|
"applicationChannel": "Application channel",
|
||||||
|
"resumeRequirement": "Resume",
|
||||||
|
"applicationEmail": "Application email",
|
||||||
|
"applicationUrl": "External link (HTTPS)",
|
||||||
|
"applicationPhone": "Phone (with country code)"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"company": "Select a company",
|
||||||
|
"select": "Select",
|
||||||
|
"jobTitle": "Ex: Senior Full Stack Developer",
|
||||||
|
"description": "Describe responsibilities, requirements, and differentiators...",
|
||||||
|
"locationSearch": "Type to search...",
|
||||||
|
"selectCountryFirst": "Select a country first",
|
||||||
|
"workingHours": "Ex: 9am to 6pm, 40h/week"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"titleLength": "{current}/255 characters",
|
||||||
|
"descriptionLength": "Minimum {min} characters ({current}/{min})"
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"companies": "No companies available"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"descriptionMin": "Job description must be at least {min} characters",
|
||||||
|
"requiredFields": "Fill in the required fields"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"loadCompaniesError": "Failed to load companies",
|
||||||
|
"createSuccess": "Job created successfully!",
|
||||||
|
"createError": "Failed to create job"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"description": "How candidates should apply for this job."
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"resultType": {
|
||||||
|
"city": "city",
|
||||||
|
"state": "state"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"status": {
|
||||||
|
"draft": "Draft",
|
||||||
|
"review": "In review",
|
||||||
|
"open": "Open",
|
||||||
|
"paused": "Paused",
|
||||||
|
"closed": "Closed"
|
||||||
|
},
|
||||||
|
"descriptionLanguage": {
|
||||||
|
"pt": "Portuguese",
|
||||||
|
"en": "English",
|
||||||
|
"es": "Spanish",
|
||||||
|
"ja": "Japanese"
|
||||||
|
},
|
||||||
|
"workMode": {
|
||||||
|
"onsite": "On-site",
|
||||||
|
"hybrid": "Hybrid",
|
||||||
|
"remote": "Remote"
|
||||||
|
},
|
||||||
|
"employmentType": {
|
||||||
|
"permanent": "Permanent",
|
||||||
|
"fullTime": "Full-time",
|
||||||
|
"partTime": "Part-time",
|
||||||
|
"contract": "Contract",
|
||||||
|
"dispatch": "Outsourced",
|
||||||
|
"temporary": "Temporary",
|
||||||
|
"training": "Internship / Trainee",
|
||||||
|
"voluntary": "Volunteer"
|
||||||
|
},
|
||||||
|
"salaryType": {
|
||||||
|
"hourly": "Per hour",
|
||||||
|
"daily": "Per day",
|
||||||
|
"weekly": "Per week",
|
||||||
|
"monthly": "Per month",
|
||||||
|
"yearly": "Per year"
|
||||||
|
},
|
||||||
|
"applicationChannel": {
|
||||||
|
"email": "Email",
|
||||||
|
"url": "External link",
|
||||||
|
"phone": "Phone"
|
||||||
|
},
|
||||||
|
"resumeRequirement": {
|
||||||
|
"required": "Required",
|
||||||
|
"optional": "Optional",
|
||||||
|
"none": "Not requested"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": "Create job"
|
||||||
|
},
|
||||||
"deleteConfirm": "Are you sure you want to delete this job?",
|
"deleteConfirm": "Are you sure you want to delete this job?",
|
||||||
"deleteError": "Failed to delete job",
|
"deleteError": "Failed to delete job",
|
||||||
"updateError": "Failed to update job"
|
"updateError": "Failed to update job"
|
||||||
|
|
@ -1398,4 +1517,4 @@
|
||||||
},
|
},
|
||||||
"copyright": "GoHorse Jobs. All rights reserved."
|
"copyright": "GoHorse Jobs. All rights reserved."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1006,6 +1006,125 @@
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"save": "Guardar Cambios"
|
"save": "Guardar Cambios"
|
||||||
},
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "Nuevo empleo",
|
||||||
|
"subtitle": "Completa los datos del empleo. Los campos marcados con * son obligatorios.",
|
||||||
|
"sections": {
|
||||||
|
"companyStatus": "Empresa y estado",
|
||||||
|
"jobData": "Datos del empleo",
|
||||||
|
"location": "Ubicacion",
|
||||||
|
"contractSalary": "Contrato y salario",
|
||||||
|
"applications": "Postulaciones"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "Cargando..."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"company": "Empresa",
|
||||||
|
"status": "Estado",
|
||||||
|
"jobTitle": "Titulo del empleo",
|
||||||
|
"description": "Descripcion del empleo",
|
||||||
|
"descriptionLanguage": "Idioma de la descripcion",
|
||||||
|
"country": "Pais",
|
||||||
|
"cityState": "Ciudad / Estado",
|
||||||
|
"workMode": "Modalidad de trabajo",
|
||||||
|
"visaSupport": "Ofrece apoyo de visa",
|
||||||
|
"employmentType": "Tipo de contrato",
|
||||||
|
"workingHours": "Jornada laboral",
|
||||||
|
"currency": "Moneda",
|
||||||
|
"salaryPeriod": "Periodo salarial",
|
||||||
|
"salaryMin": "Salario minimo",
|
||||||
|
"salaryMax": "Salario maximo",
|
||||||
|
"salaryNegotiable": "Salario negociable",
|
||||||
|
"applicationChannel": "Canal de postulacion",
|
||||||
|
"resumeRequirement": "Curriculum",
|
||||||
|
"applicationEmail": "Correo para postulacion",
|
||||||
|
"applicationUrl": "Enlace externo (HTTPS)",
|
||||||
|
"applicationPhone": "Telefono (con codigo internacional)"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"company": "Seleccione una empresa",
|
||||||
|
"select": "Seleccione",
|
||||||
|
"jobTitle": "Ej: Desarrollador(a) Full Stack Senior",
|
||||||
|
"description": "Describe responsabilidades, requisitos y diferenciales...",
|
||||||
|
"locationSearch": "Escriba para buscar...",
|
||||||
|
"selectCountryFirst": "Seleccione un pais primero",
|
||||||
|
"workingHours": "Ej: 9h a 18h, 40h/semana"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"titleLength": "{current}/255 caracteres",
|
||||||
|
"descriptionLength": "Minimo de {min} caracteres ({current}/{min})"
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"companies": "No hay empresas disponibles"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"descriptionMin": "La descripcion del empleo debe tener al menos {min} caracteres",
|
||||||
|
"requiredFields": "Complete los campos obligatorios"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"loadCompaniesError": "Error al cargar empresas",
|
||||||
|
"createSuccess": "Empleo creado con exito!",
|
||||||
|
"createError": "Error al crear el empleo"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"description": "Como deben postularse los candidatos a este empleo."
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"resultType": {
|
||||||
|
"city": "ciudad",
|
||||||
|
"state": "estado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"status": {
|
||||||
|
"draft": "Borrador",
|
||||||
|
"review": "En revision",
|
||||||
|
"open": "Abierta",
|
||||||
|
"paused": "Pausada",
|
||||||
|
"closed": "Cerrada"
|
||||||
|
},
|
||||||
|
"descriptionLanguage": {
|
||||||
|
"pt": "Portugues",
|
||||||
|
"en": "English",
|
||||||
|
"es": "Espanol",
|
||||||
|
"ja": "Japanese"
|
||||||
|
},
|
||||||
|
"workMode": {
|
||||||
|
"onsite": "Presencial",
|
||||||
|
"hybrid": "Hibrido",
|
||||||
|
"remote": "Remoto"
|
||||||
|
},
|
||||||
|
"employmentType": {
|
||||||
|
"permanent": "Permanente",
|
||||||
|
"fullTime": "Tiempo completo",
|
||||||
|
"partTime": "Medio tiempo",
|
||||||
|
"contract": "Contrato",
|
||||||
|
"dispatch": "Tercerizado",
|
||||||
|
"temporary": "Temporal",
|
||||||
|
"training": "Pasantia / Trainee",
|
||||||
|
"voluntary": "Voluntario"
|
||||||
|
},
|
||||||
|
"salaryType": {
|
||||||
|
"hourly": "Por hora",
|
||||||
|
"daily": "Por dia",
|
||||||
|
"weekly": "Por semana",
|
||||||
|
"monthly": "Por mes",
|
||||||
|
"yearly": "Por ano"
|
||||||
|
},
|
||||||
|
"applicationChannel": {
|
||||||
|
"email": "Correo",
|
||||||
|
"url": "Enlace externo",
|
||||||
|
"phone": "Telefono"
|
||||||
|
},
|
||||||
|
"resumeRequirement": {
|
||||||
|
"required": "Obligatorio",
|
||||||
|
"optional": "Opcional",
|
||||||
|
"none": "No solicitado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": "Crear empleo"
|
||||||
|
},
|
||||||
"deleteConfirm": "¿Está seguro de que desea eliminar este empleo?",
|
"deleteConfirm": "¿Está seguro de que desea eliminar este empleo?",
|
||||||
"deleteError": "Error al eliminar el empleo",
|
"deleteError": "Error al eliminar el empleo",
|
||||||
"updateError": "Error al actualizar el empleo"
|
"updateError": "Error al actualizar el empleo"
|
||||||
|
|
@ -1399,4 +1518,4 @@
|
||||||
},
|
},
|
||||||
"copyright": "GoHorse Jobs. Todos los derechos reservados."
|
"copyright": "GoHorse Jobs. Todos los derechos reservados."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1183,6 +1183,125 @@
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"save": "Salvar Alterações"
|
"save": "Salvar Alterações"
|
||||||
},
|
},
|
||||||
|
"create": {
|
||||||
|
"title": "Nova vaga",
|
||||||
|
"subtitle": "Preencha os dados da vaga. Os campos marcados com * sao obrigatorios.",
|
||||||
|
"sections": {
|
||||||
|
"companyStatus": "Empresa e status",
|
||||||
|
"jobData": "Dados da vaga",
|
||||||
|
"location": "Localizacao",
|
||||||
|
"contractSalary": "Contrato e salario",
|
||||||
|
"applications": "Candidaturas"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"loading": "Carregando..."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"company": "Empresa",
|
||||||
|
"status": "Status",
|
||||||
|
"jobTitle": "Titulo da vaga",
|
||||||
|
"description": "Descricao da vaga",
|
||||||
|
"descriptionLanguage": "Idioma da descricao",
|
||||||
|
"country": "Pais",
|
||||||
|
"cityState": "Cidade / Estado",
|
||||||
|
"workMode": "Modo de trabalho",
|
||||||
|
"visaSupport": "Oferece suporte de visto",
|
||||||
|
"employmentType": "Tipo de contrato",
|
||||||
|
"workingHours": "Jornada de trabalho",
|
||||||
|
"currency": "Moeda",
|
||||||
|
"salaryPeriod": "Periodo",
|
||||||
|
"salaryMin": "Salario minimo",
|
||||||
|
"salaryMax": "Salario maximo",
|
||||||
|
"salaryNegotiable": "Salario negociavel",
|
||||||
|
"applicationChannel": "Canal de candidatura",
|
||||||
|
"resumeRequirement": "Curriculo",
|
||||||
|
"applicationEmail": "E-mail para candidatura",
|
||||||
|
"applicationUrl": "Link externo (HTTPS)",
|
||||||
|
"applicationPhone": "Telefone (com DDI)"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"company": "Selecione uma empresa",
|
||||||
|
"select": "Selecione",
|
||||||
|
"jobTitle": "Ex: Desenvolvedor(a) Full Stack Senior",
|
||||||
|
"description": "Descreva responsabilidades, requisitos e diferenciais...",
|
||||||
|
"locationSearch": "Digite para buscar...",
|
||||||
|
"selectCountryFirst": "Selecione um pais primeiro",
|
||||||
|
"workingHours": "Ex: 9h as 18h, 40h/semana"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"titleLength": "{current}/255 caracteres",
|
||||||
|
"descriptionLength": "Minimo de {min} caracteres ({current}/{min})"
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"companies": "Nenhuma empresa disponivel"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"descriptionMin": "Descricao da vaga deve ter no minimo {min} caracteres",
|
||||||
|
"requiredFields": "Preencha os campos obrigatorios"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"loadCompaniesError": "Falha ao carregar empresas",
|
||||||
|
"createSuccess": "Vaga cadastrada com sucesso!",
|
||||||
|
"createError": "Falha ao cadastrar vaga"
|
||||||
|
},
|
||||||
|
"application": {
|
||||||
|
"description": "Como os candidatos devem se candidatar a vaga."
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"resultType": {
|
||||||
|
"city": "cidade",
|
||||||
|
"state": "estado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"status": {
|
||||||
|
"draft": "Rascunho",
|
||||||
|
"review": "Em revisao",
|
||||||
|
"open": "Aberta",
|
||||||
|
"paused": "Pausada",
|
||||||
|
"closed": "Encerrada"
|
||||||
|
},
|
||||||
|
"descriptionLanguage": {
|
||||||
|
"pt": "Portugues",
|
||||||
|
"en": "English",
|
||||||
|
"es": "Espanol",
|
||||||
|
"ja": "Japanese"
|
||||||
|
},
|
||||||
|
"workMode": {
|
||||||
|
"onsite": "Presencial",
|
||||||
|
"hybrid": "Hibrido",
|
||||||
|
"remote": "Remoto"
|
||||||
|
},
|
||||||
|
"employmentType": {
|
||||||
|
"permanent": "Permanente",
|
||||||
|
"fullTime": "Tempo integral",
|
||||||
|
"partTime": "Meio periodo",
|
||||||
|
"contract": "Contrato (PJ)",
|
||||||
|
"dispatch": "Terceirizado",
|
||||||
|
"temporary": "Temporario",
|
||||||
|
"training": "Estagio/Trainee",
|
||||||
|
"voluntary": "Voluntario"
|
||||||
|
},
|
||||||
|
"salaryType": {
|
||||||
|
"hourly": "Por hora",
|
||||||
|
"daily": "Por dia",
|
||||||
|
"weekly": "Por semana",
|
||||||
|
"monthly": "Por mes",
|
||||||
|
"yearly": "Por ano"
|
||||||
|
},
|
||||||
|
"applicationChannel": {
|
||||||
|
"email": "E-mail",
|
||||||
|
"url": "Link externo",
|
||||||
|
"phone": "Telefone"
|
||||||
|
},
|
||||||
|
"resumeRequirement": {
|
||||||
|
"required": "Obrigatorio",
|
||||||
|
"optional": "Opcional",
|
||||||
|
"none": "Nao solicitado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"submit": "Criar vaga"
|
||||||
|
},
|
||||||
"deleteConfirm": "Tem certeza que deseja excluir esta vaga?",
|
"deleteConfirm": "Tem certeza que deseja excluir esta vaga?",
|
||||||
"deleteError": "Falha ao excluir vaga",
|
"deleteError": "Falha ao excluir vaga",
|
||||||
"updateError": "Falha ao atualizar vaga"
|
"updateError": "Falha ao atualizar vaga"
|
||||||
|
|
@ -1407,4 +1526,4 @@
|
||||||
},
|
},
|
||||||
"copyright": "GoHorse Jobs. Todos os direitos reservados."
|
"copyright": "GoHorse Jobs. Todos os direitos reservados."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCc9suDobwJwCGJ
|
|
||||||
VFvga1BxKkmmGOxoF8zNibv6l33/SCFEBb5fFaenxvYotEGWUw0fed4zIcX3s6hA
|
|
||||||
q2yLr3nIygpLpcOfzpzPxas49P17NA3Chvo3k/0eGkBD6PHM1s62qPP+fKEZtwlS
|
|
||||||
q1WaFxfc949iqJAQvW6w/7WgMZDineq3IzhVVUAFdw3icZru97hCjPDU/v3eFTS7
|
|
||||||
kvGrDYGAHZXzylu3Er9ifKYHdKxOrWFmGaSsPsYMdKNxWFk+Z38NVUnwSH3TEiV/
|
|
||||||
S4e33tTkdMmNpY+6e9Cigb09RnOalj5lPjFGA9nTHMJxpsHvSKu8vMBr+OZ4CM3U
|
|
||||||
RH7MUX01AgMBAAECggEAMKxdFo/4MePY4m984B4W/0iYNv/iizLaKOBtoLsKcLeK
|
|
||||||
zT+ktXKPHzlUyvF+pyFQ3/JYA24VKAcXhRpDWhuLfcadI7Ee9PbKbKmEu3BJDEPr
|
|
||||||
gmd9vu9Ond+RDx30oUr5Je5FXySBhmpaYz7LGDHSDgzcc0EHD5HWed+JkEfegE7w
|
|
||||||
Mvt9KK41mGdaQwiPHS43uzZhQJEqybP3i/6SUnV2CntOhutxLlPk2rpHnns0p/St
|
|
||||||
Dvlcv61vduIaej4IFBrpSwTE45pvIfkvNZx0pJapM1jZhe8F/2T7GtXDkoFQveo1
|
|
||||||
3YB1aadpCx7u28IzQTwBZVwqhCpi2a5+qVYUT0AU3wKBgQDYYUxQUBiUn6bXoAsx
|
|
||||||
JTozoX0K50cX2d8LVY1OUuhpRXbztS2XXtyfeoAQtEWoT3UO7vjEedWswfo2j+N3
|
|
||||||
ZIXig7Vyj/LN5lZyCwWYn4S4inESjKlzi4Pv8D4F+Fkgg0WsVgzbTa4P7faHnDNn
|
|
||||||
eEHdyJ/ZQ8+XYxBpSAE8ecWQlwKBgQC5tGbfzh77REsv1h6b87vulrGHc+OBITTU
|
|
||||||
YFu1YfXpvbXx9geRfNLDtUhUis6vgfcQV6sxZVf78UdlqiTBebRLpcvoBlHV/MPZ
|
|
||||||
T3TsZH1vXwiitOsBIFzKkn8xdjuN6mN5lLjI6KkYeVoULYiUNbiZ+Wi7PXBPnc5I
|
|
||||||
jBO5EayOEwKBgQDU2pnso24avhatJKX92WYwphpQoISCBPPxvV38/3fbHtdOFBte
|
|
||||||
PZYAV8wlIoEnecpoP1J+TG+Su1r9U3xq1XsTAYd7w/kQ7RZ6pzcBFWLE+oMSwUZs
|
|
||||||
AIFwhb8ttklOv3PJfPi2vuqMhwUuD81NarI4jwQYASnz/SKGvqtgp1VezwKBgDoL
|
|
||||||
DOx+/GgE3ItDHaYY9HCKYUq5Ci7eNij7RS7YQ4ifZzMNdygeH7JUAxuJlzh8IsDU
|
|
||||||
5gk2Z92zeGFqYLqoU5YhaC5Ja2K68mwFzcHlVt9skMJqUdm0R8x5JZBMKCkfTaA+
|
|
||||||
v9LsBY5Ev8b2xG2urNhTgEyl02jPJh6+yZtazthJAoGAHRIX/W0IlyaLno7WzAwM
|
|
||||||
lSsNfJpTvZmkri0UOGXM2YaKuQZ652t6EBDtfM7O16eV3KNBblt1LjItz/S8kiFi
|
|
||||||
Q8tGluO27Hn5/auixJjlcZnzoUXrEjAra8lmgAo41Dm0icDpLUzhixZ0qS8d6Yfp
|
|
||||||
RIT1IoWSuu2fvOOvqezq6bg=
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
12
remotes.txt
12
remotes.txt
|
|
@ -1,12 +0,0 @@
|
||||||
dokku dokku@localhost:gohorsejobs (fetch)
|
|
||||||
dokku dokku@localhost:gohorsejobs (push)
|
|
||||||
dokku-frontend dokku@localhost:gohorse-frontend (fetch)
|
|
||||||
dokku-frontend dokku@localhost:gohorse-frontend (push)
|
|
||||||
dokku-restore dokku@localhost:gohorse-frontend (fetch)
|
|
||||||
dokku-restore dokku@localhost:gohorse-frontend (push)
|
|
||||||
forgejo git@pipe.gohorsejobs.com:bohessefm/gohorsejobs.git (fetch)
|
|
||||||
forgejo git@pipe.gohorsejobs.com:bohessefm/gohorsejobs.git (push)
|
|
||||||
origin git@github.com:rede5/gohorsejobs.git (fetch)
|
|
||||||
origin git@github.com:rede5/gohorsejobs.git (push)
|
|
||||||
pipe https://pipe.gohorsejobs.com/bohessefm/gohorsejobs.git (fetch)
|
|
||||||
pipe https://pipe.gohorsejobs.com/bohessefm/gohorsejobs.git (push)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2M5c3VEb2J3SndDR0oKVkZ2Z2ExQnhLa21tR094b0Y4ek5pYnY2bDMzL1NDRkVCYjVmRmFlbnh2WW90RUdXVXcwZmVkNHpJY1gzczZoQQpxMnlMcjNuSXlncExwY09menB6UHhhczQ5UDE3TkEzQ2h2bzNrLzBlR2tCRDZQSE0xczYycVBQK2ZLRVp0d2xTCnExV2FGeGZjOTQ5aXFKQVF2VzZ3LzdXZ01aRGluZXEzSXpoVlZVQUZkdzNpY1pydTk3aENqUERVL3YzZUZUUzcKa3ZHckRZR0FIWlh6eWx1M0VyOWlmS1lIZEt4T3JXRm1HYVNzUHNZTWRLTnhXRmsrWjM4TlZVbndTSDNURWlWLwpTNGUzM3RUa2RNbU5wWSs2ZTlDaWdiMDlSbk9hbGo1bFBqRkdBOW5USE1KeHBzSHZTS3U4dk1CcitPWjRDTTNVClJIN01VWDAxQWdNQkFBRUNnZ0VBTUt4ZEZvLzRNZVBZNG05ODRCNFcvMGlZTnYvaWl6TGFLT0J0b0xzS2NMZUsKelQra3RYS1BIemxVeXZGK3B5RlEzL0pZQTI0VktBY1hoUnBEV2h1TGZjYWRJN0VlOVBiS2JLbUV1M0JKREVQcgpnbWQ5dnU5T25kK1JEeDMwb1VyNUplNUZYeVNCaG1wYVl6N0xHREhTRGd6Y2MwRUhENUhXZWQrSmtFZmVnRTd3Ck12dDlLSzQxbUdkYVF3aVBIUzQzdXpaaFFKRXF5YlAzaS82U1VuVjJDbnRPaHV0eExsUGsycnBIbm5zMHAvU3QKRHZsY3Y2MXZkdUlhZWo0SUZCcnBTd1RFNDVwdklma3ZOWngwcEphcE0xalpoZThGLzJUN0d0WERrb0ZRdmVvMQozWUIxYWFkcEN4N3UyOEl6UVR3QlpWd3FoQ3BpMmE1K3FWWVVUMEFVM3dLQmdRRFlZVXhRVUJpVW42YlhvQXN4CkpUb3pvWDBLNTBjWDJkOExWWTFPVXVocFJYYnp0UzJYWHR5ZmVvQVF0RVdvVDNVTzd2akVlZFdzd2ZvMmorTjMKWklYaWc3VnlqL0xONWxaeUN3V1luNFM0aW5FU2pLbHppNFB2OEQ0RitGa2dnMFdzVmd6YlRhNFA3ZmFIbkRObgplRUhkeUovWlE4K1hZeEJwU0FFOGVjV1Fsd0tCZ1FDNXRHYmZ6aDc3UkVzdjFoNmI4N3Z1bHJHSGMrT0JJVFRVCllGdTFZZlhwdmJYeDlnZVJmTkxEdFVoVWlzNnZnZmNRVjZzeFpWZjc4VWRscWlUQmViUkxwY3ZvQmxIVi9NUFoKVDNUc1pIMXZYd2lpdE9zQklGektrbjh4ZGp1TjZtTjVsTGpJNktrWWVWb1VMWWlVTmJpWitXaTdQWEJQbmM1SQpqQk81RWF5T0V3S0JnUURVMnBuc28yNGF2aGF0SktYOTJXWXdwaHBRb0lTQ0JQUHh2VjM4LzNmYkh0ZE9GQnRlClBaWUFWOHdsSW9FbmVjcG9QMUorVEcrU3UxcjlVM3hxMVhzVEFZZDd3L2tRN1JaNnB6Y0JGV0xFK29NU3dVWnMKQUlGd2hiOHR0a2xPdjNQSmZQaTJ2dXFNaHdVdUQ4MU5hckk0andRWUFTbnovU0tHdnF0Z3AxVmV6d0tCZ0RvTApET3grL0dnRTNJdERIYVlZOUhDS1lVcTVDaTdlTmlqN1JTN1lRNGlmWnpNTmR5Z2VIN0pVQXh1Smx6aDhJc0RVCjVnazJaOTJ6ZUdGcVlMcW9VNVloYUM1SmEySzY4bXdGemNIbFZ0OXNrTUpxVWRtMFI4eDVKWkJNS0NrZlRhQSsKdjlMc0JZNUV2OGIyeEcydXJOaFRnRXlsMDJqUEpoNit5WnRhenRoSkFvR0FIUklYL1cwSWx5YUxubzdXekF3TQpsU3NOZkpwVHZabWtyaTBVT0dYTTJZYUt1UVo2NTJ0NkVCRHRmTTdPMTZlVjNLTkJibHQxTGpJdHovUzhraUZpClE4dEdsdU8yN0huNS9hdWl4SmpsY1puem9VWHJFakFyYThsbWdBbzQxRG0waWNEcExVemhpeFowcVM4ZDZZZnAKUklUMUlvV1N1dTJmdk9PdnFlenE2Ymc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
|
||||||
341
start.sh
341
start.sh
|
|
@ -1,341 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
CYAN='\033[0;36m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Function to kill background processes on exit
|
|
||||||
cleanup() {
|
|
||||||
echo -e "\n${YELLOW}🛑 Stopping services...${NC}"
|
|
||||||
kill $(jobs -p) 2>/dev/null
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
trap cleanup SIGINT SIGTERM
|
|
||||||
|
|
||||||
# Header
|
|
||||||
clear
|
|
||||||
echo -e "${CYAN}"
|
|
||||||
echo "╔══════════════════════════════════════════════╗"
|
|
||||||
echo "║ 🐴 GoHorse Jobs - Dev Server ║"
|
|
||||||
echo "╚══════════════════════════════════════════════╝"
|
|
||||||
echo -e "${NC}"
|
|
||||||
|
|
||||||
# Interactive menu
|
|
||||||
echo -e "${GREEN}Select an option:${NC}\n"
|
|
||||||
echo -e " ${CYAN}── Services ──${NC}"
|
|
||||||
echo -e " ${YELLOW}1)${NC} 🚀 Start (Frontend + Backend)"
|
|
||||||
echo -e " ${YELLOW}2)${NC} 🌱 Start with Seed (Reset DB + Seed + Start)"
|
|
||||||
echo -e " ${YELLOW}3)${NC} 📦 Start All (Frontend + Backend + Backoffice)"
|
|
||||||
echo -e ""
|
|
||||||
echo -e " ${CYAN}── Database ──${NC}"
|
|
||||||
echo -e " ${YELLOW}4)${NC} 🗄️ Run Migrations"
|
|
||||||
echo -e " ${YELLOW}5)${NC} 🌿 Seed Only (append data)"
|
|
||||||
echo -e " ${YELLOW}6)${NC} 🔄 Seed Reset (drop all + seed fresh)"
|
|
||||||
echo -e ""
|
|
||||||
echo -e " ${CYAN}── Testing ──${NC}"
|
|
||||||
echo -e " ${YELLOW}7)${NC} 🧪 Run Tests (Backend E2E)"
|
|
||||||
echo -e " ${YELLOW}0)${NC} ❌ Exit"
|
|
||||||
echo -e ""
|
|
||||||
echo -e " ${CYAN}── Fast Options ──${NC}"
|
|
||||||
echo -e " ${YELLOW}8)${NC} ⚡ Seed Reset LITE (skip 153k cities)"
|
|
||||||
echo -e " ${YELLOW}9)${NC} 🔬 Run All Tests (Backend + Frontend)"
|
|
||||||
echo -e ""
|
|
||||||
echo -e " ${CYAN}── Docker/Deploy ──${NC}"
|
|
||||||
echo -e " ${YELLOW}a)${NC} 🐳 Build Docker Images"
|
|
||||||
echo -e " ${YELLOW}b)${NC} 🚀 Build & Push to Forgejo"
|
|
||||||
echo ""
|
|
||||||
read -p "Enter option [0-9,a,b]: " choice
|
|
||||||
|
|
||||||
case $choice in
|
|
||||||
1)
|
|
||||||
echo -e "\n${GREEN}🚀 Starting Development Environment...${NC}\n"
|
|
||||||
|
|
||||||
# Backend
|
|
||||||
echo -e "${BLUE}🔹 Checking Backend...${NC}"
|
|
||||||
cd backend && go mod tidy
|
|
||||||
if command -v swag &> /dev/null; then
|
|
||||||
echo -e "${BLUE}🔹 Generating Swagger Docs...${NC}"
|
|
||||||
swag init -g cmd/api/main.go --parseDependency --parseInternal 2>/dev/null
|
|
||||||
fi
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Frontend deps
|
|
||||||
if [ ! -d "frontend/node_modules" ]; then
|
|
||||||
echo -e "${BLUE}🔹 Installing Frontend Dependencies...${NC}"
|
|
||||||
cd frontend && npm install && cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start services
|
|
||||||
echo -e "${BLUE}🔹 Starting Backend on port 8521...${NC}"
|
|
||||||
(cd backend && go run cmd/api/main.go) &
|
|
||||||
BACKEND_PID=$!
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Starting Frontend on port 8963...${NC}"
|
|
||||||
(cd frontend && npm run dev -- -p 8963) &
|
|
||||||
FRONTEND_PID=$!
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ Services running:${NC}"
|
|
||||||
echo -e " ${CYAN}Backend:${NC} http://localhost:8521"
|
|
||||||
echo -e " ${CYAN}Frontend:${NC} http://localhost:8963"
|
|
||||||
echo -e " ${CYAN}Swagger:${NC} http://localhost:8521/swagger/index.html"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
wait $BACKEND_PID $FRONTEND_PID
|
|
||||||
;;
|
|
||||||
|
|
||||||
2)
|
|
||||||
echo -e "\n${GREEN}🌱 Starting with Database Reset & Seed...${NC}\n"
|
|
||||||
|
|
||||||
# Backend prep
|
|
||||||
echo -e "${BLUE}🔹 Checking Backend...${NC}"
|
|
||||||
cd backend && go mod tidy
|
|
||||||
if command -v swag &> /dev/null; then
|
|
||||||
swag init -g cmd/api/main.go --parseDependency --parseInternal 2>/dev/null
|
|
||||||
fi
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Seeder deps
|
|
||||||
cd seeder-api
|
|
||||||
if [ ! -d "node_modules" ]; then
|
|
||||||
echo -e "${BLUE}🔹 Installing Seeder Dependencies...${NC}"
|
|
||||||
npm install
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${YELLOW}🔹 Resetting Database...${NC}"
|
|
||||||
npm run seed:reset
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Start backend
|
|
||||||
echo -e "${BLUE}🔹 Starting Backend...${NC}"
|
|
||||||
(cd backend && go run cmd/api/main.go) &
|
|
||||||
BACKEND_PID=$!
|
|
||||||
|
|
||||||
echo -e "${YELLOW}⏳ Waiting for Backend...${NC}"
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
# Seed
|
|
||||||
echo -e "${BLUE}🔹 Seeding Database (30 companies, 990 jobs)...${NC}"
|
|
||||||
cd seeder-api && npm run seed && cd ..
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
if [ ! -d "frontend/node_modules" ]; then
|
|
||||||
cd frontend && npm install && cd ..
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Starting Frontend...${NC}"
|
|
||||||
(cd frontend && npm run dev -- -p 8963) &
|
|
||||||
FRONTEND_PID=$!
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ Services running with fresh data!${NC}"
|
|
||||||
echo -e " ${CYAN}Backend:${NC} http://localhost:8521"
|
|
||||||
echo -e " ${CYAN}Frontend:${NC} http://localhost:8963"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
wait $BACKEND_PID $FRONTEND_PID
|
|
||||||
;;
|
|
||||||
|
|
||||||
3)
|
|
||||||
echo -e "\n${GREEN}📦 Starting All Services (Including Backoffice)...${NC}\n"
|
|
||||||
|
|
||||||
# Backend
|
|
||||||
cd backend && go mod tidy && cd ..
|
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
[ ! -d "frontend/node_modules" ] && (cd frontend && npm install && cd ..)
|
|
||||||
[ ! -d "backoffice/node_modules" ] && (cd backoffice && npm install && cd ..)
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Starting Backend on port 8521...${NC}"
|
|
||||||
(cd backend && go run cmd/api/main.go) &
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Starting Frontend on port 8963...${NC}"
|
|
||||||
(cd frontend && npm run dev -- -p 8963) &
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Starting Backoffice on port 3001...${NC}"
|
|
||||||
(cd backoffice && npm run start:dev) &
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ All services running:${NC}"
|
|
||||||
echo -e " ${CYAN}Backend:${NC} http://localhost:8521"
|
|
||||||
echo -e " ${CYAN}Frontend:${NC} http://localhost:8963"
|
|
||||||
echo -e " ${CYAN}Backoffice:${NC} http://localhost:3001"
|
|
||||||
echo -e " ${CYAN}Swagger:${NC} http://localhost:3001/api/docs"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
wait
|
|
||||||
;;
|
|
||||||
|
|
||||||
4)
|
|
||||||
echo -e "\n${GREEN}🗄️ Running Migrations...${NC}\n"
|
|
||||||
cd seeder-api
|
|
||||||
[ ! -d "node_modules" ] && npm install
|
|
||||||
npm run migrate
|
|
||||||
;;
|
|
||||||
|
|
||||||
5)
|
|
||||||
echo -e "\n${GREEN}🌿 Seeding Database Only...${NC}\n"
|
|
||||||
cd seeder-api
|
|
||||||
[ ! -d "node_modules" ] && npm install
|
|
||||||
npm run seed
|
|
||||||
echo -e "\n${GREEN}✅ Seeding completed!${NC}"
|
|
||||||
;;
|
|
||||||
|
|
||||||
6)
|
|
||||||
echo -e "\n${GREEN}🔄 Resetting Database & Seeding Fresh...${NC}\n"
|
|
||||||
cd seeder-api
|
|
||||||
[ ! -d "node_modules" ] && npm install
|
|
||||||
|
|
||||||
echo -e "${YELLOW}⚠️ This will DROP all tables and recreate!${NC}"
|
|
||||||
read -p "Are you sure? [y/N]: " confirm
|
|
||||||
|
|
||||||
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
|
|
||||||
echo -e "\n${BLUE}🔹 Step 1/3: Dropping all tables...${NC}"
|
|
||||||
npm run seed:reset
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Step 2/3: Running migrations...${NC}"
|
|
||||||
npm run migrate
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Step 3/3: Seeding data...${NC}"
|
|
||||||
npm run seed
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ Database fully reset and seeded!${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}Cancelled.${NC}"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
7)
|
|
||||||
echo -e "\n${GREEN}🧪 Running Backend E2E Tests...${NC}\n"
|
|
||||||
cd backend && go test -tags=e2e -v ./tests/e2e/... 2>&1
|
|
||||||
echo -e "\n${GREEN}✅ Tests completed!${NC}"
|
|
||||||
;;
|
|
||||||
|
|
||||||
8)
|
|
||||||
echo -e "\n${GREEN}⚡ Fast Reset - Seed LITE (no cities)...${NC}\n"
|
|
||||||
cd seeder-api
|
|
||||||
[ ! -d "node_modules" ] && npm install
|
|
||||||
|
|
||||||
echo -e "${YELLOW}⚠️ This will DROP all tables and recreate (WITHOUT 153k cities)${NC}"
|
|
||||||
read -p "Are you sure? [y/N]: " confirm
|
|
||||||
|
|
||||||
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
|
|
||||||
echo -e "\n${BLUE}🔹 Step 1/3: Dropping all tables...${NC}"
|
|
||||||
npm run seed:reset
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Step 2/3: Running migrations...${NC}"
|
|
||||||
npm run migrate
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Step 3/3: Seeding data (LITE - no cities)...${NC}"
|
|
||||||
npm run seed:lite
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ Database reset (LITE) completed! Cities skipped for speed.${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}Cancelled.${NC}"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
9)
|
|
||||||
echo -e "\n${GREEN}🔬 Running All Tests...${NC}\n"
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Backend Unit Tests...${NC}"
|
|
||||||
cd backend && go test -v ./... -count=1 2>&1 | tail -20
|
|
||||||
BACKEND_RESULT=$?
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Backend E2E Tests...${NC}"
|
|
||||||
cd backend && go test -tags=e2e -v ./tests/e2e/... 2>&1
|
|
||||||
E2E_RESULT=$?
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
if [ -d "frontend/node_modules" ]; then
|
|
||||||
echo -e "\n${BLUE}🔹 Frontend Tests...${NC}"
|
|
||||||
cd frontend && npm test -- --passWithNoTests 2>&1
|
|
||||||
FRONTEND_RESULT=$?
|
|
||||||
cd ..
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}⚠️ Frontend node_modules not found, skipping frontend tests${NC}"
|
|
||||||
FRONTEND_RESULT=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}═══════════════════════════════════════${NC}"
|
|
||||||
if [ $BACKEND_RESULT -eq 0 ] && [ $E2E_RESULT -eq 0 ] && [ $FRONTEND_RESULT -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✅ All tests passed!${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ Some tests failed${NC}"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
0)
|
|
||||||
echo -e "${YELLOW}Bye! 👋${NC}"
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
echo -e "${RED}Invalid option. Exiting.${NC}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
|
|
||||||
a)
|
|
||||||
echo -e "\n${GREEN}🐳 Building Docker Images...${NC}\n"
|
|
||||||
|
|
||||||
REGISTRY="forgejo-gru.rede5.com.br/rede5"
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔹 Building Backend...${NC}"
|
|
||||||
podman build -t $REGISTRY/gohorsejobs-backend:latest -f backend/Dockerfile backend/
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Building Frontend...${NC}"
|
|
||||||
podman build -t $REGISTRY/gohorsejobs-frontend:latest -f frontend/Dockerfile frontend/
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Building Seeder...${NC}"
|
|
||||||
podman build -t $REGISTRY/gohorsejobs-seeder:latest -f seeder-api/Dockerfile seeder-api/
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ All images built successfully!${NC}"
|
|
||||||
echo -e " ${CYAN}Backend:${NC} $REGISTRY/gohorsejobs-backend:latest"
|
|
||||||
echo -e " ${CYAN}Frontend:${NC} $REGISTRY/gohorsejobs-frontend:latest"
|
|
||||||
echo -e " ${CYAN}Seeder:${NC} $REGISTRY/gohorsejobs-seeder:latest"
|
|
||||||
;;
|
|
||||||
|
|
||||||
b)
|
|
||||||
echo -e "\n${GREEN}🚀 Building & Pushing to Forgejo Registry...${NC}\n"
|
|
||||||
|
|
||||||
REGISTRY="forgejo-gru.rede5.com.br/rede5"
|
|
||||||
|
|
||||||
# Check if logged in
|
|
||||||
echo -e "${BLUE}🔹 Checking registry login...${NC}"
|
|
||||||
if ! podman login --get-login $REGISTRY 2>/dev/null; then
|
|
||||||
echo -e "${YELLOW}⚠️ Not logged in. Please enter your credentials:${NC}"
|
|
||||||
read -p "Username: " REGISTRY_USER
|
|
||||||
read -s -p "Password/Token: " REGISTRY_PASS
|
|
||||||
echo ""
|
|
||||||
podman login forgejo-gru.rede5.com.br -u "$REGISTRY_USER" -p "$REGISTRY_PASS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Building Backend...${NC}"
|
|
||||||
podman build -t $REGISTRY/gohorsejobs-backend:latest -f backend/Dockerfile backend/
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Building Frontend...${NC}"
|
|
||||||
podman build -t $REGISTRY/gohorsejobs-frontend:latest -f frontend/Dockerfile frontend/
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Building Seeder...${NC}"
|
|
||||||
podman build -t $REGISTRY/gohorsejobs-seeder:latest -f seeder-api/Dockerfile seeder-api/
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Pushing Backend...${NC}"
|
|
||||||
podman push $REGISTRY/gohorsejobs-backend:latest
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Pushing Frontend...${NC}"
|
|
||||||
podman push $REGISTRY/gohorsejobs-frontend:latest
|
|
||||||
|
|
||||||
echo -e "\n${BLUE}🔹 Pushing Seeder...${NC}"
|
|
||||||
podman push $REGISTRY/gohorsejobs-seeder:latest
|
|
||||||
|
|
||||||
echo -e "\n${GREEN}✅ All images built and pushed!${NC}"
|
|
||||||
echo -e " ${CYAN}Registry:${NC} $REGISTRY"
|
|
||||||
echo -e " ${CYAN}Images:${NC} gohorsejobs-backend, gohorsejobs-frontend, gohorsejobs-seeder"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
Loading…
Reference in a new issue