Compare commits
2 Commits
e8282bb118
...
cc7e73ce7b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc7e73ce7b | ||
|
|
a8f53bfe2a |
@@ -1,6 +1,7 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
|
import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
|
||||||
import { getMotivatorSessionById } from '@/services/moving-motivators';
|
import { getMotivatorSessionById } from '@/services/moving-motivators';
|
||||||
import { MotivatorBoard, MotivatorLiveWrapper } from '@/components/moving-motivators';
|
import { MotivatorBoard, MotivatorLiveWrapper } from '@/components/moving-motivators';
|
||||||
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
||||||
@@ -29,8 +30,8 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
<Link href="/sessions?tab=motivators" className="hover:text-foreground">
|
<Link href={getSessionsTabUrl('motivators')} className="hover:text-foreground">
|
||||||
Moving Motivators
|
{getWorkshop('motivators').label}
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-foreground">{session.title}</span>
|
<span className="text-foreground">{session.title}</span>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { WORKSHOPS, getSessionsTabUrl } from '@/lib/workshops';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@@ -21,85 +22,19 @@ export default function Home() {
|
|||||||
Choisissez votre atelier
|
Choisissez votre atelier
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 max-w-6xl mx-auto">
|
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 max-w-6xl mx-auto">
|
||||||
{/* SWOT Workshop Card */}
|
{WORKSHOPS.map((w) => (
|
||||||
<WorkshopCard
|
<WorkshopCard
|
||||||
href="/sessions?tab=swot"
|
key={w.id}
|
||||||
icon="📊"
|
href={getSessionsTabUrl(w.id)}
|
||||||
title="Analyse SWOT"
|
icon={w.icon}
|
||||||
tagline="Analysez. Planifiez. Progressez."
|
title={w.cardLabel}
|
||||||
description="Cartographiez les forces et faiblesses de vos collaborateurs. Identifiez opportunités et menaces pour définir des actions concrètes."
|
tagline={w.home.tagline}
|
||||||
features={[
|
description={w.home.description}
|
||||||
'Matrice interactive Forces/Faiblesses/Opportunités/Menaces',
|
features={w.home.features}
|
||||||
'Actions croisées et plan de développement',
|
accentColor={w.accentColor}
|
||||||
'Collaboration en temps réel',
|
newHref={w.newPath}
|
||||||
]}
|
/>
|
||||||
accentColor="#06b6d4"
|
))}
|
||||||
newHref="/sessions/new"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Moving Motivators Workshop Card */}
|
|
||||||
<WorkshopCard
|
|
||||||
href="/sessions?tab=motivators"
|
|
||||||
icon="🎯"
|
|
||||||
title="Moving Motivators"
|
|
||||||
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',
|
|
||||||
]}
|
|
||||||
accentColor="#8b5cf6"
|
|
||||||
newHref="/motivators/new"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Year Review Workshop Card */}
|
|
||||||
<WorkshopCard
|
|
||||||
href="/sessions?tab=year-review"
|
|
||||||
icon="📅"
|
|
||||||
title="Year Review"
|
|
||||||
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',
|
|
||||||
]}
|
|
||||||
accentColor="#f59e0b"
|
|
||||||
newHref="/year-review/new"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Weekly Check-in Workshop Card */}
|
|
||||||
<WorkshopCard
|
|
||||||
href="/sessions?tab=weekly-checkin"
|
|
||||||
icon="📝"
|
|
||||||
title="Weekly Check-in"
|
|
||||||
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',
|
|
||||||
]}
|
|
||||||
accentColor="#10b981"
|
|
||||||
newHref="/weekly-checkin/new"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Weather Workshop Card */}
|
|
||||||
<WorkshopCard
|
|
||||||
href="/sessions?tab=weather"
|
|
||||||
icon="🌤️"
|
|
||||||
title="Météo"
|
|
||||||
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',
|
|
||||||
]}
|
|
||||||
accentColor="#3b82f6"
|
|
||||||
newHref="/weather/new"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
51
src/app/sessions/NewWorkshopDropdown.tsx
Normal file
51
src/app/sessions/NewWorkshopDropdown.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { Button } from '@/components/ui';
|
||||||
|
import { WORKSHOPS } from '@/lib/workshops';
|
||||||
|
|
||||||
|
export function NewWorkshopDropdown() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
onBlur={() => setTimeout(() => setOpen(false), 150)}
|
||||||
|
className="gap-1.5"
|
||||||
|
>
|
||||||
|
Nouvel atelier
|
||||||
|
<svg
|
||||||
|
className={`h-4 w-4 transition-transform ${open ? 'rotate-180' : ''}`}
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
{open && (
|
||||||
|
<div className="absolute right-0 z-20 mt-2 w-56 rounded-lg border border-border bg-card py-1 shadow-lg">
|
||||||
|
{WORKSHOPS.map((w) => (
|
||||||
|
<Link
|
||||||
|
key={w.id}
|
||||||
|
href={w.newPath}
|
||||||
|
className="flex items-center gap-3 px-4 py-2.5 text-sm text-foreground hover:bg-card-hover"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<span className="text-lg">{w.icon}</span>
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">{w.label}</div>
|
||||||
|
<div className="text-xs text-muted">{w.description}</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,17 +17,18 @@ import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving
|
|||||||
import { deleteYearReviewSession, updateYearReviewSession } from '@/actions/year-review';
|
import { deleteYearReviewSession, updateYearReviewSession } from '@/actions/year-review';
|
||||||
import { deleteWeeklyCheckInSession, updateWeeklyCheckInSession } from '@/actions/weekly-checkin';
|
import { deleteWeeklyCheckInSession, updateWeeklyCheckInSession } from '@/actions/weekly-checkin';
|
||||||
import { deleteWeatherSession, updateWeatherSession } from '@/actions/weather';
|
import { deleteWeatherSession, updateWeatherSession } from '@/actions/weather';
|
||||||
|
import {
|
||||||
|
type WorkshopTabType,
|
||||||
|
type WorkshopTypeId,
|
||||||
|
WORKSHOPS,
|
||||||
|
VALID_TAB_PARAMS,
|
||||||
|
getWorkshop,
|
||||||
|
getSessionPath,
|
||||||
|
} from '@/lib/workshops';
|
||||||
|
|
||||||
type WorkshopType = 'all' | 'swot' | 'motivators' | 'year-review' | 'weekly-checkin' | 'weather' | 'byPerson';
|
const TYPE_TABS = [
|
||||||
|
{ value: 'all' as const, icon: '📋', label: 'Tous' },
|
||||||
const VALID_TABS: WorkshopType[] = [
|
...WORKSHOPS.map((w) => ({ value: w.id, icon: w.icon, label: w.labelShort })),
|
||||||
'all',
|
|
||||||
'swot',
|
|
||||||
'motivators',
|
|
||||||
'year-review',
|
|
||||||
'weekly-checkin',
|
|
||||||
'weather',
|
|
||||||
'byPerson',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
interface ShareUser {
|
interface ShareUser {
|
||||||
@@ -199,13 +200,16 @@ export function WorkshopTabs({
|
|||||||
}: WorkshopTabsProps) {
|
}: WorkshopTabsProps) {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [typeDropdownOpen, setTypeDropdownOpen] = useState(false);
|
||||||
|
|
||||||
// Get tab from URL or default to 'all'
|
// Get tab from URL or default to 'all'
|
||||||
const tabParam = searchParams.get('tab');
|
const tabParam = searchParams.get('tab');
|
||||||
const activeTab: WorkshopType =
|
const activeTab: WorkshopTabType =
|
||||||
tabParam && VALID_TABS.includes(tabParam as WorkshopType) ? (tabParam as WorkshopType) : 'all';
|
tabParam && VALID_TAB_PARAMS.includes(tabParam as WorkshopTabType)
|
||||||
|
? (tabParam as WorkshopTabType)
|
||||||
|
: 'all';
|
||||||
|
|
||||||
const setActiveTab = (tab: WorkshopType) => {
|
const setActiveTab = (tab: WorkshopTabType) => {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
if (tab === 'all') {
|
if (tab === 'all') {
|
||||||
params.delete('tab');
|
params.delete('tab');
|
||||||
@@ -251,7 +255,7 @@ export function WorkshopTabs({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="flex gap-2 border-b border-border pb-4 flex-wrap">
|
<div className="flex gap-2 border-b border-border pb-4 flex-wrap items-center">
|
||||||
<TabButton
|
<TabButton
|
||||||
active={activeTab === 'all'}
|
active={activeTab === 'all'}
|
||||||
onClick={() => setActiveTab('all')}
|
onClick={() => setActiveTab('all')}
|
||||||
@@ -266,40 +270,18 @@ export function WorkshopTabs({
|
|||||||
label="Par personne"
|
label="Par personne"
|
||||||
count={sessionsByPerson.size}
|
count={sessionsByPerson.size}
|
||||||
/>
|
/>
|
||||||
<TabButton
|
<TypeFilterDropdown
|
||||||
active={activeTab === 'swot'}
|
activeTab={activeTab}
|
||||||
onClick={() => setActiveTab('swot')}
|
setActiveTab={setActiveTab}
|
||||||
icon="📊"
|
open={typeDropdownOpen}
|
||||||
label="SWOT"
|
onOpenChange={setTypeDropdownOpen}
|
||||||
count={swotSessions.length}
|
counts={{
|
||||||
/>
|
swot: swotSessions.length,
|
||||||
<TabButton
|
motivators: motivatorSessions.length,
|
||||||
active={activeTab === 'motivators'}
|
'year-review': yearReviewSessions.length,
|
||||||
onClick={() => setActiveTab('motivators')}
|
'weekly-checkin': weeklyCheckInSessions.length,
|
||||||
icon="🎯"
|
weather: weatherSessions.length,
|
||||||
label="Moving Motivators"
|
}}
|
||||||
count={motivatorSessions.length}
|
|
||||||
/>
|
|
||||||
<TabButton
|
|
||||||
active={activeTab === 'year-review'}
|
|
||||||
onClick={() => setActiveTab('year-review')}
|
|
||||||
icon="📅"
|
|
||||||
label="Year Review"
|
|
||||||
count={yearReviewSessions.length}
|
|
||||||
/>
|
|
||||||
<TabButton
|
|
||||||
active={activeTab === 'weekly-checkin'}
|
|
||||||
onClick={() => setActiveTab('weekly-checkin')}
|
|
||||||
icon="📝"
|
|
||||||
label="Weekly Check-in"
|
|
||||||
count={weeklyCheckInSessions.length}
|
|
||||||
/>
|
|
||||||
<TabButton
|
|
||||||
active={activeTab === 'weather'}
|
|
||||||
onClick={() => setActiveTab('weather')}
|
|
||||||
icon="🌤️"
|
|
||||||
label="Météo"
|
|
||||||
count={weatherSessions.length}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -367,6 +349,92 @@ export function WorkshopTabs({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TypeFilterDropdown({
|
||||||
|
activeTab,
|
||||||
|
setActiveTab,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
counts,
|
||||||
|
}: {
|
||||||
|
activeTab: WorkshopTabType;
|
||||||
|
setActiveTab: (t: WorkshopTabType) => void;
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (v: boolean) => void;
|
||||||
|
counts: Record<string, number>;
|
||||||
|
}) {
|
||||||
|
const typeTabs = TYPE_TABS.filter((t) => t.value !== 'all');
|
||||||
|
const current = TYPE_TABS.find((t) => t.value === activeTab) ?? TYPE_TABS[0];
|
||||||
|
const isTypeSelected = activeTab !== 'all' && activeTab !== 'byPerson';
|
||||||
|
const totalCount = typeTabs.reduce((s, t) => s + (counts[t.value] ?? 0), 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onOpenChange(!open)}
|
||||||
|
onBlur={() => setTimeout(() => onOpenChange(false), 150)}
|
||||||
|
className={`
|
||||||
|
flex items-center gap-2 px-3 py-2 rounded-lg font-medium text-sm transition-colors
|
||||||
|
${isTypeSelected ? 'bg-primary text-primary-foreground' : 'text-muted hover:bg-card-hover hover:text-foreground'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span>{current.icon}</span>
|
||||||
|
<span>{current.label}</span>
|
||||||
|
<Badge variant={isTypeSelected ? 'default' : 'primary'} className="ml-1 text-xs">
|
||||||
|
{isTypeSelected ? counts[activeTab] ?? 0 : totalCount}
|
||||||
|
</Badge>
|
||||||
|
<svg
|
||||||
|
className={`h-4 w-4 transition-transform ${open ? 'rotate-180' : ''}`}
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{open && (
|
||||||
|
<div className="absolute left-0 z-20 mt-2 w-44 rounded-lg border border-border bg-card py-1 shadow-lg">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTab('all');
|
||||||
|
onOpenChange(false);
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center justify-between gap-2 px-4 py-2 text-left text-sm text-foreground hover:bg-card-hover border-b border-border"
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span>📋</span>
|
||||||
|
<span>Tous</span>
|
||||||
|
</span>
|
||||||
|
<Badge variant="primary" className="text-xs">
|
||||||
|
{totalCount}
|
||||||
|
</Badge>
|
||||||
|
</button>
|
||||||
|
{typeTabs.map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.value}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTab(t.value);
|
||||||
|
onOpenChange(false);
|
||||||
|
}}
|
||||||
|
className="flex w-full items-center justify-between gap-2 px-4 py-2 text-left text-sm text-foreground hover:bg-card-hover"
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<span>{t.icon}</span>
|
||||||
|
<span>{t.label}</span>
|
||||||
|
</span>
|
||||||
|
<Badge variant="primary" className="text-xs">
|
||||||
|
{counts[t.value] ?? 0}
|
||||||
|
</Badge>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function TabButton({
|
function TabButton({
|
||||||
active,
|
active,
|
||||||
onClick,
|
onClick,
|
||||||
@@ -382,9 +450,10 @@ function TabButton({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`
|
className={`
|
||||||
flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-colors
|
flex items-center gap-2 px-3 py-2 rounded-lg font-medium text-sm transition-colors
|
||||||
${
|
${
|
||||||
active
|
active
|
||||||
? 'bg-primary text-primary-foreground'
|
? 'bg-primary text-primary-foreground'
|
||||||
@@ -394,7 +463,7 @@ function TabButton({
|
|||||||
>
|
>
|
||||||
<span>{icon}</span>
|
<span>{icon}</span>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
<Badge variant={active ? 'default' : 'primary'} className="ml-1">
|
<Badge variant={active ? 'default' : 'primary'} className="ml-1 text-xs">
|
||||||
{count}
|
{count}
|
||||||
</Badge>
|
</Badge>
|
||||||
</button>
|
</button>
|
||||||
@@ -418,20 +487,12 @@ function SessionCard({ session }: { session: AnySession }) {
|
|||||||
: (session as MotivatorSession).participant
|
: (session as MotivatorSession).participant
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const workshop = getWorkshop(session.workshopType as WorkshopTypeId);
|
||||||
const isSwot = session.workshopType === 'swot';
|
const isSwot = session.workshopType === 'swot';
|
||||||
const isYearReview = session.workshopType === 'year-review';
|
const isYearReview = session.workshopType === 'year-review';
|
||||||
const isWeeklyCheckIn = session.workshopType === 'weekly-checkin';
|
const isWeeklyCheckIn = session.workshopType === 'weekly-checkin';
|
||||||
const isWeather = session.workshopType === 'weather';
|
const isWeather = session.workshopType === 'weather';
|
||||||
const href = isSwot
|
const href = getSessionPath(session.workshopType as WorkshopTypeId, session.id);
|
||||||
? `/sessions/${session.id}`
|
|
||||||
: isYearReview
|
|
||||||
? `/year-review/${session.id}`
|
|
||||||
: isWeeklyCheckIn
|
|
||||||
? `/weekly-checkin/${session.id}`
|
|
||||||
: isWeather
|
|
||||||
? `/weather/${session.id}`
|
|
||||||
: `/motivators/${session.id}`;
|
|
||||||
const icon = isSwot ? '📊' : isYearReview ? '📅' : isWeeklyCheckIn ? '📝' : isWeather ? '🌤️' : '🎯';
|
|
||||||
const participant = isSwot
|
const participant = isSwot
|
||||||
? (session as SwotSession).collaborator
|
? (session as SwotSession).collaborator
|
||||||
: isYearReview
|
: isYearReview
|
||||||
@@ -441,15 +502,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
|||||||
: isWeather
|
: isWeather
|
||||||
? (session as WeatherSession).user.name || (session as WeatherSession).user.email
|
? (session as WeatherSession).user.name || (session as WeatherSession).user.email
|
||||||
: (session as MotivatorSession).participant;
|
: (session as MotivatorSession).participant;
|
||||||
const accentColor = isSwot
|
const accentColor = workshop.accentColor;
|
||||||
? '#06b6d4'
|
|
||||||
: isYearReview
|
|
||||||
? '#f59e0b'
|
|
||||||
: isWeeklyCheckIn
|
|
||||||
? '#10b981'
|
|
||||||
: isWeather
|
|
||||||
? '#3b82f6'
|
|
||||||
: '#8b5cf6';
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
@@ -507,7 +560,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
|||||||
setShowEditModal(true);
|
setShowEditModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const editParticipantLabel = isSwot ? 'Collaborateur' : isWeather ? '' : 'Participant';
|
const editParticipantLabel = workshop.participantLabel;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -522,7 +575,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
|||||||
|
|
||||||
{/* Header: Icon + Title + Role badge */}
|
{/* Header: Icon + Title + Role badge */}
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span className="text-xl">{icon}</span>
|
<span className="text-xl">{workshop.icon}</span>
|
||||||
<h3 className="font-semibold text-foreground line-clamp-1 flex-1">{session.title}</h3>
|
<h3 className="font-semibold text-foreground line-clamp-1 flex-1">{session.title}</h3>
|
||||||
{!session.isOwner && (
|
{!session.isOwner && (
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
|
import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
|
||||||
import { getSessionById } from '@/services/sessions';
|
import { getSessionById } from '@/services/sessions';
|
||||||
import { SwotBoard } from '@/components/swot/SwotBoard';
|
import { SwotBoard } from '@/components/swot/SwotBoard';
|
||||||
import { SessionLiveWrapper } from '@/components/collaboration';
|
import { SessionLiveWrapper } from '@/components/collaboration';
|
||||||
@@ -30,8 +31,8 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
<Link href="/sessions?tab=swot" className="hover:text-foreground">
|
<Link href={getSessionsTabUrl('swot')} className="hover:text-foreground">
|
||||||
SWOT
|
{getWorkshop('swot').labelShort}
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-foreground">{session.title}</span>
|
<span className="text-foreground">{session.title}</span>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
import { getSessionsByUserId } from '@/services/sessions';
|
import { getSessionsByUserId } from '@/services/sessions';
|
||||||
import { getMotivatorSessionsByUserId } from '@/services/moving-motivators';
|
import { getMotivatorSessionsByUserId } from '@/services/moving-motivators';
|
||||||
import { getYearReviewSessionsByUserId } from '@/services/year-review';
|
import { getYearReviewSessionsByUserId } from '@/services/year-review';
|
||||||
import { getWeeklyCheckInSessionsByUserId } from '@/services/weekly-checkin';
|
import { getWeeklyCheckInSessionsByUserId } from '@/services/weekly-checkin';
|
||||||
import { getWeatherSessionsByUserId } from '@/services/weather';
|
import { getWeatherSessionsByUserId } from '@/services/weather';
|
||||||
import { Card, Button } from '@/components/ui';
|
import { Card } from '@/components/ui';
|
||||||
|
import { withWorkshopType } from '@/lib/workshops';
|
||||||
import { WorkshopTabs } from './WorkshopTabs';
|
import { WorkshopTabs } from './WorkshopTabs';
|
||||||
|
import { NewWorkshopDropdown } from './NewWorkshopDropdown';
|
||||||
|
|
||||||
function WorkshopTabsSkeleton() {
|
function WorkshopTabsSkeleton() {
|
||||||
return (
|
return (
|
||||||
@@ -45,31 +46,12 @@ export default async function SessionsPage() {
|
|||||||
getWeatherSessionsByUserId(session.user.id),
|
getWeatherSessionsByUserId(session.user.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add type to each session for unified display
|
// Add workshopType to each session for unified display
|
||||||
const allSwotSessions = swotSessions.map((s) => ({
|
const allSwotSessions = withWorkshopType(swotSessions, 'swot');
|
||||||
...s,
|
const allMotivatorSessions = withWorkshopType(motivatorSessions, 'motivators');
|
||||||
workshopType: 'swot' as const,
|
const allYearReviewSessions = withWorkshopType(yearReviewSessions, 'year-review');
|
||||||
}));
|
const allWeeklyCheckInSessions = withWorkshopType(weeklyCheckInSessions, 'weekly-checkin');
|
||||||
|
const allWeatherSessions = withWorkshopType(weatherSessions, 'weather');
|
||||||
const allMotivatorSessions = motivatorSessions.map((s) => ({
|
|
||||||
...s,
|
|
||||||
workshopType: 'motivators' as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const allYearReviewSessions = yearReviewSessions.map((s) => ({
|
|
||||||
...s,
|
|
||||||
workshopType: 'year-review' as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const allWeeklyCheckInSessions = weeklyCheckInSessions.map((s) => ({
|
|
||||||
...s,
|
|
||||||
workshopType: 'weekly-checkin' as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const allWeatherSessions = weatherSessions.map((s) => ({
|
|
||||||
...s,
|
|
||||||
workshopType: 'weather' as const,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Combine and sort by updatedAt
|
// Combine and sort by updatedAt
|
||||||
const allSessions = [
|
const allSessions = [
|
||||||
@@ -90,38 +72,7 @@ export default async function SessionsPage() {
|
|||||||
<h1 className="text-3xl font-bold text-foreground">Mes Ateliers</h1>
|
<h1 className="text-3xl font-bold text-foreground">Mes Ateliers</h1>
|
||||||
<p className="mt-1 text-muted">Tous vos ateliers en un seul endroit</p>
|
<p className="mt-1 text-muted">Tous vos ateliers en un seul endroit</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<NewWorkshopDropdown />
|
||||||
<Link href="/sessions/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>📊</span>
|
|
||||||
Nouveau SWOT
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/motivators/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>🎯</span>
|
|
||||||
Nouveau Motivators
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/year-review/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>📅</span>
|
|
||||||
Nouveau Year Review
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/weekly-checkin/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>📝</span>
|
|
||||||
Nouveau Check-in
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/weather/new">
|
|
||||||
<Button>
|
|
||||||
<span>🌤️</span>
|
|
||||||
Nouvelle Météo
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
@@ -136,37 +87,8 @@ export default async function SessionsPage() {
|
|||||||
découvrir les motivations, un Year Review pour faire le bilan de l'année, ou un
|
découvrir les motivations, un Year Review pour faire le bilan de l'année, ou un
|
||||||
Weekly Check-in pour le suivi hebdomadaire.
|
Weekly Check-in pour le suivi hebdomadaire.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-3 justify-center flex-wrap">
|
<div className="flex justify-center">
|
||||||
<Link href="/sessions/new">
|
<NewWorkshopDropdown />
|
||||||
<Button variant="outline">
|
|
||||||
<span>📊</span>
|
|
||||||
Créer un SWOT
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/motivators/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>🎯</span>
|
|
||||||
Créer un Moving Motivators
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/year-review/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>📅</span>
|
|
||||||
Créer un Year Review
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/weekly-checkin/new">
|
|
||||||
<Button variant="outline">
|
|
||||||
<span>📝</span>
|
|
||||||
Créer un Check-in
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Link href="/weather/new">
|
|
||||||
<Button>
|
|
||||||
<span>🌤️</span>
|
|
||||||
Créer une Météo
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
|
import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
|
||||||
import { getWeatherSessionById } from '@/services/weather';
|
import { getWeatherSessionById } from '@/services/weather';
|
||||||
import { getUserTeams } from '@/services/teams';
|
import { getUserTeams } from '@/services/teams';
|
||||||
import { WeatherBoard, WeatherLiveWrapper, WeatherInfoPanel } from '@/components/weather';
|
import { WeatherBoard, WeatherLiveWrapper, WeatherInfoPanel } from '@/components/weather';
|
||||||
@@ -33,8 +34,8 @@ export default async function WeatherSessionPage({ params }: WeatherSessionPageP
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
<Link href="/sessions?tab=weather" className="hover:text-foreground">
|
<Link href={getSessionsTabUrl('weather')} className="hover:text-foreground">
|
||||||
Météo
|
{getWorkshop('weather').labelShort}
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-foreground">{session.title}</span>
|
<span className="text-foreground">{session.title}</span>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
|
import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
|
||||||
import { getWeeklyCheckInSessionById } from '@/services/weekly-checkin';
|
import { getWeeklyCheckInSessionById } from '@/services/weekly-checkin';
|
||||||
import { getUserOKRsForPeriod } from '@/services/okrs';
|
import { getUserOKRsForPeriod } from '@/services/okrs';
|
||||||
import { getCurrentQuarterPeriod } from '@/lib/okr-utils';
|
import { getCurrentQuarterPeriod } from '@/lib/okr-utils';
|
||||||
@@ -44,8 +45,8 @@ export default async function WeeklyCheckInSessionPage({ params }: WeeklyCheckIn
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
<Link href="/sessions?tab=weekly-checkin" className="hover:text-foreground">
|
<Link href={getSessionsTabUrl('weekly-checkin')} className="hover:text-foreground">
|
||||||
Weekly Check-in
|
{getWorkshop('weekly-checkin').label}
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-foreground">{session.title}</span>
|
<span className="text-foreground">{session.title}</span>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
|
import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
|
||||||
import { getYearReviewSessionById } from '@/services/year-review';
|
import { getYearReviewSessionById } from '@/services/year-review';
|
||||||
import { YearReviewBoard, YearReviewLiveWrapper } from '@/components/year-review';
|
import { YearReviewBoard, YearReviewLiveWrapper } from '@/components/year-review';
|
||||||
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
||||||
@@ -29,8 +30,8 @@ export default async function YearReviewSessionPage({ params }: YearReviewSessio
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||||
<Link href="/sessions?tab=year-review" className="hover:text-foreground">
|
<Link href={getSessionsTabUrl('year-review')} className="hover:text-foreground">
|
||||||
Year Review
|
{getWorkshop('year-review').label}
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-foreground">{session.title}</span>
|
<span className="text-foreground">{session.title}</span>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useSession, signOut } from 'next-auth/react';
|
|||||||
import { useTheme } from '@/contexts/ThemeContext';
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Avatar, RocketIcon } from '@/components/ui';
|
import { Avatar, RocketIcon } from '@/components/ui';
|
||||||
|
import { WORKSHOPS } from '@/lib/workshops';
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
@@ -59,18 +60,18 @@ export function Header() {
|
|||||||
👥 Équipes
|
👥 Équipes
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Workshops Dropdown */}
|
{/* New Workshop Dropdown */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => setWorkshopsOpen(!workshopsOpen)}
|
onClick={() => setWorkshopsOpen(!workshopsOpen)}
|
||||||
onBlur={() => setTimeout(() => setWorkshopsOpen(false), 150)}
|
onBlur={() => setTimeout(() => setWorkshopsOpen(false), 150)}
|
||||||
className={`flex items-center gap-1 text-sm font-medium transition-colors ${
|
className={`flex items-center gap-1 text-sm font-medium transition-colors ${
|
||||||
isActiveLink('/sessions/') || isActiveLink('/motivators')
|
WORKSHOPS.some((w) => isActiveLink(w.path))
|
||||||
? 'text-primary'
|
? 'text-primary'
|
||||||
: 'text-muted hover:text-foreground'
|
: 'text-muted hover:text-foreground'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Ateliers
|
Nouvel atelier
|
||||||
<svg
|
<svg
|
||||||
className={`h-4 w-4 transition-transform ${workshopsOpen ? 'rotate-180' : ''}`}
|
className={`h-4 w-4 transition-transform ${workshopsOpen ? 'rotate-180' : ''}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -88,28 +89,20 @@ export function Header() {
|
|||||||
|
|
||||||
{workshopsOpen && (
|
{workshopsOpen && (
|
||||||
<div className="absolute left-0 z-20 mt-2 w-56 rounded-lg border border-border bg-card py-1 shadow-lg">
|
<div className="absolute left-0 z-20 mt-2 w-56 rounded-lg border border-border bg-card py-1 shadow-lg">
|
||||||
<Link
|
{WORKSHOPS.map((w) => (
|
||||||
href="/sessions/new"
|
<Link
|
||||||
className="flex items-center gap-3 px-4 py-2.5 text-sm text-foreground hover:bg-card-hover"
|
key={w.id}
|
||||||
onClick={() => setWorkshopsOpen(false)}
|
href={w.newPath}
|
||||||
>
|
className="flex items-center gap-3 px-4 py-2.5 text-sm text-foreground hover:bg-card-hover"
|
||||||
<span className="text-lg">📊</span>
|
onClick={() => setWorkshopsOpen(false)}
|
||||||
<div>
|
>
|
||||||
<div className="font-medium">Analyse SWOT</div>
|
<span className="text-lg">{w.icon}</span>
|
||||||
<div className="text-xs text-muted">Forces, faiblesses, opportunités</div>
|
<div>
|
||||||
</div>
|
<div className="font-medium">{w.label}</div>
|
||||||
</Link>
|
<div className="text-xs text-muted">{w.description}</div>
|
||||||
<Link
|
</div>
|
||||||
href="/motivators/new"
|
</Link>
|
||||||
className="flex items-center gap-3 px-4 py-2.5 text-sm text-foreground hover:bg-card-hover"
|
))}
|
||||||
onClick={() => setWorkshopsOpen(false)}
|
|
||||||
>
|
|
||||||
<span className="text-lg">🎯</span>
|
|
||||||
<div>
|
|
||||||
<div className="font-medium">Moving Motivators</div>
|
|
||||||
<div className="text-xs text-muted">Motivations intrinsèques</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
185
src/lib/workshops.ts
Normal file
185
src/lib/workshops.ts
Normal file
@@ -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<T, K extends WorkshopTypeId>(
|
||||||
|
sessions: T[],
|
||||||
|
type: K
|
||||||
|
): (T & { workshopType: K })[] {
|
||||||
|
return sessions.map((s) => ({ ...s, workshopType: type })) as (T & { workshopType: K })[];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user