import type { DsProps } from './props'; import * as React from 'react'; import { cn } from '@/lib/utils'; import { Badge } from '@/components/ui/badge'; import { useTranslation } from 'react-i18next'; /** * The domain's states, mapped to a tone and a human label in one place. * * This map is the reason StatusBadge exists: `ai_screened` appeared with three * different colours and two different spellings across the reference pages. * Adding a state here makes it correct everywhere at once. */ export const STATUS_MAP = { /* Application lifecycle */ applied: { label: 'Applied', tone: 'neutral' }, ai_screened: { label: 'AI Screened', tone: 'info' }, shortlisted: { label: 'Shortlisted', tone: 'accent' }, interview: { label: 'Interviewing', tone: 'warning' }, hired: { label: 'Hired', tone: 'success' }, rejected: { label: 'Declined', tone: 'destructive' }, /* Job posting status */ active: { label: 'Hiring', tone: 'default' }, draft: { label: 'Draft', tone: 'neutral' }, paused: { label: 'Paused', tone: 'warning' }, closed: { label: 'Closed', tone: 'destructive' }, /* Staff status */ onboarding: { label: 'Onboarding', tone: 'warning' }, inactive: { label: 'Inactive', tone: 'neutral' }, /* Score bands — the same vocabulary talentHome.js uses */ elite: { label: 'Elite', tone: 'default' }, excellent: { label: 'Excellent', tone: 'success' }, solid: { label: 'Solid', tone: 'info' }, building: { label: 'Building', tone: 'warning' }, new: { label: 'New', tone: 'neutral' }, no_score: { label: 'No score yet', tone: 'neutral' }, /* Interview verdicts */ hire: { label: 'Hire', tone: 'success' }, maybe: { label: 'Maybe', tone: 'warning' }, no: { label: 'No', tone: 'destructive' }, /* Evidence verdicts */ verified: { label: 'Verified', tone: 'success' }, needs_work: { label: 'Needs work', tone: 'warning' }, failed: { label: 'Failed', tone: 'destructive' }, pending: { label: 'Pending', tone: 'neutral' }, expired: { label: 'Expired', tone: 'destructive' }, /* Profile tiers */ Skilled: { label: 'Skilled', tone: 'default' }, 'Cross-Trained': { label: 'Cross-Trained', tone: 'info' }, Beginner: { label: 'Beginner', tone: 'neutral' }, }; /** Title-cases an unknown status so it never renders as a raw enum. */ const humanize = (status) => String(status).replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); const DOT_COLOR = { default: 'bg-krow-blue', soft: 'bg-krow-blue', accent: 'bg-krow-blue', navy: 'bg-krow-navy', success: 'bg-success', warning: 'bg-warning', destructive: 'bg-destructive', info: 'bg-info', neutral: 'bg-ink-4', outline: 'bg-ink-4', }; /** * StatusBadge — renders a domain status with its canonical colour and wording. * * `dot` adds a leading indicator for dense rows where colour alone is doing too * much work; it also gives colour-blind users a second cue alongside the label. */ /** @param {any} props */ export function StatusBadge({ status, label, tone, dot = false, size = 'default', className, ...props }: DsProps) { const { t, i18n } = useTranslation(); const entry = STATUS_MAP[status] || {}; const resolvedTone = tone || entry.tone || 'neutral'; /* STATUS_MAP stays the single list of statuses and their tones; the catalogue holds the words. Translated by STATUS rather than by the English label, so "Hired" the application state and "Hired" the badge on a worker can diverge later without one of them silently changing the other. An explicit `label` prop still wins — a caller passing its own wording has already decided, and `i18n.exists` keeps an unknown status on the humanised fallback instead of inventing a key for it. */ const key = `status.${status}`; const resolvedLabel = label || (status && i18n.exists(key) ? t(key) : null) || entry.label || humanize(status); return ( {dot && ( ); }