diff --git a/src/app/year-review/[id]/page.tsx b/src/app/year-review/[id]/page.tsx
index 7e9bc70..3c55377 100644
--- a/src/app/year-review/[id]/page.tsx
+++ b/src/app/year-review/[id]/page.tsx
@@ -1,6 +1,7 @@
import { notFound } from 'next/navigation';
import Link from 'next/link';
import { auth } from '@/lib/auth';
+import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
import { getYearReviewSessionById } from '@/services/year-review';
import { YearReviewBoard, YearReviewLiveWrapper } from '@/components/year-review';
import { Badge, CollaboratorDisplay } from '@/components/ui';
@@ -29,8 +30,8 @@ export default async function YearReviewSessionPage({ params }: YearReviewSessio
{/* Header */}
-
- Year Review
+
+ {getWorkshop('year-review').label}
/
{session.title}
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index 26f45f5..66822bc 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -6,6 +6,7 @@ import { useSession, signOut } from 'next-auth/react';
import { useTheme } from '@/contexts/ThemeContext';
import { useState } from 'react';
import { Avatar, RocketIcon } from '@/components/ui';
+import { WORKSHOPS } from '@/lib/workshops';
export function Header() {
const { theme, toggleTheme } = useTheme();
@@ -65,11 +66,7 @@ export function Header() {
onClick={() => setWorkshopsOpen(!workshopsOpen)}
onBlur={() => setTimeout(() => setWorkshopsOpen(false), 150)}
className={`flex items-center gap-1 text-sm font-medium transition-colors ${
- isActiveLink('/sessions/') ||
- isActiveLink('/motivators') ||
- isActiveLink('/year-review') ||
- isActiveLink('/weekly-checkin') ||
- isActiveLink('/weather')
+ WORKSHOPS.some((w) => isActiveLink(w.path))
? 'text-primary'
: 'text-muted hover:text-foreground'
}`}
@@ -92,61 +89,20 @@ export function Header() {
{workshopsOpen && (
-
setWorkshopsOpen(false)}
- >
-
📊
-
-
Analyse SWOT
-
Forces, faiblesses, opportunités
-
-
-
setWorkshopsOpen(false)}
- >
-
🎯
-
-
Moving Motivators
-
Motivations intrinsèques
-
-
-
setWorkshopsOpen(false)}
- >
-
📅
-
-
Year Review
-
Bilan de l'année
-
-
-
setWorkshopsOpen(false)}
- >
-
📝
-
-
Weekly Check-in
-
Suivi hebdomadaire
-
-
-
setWorkshopsOpen(false)}
- >
-
🌤️
-
-
Météo d'équipe
-
Humeur et énergie
-
-
+ {WORKSHOPS.map((w) => (
+
setWorkshopsOpen(false)}
+ >
+
{w.icon}
+
+
{w.label}
+
{w.description}
+
+
+ ))}
)}
diff --git a/src/lib/workshops.ts b/src/lib/workshops.ts
new file mode 100644
index 0000000..bae092a
--- /dev/null
+++ b/src/lib/workshops.ts
@@ -0,0 +1,185 @@
+/**
+ * Single source of truth for workshop types and metadata.
+ * Used by: Header, NewWorkshopDropdown, WorkshopTabs, sessions page.
+ */
+
+export const WORKSHOP_TYPE_IDS = [
+ 'swot',
+ 'motivators',
+ 'year-review',
+ 'weekly-checkin',
+ 'weather',
+] as const;
+
+export type WorkshopTypeId = (typeof WORKSHOP_TYPE_IDS)[number];
+
+export type WorkshopTabType = WorkshopTypeId | 'all' | 'byPerson';
+
+export const VALID_TAB_PARAMS: WorkshopTabType[] = [
+ 'all',
+ ...WORKSHOP_TYPE_IDS,
+ 'byPerson',
+];
+
+export interface WorkshopConfig {
+ id: WorkshopTypeId;
+ icon: string;
+ label: string;
+ labelShort: string;
+ cardLabel: string; // For home page cards (e.g. "Météo" vs "Météo d'équipe")
+ description: string;
+ path: string; // e.g. /sessions, /motivators
+ newPath: string; // e.g. /sessions/new
+ accentColor: string;
+ hasParticipant: boolean; // false for weather
+ participantLabel: string; // 'Collaborateur' | 'Participant' | ''
+ /** Home page marketing content */
+ home: {
+ tagline: string;
+ description: string;
+ features: string[];
+ };
+}
+
+export const WORKSHOPS: WorkshopConfig[] = [
+ {
+ id: 'swot',
+ icon: '📊',
+ label: 'Analyse SWOT',
+ labelShort: 'SWOT',
+ cardLabel: 'Analyse SWOT',
+ description: 'Forces, faiblesses, opportunités',
+ path: '/sessions',
+ newPath: '/sessions/new',
+ accentColor: '#06b6d4',
+ hasParticipant: true,
+ participantLabel: 'Collaborateur',
+ home: {
+ tagline: 'Analysez. Planifiez. Progressez.',
+ description:
+ "Cartographiez les forces et faiblesses de vos collaborateurs. Identifiez opportunités et menaces pour définir des actions concrètes.",
+ features: [
+ 'Matrice interactive Forces/Faiblesses/Opportunités/Menaces',
+ 'Actions croisées et plan de développement',
+ 'Collaboration en temps réel',
+ ],
+ },
+ },
+ {
+ id: 'motivators',
+ icon: '🎯',
+ label: 'Moving Motivators',
+ labelShort: 'Motivators',
+ cardLabel: 'Moving Motivators',
+ description: 'Motivations intrinsèques',
+ path: '/motivators',
+ newPath: '/motivators/new',
+ accentColor: '#8b5cf6',
+ hasParticipant: true,
+ participantLabel: 'Participant',
+ home: {
+ tagline: 'Révélez ce qui motive vraiment',
+ description:
+ "Explorez les 10 motivations intrinsèques de vos collaborateurs. Comprenez leur impact et alignez aspirations et missions.",
+ features: [
+ '10 cartes de motivation à classer',
+ "Évaluation de l'influence positive/négative",
+ 'Récapitulatif personnalisé des motivations',
+ ],
+ },
+ },
+ {
+ id: 'year-review',
+ icon: '📅',
+ label: 'Year Review',
+ labelShort: 'Year Review',
+ cardLabel: 'Year Review',
+ description: "Bilan de l'année",
+ path: '/year-review',
+ newPath: '/year-review/new',
+ accentColor: '#f59e0b',
+ hasParticipant: true,
+ participantLabel: 'Participant',
+ home: {
+ tagline: "Faites le bilan de l'année",
+ description:
+ "Réalisez un bilan complet de l'année écoulée. Identifiez réalisations, défis, apprentissages et définissez vos objectifs pour l'année à venir.",
+ features: [
+ "5 catégories : Réalisations, Défis, Apprentissages, Objectifs, Moments",
+ 'Organisation par drag & drop',
+ "Vue d'ensemble de l'année",
+ ],
+ },
+ },
+ {
+ id: 'weekly-checkin',
+ icon: '📝',
+ label: 'Weekly Check-in',
+ labelShort: 'Check-in',
+ cardLabel: 'Weekly Check-in',
+ description: 'Suivi hebdomadaire',
+ path: '/weekly-checkin',
+ newPath: '/weekly-checkin/new',
+ accentColor: '#10b981',
+ hasParticipant: true,
+ participantLabel: 'Participant',
+ home: {
+ tagline: 'Le point hebdomadaire avec vos collaborateurs',
+ description:
+ "Chaque semaine, faites le point avec vos collaborateurs sur ce qui s'est bien passé, ce qui s'est mal passé, les enjeux du moment et les prochains enjeux.",
+ features: [
+ "4 catégories : Bien passé, Mal passé, Enjeux du moment, Prochains enjeux",
+ "Ajout d'émotions à chaque item (fierté, joie, frustration, etc.)",
+ 'Suivi hebdomadaire régulier',
+ ],
+ },
+ },
+ {
+ id: 'weather',
+ icon: '🌤️',
+ label: "Météo d'équipe",
+ labelShort: 'Météo',
+ cardLabel: 'Météo',
+ description: 'Humeur et énergie',
+ path: '/weather',
+ newPath: '/weather/new',
+ accentColor: '#3b82f6',
+ hasParticipant: false,
+ participantLabel: '',
+ home: {
+ tagline: "Votre état en un coup d'œil",
+ description:
+ "Créez votre météo personnelle sur 4 axes clés (Performance, Moral, Flux, Création de valeur) et partagez-la avec votre équipe pour une meilleure visibilité de votre état.",
+ features: [
+ '4 axes : Performance, Moral, Flux, Création de valeur',
+ 'Emojis météo pour exprimer votre état visuellement',
+ 'Notes globales pour détailler votre ressenti',
+ ],
+ },
+ },
+];
+
+export const WORKSHOP_BY_ID = Object.fromEntries(WORKSHOPS.map((w) => [w.id, w])) as Record<
+ WorkshopTypeId,
+ WorkshopConfig
+>;
+
+export function getWorkshop(id: WorkshopTypeId): WorkshopConfig {
+ return WORKSHOP_BY_ID[id];
+}
+
+export function getSessionPath(id: WorkshopTypeId, sessionId: string): string {
+ return `${getWorkshop(id).path}/${sessionId}`;
+}
+
+export function getSessionsTabUrl(id: WorkshopTypeId): string {
+ return `/sessions?tab=${id}`;
+}
+
+/** Add workshopType to session objects for unified display. Preserves literal type for type safety. */
+export function withWorkshopType
(
+ sessions: T[],
+ type: K
+): (T & { workshopType: K })[] {
+ return sessions.map((s) => ({ ...s, workshopType: type })) as (T & { workshopType: K })[];
+}