114 lines
3.4 KiB
JavaScript
114 lines
3.4 KiB
JavaScript
import { request } from '../lib/apiClient'
|
|
import { toStageCounts } from './pipeline'
|
|
|
|
/**
|
|
* Dashboard analytics aggregates — backend/analytics/app.py.
|
|
* Permissioned with require_permission(ANALYTICS_VIEW).
|
|
*/
|
|
|
|
export function kpis({ fromDate, toDate, department, recruiterId } = {}) {
|
|
return request('/analytics/kpis/fetch', {
|
|
params: {
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
department,
|
|
recruiter_id: recruiterId,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function funnel({ fromDate, toDate, department, recruiterId } = {}) {
|
|
return request('/analytics/funnel/fetch', {
|
|
params: {
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
department,
|
|
recruiter_id: recruiterId,
|
|
},
|
|
})
|
|
}
|
|
|
|
/** Board column order used by the pipeline page. Rejected is last so callers
|
|
* that drop outcomes can slice it off without re-sorting. */
|
|
const BOARD_STAGE_ORDER = [
|
|
'CLOSED', 'Shortlist', 'Screening', 'Assessment', 'Interview', 'Offer',
|
|
'Approved', 'Hired', 'On Hold', 'Rejected',
|
|
]
|
|
|
|
/**
|
|
* Fold /analytics/funnel/fetch rows (11 enum statuses) onto the pipeline
|
|
* columns. Same mapping the board uses, so CLOSED stays CLOSED and only
|
|
* REJECTED is Rejected. ONHOLD / APPROVED keep their own columns.
|
|
*/
|
|
export function toBoardStageRows(funnelRows, { includeRejected = false } = {}) {
|
|
const folded = toStageCounts(
|
|
Object.fromEntries((funnelRows || []).map((r) => [r.stage, r.count || 0])),
|
|
)
|
|
const order = includeRejected
|
|
? BOARD_STAGE_ORDER
|
|
: BOARD_STAGE_ORDER.filter((s) => s !== 'Rejected')
|
|
return order.map((stage) => ({ stage, count: folded[stage] || 0 }))
|
|
}
|
|
|
|
export function hiringTrend({ months = 7, fromDate, toDate, department, recruiterId } = {}) {
|
|
return request('/analytics/hiring-trend/fetch', {
|
|
params: {
|
|
months,
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
department,
|
|
recruiter_id: recruiterId,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function sourcePerformance({ fromDate, toDate, department, recruiterId } = {}) {
|
|
return request('/analytics/source-performance/fetch', {
|
|
params: {
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
department,
|
|
recruiter_id: recruiterId,
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Applications received per job post — inbox + manual upload, the same two
|
|
* sources the funnel counts, so this card and the pipeline card always agree.
|
|
* Open reqs with zero applications are included; that emptiness is the signal.
|
|
*/
|
|
export function applicationsPerJob({ top = 10, fromDate, toDate, department, recruiterId } = {}) {
|
|
return request('/analytics/applications-per-job/fetch', {
|
|
params: {
|
|
top,
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
department,
|
|
recruiter_id: recruiterId,
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Natural-language analytics. The backend maps the question onto one whitelisted
|
|
* analytics intent, runs the same governed query the dashboard uses, and returns
|
|
* { answer, intent, params, data }. 503 means the AI service is unreachable —
|
|
* the charts on this screen are unaffected.
|
|
*/
|
|
export function ask(question) {
|
|
return request('/analytics/ask', { method: 'POST', body: { question } })
|
|
}
|
|
|
|
export function recruiterPerformance({ top = 5, fromDate, toDate, department, recruiterId } = {}) {
|
|
return request('/analytics/recruiter-performance/fetch', {
|
|
params: {
|
|
top,
|
|
from_date: fromDate,
|
|
to_date: toDate,
|
|
department,
|
|
recruiter_id: recruiterId,
|
|
},
|
|
})
|
|
}
|