HR-ATS-Portal/frontend/src/components/ReapplicantHistory.jsx

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>
)
}