156 lines
4.9 KiB
JavaScript
156 lines
4.9 KiB
JavaScript
import { Badge } from '../ui/primitives'
|
|
import { STAGE_FROM_STATUS } from '../api/pipeline'
|
|
import { fmtDate } from '../lib/format'
|
|
|
|
const SOURCE_LABEL = {
|
|
inbox: 'Email',
|
|
manual: 'Manual',
|
|
form: 'Form',
|
|
ats: 'ATS',
|
|
filtered: 'Email',
|
|
}
|
|
|
|
const STAGE_BADGE = {
|
|
Shortlist: 'b-indigo',
|
|
Screening: 'b-teal',
|
|
Assessment: 'b-purple',
|
|
Interview: 'b-amber',
|
|
Offer: 'b-green',
|
|
Approved: 'b-green',
|
|
Hired: 'b-green',
|
|
'On Hold': 'b-amber',
|
|
Rejected: 'b-gray',
|
|
'Rejected — wrong format': 'b-red',
|
|
'No job assigned': 'b-gray',
|
|
}
|
|
|
|
export function applicationStatusLabel(status, item) {
|
|
if (item?.rejection_reason === 'wrong_format' || String(status || '').toUpperCase() === 'WRONG_FORMAT') {
|
|
return 'Rejected — wrong format'
|
|
}
|
|
const assigned = Boolean(item?.job_post_id || item?.jobPostId || (item?.source === 'form' && (item.job_title || item.jobTitle)))
|
|
if (!assigned && (status == null || status === '' || String(status).toUpperCase() === 'CLOSED')) {
|
|
return 'No job assigned'
|
|
}
|
|
if (status == null || status === '') return 'Shortlist'
|
|
const key = String(status).toUpperCase()
|
|
if (STAGE_FROM_STATUS[key]) return STAGE_FROM_STATUS[key]
|
|
const extras = {
|
|
UNREAD: 'Unread',
|
|
IMPORTED: 'Imported',
|
|
PROCESSED: 'Processed',
|
|
COMPLETED: 'ATS scored',
|
|
FAILED: 'ATS failed',
|
|
BANKED: 'CV bank',
|
|
}
|
|
if (extras[key]) return extras[key]
|
|
return key.charAt(0) + key.slice(1).toLowerCase()
|
|
}
|
|
|
|
export function previousApplicationsOf(row) {
|
|
if (!row) return []
|
|
if (Array.isArray(row.previousApplications)) return row.previousApplications
|
|
if (Array.isArray(row.previous_applications)) return row.previous_applications
|
|
return []
|
|
}
|
|
|
|
function hasAssignedJob(item) {
|
|
if (!item) return false
|
|
if (item.job_post_id || item.jobPostId) return true
|
|
return item.source === 'form' && Boolean(item.job_title || item.jobTitle)
|
|
}
|
|
|
|
export function isReapplicant(row) {
|
|
if (!row) return false
|
|
const previous = previousApplicationsOf(row)
|
|
if (previous.length) return previous.some(hasAssignedJob)
|
|
return row.isReapplicant === true || row.is_reapplicant === true
|
|
}
|
|
|
|
export function previousApplicationsTip(row) {
|
|
const items = previousApplicationsOf(row)
|
|
if (!items.length) return 'Applied before'
|
|
return items.map((item) => {
|
|
const job = item.job_title || item.jobTitle || 'No job assigned'
|
|
return `${job} — ${applicationStatusLabel(item.status, item)}`
|
|
}).join('\n')
|
|
}
|
|
|
|
/** Compact chip for tables, kanban cards, and inbox rows. */
|
|
export function ReappliedBadge({ row, className = '' }) {
|
|
if (!isReapplicant(row)) return null
|
|
const count = previousApplicationsOf(row).filter(hasAssignedJob).length
|
|
return (
|
|
<span
|
|
className={`badge b-amber badge-plain ${className}`.trim()}
|
|
style={{ marginLeft: 6, fontSize: 10, padding: '1px 6px' }}
|
|
title={previousApplicationsTip(row)}
|
|
>
|
|
Reapplied{count > 1 ? ` · ${count}` : ''}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
/** Full prior-job list for profile / inbox / add-candidate. */
|
|
export function PreviousApplications({ row, title = 'Previous applications' }) {
|
|
const items = previousApplicationsOf(row)
|
|
if (!items.length) return null
|
|
return (
|
|
<div
|
|
className="card"
|
|
style={{
|
|
boxShadow: 'none',
|
|
background: 'var(--warning-soft)',
|
|
border: '1px solid var(--warning)',
|
|
marginBottom: 18,
|
|
}}
|
|
>
|
|
<div className="card-body">
|
|
<div
|
|
style={{
|
|
fontSize: 12,
|
|
color: 'var(--warning)',
|
|
fontWeight: 700,
|
|
textTransform: 'uppercase',
|
|
marginBottom: 10,
|
|
}}
|
|
>
|
|
{title}
|
|
</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
{items.map((item, idx) => {
|
|
const stage = applicationStatusLabel(item.status, item)
|
|
const key = [
|
|
item.source,
|
|
item.inbox_id,
|
|
item.manual_upload_candidate_id,
|
|
item.form_data_id,
|
|
item.candidate_id,
|
|
item.job_post_id,
|
|
idx,
|
|
].filter(Boolean).join(':')
|
|
return (
|
|
<div
|
|
key={key}
|
|
className="flex items-center gap-8"
|
|
style={{ justifyContent: 'space-between', flexWrap: 'wrap' }}
|
|
>
|
|
<div style={{ minWidth: 0 }}>
|
|
<div className="fw-600" style={{ fontSize: 13 }}>
|
|
{item.job_title || item.jobTitle || 'No job assigned'}
|
|
</div>
|
|
<div className="cell-sub">
|
|
{SOURCE_LABEL[item.source] || item.source || 'Application'}
|
|
{item.applied_at ? ` · ${fmtDate(item.applied_at)}` : ''}
|
|
</div>
|
|
</div>
|
|
<Badge className={STAGE_BADGE[stage] || 'b-gray'}>{stage}</Badge>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|