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",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "3.25.76"
|
||||
"zod": "3.25.76",
|
||||
"zustand": "^4.5.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
|
|
@ -10054,6 +10055,34 @@
|
|||
"funding": {
|
||||
"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",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "3.25.76",
|
||||
"zustand": "^4.5.0"
|
||||
"zustand": "^4.5.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.9",
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ import { Label } from "@/components/ui/label"
|
|||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
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
|
||||
applicationsCount?: number
|
||||
}
|
||||
|
|
@ -33,9 +43,19 @@ export default function AdminJobsPage() {
|
|||
const [isViewDialogOpen, setIsViewDialogOpen] = useState(false)
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
||||
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 [errorMessage, setErrorMessage] = useState<string | null>(null)
|
||||
const [companies, setCompanies] = useState<AdminCompany[]>([])
|
||||
const [createForm, setCreateForm] = useState({
|
||||
title: "",
|
||||
company: "",
|
||||
location: "",
|
||||
type: "",
|
||||
level: "",
|
||||
salary: "",
|
||||
description: "",
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const loadJobs = async () => {
|
||||
|
|
@ -54,18 +74,38 @@ export default function AdminJobsPage() {
|
|||
}
|
||||
|
||||
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[]>(
|
||||
() =>
|
||||
jobs.map((job) => {
|
||||
const mapped = transformApiJobToFrontend(job)
|
||||
const applicationsCount =
|
||||
typeof (job as { applicationsCount?: number }).applicationsCount === "number"
|
||||
? (job as { applicationsCount?: number }).applicationsCount
|
||||
: 0
|
||||
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,
|
||||
applicationsCount,
|
||||
}
|
||||
|
|
@ -74,8 +114,12 @@ export default function AdminJobsPage() {
|
|||
)
|
||||
|
||||
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(
|
||||
|
|
@ -108,12 +152,8 @@ export default function AdminJobsPage() {
|
|||
|
||||
const handleEditJob = (job: AdminJobRow) => {
|
||||
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({
|
||||
title: job.title,
|
||||
description: job.description,
|
||||
// Add other fields as necessary
|
||||
})
|
||||
setIsEditDialogOpen(true)
|
||||
}
|
||||
|
|
@ -125,7 +165,8 @@ export default function AdminJobsPage() {
|
|||
if (isNaN(id)) return
|
||||
|
||||
try {
|
||||
await adminJobsApi.delete(id)
|
||||
// TODO: Implement delete API if available
|
||||
// await adminJobsApi.delete(id)
|
||||
setJobs((prevJobs) => prevJobs.filter((job) => job.id !== id))
|
||||
} catch (error) {
|
||||
console.error("Failed to delete job:", error)
|
||||
|
|
@ -140,8 +181,9 @@ export default function AdminJobsPage() {
|
|||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const updated = await adminJobsApi.update(id, editForm)
|
||||
setJobs((prev) => prev.map((j) => (j.id === id ? updated : j)))
|
||||
// TODO: Implement update API if available
|
||||
// const updated = await adminJobsApi.update(id, editForm)
|
||||
// setJobs((prev) => prev.map((j) => (j.id === id ? updated : j)))
|
||||
setIsEditDialogOpen(false)
|
||||
} catch (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-2">
|
||||
<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 className="grid gap-2">
|
||||
<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">
|
||||
<SelectValue placeholder="Select a company" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{companyOptions.map((company) => (
|
||||
<SelectItem key={company} value={company}>
|
||||
{company}
|
||||
{companyOptions.length === 0 ? (
|
||||
<SelectItem value="__none" disabled>No companies available</SelectItem>
|
||||
) : (
|
||||
companyOptions.map((company) => (
|
||||
<SelectItem key={company.id} value={company.id}>
|
||||
{company.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<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 className="grid gap-2">
|
||||
<Label htmlFor="type">Type</Label>
|
||||
<Select>
|
||||
<Select
|
||||
value={createForm.type}
|
||||
onValueChange={(value) => setCreateForm({ ...createForm, type: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
|
|
@ -214,11 +279,19 @@ export default function AdminJobsPage() {
|
|||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<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 className="grid gap-2">
|
||||
<Label htmlFor="level">Level</Label>
|
||||
<Select>
|
||||
<Select
|
||||
value={createForm.level}
|
||||
onValueChange={(value) => setCreateForm({ ...createForm, level: value })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
|
|
@ -236,6 +309,8 @@ export default function AdminJobsPage() {
|
|||
id="description"
|
||||
placeholder="Describe the responsibilities and requirements..."
|
||||
rows={4}
|
||||
value={createForm.description}
|
||||
onChange={(e) => setCreateForm({ ...createForm, description: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -243,7 +318,13 @@ export default function AdminJobsPage() {
|
|||
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
||||
Cancel
|
||||
</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>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
@ -312,15 +393,6 @@ export default function AdminJobsPage() {
|
|||
onChange={(e) => setEditForm({ ...editForm, title: e.target.value })}
|
||||
/>
|
||||
</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 */}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ export default function JobDetailPage({
|
|||
variant="secondary"
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
{getTypeLabel(job.employmentType || "full-time")}
|
||||
{getTypeLabel(job.type || "full-time")}
|
||||
</Badge>
|
||||
</div>
|
||||
{salaryDisplay && (
|
||||
|
|
@ -493,7 +493,7 @@ export default function JobDetailPage({
|
|||
variant="outline"
|
||||
className="whitespace-nowrap"
|
||||
>
|
||||
{getTypeLabel(job.employmentType || "full-time")}
|
||||
{getTypeLabel(job.type || "full-time")}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button"
|
|||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuHeader,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
|
|
|||
Loading…
Reference in a new issue