import { Briefcase, ClipboardList, RefreshCw, ShieldCheck, Users } from 'lucide-react' import { ToolHeader } from '../../components/ToolHeader' import { Bar, Panel, Stat } from '../../components/ui' import { num } from '../../lib/format' import type { RMFunnelStage } from '../../lib/types' import { recruitBrand } from './brand' import { useAlerts, useRecommendations, useRecruitDashboard } from './queries' // Severity maps onto the shared status palette. An unknown severity falls back to muted rather than // throwing, because the alert_type CHECK can gain values without the UI shipping at the same time. function severityColor(s: string): string { if (s === 'urgent') return 'var(--color-risk)' if (s === 'watch') return 'var(--color-warn)' return 'var(--color-muted)' } // Funnel colors are a fixed progression, deliberately NOT the engine bands. A screen through rate of 50 // percent is not a red candidate: banding a conversion percentage with scoring thresholds would conflate // two different scales. The backend emits no color for the same reason. const STAGE_COLOR: Record = { identified: 'var(--color-blue-soft)', screened: 'var(--color-screen)', submitted: 'var(--color-recruit)', approved: 'var(--color-ok)', } function stageColor(s: RMFunnelStage): string { return STAGE_COLOR[s.key] ?? 'var(--color-blue)' } export function RecruiterDashboard() { const { data: d, isLoading, isError, error, refetch, isFetching } = useRecruitDashboard() const alerts = useAlerts() const recs = useRecommendations() if (isLoading) { return Aggregating recruiting operations… } if (isError || !d) { return ( Could not load the recruiting dashboard. {(error as Error)?.message} ) } const benchEmpty = d.headline.identified === 0 return (

as of {d.as_of}

} accent="var(--color-recruit)" /> } /> } />
{/* items-start stops the grid stretching every panel to the tallest column, which is what left the short lists sitting in a tall empty box. */}
{benchEmpty ? (

No candidates yet. The funnel fills as candidates are added and screened.

) : (
    {d.funnel.map((s) => (
  • {s.label} {s.count} ({num(s.pct).toFixed(1)}%)
  • ))}
)}
{benchEmpty ? (

No candidates on the bench yet.

) : (
    {d.clearance.map((c) => (
  • {c.label} {c.count} ({num(c.pct).toFixed(1)}%)
  • ))}
)}
{d.decisions.total === 0 ? (

No slate members yet. Generate a slate in slateMATCH.

) : (
  • Approved {d.decisions.approved}
  • Rejected {d.decisions.rejected}
  • Pending {d.decisions.pending}
  • Approve rate {num(d.decisions.approve_rate).toFixed(1)}%
)}
{d.band_mix.total === 0 ? (

Nothing screened yet.

) : (
  • Proposal ready {d.band_mix.green}
  • Potential {d.band_mix.yellow}
  • Recruiting required {d.band_mix.red}
)}
{d.top_jobs.length === 0 ? (

No open requirements yet.

) : ( // A list rather than a table. Federal requirement titles and agency names are long enough // that a three column table forced the agency to wrap onto three lines and left every row a // different height. The agency reads as a subtitle instead, so the title gets the full width.
    {d.top_jobs.map((j) => (
  • {j.title}

    {j.customer_agency || 'n/a'}

    {j.submitted}
  • ))}
)}
{d.recruiters.length === 0 ? (

No active users in this organization yet.

) : ( {d.recruiters.map((r) => ( ))}
Recruiter Screened Decided Approved
{r.full_name} {r.screened} {r.decided} {r.approved}
)}
{alerts.isError ? (

Alerts are unavailable for your role.

) : !alerts.data || alerts.data.length === 0 ? (

No open alerts. The watch loop runs daily and posts clearance expiry, recompete and coverage findings here.

) : (
    {alerts.data.slice(0, 6).map((a) => (
  • {a.title}

    {a.detail ? (

    {a.detail}

    ) : null}
    {a.severity}
  • ))}
)}
{recs.isError ? (

Recommendations are unavailable for your role.

) : !recs.data || recs.data.recommendations.length === 0 ? (

No recommendations yet. These are generated from open requirements and the current bench.

) : (
    {recs.data.recommendations.map((r) => (
  • {r.action} confidence {(num(r.confidence) * 100).toFixed(0)}%

    {r.rationale}

    {r.rationale_source === 'deterministic' ? (

    deterministic rationale

    ) : null}
  • ))}
)}
    {d.empty_panels.map((p) => (
  • {p.title}

    {p.reason}

  • ))}
) }