import React from 'react'; import { Shield, Star, TrendingUp } from 'lucide-react'; import { Gauge, gaugeClasses } from '@mui/x-charts/Gauge'; import { Badge } from '@/components/ui/badge'; import { getScoreBand } from '@/lib/talentHome'; import ReferencesEndorsements from '@/components/krow/ReferencesEndorsements'; const FICO_MIN = 300; const FICO_MAX = 850; function careerScoreFromApp(app) { const s = Math.max(0, Math.min(100, Math.round(app?.ai_score || 0))); return 300 + Math.round((s / 100) * 550); } function marketRangeFromApp(app) { const s = Math.max(0, Math.min(100, Math.round(app?.ai_score || 0))); const uplift = Math.max(0, Math.round((s - 70) / 5)); const low = 20 + uplift; return `$${low}-$${low + 4}/hr`; } function nextTierFromApp(app) { const s = Math.round(app?.ai_score || 0); const yrs = app?.years_experience || 0; if (s >= 85 || yrs >= 8) return 'Skilled'; if (s >= 60 || yrs >= 3) return 'Cross-Trained'; return 'Beginner'; } /** * The score, its band, and what both are worth — one block, read top to bottom. * * The gauge is the anchor rather than an ornament beside the numbers, so it is * sized to be the first thing seen and carries the score inside it: label, value * and range stacked in the arc's own empty middle, which is the only place where * "834" and "300–850" read as one statement instead of two. * * Everything to the right is interpretation — where this person sits and where * they go next — kept at a subordinate weight so the eye lands on the score * first and travels outward. */ const GAUGE_W = 184; function CareerScorePrimary({ application }) { const score = careerScoreFromApp(application); const band = getScoreBand(Math.round(application?.ai_score || 0)); return (
{/* Layer 1 — the primary signal */}
null} skipAnimation aria-hidden="true" sx={{ width: GAUGE_W, height: GAUGE_W, marginTop: `-${Math.round(GAUGE_W * 0.215)}px`, /* Brand blue for the score it earned, a hairline-weight neutral for the rest of the range — the same pairing the rest of the product uses for "achieved against available". */ [`& .${gaugeClasses.valueArc}`]: { fill: '#0838E0' }, [`& .${gaugeClasses.referenceArc}`]: { fill: 'hsl(var(--border))' }, }} /> {/* The arc's middle is empty by construction; the score belongs in it. Stacked with room between each line so the label never crowds the number it is labelling. */}
Career score {score} {FICO_MIN}–{FICO_MAX}
{/* Layer 2 — the band this score falls in. A status marker, sized like one: the colour is the band's own, carried by a dot rather than by a filled pill the width of the column. */}
{/* Layer 2 — what the score is worth, and what comes next. */}
Potential market value
{marketRangeFromApp(application)}
Next promotion
{nextTierFromApp(application)}
); } const COMM_LABELS = [ { min: 85, label: 'Excellent' }, { min: 70, label: 'Good' }, { min: 55, label: 'Average' }, { min: 0, label: 'Needs Work' }, ]; const commLabel = (score) => { const v = score || 0; return (COMM_LABELS.find((c) => v >= c.min) || COMM_LABELS[COMM_LABELS.length - 1]).label; }; const COMPANY_COLORS = [ 'bg-[#0838E0]', 'bg-[#333F48]', 'bg-[#062BAF]', ]; const colorFor = (name) => { let hash = 0; for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash); return COMPANY_COLORS[Math.abs(hash) % COMPANY_COLORS.length]; }; const initialsFor = (name) => { const parts = name.trim().split(/\s+/); if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase(); return (parts[0][0] + parts[1][0]).toUpperCase(); }; /** * A supporting signal — evidence for the score, not a headline of its own. * * Label and value on one line, no border, no tile: five bordered boxes under the * gauge read as five more metrics competing with it, which is exactly the * hierarchy this card is meant to establish. Values share one ink colour so no * single signal shouts louder than the rest. */ /** @param {any} props */ function SupportingSignal({ label, value, suffix = '', icon: Icon = null }) { const shown = value != null && value !== '' ? value : '—'; return (
{label}
{Icon &&
); } export default function WorkforceReputation({ application }) { const breakdown = application?.score_breakdown || {}; const companies = application?.companies_worked || []; const clientRating = application?.client_rating || 0; const reliability = breakdown.reliability; const attendance = breakdown.attendance_expectations; const leadership = breakdown.leadership_expectations; const communication = breakdown.communication_style; const commText = communication != null ? commLabel(communication) : null; const hasScores = [reliability, attendance, leadership, communication].some((v) => v != null); const hasData = hasScores || companies.length > 0 || clientRating > 0 || (application?.ai_score || 0) > 0; if (!hasData) return null; return (

WORKFORCE REPUTATION

{/* Layer 3 — supporting evidence. A hairline is enough to separate it from the score; another container would make it a second card. */}
0 ? clientRating.toFixed(1) : null} icon={Star} />
{companies.length > 0 && (

WORKED

{companies.map((c, i) => (
{initialsFor(c)}
{c}
))}
)}
); }