Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/tools/screenmatch/ScreenCompare.tsx |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | frontend/src/tools/screenmatch/ScreenCompare.tsx |
| Original SHA-256 | 5ccfcf39abb8a9df2d44ee689a77156d7dbcec8a994e2d8d60d526fae0546340 |
| Git blob SHA | 48124b6cdb6ef23a401c6563198c3fea86dbf602 |
| Reading-copy SHA-256 | 5ccfcf39abb8a9df2d44ee689a77156d7dbcec8a994e2d8d60d526fae0546340 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / frontend/src/tools/screenmatch/ScreenCompare.tsx | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / frontend/src/tools/screenmatch/ScreenCompare.tsx | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓146 lines · 5.5 KB
import { useMemo } from 'react'
import { Link, useSearchParams } from 'react-router-dom'
import { useQueries, useQuery } from '@tanstack/react-query'
import { ArrowLeft, Loader2, Trophy } from 'lucide-react'
import { screenApi } from './api'
import { screenBrand } from './brand'
import { Badge, Panel } from '../../components/ui'
import { ToolHeader } from '../../components/ToolHeader'
import { Gauge } from '../../components/Gauge'
import { bandColor, num } from '../../lib/format'
import type { ScreenView } from '../../lib/types'
// Side by side comparison of up to three candidates against one requirement. Each column is the
// SAME explainable screen the single evaluation page shows, fetched through the same endpoint, so
// the numbers can never disagree between views.
export function ScreenCompare() {
const [params] = useSearchParams()
const requirementId = params.get('req') ?? ''
const candidateIds = useMemo(
() => (params.get('c') ?? '').split(',').filter(Boolean).slice(0, 3),
[params],
)
const requirement = useQuery({
queryKey: ['screenmatch', 'requirement', requirementId],
queryFn: () => screenApi.requirement(requirementId),
enabled: !!requirementId,
})
const screens = useQueries({
queries: candidateIds.map((cid) => ({
queryKey: ['screenmatch', 'compare', cid, requirementId],
queryFn: async () => ({
candidate: await screenApi.candidate(cid),
view: await screenApi.screen(cid, requirementId),
}),
enabled: !!requirementId,
})),
})
const loaded = screens.filter((s) => s.data).map((s) => s.data!)
const bestTotal = loaded.length
? Math.max(...loaded.map((s) => num(s.view.match.total_score)))
: 0
return (
<div className="space-y-4">
<ToolHeader name={screenBrand.name} tagline="Side by side candidate comparison" accent={screenBrand.accent} />
<Link to="/screen" className="inline-flex items-center gap-1.5 text-sm text-muted hover:text-ink">
<ArrowLeft size={15} /> Back to Screening Queue
</Link>
{!requirementId || candidateIds.length < 2 ? (
<Panel>
<p className="py-6 text-center text-sm text-muted">
Pick two or three candidates in the Screening Queue and choose Compare.
</p>
</Panel>
) : (
<>
{requirement.data && (
<Panel className="p-4">
<div className="text-[11px] font-bold uppercase tracking-wider text-muted">Comparing against</div>
<div className="text-base font-bold">{requirement.data.title}</div>
</Panel>
)}
<div className={`grid grid-cols-1 gap-4 ${candidateIds.length === 3 ? 'lg:grid-cols-3' : 'lg:grid-cols-2'}`}>
{screens.map((s, i) => (
<div key={candidateIds[i]}>
{s.isLoading && (
<Panel className="flex items-center justify-center gap-2 py-16 text-muted">
<Loader2 className="animate-spin" size={16} /> Scoring…
</Panel>
)}
{s.isError && (
<Panel>
<p className="text-sm text-risk">{(s.error as Error).message}</p>
</Panel>
)}
{s.data && (
<CompareColumn
name={s.data.candidate.full_name}
view={s.data.view}
best={num(s.data.view.match.total_score) === bestTotal && loaded.length > 1}
/>
)}
</div>
))}
</div>
</>
)}
</div>
)
}
function CompareColumn({ name, view, best }: { name: string; view: ScreenView; best: boolean }) {
const m = view.match
return (
<Panel className={best ? 'ring-1 ring-gold/60' : ''}>
<div className="flex items-center justify-between">
<h3 className="text-base font-bold">{name}</h3>
{best && (
<Badge color="var(--color-gold)" subtle>
<Trophy size={12} /> Strongest fit
</Badge>
)}
</div>
<div className="mt-3 grid place-items-center">
<Gauge value={num(m.total_score)} color={bandColor(m.band)} status={m.band_label} />
</div>
<div className="mt-4 space-y-2">
{m.trace.map((t) => (
<div key={t.component}>
<div className="flex items-center justify-between text-xs">
<span className="text-muted">{t.component}</span>
<span className="font-semibold tabular-nums">{num(t.raw).toFixed(0)}</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-navy-800">
<div
className="h-full rounded-full"
style={{ width: `${Math.min(100, num(t.raw))}%`, background: bandColor(m.band) }}
/>
</div>
</div>
))}
</div>
<div className="mt-3 border-t border-line/60 pt-2.5">
<div className="mb-1 text-[11px] font-bold uppercase tracking-wider text-muted">Gaps</div>
{m.gap_summary.length === 0 ? (
<p className="text-xs" style={{ color: 'var(--color-ok)' }}>
No mandatory gaps.
</p>
) : (
<ul className="space-y-1">
{m.gap_summary.map((g) => (
<li key={g} className="text-xs text-warn">
{g}
</li>
))}
</ul>
)}
</div>
<p className="mt-2 text-[10px] text-faint">Weight profile {m.weight_profile_version}</p>
</Panel>
)
}