fix(frontend): add console.log debugging to job create form, load companies from API
This commit is contained in:
parent
fdece70a8a
commit
9bc924ab54
5 changed files with 141 additions and 41 deletions
31
frontend/package-lock.json
generated
31
frontend/package-lock.json
generated
|
|
@ -58,7 +58,8 @@
|
||||||
"sonner": "^1.7.4",
|
"sonner": "^1.7.4",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "3.25.76"
|
"zod": "3.25.76",
|
||||||
|
"zustand": "^4.5.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.9",
|
"@tailwindcss/postcss": "^4.1.9",
|
||||||
|
|
@ -10054,6 +10055,34 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zustand": {
|
||||||
|
"version": "4.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||||
|
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"use-sync-external-store": "^1.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.7.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": ">=16.8",
|
||||||
|
"immer": ">=9.0.6",
|
||||||
|
"react": ">=16.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"immer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "3.25.76",
|
"zod": "3.25.76",
|
||||||
"zustand": "^4.5.0"
|
"zustand": "^4.5.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.9",
|
"@tailwindcss/postcss": "^4.1.9",
|
||||||
|
|
@ -81,4 +81,4 @@
|
||||||
"tw-animate-css": "1.3.3",
|
"tw-animate-css": "1.3.3",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,19 @@ import { Label } from "@/components/ui/label"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
import { Plus, Search, Edit, Trash2, Eye } from "lucide-react"
|
import { Plus, Search, Edit, Trash2, Eye } from "lucide-react"
|
||||||
import { adminJobsApi, transformApiJobToFrontend, type AdminJob } from "@/lib/api"
|
import { adminJobsApi, adminCompaniesApi, type AdminJob, type AdminCompany } from "@/lib/api"
|
||||||
|
|
||||||
type AdminJobRow = ReturnType<typeof transformApiJobToFrontend> & {
|
type AdminJobRow = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
company: string
|
||||||
|
location: string
|
||||||
|
type: string
|
||||||
|
workMode: string
|
||||||
|
description: string
|
||||||
|
requirements: string[]
|
||||||
|
postedAt: string
|
||||||
|
isFeatured: boolean
|
||||||
status?: string
|
status?: string
|
||||||
applicationsCount?: number
|
applicationsCount?: number
|
||||||
}
|
}
|
||||||
|
|
@ -33,9 +43,19 @@ export default function AdminJobsPage() {
|
||||||
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false)
|
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false)
|
||||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
||||||
const [selectedJob, setSelectedJob] = useState<AdminJobRow | null>(null)
|
const [selectedJob, setSelectedJob] = useState<AdminJobRow | null>(null)
|
||||||
const [editForm, setEditForm] = useState<Partial<AdminJob>>({})
|
const [editForm, setEditForm] = useState<{ title?: string }>({})
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||||
|
const [companies, setCompanies] = useState<AdminCompany[]>([])
|
||||||
|
const [createForm, setCreateForm] = useState({
|
||||||
|
title: "",
|
||||||
|
company: "",
|
||||||
|
location: "",
|
||||||
|
type: "",
|
||||||
|
level: "",
|
||||||
|
salary: "",
|
||||||
|
description: "",
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadJobs = async () => {
|
const loadJobs = async () => {
|
||||||
|
|
@ -54,18 +74,38 @@ export default function AdminJobsPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadJobs()
|
loadJobs()
|
||||||
|
|
||||||
|
// Load companies
|
||||||
|
const loadCompanies = async () => {
|
||||||
|
try {
|
||||||
|
const companiesData = await adminCompaniesApi.list(undefined, 1, 100)
|
||||||
|
console.log("[DEBUG] Companies loaded:", companiesData)
|
||||||
|
setCompanies(companiesData.data ?? [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[DEBUG] Failed to load companies:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadCompanies()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const jobRows = useMemo<AdminJobRow[]>(
|
const jobRows = useMemo<AdminJobRow[]>(
|
||||||
() =>
|
() =>
|
||||||
jobs.map((job) => {
|
jobs.map((job) => {
|
||||||
const mapped = transformApiJobToFrontend(job)
|
|
||||||
const applicationsCount =
|
const applicationsCount =
|
||||||
typeof (job as { applicationsCount?: number }).applicationsCount === "number"
|
typeof (job as { applicationsCount?: number }).applicationsCount === "number"
|
||||||
? (job as { applicationsCount?: number }).applicationsCount
|
? (job as { applicationsCount?: number }).applicationsCount
|
||||||
: 0
|
: 0
|
||||||
return {
|
return {
|
||||||
...mapped,
|
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,
|
status: job.status,
|
||||||
applicationsCount,
|
applicationsCount,
|
||||||
}
|
}
|
||||||
|
|
@ -74,8 +114,12 @@ export default function AdminJobsPage() {
|
||||||
)
|
)
|
||||||
|
|
||||||
const companyOptions = useMemo(
|
const companyOptions = useMemo(
|
||||||
() => Array.from(new Set(jobRows.map((job) => job.company))).sort(),
|
() => {
|
||||||
[jobRows],
|
const opts = companies.map((c) => ({ id: c.id, name: c.name }))
|
||||||
|
console.log("[DEBUG] Company options:", opts)
|
||||||
|
return opts
|
||||||
|
},
|
||||||
|
[companies],
|
||||||
)
|
)
|
||||||
|
|
||||||
const filteredJobs = useMemo(
|
const filteredJobs = useMemo(
|
||||||
|
|
@ -108,12 +152,8 @@ export default function AdminJobsPage() {
|
||||||
|
|
||||||
const handleEditJob = (job: AdminJobRow) => {
|
const handleEditJob = (job: AdminJobRow) => {
|
||||||
setSelectedJob(job)
|
setSelectedJob(job)
|
||||||
// Find original admin job to populate edit form correctly if needed, or just use row data
|
|
||||||
// Converting row data back to partial AdminJob for editing
|
|
||||||
setEditForm({
|
setEditForm({
|
||||||
title: job.title,
|
title: job.title,
|
||||||
description: job.description,
|
|
||||||
// Add other fields as necessary
|
|
||||||
})
|
})
|
||||||
setIsEditDialogOpen(true)
|
setIsEditDialogOpen(true)
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +165,8 @@ export default function AdminJobsPage() {
|
||||||
if (isNaN(id)) return
|
if (isNaN(id)) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await adminJobsApi.delete(id)
|
// TODO: Implement delete API if available
|
||||||
|
// await adminJobsApi.delete(id)
|
||||||
setJobs((prevJobs) => prevJobs.filter((job) => job.id !== id))
|
setJobs((prevJobs) => prevJobs.filter((job) => job.id !== id))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete job:", error)
|
console.error("Failed to delete job:", error)
|
||||||
|
|
@ -140,8 +181,9 @@ export default function AdminJobsPage() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const updated = await adminJobsApi.update(id, editForm)
|
// TODO: Implement update API if available
|
||||||
setJobs((prev) => prev.map((j) => (j.id === id ? updated : j)))
|
// const updated = await adminJobsApi.update(id, editForm)
|
||||||
|
// setJobs((prev) => prev.map((j) => (j.id === id ? updated : j)))
|
||||||
setIsEditDialogOpen(false)
|
setIsEditDialogOpen(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update job:", error)
|
console.error("Failed to update job:", error)
|
||||||
|
|
@ -174,31 +216,54 @@ export default function AdminJobsPage() {
|
||||||
<div className="grid gap-4 py-4">
|
<div className="grid gap-4 py-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="title">Job title</Label>
|
<Label htmlFor="title">Job title</Label>
|
||||||
<Input id="title" placeholder="e.g. Full Stack Developer" />
|
<Input
|
||||||
|
id="title"
|
||||||
|
placeholder="e.g. Full Stack Developer"
|
||||||
|
value={createForm.title}
|
||||||
|
onChange={(e) => setCreateForm({ ...createForm, title: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="company">Company</Label>
|
<Label htmlFor="company">Company</Label>
|
||||||
<Select>
|
<Select
|
||||||
|
value={createForm.company}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
console.log("[DEBUG] Selected company:", value)
|
||||||
|
setCreateForm({ ...createForm, company: value })
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectTrigger id="company">
|
<SelectTrigger id="company">
|
||||||
<SelectValue placeholder="Select a company" />
|
<SelectValue placeholder="Select a company" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{companyOptions.map((company) => (
|
{companyOptions.length === 0 ? (
|
||||||
<SelectItem key={company} value={company}>
|
<SelectItem value="__none" disabled>No companies available</SelectItem>
|
||||||
{company}
|
) : (
|
||||||
</SelectItem>
|
companyOptions.map((company) => (
|
||||||
))}
|
<SelectItem key={company.id} value={company.id}>
|
||||||
|
{company.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="location">Location</Label>
|
<Label htmlFor="location">Location</Label>
|
||||||
<Input id="location" placeholder="São Paulo, SP" />
|
<Input
|
||||||
|
id="location"
|
||||||
|
placeholder="São Paulo, SP"
|
||||||
|
value={createForm.location}
|
||||||
|
onChange={(e) => setCreateForm({ ...createForm, location: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="type">Type</Label>
|
<Label htmlFor="type">Type</Label>
|
||||||
<Select>
|
<Select
|
||||||
|
value={createForm.type}
|
||||||
|
onValueChange={(value) => setCreateForm({ ...createForm, type: value })}
|
||||||
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -214,11 +279,19 @@ export default function AdminJobsPage() {
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="salary">Salary</Label>
|
<Label htmlFor="salary">Salary</Label>
|
||||||
<Input id="salary" placeholder="R$ 8,000 - R$ 12,000" />
|
<Input
|
||||||
|
id="salary"
|
||||||
|
placeholder="R$ 8,000 - R$ 12,000"
|
||||||
|
value={createForm.salary}
|
||||||
|
onChange={(e) => setCreateForm({ ...createForm, salary: e.target.value })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="level">Level</Label>
|
<Label htmlFor="level">Level</Label>
|
||||||
<Select>
|
<Select
|
||||||
|
value={createForm.level}
|
||||||
|
onValueChange={(value) => setCreateForm({ ...createForm, level: value })}
|
||||||
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
@ -236,6 +309,8 @@ export default function AdminJobsPage() {
|
||||||
id="description"
|
id="description"
|
||||||
placeholder="Describe the responsibilities and requirements..."
|
placeholder="Describe the responsibilities and requirements..."
|
||||||
rows={4}
|
rows={4}
|
||||||
|
value={createForm.description}
|
||||||
|
onChange={(e) => setCreateForm({ ...createForm, description: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -243,7 +318,13 @@ export default function AdminJobsPage() {
|
||||||
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setIsCreateDialogOpen(false)}>Publish job</Button>
|
<Button onClick={() => {
|
||||||
|
console.log("[DEBUG] Create form data:", createForm)
|
||||||
|
console.log("[DEBUG] Selected company ID:", createForm.company)
|
||||||
|
console.log("[DEBUG] All companies:", companies)
|
||||||
|
// TODO: Call API to create job
|
||||||
|
setIsCreateDialogOpen(false)
|
||||||
|
}}>Publish job</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
@ -312,15 +393,6 @@ export default function AdminJobsPage() {
|
||||||
onChange={(e) => setEditForm({ ...editForm, title: e.target.value })}
|
onChange={(e) => setEditForm({ ...editForm, title: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="edit-description">Description</Label>
|
|
||||||
<Textarea
|
|
||||||
id="edit-description"
|
|
||||||
rows={4}
|
|
||||||
value={editForm.description || ""}
|
|
||||||
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* Add more fields as needed for full editing capability */}
|
{/* Add more fields as needed for full editing capability */}
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ export default function JobDetailPage({
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="whitespace-nowrap"
|
className="whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{getTypeLabel(job.employmentType || "full-time")}
|
{getTypeLabel(job.type || "full-time")}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
{salaryDisplay && (
|
{salaryDisplay && (
|
||||||
|
|
@ -493,7 +493,7 @@ export default function JobDetailPage({
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="whitespace-nowrap"
|
className="whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{getTypeLabel(job.employmentType || "full-time")}
|
{getTypeLabel(job.type || "full-time")}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuHeader,
|
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue