import { useState } from 'react' import { ArrowLeft, Briefcase, CalendarClock, CheckCircle2, FileText, ShieldAlert } from 'lucide-react' import { Link, useParams } from 'react-router-dom' import { ToolHeader } from '../../components/ToolHeader' import { Modal, Panel } from '../../components/ui' import type { ROffer, RPlacement } from '../../lib/types' import { recruitBrand } from './brand' import { useAssignment, useCreatePlacement, useEndPlacement, useExtendOffer, useRespondToOffer, useScheduleInterview, useUpdateInterview, } from './assignmentQueries' import { useDispositions } from './pipelineQueries' import { ActivityFeed } from './ActivityFeed' const inputCls = 'w-full rounded-lg border border-line/70 bg-navy-900 px-3 py-2 text-sm text-ink outline-none focus:border-recruit' 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) } function statusPill(status: string): string { switch (status) { case 'accepted': case 'active': return 'var(--color-ok)' case 'extended': case 'pending_start': return 'var(--color-gold)' case 'declined': case 'rescinded': case 'fell_off': case 'terminated': return 'var(--color-risk)' default: return 'var(--color-muted)' } } function Pill({ text }: { text: string }) { return ( {text.replace(/_/g, ' ')} ) } export function Assignment() { const { appId } = useParams() const q = useAssignment(appId ?? null) const disp = useDispositions() const schedule = useScheduleInterview(appId ?? '') const updateIv = useUpdateInterview(appId ?? '') const extend = useExtendOffer(appId ?? '') const respond = useRespondToOffer(appId ?? '') const place = useCreatePlacement(appId ?? '') const end = useEndPlacement(appId ?? '') const [ivOpen, setIvOpen] = useState(false) const [round, setRound] = useState('1') const [kind, setKind] = useState('client') const [when, setWhen] = useState('') const [who, setWho] = useState('') const [offerOpen, setOfferOpen] = useState(false) const [empType, setEmpType] = useState('contract') const [bill, setBill] = useState('') const [pay, setPay] = useState('') const [salary, setSalary] = useState('') const [feePct, setFeePct] = useState('') const [placeOpen, setPlaceOpen] = useState(false) const [startDate, setStartDate] = useState('') const [guarantee, setGuarantee] = useState('90') const [ending, setEnding] = useState(null) const [endStatus, setEndStatus] = useState('completed') const [endDate, setEndDate] = useState('') const [endCode, setEndCode] = useState('') if (q.isLoading) return Loading assignment… if (q.isError || !q.data) { return ( Could not load this assignment. {(q.error as Error)?.message} ) } const a = q.data const livePlacement = a.placements.find((p) => p.live) const acceptedOffer = a.offers.find((o) => o.status === 'accepted') const marginLine = (o: ROffer | RPlacement) => { if (o.margin.fee) return `Fee ${money(o.margin.fee)}` if (!o.margin.gross_per_hour) return '' const pct = o.margin.margin_pct ? ` · ${o.margin.margin_pct}%` : '' return `Margin ${money(o.margin.gross_per_hour)}/hr${pct}` } return (
Job orders

{a.candidate_name}

{a.interviews.length === 0 ? (

No interviews scheduled.

) : (
    {a.interviews.map((v) => (
  • Round {v.round} · {v.kind.replace(/_/g, ' ')}

    {v.scheduled_at ? new Date(v.scheduled_at).toLocaleString() : 'No time set'} {v.interviewer ? ` · ${v.interviewer}` : ''}

    {v.outcome === 'scheduled' && (
    {/* A concluded outcome needs a time, so completing one stamps the moment it was recorded rather than asking for a date nobody has to hand. */} {(['passed', 'failed', 'no_show'] as const).map((o) => ( ))}
    )}
  • ))}
)} {updateIv.isError &&

{(updateIv.error as Error).message}

}
{/* Only one offer may be live at a time, so the button says why it is unavailable rather than vanishing and leaving the reason to be guessed. */}
{a.offers.length === 0 ? (

No offers extended.

) : (
    {a.offers.map((o) => (
  • {o.employment_type.replace(/_/g, ' ')}

    {o.bill_rate ? `Bill ${money(o.bill_rate)}/hr · Pay ${money(o.pay_rate)}/hr · ` : ''} {o.salary ? `Salary ${money(o.salary)} · ` : ''} {marginLine(o)}

    {o.decline_reason &&

    {o.decline_reason}

    }
    {/* Only the moves the database will accept are offered. */} {o.next_statuses.map((s) => ( ))}
  • ))}
)} {(respond.isError || extend.isError) && (

{((respond.error ?? extend.error) as Error)?.message}

)}
{a.placements.length === 0 ? (

No placement yet. Recording one moves this candidate to placed and takes a seat on the job order.

) : (
    {a.placements.map((p) => (
  • {p.requisition_title || 'Assignment'} {p.guarantee.breached && ( guarantee breached )}

    {p.start_date} {p.end_date ? ` to ${p.end_date}` : ''} · {marginLine(p)} {p.guarantee.ends_on && !p.end_date ? ` · guarantee to ${p.guarantee.ends_on}${ p.guarantee.days_remaining !== undefined ? p.guarantee.days_remaining >= 0 ? ` (${p.guarantee.days_remaining}d left)` : ' (elapsed)' : '' }` : ''}

    {p.live && ( )}
  • ))}
)} {(place.isError || end.isError) && (

{((place.error ?? end.error) as Error)?.message}

)}
setIvOpen(false)} title="Schedule an interview">
{schedule.isError &&

{(schedule.error as Error).message}

}
setOfferOpen(false)} title="Extend an offer">
{empType === 'direct_hire' ? (
) : (
)} {extend.isError &&

{(extend.error as Error).message}

}
setPlaceOpen(false)} title="Record a placement">

This moves {a.candidate_name} to placed and takes a seat on the job order. If the job order is already full it will be refused.

{empType === 'direct_hire' ? ( ) : (
)} {place.isError &&

{(place.error as Error).message}

}
setEnding(null)} title="End this assignment">
{(['completed', 'terminated', 'fell_off'] as const).map((s) => ( ))}
{/* A completed assignment never breaches the guarantee, however short. Ending it early was the client's call, not a failure to deliver. */} {endStatus !== 'completed' && ending?.guarantee.ends_on && (

The guarantee runs to {ending.guarantee.ends_on}. Ending this way inside that window puts the fee at risk.

)} {end.isError &&

{(end.error as Error).message}

}
) }