fix error line
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Avatar, Badge, Button, Drawer, EmptyState, ProgressBar, SearchInput, SegmentedToggle,
|
||||
Avatar, Badge, Button, Drawer, EmptyState, SearchInput, SegmentedToggle,
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Skeleton, StatusBadge, toast,
|
||||
} from '@/components/ds';
|
||||
import {
|
||||
@@ -59,7 +59,7 @@ const SORTS = {
|
||||
starved: { label: 'Fewest applicants', compare: (a, b) => a.stats.applied - b.stats.applied },
|
||||
updated: {
|
||||
label: 'Recently updated',
|
||||
compare: (a, b) => new Date(b.updated_date || b.created_date) - new Date(a.updated_date || a.created_date),
|
||||
compare: (a, b) => new Date(b.updated_date || b.created_date).getTime() - new Date(a.updated_date || a.created_date).getTime(),
|
||||
},
|
||||
title: { label: 'Title A–Z', compare: (a, b) => a.title.localeCompare(b.title) },
|
||||
};
|
||||
@@ -72,7 +72,8 @@ const HEALTH_TONE = {
|
||||
};
|
||||
|
||||
/** Hiring health as a small filled chip — it is a state, so it reads as one. */
|
||||
function HealthChip({ health, className }) {
|
||||
/** @param {any} props */
|
||||
function HealthChip({ health, className = '' }) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
@@ -740,43 +741,48 @@ function PositionDrawer({ position, onClose, onViewCandidates, onOpenPosition })
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Hiring status */}
|
||||
<section>
|
||||
<SectionTitle title="Hiring status" />
|
||||
<dl className="grid grid-cols-2 gap-x-4 gap-y-3 sm:grid-cols-3">
|
||||
{[
|
||||
{ label: 'Applicants', value: s.applied },
|
||||
{ label: 'Shortlisted', value: s.shortlisted },
|
||||
{ label: 'Interviews', value: s.interviews },
|
||||
{ label: 'Hired', value: s.hired },
|
||||
{ label: 'Qualified (70+)', value: s.qualified },
|
||||
{ label: 'Avg score', value: s.avgScore || '—' },
|
||||
].map((m) => (
|
||||
<div key={m.label}>
|
||||
<dt className="text-[10px] font-medium uppercase tracking-wide text-ink-4">{m.label}</dt>
|
||||
<dd className="font-heading text-title font-bold tabular-nums text-ink-1">{m.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="mb-1.5 flex items-baseline justify-between">
|
||||
<span className="text-caption text-ink-2">Screening progress</span>
|
||||
<span className="text-caption font-semibold tabular-nums text-ink-1">{s.coverage}%</span>
|
||||
</div>
|
||||
<ProgressBar
|
||||
value={s.coverage}
|
||||
tone={s.coverage === 100 ? 'success' : s.coverage >= 60 ? 'brand' : 'warning'}
|
||||
size="sm"
|
||||
/>
|
||||
<p className="mt-1 text-[11px] text-ink-4">
|
||||
{s.applied - s.unscreened} of {s.applied} applicants scored
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Hiring progression — the single source of truth for the pipeline.
|
||||
A separate "Hiring status" block used to sit above this one repeating
|
||||
Shortlisted, Interviews and Hired as figures, which is exactly what the
|
||||
progression already draws. Two readings of the same funnel invite the
|
||||
reader to check whether they agree, so the duplicated stage counts are
|
||||
gone and the three figures the progression does *not* carry — supply,
|
||||
quality and the screening average — lead it as one line. */}
|
||||
<section>
|
||||
<SectionTitle title="Hiring progression" />
|
||||
|
||||
<dl className="mb-3 flex flex-wrap items-baseline gap-x-2 gap-y-1 text-body-sm">
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<dt className="sr-only">Total applicants</dt>
|
||||
<dd className="font-semibold tabular-nums text-ink-1">{s.applied}</dd>
|
||||
<span className="text-ink-3">Applicant{s.applied === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
<span className="text-ink-4" aria-hidden="true">·</span>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<dt className="sr-only">Qualified, scoring 70 or above</dt>
|
||||
<dd className="font-semibold tabular-nums text-ink-1">{s.qualified}</dd>
|
||||
<span className="text-ink-3">Qualified (70+)</span>
|
||||
</div>
|
||||
<span className="text-ink-4" aria-hidden="true">·</span>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<dt className="text-ink-3">Avg Score</dt>
|
||||
<dd className="font-semibold tabular-nums text-ink-1">{s.avgScore || '—'}</dd>
|
||||
</div>
|
||||
{/* Screening coverage was the one figure in the old block that the
|
||||
progression cannot show — a stage count says how many reached
|
||||
Screened, not what share of the intake that is. */}
|
||||
{s.applied > 0 && (
|
||||
<>
|
||||
<span className="text-ink-4" aria-hidden="true">·</span>
|
||||
<div className="flex items-baseline gap-1.5">
|
||||
<dt className="sr-only">Screening coverage</dt>
|
||||
<dd className="font-semibold tabular-nums text-ink-1">{s.coverage}%</dd>
|
||||
<span className="text-ink-3">screened</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
|
||||
<Progression steps={p.progression} />
|
||||
</section>
|
||||
|
||||
@@ -811,22 +817,45 @@ function PositionDrawer({ position, onClose, onViewCandidates, onOpenPosition })
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-body-sm text-ink-3">
|
||||
Nobody has been screened for this role yet, so there is no ranking to show.
|
||||
</p>
|
||||
/* Two short lines, not a paragraph. The reason nobody is ranked is
|
||||
always the same — screening has not run — so it needs stating once,
|
||||
plainly, rather than explained. */
|
||||
<div className="rounded-lg border border-dashed border-border px-3 py-4">
|
||||
<p className="text-body-sm font-medium text-ink-2">No screened candidates yet</p>
|
||||
<p className="mt-0.5 text-caption leading-relaxed text-ink-3">
|
||||
Candidates will appear here once screening is completed.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* The timeline sits beside Top candidates, so it earns a rail and nodes:
|
||||
two plain label/date lists side by side read as one table split in
|
||||
half. The dots say "this one is chronological" without a heavier
|
||||
treatment. */}
|
||||
<section>
|
||||
<SectionTitle title="Hiring timeline" />
|
||||
<ol className="space-y-2">
|
||||
<ol className="space-y-0">
|
||||
{timeline.map((e, i) => (
|
||||
<li key={i} className="flex items-baseline justify-between gap-3 text-body-sm">
|
||||
<span className="text-ink-2">{e.label}</span>
|
||||
<span className="shrink-0 text-caption text-ink-4">
|
||||
{e.date
|
||||
? new Date(e.date).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })
|
||||
: '—'}
|
||||
<li key={i} className="flex gap-2.5 pb-3 last:pb-0">
|
||||
<span className="flex flex-col items-center" aria-hidden="true">
|
||||
<span
|
||||
className={cn(
|
||||
'mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full',
|
||||
i === timeline.length - 1 ? 'bg-krow-blue' : 'bg-ink-4'
|
||||
)}
|
||||
/>
|
||||
{i < timeline.length - 1 && <span className="mt-1 w-px flex-1 bg-border" />}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block text-body-sm leading-snug text-ink-2">{e.label}</span>
|
||||
<span className="block text-caption text-ink-4">
|
||||
{e.date
|
||||
? new Date(e.date).toLocaleDateString(undefined, {
|
||||
month: 'short', day: 'numeric', year: 'numeric',
|
||||
})
|
||||
: '—'}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
@@ -841,7 +870,7 @@ function PositionDrawer({ position, onClose, onViewCandidates, onOpenPosition })
|
||||
{p.applications.length ? (
|
||||
<ol className="space-y-2">
|
||||
{[...p.applications]
|
||||
.sort((a, b) => new Date(b.updated_date) - new Date(a.updated_date))
|
||||
.sort((a, b) => new Date(b.updated_date).getTime() - new Date(a.updated_date).getTime())
|
||||
.slice(0, 5)
|
||||
.map((a) => (
|
||||
<li key={a.id} className="flex items-baseline justify-between gap-3 text-body-sm">
|
||||
@@ -1161,7 +1190,8 @@ export default function AdminPositions() {
|
||||
}
|
||||
|
||||
/** A compact labelled select pill — consistent on every Admin management page. */
|
||||
export function FilterSelect({ value, onChange, label, options, className }) {
|
||||
/** @param {any} props */
|
||||
export function FilterSelect({ value, onChange, label, options, className = '' }) {
|
||||
return (
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger
|
||||
|
||||
Reference in New Issue
Block a user