YBoard is a web application developed for Ynov Toulouse to manage teacher candidates, module assignments, and workload tracking across all programs and specialties of the school.
Teacher management — Create and maintain instructor profiles with contact details, diploma, hourly rate, and CV (PDF upload, preview, replacement, deletion).
Module and program management — Organize educational modules by promo (level + specialty) with associated workload targets.
Assignment workflow — Assign teachers to modules through three statuses: Ongoing (currently assigned), Potential (under evaluation with interview tracking), Selected (validated for assignment).
Workload tracking — Monitor the hourly volume per instructor per module, with visibility across all programs.
Drag-and-drop interface — Move teachers between assignment statuses with an intuitive kanban-style board.
Secure authentication — JWT-based login with bcrypt password hashing.
| Layer | Technology |
|---|---|
| Frontend | Next.js 15, React 19, TypeScript |
| Backend | tRPC, Next.js API Routes |
| Database | PostgreSQL 15, Prisma ORM |
| UI | Mantine UI, Tailwind CSS, Tabler Icons |
| Auth | JWT, bcrypt |
| Drag & Drop | @dnd-kit |
| Validation | Zod |
| Containerization | Docker, Docker Compose |
- Node.js 22+
- npm 10+
- PostgreSQL 15+
git clone <repository-url>
cd YBoard-Intervenants
npm installCreate a .env file at the project root:
DATABASE_URL="postgresql://postgres:password@localhost:5432/yboard"
JWT_SECRET="change-this-in-production"
NODE_ENV="development"npm run db:generate # Generate Prisma client
npm run db:migrate # Apply migrations
npm run db:seed # (Optional) Seed initial datanpm run devThe application will be available at http://localhost:3000.
# Development
npm run dev # Start dev server (Turbo)
npm run build # Production build
npm run start # Production start
npm run preview # Build then start
# Database
npm run db:generate # Generate Prisma client
npm run db:migrate # Apply migrations
npm run db:push # Push schema without migration (dev only)
npm run db:studio # Open Prisma Studio
# Code quality
npm run check # Lint + TypeScript check
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run typecheck # TypeScript check only
npm run format:check # Prettier check
npm run format:write # Prettier formatdocker-compose up -d # Start application and database
docker-compose logs -f # Follow logs
docker-compose down # Stop
docker-compose down -v # Stop and remove all volumes (deletes data)Two persistent volumes are created automatically:
postgres_data— PostgreSQL datacv_storage— Uploaded CV files (/app/uploads/cv)
# Backup
docker cp $(docker-compose ps -q app):/app/uploads/cv ./cv_backup
# Restore
docker cp ./cv_backup/. $(docker-compose ps -q app):/app/uploads/cv/docker build -t yboard-app .
docker run -p 3000:3000 \
-v yboard_cv_storage:/app/uploads/cv \
-e DATABASE_URL="postgresql://postgres:password@host.docker.internal:5432/yboard" \
-e JWT_SECRET="your-secret-key" \
yboard-apperDiagram
Users {
uuid id PK
varchar firstname
varchar lastname
varchar email
varchar password
timestamp last_connected
uuid active_perimeter_id FK
}
Perimeter {
uuid id PK
varchar title
varchar slug
varchar origin
varchar color
timestamp created_at
}
PerimeterMember {
uuid id PK
uuid perimeter_id FK
uuid user_id FK
timestamp joined_at
}
Promos {
uuid id PK
varchar level
varchar specialty
varchar icon
uuid perimeter_id FK
varchar survey_title
text survey_introduction
}
Modules {
uuid id PK
varchar name
text description
varchar periode
boolean avec_mentor
int nombre_heure_tdp
int nombre_heure_ffp
uuid perimeter_id FK
}
Teacher {
uuid id PK
varchar lastname
varchar firstname
varchar status
varchar diploma
decimal rate
varchar email_perso
varchar email_ynov
varchar phone_number
varchar cv_filename
timestamp cv_uploaded_at
}
TeacherComment {
uuid id PK
uuid teacher_id FK
uuid perimeter_id FK
text comment
timestamp updated_at
}
PromoModules {
uuid id PK
uuid module_id FK
uuid promo_id FK
int workload
}
ongoing {
uuid teacher_id FK
uuid promo_modules_id FK
int workload
decimal rate
decimal rateTDP
decimal rateFFP
}
potential {
uuid teacher_id FK
uuid promo_modules_id FK
int workload
decimal rate
decimal rateTDP
decimal rateFFP
date interview_date
text interview_comments
boolean decision
}
selected {
uuid teacher_id FK
uuid promo_modules_id FK
int workload
decimal rate
decimal rateTDP
decimal rateFFP
}
SurveySubmission {
uuid id PK
uuid perimeter_id FK
varchar nom
varchar prenom
varchar email
varchar telephone
varchar niveau_academique
text intitule_diplome
varchar annees_experience
text domaines_exercice
varchar final_choice
timestamp created_at
}
SurveyResponse {
uuid id PK
uuid submission_id FK
uuid promo_modules_id FK
varchar module_label_snapshot
varchar response
text condition_text
}
SurveySubmissionTeacher {
uuid id PK
uuid submission_id FK
uuid teacher_id FK
timestamp linked_at
}
Perimeter ||--o{ PerimeterMember : "has members"
Perimeter ||--o{ Promos : "contains"
Perimeter ||--o{ Modules : "contains"
Perimeter ||--o{ TeacherComment : "scopes"
Perimeter ||--o{ SurveySubmission : "receives"
Perimeter ||--o{ Users : "active for"
Users ||--o{ PerimeterMember : "belongs to"
Promos ||--o{ PromoModules : "has"
Modules ||--o{ PromoModules : "linked via"
Teacher ||--o{ ongoing : "assigned"
Teacher ||--o{ potential : "candidate"
Teacher ||--o{ selected : "selected"
Teacher ||--o{ TeacherComment : "has"
PromoModules ||--o{ ongoing : "contains"
PromoModules ||--o{ potential : "contains"
PromoModules ||--o{ selected : "contains"
PromoModules ||--o{ SurveyResponse : "referenced in"
SurveySubmission ||--o{ SurveyResponse : "has"
SurveySubmission ||--o{ SurveySubmissionTeacher : "linked to"
src/
├── app/
│ ├── api/
│ │ ├── cv/ # CV upload, view, delete
│ │ └── trpc/ # tRPC handler
│ ├── login/
│ ├── modules/
│ ├── teachers/
│ ├── promos/
│ └── users/
├── components/
│ ├── AppLayout.tsx
│ ├── Sidebar.tsx
│ └── modals/
├── contexts/
│ ├── AuthContext.tsx
│ └── ModalContext.tsx
├── server/
│ ├── api/
│ └── db.ts
├── trpc/
└── types/
uploads/
└── cv/ # CV files (not tracked in git)
Uploaded CVs are stored under uploads/cv/ with the naming convention [uuid]_[timestamp].pdf.
- Accepted format: PDF only
- Maximum size: 5 MB
- API endpoints:
POST /api/cv/upload,GET /api/cv/[filename],DELETE /api/cv/[filename]
In production (Docker), the directory is mapped to a persistent volume to survive container restarts.
Create an admin user directly in the database, then log in at /login.
Proprietary — Eric PHILIPPE © 2026
