import { useMemo, useState } from 'react'
import { AlertTriangle, ArrowRight, Globe, Info, TrendingUp } from 'lucide-react'
import { ToolHeader } from '../../components/ToolHeader'
import { Panel } from '../../components/ui'
import type { RTimeToFill } from '../../lib/types'
import { recruitBrand } from './brand'
import { useAnalytics } from './analyticsQueries'
const chip = (on: boolean) =>
`rounded-full px-3 py-1 text-xs font-semibold transition ${
on
? 'bg-recruit/25 text-recruit shadow-[inset_0_0_0_1px_var(--color-recruit)]'
: 'bg-navy-900 text-faint hover:text-muted border border-line/60'
}`
const money = (v?: string) => {
if (!v) return ''
const [w, f = ''] = v.split('.')
const t = f.replace(/0+$/, '')
return '$' + w + '.' + (t.length <= 2 ? t.padEnd(2, '0') : t)
}
// The time to fill card, which is the whole point of this screen.
//
// Both figures are shown side by side and labelled. The naive one is what every other tool reports; the
// corrected one keeps the still open orders in the calculation. When they differ, the gap IS the
// survivorship bias, and showing only one of them is how that bias goes unnoticed.
function TimeToFillCard({ title, hint, ttf }: { title: string; hint: string; ttf: RTimeToFill }) {
const biased =
ttf.median_reached && ttf.naive_median_days && ttf.median_days
? Number(ttf.median_days) - Number(ttf.naive_median_days)
: null
return (
No requisitions opened in this window. Median, corrected
{Number(ttf.median_days).toFixed(1)}
days
Not yet reached
keeps the {ttf.still_open} still open in the calculation
Median, filled only
{ttf.naive_median_days ? Number(ttf.naive_median_days).toFixed(1) : '—'}
{ttf.naive_median_days && days}
ignores everything still open
{ttf.cohort} opened · {ttf.filled} filled · {ttf.still_open} still open
{x.l}
{x.n}
Too few resolved job orders to report a rate. The counts above are exact.
) : (Fill rate, counting cancellations {v.fill_rate.rate_of_resolved}%
Fill rate, excluding cancellations {v.fill_rate.rate_of_decided ?? '—'}%
A cancelled order is a role the client pulled. Both denominators are shown because the definition genuinely forks and picking one silently makes two reports disagree.
Nothing open.
) : (A distribution, not an average. An average age hides the tail, and the tail is the problem.
No candidates in this window.
) : (First touch, never last. Attributing a placement to the most recent contact is how every channel eventually looks like a referral.
{x.l}
{x.n}
Live contract margin {money(v.placements.contract_gross_per_hour)}/hr
)} {v.placements.direct_hire_fees && (Direct hire fees {money(v.placements.direct_hire_fees)}
)} {v.placements.guarantee_breaches > 0 && (Guarantee breaches {v.placements.guarantee_breaches}
)}Hourly margin is not annualised. Doing so would assume a full time full year assignment nobody agreed to.
No activity logged in this window.
) : (