116 lines
4.0 KiB
JavaScript
116 lines
4.0 KiB
JavaScript
import { request } from '../lib/apiClient'
|
|
|
|
/* ============================================================
|
|
interviews.js — backend/job/app.py `/interview/*`.
|
|
|
|
THREE READ MODES on one endpoint, selected by which params are present
|
|
(backend/job/app.py::fetch_interview):
|
|
interview_id -> one row, bare object
|
|
inbox_id -> every interview on one application, list
|
|
range -> from_date / to_date / status / top
|
|
|
|
Range mode only engages when at least ONE of from_date, to_date, status or
|
|
top is set. With none of them the route raises 400 "interview_id or inbox_id
|
|
is required" — so `list()` always sends `top`, and the screens never call it
|
|
bare.
|
|
|
|
Permissions are candidates.*, NOT interviews.* — the eight interviews.* tags
|
|
exist in the catalogue but no route reads them. A user holding only
|
|
interviews.view gets a 403 here.
|
|
============================================================ */
|
|
|
|
/** Status vocabulary. `interview_status` is a free-text column, so this file is
|
|
the only place the spelling is decided; writes and filters share it. */
|
|
export const INTERVIEW_STATUSES = ['Scheduled', 'Completed', 'Cancelled', 'No Show']
|
|
|
|
/** Round vocabulary — same story: free text, pinned here. */
|
|
export const INTERVIEW_TYPES = [
|
|
'Phone Screen',
|
|
'Technical',
|
|
'System Design',
|
|
'Onsite Loop',
|
|
'Hiring Manager',
|
|
'Culture Fit',
|
|
'Final Round',
|
|
]
|
|
|
|
/** Range read. `top` is always sent so the route takes the range branch. */
|
|
export function listRange({ fromDate, toDate, status, top = 200, skip } = {}) {
|
|
return request('/interview/fetch', {
|
|
params: {
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
status,
|
|
top,
|
|
skip,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function listByInbox(inboxId) {
|
|
return request('/interview/fetch', { params: { inbox_id: inboxId } })
|
|
}
|
|
|
|
/**
|
|
* Schedule one interview against an APPLICATION (inbox.id), not a candidate:
|
|
* `inbox_id` is the only link the table has, so a candidate with no inbox row
|
|
* (a manual upload) cannot be scheduled through this endpoint at all.
|
|
*
|
|
* interview_date and interview_time are both `datetime` columns, so the same
|
|
* instant goes to each rather than inventing a second one — the same rule the
|
|
* candidate profile's Interview tab already follows.
|
|
*/
|
|
export function create({ inboxId, instant, type, status }) {
|
|
return request('/interview/create', {
|
|
method: 'POST',
|
|
body: {
|
|
inbox_id: inboxId,
|
|
interview_date: instant,
|
|
interview_time: instant,
|
|
interview_type: type,
|
|
interview_status: status,
|
|
},
|
|
})
|
|
}
|
|
|
|
/** Partial update. Only the keys present are written (exclude_unset server-side). */
|
|
export function update(interviewId, { instant, type, status } = {}) {
|
|
const body = {}
|
|
if (instant != null) {
|
|
body.interview_date = instant
|
|
body.interview_time = instant
|
|
}
|
|
if (type != null) body.interview_type = type
|
|
if (status != null) body.interview_status = status
|
|
return request('/interview/update', {
|
|
method: 'PATCH',
|
|
params: { interview_id: interviewId },
|
|
body,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* API row -> what the Interviews table, the Calendar grid and the Up Next rail
|
|
* render.
|
|
*
|
|
* serialize_interview returns seven fields and the `interviews` table has no
|
|
* more columns than that, so five things the prototype showed have no source:
|
|
* meeting mode (video / on-site / phone), duration, interviewer list, the
|
|
* feedback verdict and a numeric score. They are absent here rather than
|
|
* defaulted, and the screens drop those columns — the same rule Jobs and
|
|
* Candidates already follow. `job_title` is likewise absent; the caller
|
|
* hydrates it from the application row when it has one.
|
|
*/
|
|
export function toInterviewView(row) {
|
|
const whenRaw = row.interview_date || row.interview_time
|
|
const when = whenRaw ? new Date(whenRaw) : null
|
|
return {
|
|
id: row.id,
|
|
inboxId: row.inbox_id,
|
|
candidate: row.candidate_name || 'Unknown candidate',
|
|
type: row.interview_type || 'Interview',
|
|
status: row.interview_status || 'Scheduled',
|
|
when: when && !Number.isNaN(when.getTime()) ? when : null,
|
|
}
|
|
}
|