Skip to content

Candidate Pool


Overview

The Candidate Pool is a recruiter-facing view that aggregates all candidates across every job into a single searchable list. Unlike the per-job pipeline view, the pool lets recruiters spot high-value candidates, track their overall status, and jump into any individual pipeline without navigating job-by-job.

Route: /dashboard/pool


Layout

Split-pane design matching Pipeline.tsx:

/dashboard/pool
┌─────────────────────────┬───────────────────────────────────┐
│  LEFT PANE (w-1/3)      │  RIGHT PANE (flex-1)              │
│                         │                                   │
│  [Total | Active | Hired│  Participant header               │
│   stat mini-cards]      │    Avatar · Name · Email          │
│  [Search input]         │    Jobs count · Interviews · ...  │
│  [Status | Sort by]     │                                   │
│                         │  [Tabs: Jobs | Activity]          │
│  • Candidate row        │                                   │
│    avatar, name, email  │  Jobs tab:                        │
│    jobs count, status,  │    Job card (title, status,       │
│    last activity        │    progress bar, last activity)   │
│  ...                    │    → click → ResponsiveSheet      │
│  [Pagination]           │         → CandidateDetailPanel    │
│                         │                                   │
│                         │  Activity tab:                    │
│                         │    Chronological timeline of      │
│                         │    all stage events (no extra     │
│                         │    API call — built from already- │
│                         │    fetched stageProgression)      │
└─────────────────────────┴───────────────────────────────────┘

Mobile: full left pane → tap a candidate → full right pane (back button restores list). Same md:hidden / md:flex breakpoint logic as Pipeline.tsx.


State Management

selectedParticipantId is plain React state in CandidatePool.tsx. No URL param changes when a candidate is selected — matches the Pipeline page pattern.


Left Pane: Candidate List

Stat Cards

Three compact cards at the top (totals for the current search scope, before the status filter):

CardValue
TotalAll unique participants found by search
ActivelatestStatus === 'active' count
HiredlatestStatus === 'hired' count

Counts come from the summary facet in the GET /v1/pipeline/pool aggregation response — zero extra requests.

400 ms debounce. Regex match on participant name and email (minimum 2 characters, same pattern as the Pipeline page). Applied before the $group stage so the grouping and counts both reflect the search.

Status Filter

Dropdown: All / Active / Shortlisted / Hired / Rejected / Withdrawn. Passes status query param. Applied inside the $facet (after grouping), so it filters the paginated data and total buckets without affecting the summary counts.

Sort Options

OptionSorts by
Last Activity (default)lastActivityAt desc
Name A–Zname asc
Most JobsjobCount desc

Candidate Row

  • Avatar with initials (first + last letter of name, or first 2 chars of email)
  • Name (primary), email (secondary if name exists)
  • Status badge (same STATUS_COLORS map used across pipeline components)
  • N jobs · date footer line

Right Pane: Candidate Profile

Loaded via GET /v1/pipeline/pool/:participantId on candidate selection.

Large avatar initials, name, email, and three stat badges:

  • N jobs — count of pipelines
  • N interviews — from participant.stats.totalInterviews
  • N stages completed — aggregated from all stageProgression entries with status === 'completed'

Tags

Unique tags collected from all pipelines ([...new Set(pipelines.flatMap(p => p.tags))]). Displayed as pills.

Jobs Tab

One card per CandidatePipeline document, showing:

  • Job title + status badge
  • Mini progress bar: completed / total stages (linear fill)
  • Last activity date

Clicking a job card opens a ResponsiveSheet containing <CandidateDetailPanel pipelineId={pipeline._id} /> — the same full-featured panel used everywhere else (stage timeline, AI report, unlock, notes, global status selector).

Activity Tab

A vertical timeline constructed client-side from the already-fetched stageProgression data — no extra API call. Events extracted per stage:

EventSource field
Invited to stageNameinvitedAt
Started stageNamestartedAt
Completed stageNamecompletedAt

All events across all pipelines are merged and sorted by timestamp descending. Each item shows the event label, job title, and formatted datetime.


No Schema Changes Required

CandidatePipeline.organizationId with the existing { organizationId: 1, status: 1 } compound index handles the pool list query efficiently. { participantId: 1, lastActivityAt: -1 } handles the profile query. No new indexes needed.