Skip to content

juanv87/screening-dashboard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Emi Labs — Candidate Screening Dashboard

Overview

A recruitment dashboard that allows recruiters to view, filter, and reclassify job candidates. Each candidate carries a rejection reason field: an empty string means the candidate is approved; a non-empty comma-separated string means they are rejected. Recruiters can approve or reject candidates inline from the table, selecting one or more structured reasons when rejecting. The dashboard supports search, status filtering, sortable columns, pagination, column visibility toggles, and CSV export of the current filtered view.


Tech Stack

Layer Technology Version
Backend runtime Node.js 22.x
Backend framework Express 4.18
Backend test runner Jest + Supertest 29.x / 7.x
Frontend framework React 18.2
Frontend build tool Vite 5.0
Frontend styling Tailwind CSS 3.4
Server state TanStack React Query 5.96
Client state React Context + useReducer
Frontend test runner Vitest + React Testing Library 4.x / 16.x

Getting Started

Prerequisites

  • Node.js 18 or higher (developed on v22.17.1)
  • npm 9 or higher

Clone the repository

git clone <repository-url>
cd emi-labs-challenge

Start the backend

cd backend
npm install
npm run dev

The API server starts at http://localhost:3001.

Start the frontend

Open a second terminal:

cd frontend
npm install
npm run dev

The app opens at http://localhost:5173.

Both servers must be running simultaneously. The Vite dev server proxies /api requests to localhost:3001, so no CORS configuration is needed in development.


Features

Core

  • Dynamic table — columns rendered from the /api/columns config; only visible columns are shown
  • Status badges — green for approved, red for rejected; driven by the reason field
  • Inline status select — a controlled <select> in each row lets recruiters switch status without leaving the table
  • Approve flow — selecting "Approved" on a rejected candidate fires a PATCH immediately, no modal required
  • Reject flow — selecting "Rejected" on an approved candidate opens a modal to choose one or more structured reasons; cancelling leaves the candidate unchanged
  • Reclassify modal — pre-populates existing reasons when editing a rejected candidate's reasons; confirms before submitting

Enhanced

  • Search bar — filters by candidate name, email, or document number; debounced to avoid excessive requests
  • Status filter — tabs for All / Approved / Rejected
  • Sortable columns — click any column header to sort ascending; click again for descending; resets on new search
  • Pagination — 20 candidates per page, rendered above and below the table with a sliding page window
  • Column toggle — dropdown to show or hide individual columns; state held in the React Query cache
  • CSV export — downloads all candidates matching the current filters (unpaginated) as a valid RFC 4180 CSV file
  • Candidate count summary — live counts for Total / Approved / Rejected in the header, consistent with active filters
  • Error boundary — catches render errors in the table and shows an inline retry button

API Reference

Method Endpoint Description Query params
GET /api/candidates Paginated candidate list search, status, page, limit
GET /api/candidates/export Full filtered list as CSV search, status
PATCH /api/candidates/:id Update candidate status
GET /api/columns Column visibility config
GET /api/reasons Available rejection reasons

Query params for GET /api/candidates:

Param Type Default Description
search string Filter by name, email, or document
status approved | rejected Filter by classification
page integer 1 Page number (non-numeric values fall back to 1)
limit integer 20 Results per page (capped at 100)

PATCH request body:

{ "reason": "" }

reason must be a string. An empty string approves the candidate. A comma-separated string (e.g. "Too old, Wrong career") rejects them with the given reasons.

GET /api/candidates response shape:

{
  "data": [ ...candidates ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 142,
    "totalPages": 8,
    "approvedCount": 57,
    "rejectedCount": 85
  }
}

Running Tests

Frontend (Vitest + React Testing Library)

cd frontend
npm test              # run all suites once and exit
npm run test:ui       # open the Vitest browser UI

Backend (Jest + Supertest)

cd backend
npm test

Test coverage summary

Suite File Tests
Utility functions src/utils/candidateHelpers.test.js 16
Reducer src/context/CandidatesContext.test.js 10
StatusBadge component src/components/StatusBadge/StatusBadge.test.jsx 4
ReclassifyModal component src/components/ReclassifyModal/ReclassifyModal.test.jsx 3
API routes (integration) src/__tests__/candidates.test.js 11
Total 44

Project Structure

emi-labs-challenge/
├── CLAUDE.md                          # Project spec and coding guidelines
├── README.md
├── backend/
│   ├── jest.config.js                 # Jest configuration (node environment)
│   ├── package.json
│   └── src/
│       ├── index.js                   # Express server entry point, port 3001
│       ├── data/
│       │   ├── candidates.json        # Source data, mutated in memory
│       │   └── columns.json           # Column visibility config (field → bool)
│       ├── routes/
│       │   ├── candidates.js          # GET /, GET /export, PATCH /:id
│       │   ├── columns.js             # GET /api/columns
│       │   └── reasons.js             # GET /api/reasons
│       └── __tests__/
│           └── candidates.test.js     # Integration tests for candidate routes
└── frontend/
    ├── vitest.config.js               # Vitest configuration (jsdom environment)
    ├── vite.config.js                 # Vite build config and /api proxy
    ├── tailwind.config.js
    ├── package.json
    └── src/
        ├── main.jsx                   # React root, QueryClientProvider
        ├── App.jsx                    # Dashboard layout, header, export button
        ├── api/
        │   └── client.js              # Axios wrapper and exportCandidates helper
        ├── context/
        │   └── CandidatesContext.jsx  # Client state: filters, page, sort, modal
        ├── hooks/
        │   ├── useCandidates.js       # useCandidatesQuery, useReasonsQuery, useUpdateCandidate
        │   ├── useColumns.js          # useColumnsQuery, useToggleColumn
        │   └── useOnClickOutside.js   # Shared click-outside detection hook
        ├── components/
        │   ├── CandidateTable/
        │   │   ├── CandidateTable.jsx # Table shell, loading skeleton, error state
        │   │   ├── TableHeader.jsx    # Sortable column headers
        │   │   └── TableRow.jsx       # Row with inline status select and cell renderer
        │   ├── ReclassifyModal/
        │   │   └── ReclassifyModal.jsx # Approve/reject modal with reason checkboxes
        │   ├── StatusBadge/
        │   │   └── StatusBadge.jsx    # Pure display badge (approved / rejected)
        │   ├── SearchBar/
        │   │   └── SearchBar.jsx      # Debounced search input and status filter tabs
        │   ├── Pagination/
        │   │   └── Pagination.jsx     # Page controls with sliding window
        │   ├── ColumnToggle/
        │   │   └── ColumnToggle.jsx   # Dropdown to show/hide columns
        │   └── ErrorBoundary/
        │       └── ErrorBoundary.jsx  # Class component boundary with retry button
        ├── utils/
        │   └── candidateHelpers.js    # Pure helpers: isApproved, parseReasons, sort, format
        └── test/
            └── setup.js               # @testing-library/jest-dom import for Vitest

AI Usage

This project was built with extensive assistance from Claude Code (Anthropic's CLI) and Claude.ai. AI was used across all phases: architecture planning, initial scaffolding, React Query migration, backend and frontend bug fixing, feature implementation, and test generation. All sessions are documented in detail — including the exact prompts used, what was generated versus manually written, and the rationale behind key technical decisions — in logs/AI_LOG.md.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages