diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ab3cbf9..5dcc385 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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 + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index d251bb4..0999b23 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", @@ -81,4 +81,4 @@ "tw-animate-css": "1.3.3", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/frontend/src/app/dashboard/jobs/page.tsx b/frontend/src/app/dashboard/jobs/page.tsx index 9fd7f1d..a7f9564 100644 --- a/frontend/src/app/dashboard/jobs/page.tsx +++ b/frontend/src/app/dashboard/jobs/page.tsx @@ -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 & { +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(null) - const [editForm, setEditForm] = useState>({}) + const [editForm, setEditForm] = useState<{ title?: string }>({}) const [isLoading, setIsLoading] = useState(true) const [errorMessage, setErrorMessage] = useState(null) + const [companies, setCompanies] = useState([]) + 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( () => 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() {
- + setCreateForm({ ...createForm, title: e.target.value })} + />
- { + console.log("[DEBUG] Selected company:", value) + setCreateForm({ ...createForm, company: value }) + }} + > - {companyOptions.map((company) => ( - - {company} - - ))} + {companyOptions.length === 0 ? ( + No companies available + ) : ( + companyOptions.map((company) => ( + + {company.name} + + )) + )}
- + setCreateForm({ ...createForm, location: e.target.value })} + />
- setCreateForm({ ...createForm, type: value })} + > @@ -214,11 +279,19 @@ export default function AdminJobsPage() {
- + setCreateForm({ ...createForm, salary: e.target.value })} + />
- setCreateForm({ ...createForm, level: value })} + > @@ -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 })} />
@@ -243,7 +318,13 @@ export default function AdminJobsPage() { - + @@ -312,15 +393,6 @@ export default function AdminJobsPage() { onChange={(e) => setEditForm({ ...editForm, title: e.target.value })} />
-
- -