import { useState } from 'react'
import { ArrowLeft, Clock, PauseCircle, Target, UserCheck, UserMinus, UserPlus, Users } from 'lucide-react'
import { Link, useParams } from 'react-router-dom'
import { ToolHeader } from '../../components/ToolHeader'
import { Modal, Panel } from '../../components/ui'
import { useAuth } from '../../lib/useAuth'
import { recruitBrand } from './brand'
import {
useChangeStatus,
useClaimRequisition,
useReleaseRequisition,
useRequisition,
} from './requisitionQueries'
import {
PRIORITY_LABEL,
REQ_TRANSITIONS,
STATUS_ACTION,
STATUS_LABEL,
priorityColor,
rateRange,
statusColor,
targetText,
} from './statusUi'
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'
function Stat({ label, value, hint, icon }: { label: string; value: string; hint?: string; icon: React.ReactNode }) {
return (
{icon}
{label}
{value}
{hint &&
{hint}
}
)
}
export function RequisitionDetail() {
const { reqId } = useParams()
const { principal } = useAuth()
const q = useRequisition(reqId ?? null)
const change = useChangeStatus()
const claim = useClaimRequisition()
const release = useReleaseRequisition()
const [pending, setPending] = useState(null)
const [note, setNote] = useState('')
if (q.isLoading) return Loading job order…
if (q.isError || !q.data) {
return (
Could not load this job order. {(q.error as Error)?.message}
)
}
const r = q.data
const mine = !!r.owner_user_id && r.owner_user_id === principal?.user_id
const canEdit = mine || !r.owner_user_id
const t = targetText(r)
const moves = REQ_TRANSITIONS[r.status] ?? []
const confirmMove = () => {
if (!pending) return
change.mutate(
{ id: r.id, expected_updated_at: r.updated_at, status: pending, note },
{
onSuccess: () => {
setPending(null)
setNote('')
},
},
)
}
return (
All job orders
Pipeline
{r.title}
{STATUS_LABEL[r.status] ?? r.status}
{r.client_id ? (
{r.client_name}
) : (
'No client'
)}
{r.hiring_manager_name ? ` · ${r.hiring_manager_name}` : ''} · {r.openings} opening
{r.openings === 1 ? '' : 's'} · {r.employment_type.replace(/_/g, ' ')}
· {PRIORITY_LABEL[r.priority]} priority
{r.owner_user_id ? (
<>
{mine ? 'You are working this' : `Worked by ${r.owner_name}`}
{mine && (
release.mutate(r.id)}
disabled={release.isPending}
className="flex items-center gap-1.5 rounded-lg border border-line/70 px-3 py-1.5 text-xs font-semibold text-muted hover:text-ink disabled:opacity-50"
>
Release
)}
>
) : (
claim.mutate(r.id)}
disabled={claim.isPending}
className="flex items-center gap-1.5 rounded-lg border border-recruit/50 px-3 py-1.5 text-xs font-semibold text-recruit hover:bg-recruit/10 disabled:opacity-50"
>
Take this job order
)}
{!canEdit && (
{r.owner_name} is working this job order. Talk to them before changing it, or ask an admin to
reassign it.
)}
{/* The three clocks. Both the wall clock and the worked time are shown, because both are
legitimate and the industry does not agree on which to report. Showing only one invites the
question of what it excludes. */}
}
/>
} />
}
/>
}
/>
{(r.bill_rate_min || r.bill_rate_max || r.salary_min || r.salary_max) && (
{rateRange(r.bill_rate_min, r.bill_rate_max) && `Bill ${rateRange(r.bill_rate_min, r.bill_rate_max)}`}
{r.pay_rate_min || r.pay_rate_max
? ` · Pay ${rateRange(r.pay_rate_min, r.pay_rate_max)}`
: ''}
{r.salary_min || r.salary_max ? ` · Salary ${rateRange(r.salary_min, r.salary_max, '')}` : ''}
)}
{canEdit && moves.length > 0 && (
Move to
{/* Only legal destinations are offered. The server refuses anything else regardless, so this
is a convenience rather than the rule, but offering a button that always fails is worse
than offering fewer buttons. */}
{moves.map((m) => (
setPending(m)}
className="rounded-lg border px-3 py-1.5 text-xs font-semibold hover:brightness-125"
style={{ borderColor: `color-mix(in srgb, ${statusColor(m)} 50%, transparent)`, color: statusColor(m) }}
>
{STATUS_ACTION[m] ?? m}
))}
)}
{r.description && {r.description}
}
{r.events.length === 0 ? (
No history yet.
) : (
{[...r.events].reverse().map((e) => (
{e.from_status
? `${STATUS_LABEL[e.from_status] ?? e.from_status} to ${STATUS_LABEL[e.to_status] ?? e.to_status}`
: `Opened as ${STATUS_LABEL[e.to_status] ?? e.to_status}`}
{e.from_status && e.days_in_from > 0 && (
after {e.days_in_from.toFixed(1)}d
)}
{e.note && {e.note}
}
{new Date(e.occurred_at).toLocaleString()}
{e.actor_user_name ? ` · ${e.actor_user_name}` : ''}
))}
)}
{
setPending(null)
setNote('')
}}
title={pending ? (STATUS_ACTION[pending] ?? pending) : ''}
>
This job order moves from {STATUS_LABEL[r.status] ?? r.status} to{' '}
{pending ? (STATUS_LABEL[pending] ?? pending) : ''} .
{pending === 'on_hold' && (
Time on hold is subtracted from how long this order took to fill, so a client freeze does not
count against the desk.
)}
Why (optional)
{/* The note is what makes the history readable a year later. "Client froze the program" and
"candidate accepted elsewhere" are the same transition and different stories. */}
{change.isError &&
{(change.error as Error).message}
}
{
setPending(null)
setNote('')
}}
className="rounded-lg border border-line/70 px-3 py-2 text-sm font-semibold text-muted hover:text-ink"
>
Cancel
Confirm
)
}