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. */}
{band.label}
{/* 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 && }
{shown}
{suffix && shown !== '—' && {suffix}}
);
}
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 (
{/* 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) => (
))}
)}
);
}