Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / frontend/src/components/OutcomeBar.tsx |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | frontend/src/components/OutcomeBar.tsx |
| Original SHA-256 | f077470f1b07f13541ac3b9148a09e44660ac2fc6d80b143e664fd997e8f3b22 |
| Git blob SHA | 1d12fa1d3d12e66f22387a973dd0ea30b71376d0 |
| Reading-copy SHA-256 | f077470f1b07f13541ac3b9148a09e44660ac2fc6d80b143e664fd997e8f3b22 |
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/components/OutcomeBar.tsx | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / frontend/src/components/OutcomeBar.tsx | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓131 lines · 5.3 KB
import { useState } from 'react'
import { CheckCircle2, Loader2, PauseCircle, XCircle } from 'lucide-react'
import { useRecordOutcome } from '../tools/screenmatch/queries'
// One click screening outcome capture: Advance, Hold, or Reject with a reason code. Every action
// posts one outcome event, the feedback signal the platform learns from. Zero friction: pick a
// reason, click once, done.
const REASONS: Record<string, { code: string; label: string }[]> = {
advance: [
{ code: 'strong_skills_match', label: 'Strong skills match' },
{ code: 'clearance_fit', label: 'Clearance fits the role' },
{ code: 'customer_experience', label: 'Prior customer experience' },
{ code: 'overall_fit', label: 'Strong overall fit' },
],
hold: [
{ code: 'pending_clearance', label: 'Pending clearance action' },
{ code: 'awaiting_availability', label: 'Awaiting availability' },
{ code: 'needs_review', label: 'Needs a second review' },
{ code: 'backup_candidate', label: 'Hold as a backup' },
],
reject: [
{ code: 'skills_gap', label: 'Skills gap' },
{ code: 'clearance_gap', label: 'Clearance gap' },
{ code: 'experience_gap', label: 'Experience gap' },
{ code: 'location_unavailable', label: 'Location or availability' },
],
}
const SIGNALS: Record<string, number> = { advance: 1, hold: 0, reject: -1 }
export function OutcomeBar({ candidateId, requirementId }: { candidateId: string; requirementId: string }) {
const record = useRecordOutcome()
const [decided, setDecided] = useState<string | null>(null)
const [pendingAction, setPendingAction] = useState<string | null>(null)
const [reason, setReason] = useState('')
const submit = (action: string, reasonCode: string) => {
record.mutate(
{
module: 'screen',
subjectType: 'candidate',
subjectId: candidateId,
requirementId,
eventType: 'screening_decision',
signalValue: SIGNALS[action],
reasonCode,
},
{ onSuccess: () => setDecided(action) },
)
setPendingAction(null)
setReason('')
}
if (decided) {
const labels: Record<string, string> = {
advance: 'Advanced. The decision was recorded and feeds future recommendations.',
hold: 'Placed on hold. The decision was recorded.',
reject: 'Rejected. The decision was recorded and feeds future recommendations.',
}
return (
<div className="flex items-center gap-2 rounded-xl border border-line/60 bg-surface/70 px-4 py-3 text-sm">
<CheckCircle2 size={16} style={{ color: 'var(--color-ok)' }} />
<span className="text-muted">{labels[decided]}</span>
</div>
)
}
if (pendingAction) {
return (
<div className="flex flex-wrap items-center gap-2 rounded-xl border border-line/60 bg-surface/70 px-4 py-3">
<span className="text-sm font-semibold capitalize">{pendingAction}:</span>
<select
className="rounded-lg border border-line/70 bg-navy-900 px-2.5 py-1.5 text-sm text-ink outline-none focus:border-blue"
value={reason}
onChange={(e) => setReason(e.target.value)}
autoFocus
>
<option value="">Pick a reason…</option>
{REASONS[pendingAction].map((r) => (
<option key={r.code} value={r.code}>
{r.label}
</option>
))}
</select>
<button
disabled={!reason || record.isPending}
onClick={() => submit(pendingAction, reason)}
className="flex items-center gap-1.5 rounded-lg bg-gradient-to-r from-blue to-navy-700 px-3.5 py-1.5 text-sm font-semibold text-white hover:brightness-110 disabled:opacity-50"
>
{record.isPending && <Loader2 size={13} className="animate-spin" />} Confirm
</button>
<button
onClick={() => setPendingAction(null)}
className="rounded-lg border border-line/70 px-3 py-1.5 text-sm text-muted hover:text-ink"
>
Cancel
</button>
{record.isError && <span className="text-xs text-risk">Could not record the decision. Try again.</span>}
</div>
)
}
return (
<div className="flex flex-wrap items-center gap-2 rounded-xl border border-line/60 bg-surface/70 px-4 py-3">
<span className="mr-1 text-[11px] font-bold uppercase tracking-wider text-muted">Screening decision</span>
<button
onClick={() => setPendingAction('advance')}
className="flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-semibold text-white hover:brightness-110"
style={{ background: 'var(--color-ok)' }}
>
<CheckCircle2 size={14} /> Advance
</button>
<button
onClick={() => setPendingAction('hold')}
className="flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-semibold text-white hover:brightness-110"
style={{ background: 'var(--color-warn)' }}
>
<PauseCircle size={14} /> Hold
</button>
<button
onClick={() => setPendingAction('reject')}
className="flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-semibold text-white hover:brightness-110"
style={{ background: 'var(--color-risk)' }}
>
<XCircle size={14} /> Reject
</button>
<span className="ml-auto text-[11px] text-faint">A person decides. The decision is recorded, never automated.</span>
</div>
)
}