HR-ATS-Portal/frontend/src/api/jobs.js

146 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { downloadFile, fetchBlobUrl, request } from '../lib/apiClient'
/**
* Job requisitions — backend/job/app.py `GET /jobs/fetch`.
*
* Distinct from api/jobPosts.js on purpose: that serves the Matching and CV-import
* PICKERS off /job/fetch (job_board.view). This is the requisition list behind
* jobs.view, and carries department / vacancies / requisition_status, which the
* picker payload does not.
*/
export function list({ search, department, requisitionStatus, employmentType,
hiringManagerId, top, skip, activeOnly } = {}) {
return request('/jobs/fetch', {
params: {
search,
department,
requisition_status: requisitionStatus,
employment_type: employmentType,
hiring_manager_id: hiringManagerId,
top,
skip,
active_only: activeOnly,
},
})
}
/* requisition_status is the HIRING lifecycle. The row's separate `status` field is
the Buffer publishing lifecycle — never map the two onto one badge. Fallback
matches GET /jobs/requisition-statuses/fetch so the dropdown still works if
that call 403s. */
export const REQUISITION_STATUSES = [
{ value: 'open', label: 'Open' },
{ value: 'on_hold', label: 'On Hold' },
{ value: 'closed', label: 'Closed' },
{ value: 'completed', label: 'Completed' },
]
const REQ_STATUS_LABEL = Object.fromEntries(REQUISITION_STATUSES.map((s) => [s.value, s.label]))
const LABEL_TO_STATUS = Object.fromEntries(REQUISITION_STATUSES.map((s) => [s.label, s.value]))
export const JOB_STATUSES = REQUISITION_STATUSES.map((s) => s.label)
export function listRequisitionStatuses() {
return request('/jobs/requisition-statuses/fetch')
}
function experienceLabel(min, max) {
if (min == null && max == null) return null
if (min != null && max != null) return `${min}${max} years`
return `${min ?? max}+ years`
}
/** API row -> what the Jobs table and detail modal render. */
export function toJobView(row) {
return {
id: row.id,
title: row.title,
department: row.department,
location: row.location,
type: row.employment_type,
vacancies: row.vacancies,
platform: row.platform || null,
status: REQ_STATUS_LABEL[row.requisition_status] ?? row.requisition_status,
publishStatus: row.status,
recruiter: row.recruiter_name,
recruiterId: row.current_recruiter_id,
hiringManager: row.hiring_manager_name,
hiringManagerId: row.hiring_manager_id,
createdByName: row.created_by_name,
applicantCount: row.applicant_count ?? 0,
// A real Date, so the existing `sortValue: j => j.created.getTime()` keeps working.
created: row.created_at ? new Date(row.created_at) : null,
closedAt: row.closed_at ? new Date(row.closed_at) : null,
requisitionStatus: row.requisition_status,
experienceMin: row.experience_min,
experienceMax: row.experience_max,
experience: experienceLabel(row.experience_min, row.experience_max),
skills: row.requirements ?? [],
optionalSkills: row.optional_skills ?? [],
description: row.description,
requisitionId: row.requisition_id || null,
requisitionTitle: row.requisition_title || '',
requisitionDepartment: row.requisition_department || '',
requisitionLabel: row.requisition_id
? `${(row.requisition_title || 'Untitled').trim() || 'Untitled'} - ${(row.requisition_department || '—').trim() || '—'}`
: null,
}
}
/**
* Styled .xlsx download of the requisition list — GET /jobs/export
* (jobs.export). Same filters as list(); `status` takes the UI label.
* downloadFile triggers the browser save from the Content-Disposition name.
*/
export function exportXlsx({ search, department, status, employmentType } = {}) {
return downloadFile('/jobs/export', {
params: {
search: search || undefined,
department: department || undefined,
requisition_status: status ? (LABEL_TO_STATUS[status] ?? status) : undefined,
employment_type: employmentType || undefined,
},
})
}
export function update(jobPostId, body) {
return request('/jobs/update', {
method: 'PATCH',
params: { job_post_id: jobPostId },
body,
})
}
export function remove(jobPostId) {
return request('/jobs/delete', {
method: 'DELETE',
params: { job_post_id: jobPostId },
})
}
/** Attach or replace a job post's cover image — POST /job/image/upload (multipart). */
export function uploadImage(jobPostId, file) {
const fd = new FormData()
fd.append('job_post_id', jobPostId)
fd.append('file', file)
return request('/job/image/upload', { method: 'POST', body: fd })
}
/** Object URL of the cover image, or null when the post has none. Caller revokes. */
export function fetchImageUrl(jobPostId) {
return fetchBlobUrl('/job/image/fetch', { params: { job_post_id: jobPostId } })
}
/** `status` is the Jobs UI label (Open / Closed / On Hold) or a raw requisition_status. */
export function setStatus(jobPostId, status) {
const requisition_status = LABEL_TO_STATUS[status] ?? status
return request('/jobs/status', {
method: 'PATCH',
params: { job_post_id: jobPostId },
body: { requisition_status },
})
}
/** Status-change audit for one requisition — GET /jobs/status-history/fetch. */
export function listStatusHistory(jobPostId) {
return request('/jobs/status-history/fetch', { params: { job_post_id: jobPostId } })
}