diff --git a/frontend/src/app/dashboard/applications/page.tsx b/frontend/src/app/dashboard/applications/page.tsx
index 7d5f228..c6e4059 100644
--- a/frontend/src/app/dashboard/applications/page.tsx
+++ b/frontend/src/app/dashboard/applications/page.tsx
@@ -39,11 +39,11 @@ import { applicationsApi, notificationsApi } from "@/lib/api"
import { toast } from "sonner"
const statusConfig = {
- pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800 border-yellow-200", icon: Clock },
- reviewing: { label: "Reviewing", color: "bg-blue-100 text-blue-800 border-blue-200", icon: Eye },
- interview: { label: "Interview", color: "bg-purple-100 text-purple-800 border-purple-200", icon: Star },
- accepted: { label: "Hired", color: "bg-green-100 text-green-800 border-green-200", icon: Check },
- rejected: { label: "Rejected", color: "bg-red-100 text-red-800 border-red-200", icon: X },
+ pending: { label: "Pendente", color: "bg-yellow-100 text-yellow-800 border-yellow-200", icon: Clock },
+ reviewed: { label: "Em análise", color: "bg-blue-100 text-blue-800 border-blue-200", icon: Eye },
+ shortlisted: { label: "Selecionado", color: "bg-purple-100 text-purple-800 border-purple-200", icon: Star },
+ hired: { label: "Contratado", color: "bg-green-100 text-green-800 border-green-200", icon: Check },
+ rejected: { label: "Rejeitado", color: "bg-red-100 text-red-800 border-red-200", icon: X },
}
interface Application {
@@ -106,8 +106,8 @@ export default function ApplicationsPage() {
const stats = {
total: applications.length,
pending: applications.filter((a) => a.status === "pending").length,
- interview: applications.filter((a) => a.status === "interview").length,
- accepted: applications.filter((a) => a.status === "accepted").length,
+ shortlisted: applications.filter((a) => a.status === "shortlisted").length,
+ hired: applications.filter((a) => a.status === "hired").length,
}
return (
@@ -140,14 +140,14 @@ export default function ApplicationsPage() {
- {stats.interview}
- Interview
+ {stats.shortlisted}
+ Selecionados
- {stats.accepted}
- Hired
+ {stats.hired}
+ Contratados
@@ -170,11 +170,11 @@ export default function ApplicationsPage() {
All Status
- Pending
- Reviewing
- Interview
- Accepted
- Rejected
+ Pendente
+ Em análise
+ Selecionado
+ Contratado
+ Rejeitado
diff --git a/frontend/src/app/dashboard/companies/page.tsx b/frontend/src/app/dashboard/companies/page.tsx
index 2bc06af..e7f936a 100644
--- a/frontend/src/app/dashboard/companies/page.tsx
+++ b/frontend/src/app/dashboard/companies/page.tsx
@@ -105,6 +105,8 @@ export default function AdminCompaniesPage() {
website: "",
address: "",
description: "",
+ logoUrl: "",
+ yearsInMarket: "",
})
const [editFormData, setEditFormData] = useState({
name: "",
@@ -115,6 +117,8 @@ export default function AdminCompaniesPage() {
document: "",
address: "",
description: "",
+ logoUrl: "",
+ yearsInMarket: "",
active: false,
verified: false,
})
@@ -173,6 +177,8 @@ export default function AdminCompaniesPage() {
website: "",
address: "",
description: "",
+ logoUrl: "",
+ yearsInMarket: "",
})
loadCompanies(1) // Reload first page
} catch (error: any) {
@@ -240,6 +246,8 @@ export default function AdminCompaniesPage() {
document: company.document || "",
address: company.address || "",
description: company.description || "",
+ logoUrl: (company as any).logoUrl || "",
+ yearsInMarket: (company as any).yearsInMarket || "",
active: company.active || false,
verified: company.verified || false,
})
@@ -269,7 +277,9 @@ export default function AdminCompaniesPage() {
document: editFormData.document,
address: editFormData.address,
description: editFormData.description,
- })
+ logoUrl: editFormData.logoUrl || undefined,
+ yearsInMarket: editFormData.yearsInMarket || undefined,
+ } as any)
toast.success(t('admin.companies.success.updated'))
setIsEditDialogOpen(false)
@@ -448,6 +458,24 @@ export default function AdminCompaniesPage() {
placeholder="Company description..."
/>
+
+
+ setFormData({ ...formData, logoUrl: e.target.value })}
+ placeholder="https://example.com/logo.png"
+ />
+
+
+
+ setFormData({ ...formData, yearsInMarket: e.target.value })}
+ placeholder="Ex: 10"
+ />
+
@@ -832,6 +860,24 @@ export default function AdminCompaniesPage() {
onChange={(e) => setEditFormData({ ...editFormData, description: e.target.value })}
/>
+
+
+ setEditFormData({ ...editFormData, logoUrl: e.target.value })}
+ placeholder="https://example.com/logo.png"
+ />
+
+
+
+ setEditFormData({ ...editFormData, yearsInMarket: e.target.value })}
+ placeholder="Ex: 10"
+ />
+
diff --git a/frontend/src/app/dashboard/jobs/page.tsx b/frontend/src/app/dashboard/jobs/page.tsx
index 74fffd1..f63b0ac 100644
--- a/frontend/src/app/dashboard/jobs/page.tsx
+++ b/frontend/src/app/dashboard/jobs/page.tsx
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
@@ -16,23 +17,46 @@ import {
DialogTitle,
} from "@/components/ui/dialog"
import { Label } from "@/components/ui/label"
-import { Plus, Search, Edit, Trash2, Eye, ChevronLeft, ChevronRight } from "lucide-react"
-import { adminJobsApi, adminCompaniesApi, jobsApi, type AdminJob, type AdminCompany } from "@/lib/api"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Switch } from "@/components/ui/switch"
+import { Plus, Search, Edit, Trash2, Eye, ChevronLeft, ChevronRight, Loader2 } from "lucide-react"
+import { adminJobsApi, jobsApi, type AdminJob } from "@/lib/api"
import { useTranslation } from "@/lib/i18n"
-type AdminJobRow = {
- id: string
+type EditForm = {
title: string
- company: string
- location: string
- type: string
- workMode: string
description: string
- requirements: string[]
- postedAt: string
+ location: string
+ employmentType: string
+ workMode: string
+ workingHours: string
+ salaryMin: string
+ salaryMax: string
+ salaryType: string
+ currency: string
+ salaryNegotiable: boolean
+ languageLevel: string
+ visaSupport: boolean
+ status: string
isFeatured: boolean
- status?: string
- applicationsCount?: number
+}
+
+const defaultEditForm: EditForm = {
+ title: "",
+ description: "",
+ location: "",
+ employmentType: "",
+ workMode: "",
+ workingHours: "",
+ salaryMin: "",
+ salaryMax: "",
+ salaryType: "monthly",
+ currency: "BRL",
+ salaryNegotiable: false,
+ languageLevel: "",
+ visaSupport: false,
+ status: "open",
+ isFeatured: false,
}
export default function AdminJobsPage() {
@@ -41,146 +65,131 @@ export default function AdminJobsPage() {
const [jobs, setJobs] = useState([])
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
- const [selectedJob, setSelectedJob] = useState(null)
- const [editForm, setEditForm] = useState<{ title?: string }>({})
+ const [selectedJob, setSelectedJob] = useState(null)
+ const [editForm, setEditForm] = useState(defaultEditForm)
const [isLoading, setIsLoading] = useState(true)
+ const [isSaving, setIsSaving] = useState(false)
const [errorMessage, setErrorMessage] = useState(null)
- // Pagination State
const [page, setPage] = useState(1)
const [limit] = useState(10)
const [totalPages, setTotalPages] = useState(1)
const [totalJobs, setTotalJobs] = useState(0)
- useEffect(() => {
- const loadJobs = async () => {
- try {
- setIsLoading(true)
- setErrorMessage(null)
- // Fetch with pagination
- const jobsData = await adminJobsApi.list({ limit, page })
- setJobs(jobsData.data ?? [])
- if (jobsData.pagination) {
- setTotalPages(Math.ceil((jobsData.pagination.total || 0) / limit))
- setTotalJobs(jobsData.pagination.total || 0)
- } else {
- // Fallback: simpler pagination if no meta
- setTotalPages(1)
- }
-
- } catch (error) {
- console.error("Failed to load jobs:", error)
- setErrorMessage(t('admin.jobs.table.error'))
- setJobs([])
- } finally {
- setIsLoading(false)
- }
- }
-
- loadJobs()
- }, [page, limit, t])
-
- const jobRows = useMemo(
- () =>
- jobs.map((job) => {
- const applicationsCount =
- typeof (job as { applicationsCount?: number }).applicationsCount === "number"
- ? (job as { applicationsCount?: number }).applicationsCount
- : 0
- return {
- id: String(job.id),
- title: job.title,
- company: job.companyName,
- location: "",
- type: "full-time" as const,
- workMode: "onsite" as const,
- description: "",
- requirements: [],
- postedAt: job.createdAt,
- isFeatured: false,
- status: job.status,
- applicationsCount,
- }
- }),
- [jobs],
- )
-
-
- const filteredJobs = useMemo(
- () =>
- jobRows.filter(
- (job) =>
- job.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
- job.company.toLowerCase().includes(searchTerm.toLowerCase()),
- ),
- [jobRows, searchTerm],
- )
-
- const activeJobs = useMemo(
- () =>
- jobs.filter((job) =>
- ["published", "open", "active"].includes(job.status?.toLowerCase() ?? ""),
- ).length,
- [jobs],
- )
-
- const totalApplications = useMemo(
- () => jobRows.reduce((sum, job) => sum + (job.applicationsCount ?? 0), 0),
- [jobRows],
- )
-
- const handleViewJob = (job: AdminJobRow) => {
- setSelectedJob(job)
- setIsViewDialogOpen(true)
- }
-
- const handleEditJob = (job: AdminJobRow) => {
- setSelectedJob(job)
- setEditForm({
- title: job.title,
- })
- setIsEditDialogOpen(true)
- }
-
- const handleDeleteJob = async (id: string) => {
- console.log("[JOBS_PAGE] handleDeleteJob called with id:", id)
- if (!confirm(t('admin.jobs.deleteConfirm'))) return
-
- try {
- await jobsApi.delete(id)
- setJobs((prevJobs) => prevJobs.filter((job) => job.id !== id))
- } catch (error) {
- console.error("[JOBS_PAGE] Failed to delete job:", error)
- alert(t('admin.jobs.deleteError'))
- }
- }
-
-
- const handleSaveEdit = async () => {
- if (!selectedJob) return
-
+ const loadJobs = async (targetPage = page) => {
try {
setIsLoading(true)
- await jobsApi.update(selectedJob.id, editForm)
- // Reload jobs to get fresh data
- const jobsData = await adminJobsApi.list({ limit: 10, page: 1 })
+ setErrorMessage(null)
+ const jobsData = await adminJobsApi.list({ limit, page: targetPage })
setJobs(jobsData.data ?? [])
- setIsEditDialogOpen(false)
+ if (jobsData.pagination) {
+ setTotalPages(Math.ceil((jobsData.pagination.total || 0) / limit))
+ setTotalJobs(jobsData.pagination.total || 0)
+ }
} catch (error) {
- console.error("[JOBS_PAGE] Failed to update job:", error)
- alert(t('admin.jobs.updateError'))
+ console.error("Failed to load jobs:", error)
+ setErrorMessage(t('admin.jobs.table.error'))
+ setJobs([])
} finally {
setIsLoading(false)
}
}
+ useEffect(() => { loadJobs() }, [page, limit])
- const handlePreviousPage = () => {
- if (page > 1) setPage(page - 1)
+ const filteredJobs = useMemo(
+ () => jobs.filter(
+ (job) =>
+ job.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ job.companyName.toLowerCase().includes(searchTerm.toLowerCase()),
+ ),
+ [jobs, searchTerm],
+ )
+
+ const activeJobs = useMemo(
+ () => jobs.filter((job) => ["published", "open", "active"].includes(job.status?.toLowerCase() ?? "")).length,
+ [jobs],
+ )
+
+ const totalApplications = useMemo(
+ () => jobs.reduce((sum, job) => sum + (job.applicationsCount ?? 0), 0),
+ [jobs],
+ )
+
+ const handleViewJob = (job: AdminJob) => {
+ setSelectedJob(job)
+ setIsViewDialogOpen(true)
}
- const handleNextPage = () => {
- if (page < totalPages) setPage(page + 1)
+ const handleEditJob = (job: AdminJob) => {
+ setSelectedJob(job)
+ setEditForm({
+ title: job.title ?? "",
+ description: job.description ?? "",
+ location: job.location ?? "",
+ employmentType: job.employmentType ?? "",
+ workMode: job.workMode ?? "",
+ workingHours: job.workingHours ?? "",
+ salaryMin: job.salaryMin != null ? String(job.salaryMin) : "",
+ salaryMax: job.salaryMax != null ? String(job.salaryMax) : "",
+ salaryType: job.salaryType ?? "monthly",
+ currency: job.currency ?? "BRL",
+ salaryNegotiable: job.salaryNegotiable ?? false,
+ languageLevel: job.languageLevel ?? "",
+ visaSupport: job.visaSupport ?? false,
+ status: job.status ?? "open",
+ isFeatured: job.isFeatured ?? false,
+ })
+ setIsEditDialogOpen(true)
+ }
+
+ const handleDeleteJob = async (id: string) => {
+ if (!confirm(t('admin.jobs.deleteConfirm'))) return
+ try {
+ await jobsApi.delete(id)
+ setJobs((prev) => prev.filter((job) => job.id !== id))
+ } catch {
+ alert(t('admin.jobs.deleteError'))
+ }
+ }
+
+ const handleSaveEdit = async () => {
+ if (!selectedJob) return
+ setIsSaving(true)
+ try {
+ await jobsApi.update(selectedJob.id, {
+ title: editForm.title || undefined,
+ description: editForm.description || undefined,
+ location: editForm.location || undefined,
+ employmentType: (editForm.employmentType as any) || undefined,
+ workMode: (editForm.workMode as any) || undefined,
+ workingHours: editForm.workingHours || undefined,
+ salaryMin: editForm.salaryMin ? parseFloat(editForm.salaryMin) : undefined,
+ salaryMax: editForm.salaryMax ? parseFloat(editForm.salaryMax) : undefined,
+ salaryType: (editForm.salaryType as any) || undefined,
+ currency: (editForm.currency as any) || undefined,
+ salaryNegotiable: editForm.salaryNegotiable,
+ languageLevel: editForm.languageLevel || undefined,
+ visaSupport: editForm.visaSupport,
+ status: (editForm.status as any) || undefined,
+ isFeatured: editForm.isFeatured,
+ })
+ await loadJobs(page)
+ setIsEditDialogOpen(false)
+ } catch {
+ alert(t('admin.jobs.updateError'))
+ } finally {
+ setIsSaving(false)
+ }
+ }
+
+ const statusColor = (status: string) => {
+ switch (status) {
+ case "open": return "default"
+ case "draft": return "secondary"
+ case "closed": case "paused": return "outline"
+ default: return "outline"
+ }
}
return (
@@ -201,7 +210,7 @@ export default function AdminJobsPage() {
{/* View Job Dialog */}