feat: refactor workshop management by centralizing workshop data and improving session navigation across components
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m0s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m0s
This commit is contained in:
@@ -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 { getMotivatorSessionById } from '@/services/moving-motivators';
|
||||
import { MotivatorBoard, MotivatorLiveWrapper } from '@/components/moving-motivators';
|
||||
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
||||
@@ -29,8 +30,8 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||
<Link href="/sessions?tab=motivators" className="hover:text-foreground">
|
||||
Moving Motivators
|
||||
<Link href={getSessionsTabUrl('motivators')} className="hover:text-foreground">
|
||||
{getWorkshop('motivators').label}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{session.title}</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import { WORKSHOPS, getSessionsTabUrl } from '@/lib/workshops';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@@ -21,85 +22,19 @@ export default function Home() {
|
||||
Choisissez votre atelier
|
||||
</h2>
|
||||
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3 max-w-6xl mx-auto">
|
||||
{/* SWOT Workshop Card */}
|
||||
<WorkshopCard
|
||||
href="/sessions?tab=swot"
|
||||
icon="📊"
|
||||
title="Analyse SWOT"
|
||||
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',
|
||||
]}
|
||||
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"
|
||||
/>
|
||||
{WORKSHOPS.map((w) => (
|
||||
<WorkshopCard
|
||||
key={w.id}
|
||||
href={getSessionsTabUrl(w.id)}
|
||||
icon={w.icon}
|
||||
title={w.cardLabel}
|
||||
tagline={w.home.tagline}
|
||||
description={w.home.description}
|
||||
features={w.home.features}
|
||||
accentColor={w.accentColor}
|
||||
newHref={w.newPath}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -3,14 +3,7 @@
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Button } from '@/components/ui';
|
||||
|
||||
const WORKSHOPS = [
|
||||
{ href: '/sessions/new', icon: '📊', label: 'SWOT', desc: 'Forces, faiblesses, opportunités' },
|
||||
{ href: '/motivators/new', icon: '🎯', label: 'Moving Motivators', desc: 'Motivations intrinsèques' },
|
||||
{ href: '/year-review/new', icon: '📅', label: 'Year Review', desc: "Bilan de l'année" },
|
||||
{ href: '/weekly-checkin/new', icon: '📝', label: 'Weekly Check-in', desc: 'Suivi hebdomadaire' },
|
||||
{ href: '/weather/new', icon: '🌤️', label: 'Météo d\'équipe', desc: 'Humeur et énergie' },
|
||||
];
|
||||
import { WORKSHOPS } from '@/lib/workshops';
|
||||
|
||||
export function NewWorkshopDropdown() {
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -39,15 +32,15 @@ export function NewWorkshopDropdown() {
|
||||
<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.href}
|
||||
href={w.href}
|
||||
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.desc}</div>
|
||||
<div className="text-xs text-muted">{w.description}</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@@ -17,26 +17,18 @@ import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving
|
||||
import { deleteYearReviewSession, updateYearReviewSession } from '@/actions/year-review';
|
||||
import { deleteWeeklyCheckInSession, updateWeeklyCheckInSession } from '@/actions/weekly-checkin';
|
||||
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 VALID_TABS: WorkshopType[] = [
|
||||
'all',
|
||||
'swot',
|
||||
'motivators',
|
||||
'year-review',
|
||||
'weekly-checkin',
|
||||
'weather',
|
||||
'byPerson',
|
||||
];
|
||||
|
||||
const TYPE_TABS: { value: WorkshopType; icon: string; label: string }[] = [
|
||||
{ value: 'all', icon: '📋', label: 'Tous' },
|
||||
{ value: 'swot', icon: '📊', label: 'SWOT' },
|
||||
{ value: 'motivators', icon: '🎯', label: 'Motivators' },
|
||||
{ value: 'year-review', icon: '📅', label: 'Year Review' },
|
||||
{ value: 'weekly-checkin', icon: '📝', label: 'Check-in' },
|
||||
{ value: 'weather', icon: '🌤️', label: 'Météo' },
|
||||
const TYPE_TABS = [
|
||||
{ value: 'all' as const, icon: '📋', label: 'Tous' },
|
||||
...WORKSHOPS.map((w) => ({ value: w.id, icon: w.icon, label: w.labelShort })),
|
||||
];
|
||||
|
||||
interface ShareUser {
|
||||
@@ -212,10 +204,12 @@ export function WorkshopTabs({
|
||||
|
||||
// Get tab from URL or default to 'all'
|
||||
const tabParam = searchParams.get('tab');
|
||||
const activeTab: WorkshopType =
|
||||
tabParam && VALID_TABS.includes(tabParam as WorkshopType) ? (tabParam as WorkshopType) : 'all';
|
||||
const activeTab: WorkshopTabType =
|
||||
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());
|
||||
if (tab === 'all') {
|
||||
params.delete('tab');
|
||||
@@ -362,8 +356,8 @@ function TypeFilterDropdown({
|
||||
onOpenChange,
|
||||
counts,
|
||||
}: {
|
||||
activeTab: WorkshopType;
|
||||
setActiveTab: (t: WorkshopType) => void;
|
||||
activeTab: WorkshopTabType;
|
||||
setActiveTab: (t: WorkshopTabType) => void;
|
||||
open: boolean;
|
||||
onOpenChange: (v: boolean) => void;
|
||||
counts: Record<string, number>;
|
||||
@@ -493,20 +487,12 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
: (session as MotivatorSession).participant
|
||||
);
|
||||
|
||||
const workshop = getWorkshop(session.workshopType as WorkshopTypeId);
|
||||
const isSwot = session.workshopType === 'swot';
|
||||
const isYearReview = session.workshopType === 'year-review';
|
||||
const isWeeklyCheckIn = session.workshopType === 'weekly-checkin';
|
||||
const isWeather = session.workshopType === 'weather';
|
||||
const href = isSwot
|
||||
? `/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 href = getSessionPath(session.workshopType as WorkshopTypeId, session.id);
|
||||
const participant = isSwot
|
||||
? (session as SwotSession).collaborator
|
||||
: isYearReview
|
||||
@@ -516,15 +502,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
: isWeather
|
||||
? (session as WeatherSession).user.name || (session as WeatherSession).user.email
|
||||
: (session as MotivatorSession).participant;
|
||||
const accentColor = isSwot
|
||||
? '#06b6d4'
|
||||
: isYearReview
|
||||
? '#f59e0b'
|
||||
: isWeeklyCheckIn
|
||||
? '#10b981'
|
||||
: isWeather
|
||||
? '#3b82f6'
|
||||
: '#8b5cf6';
|
||||
const accentColor = workshop.accentColor;
|
||||
|
||||
const handleDelete = () => {
|
||||
startTransition(async () => {
|
||||
@@ -582,7 +560,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
setShowEditModal(true);
|
||||
};
|
||||
|
||||
const editParticipantLabel = isSwot ? 'Collaborateur' : isWeather ? '' : 'Participant';
|
||||
const editParticipantLabel = workshop.participantLabel;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -597,7 +575,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
|
||||
{/* Header: Icon + Title + Role badge */}
|
||||
<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>
|
||||
{!session.isOwner && (
|
||||
<span
|
||||
|
||||
@@ -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 { getSessionById } from '@/services/sessions';
|
||||
import { SwotBoard } from '@/components/swot/SwotBoard';
|
||||
import { SessionLiveWrapper } from '@/components/collaboration';
|
||||
@@ -30,8 +31,8 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||
<Link href="/sessions?tab=swot" className="hover:text-foreground">
|
||||
SWOT
|
||||
<Link href={getSessionsTabUrl('swot')} className="hover:text-foreground">
|
||||
{getWorkshop('swot').labelShort}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{session.title}</span>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getYearReviewSessionsByUserId } from '@/services/year-review';
|
||||
import { getWeeklyCheckInSessionsByUserId } from '@/services/weekly-checkin';
|
||||
import { getWeatherSessionsByUserId } from '@/services/weather';
|
||||
import { Card } from '@/components/ui';
|
||||
import { withWorkshopType } from '@/lib/workshops';
|
||||
import { WorkshopTabs } from './WorkshopTabs';
|
||||
import { NewWorkshopDropdown } from './NewWorkshopDropdown';
|
||||
|
||||
@@ -45,31 +46,12 @@ export default async function SessionsPage() {
|
||||
getWeatherSessionsByUserId(session.user.id),
|
||||
]);
|
||||
|
||||
// Add type to each session for unified display
|
||||
const allSwotSessions = swotSessions.map((s) => ({
|
||||
...s,
|
||||
workshopType: 'swot' as const,
|
||||
}));
|
||||
|
||||
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,
|
||||
}));
|
||||
// Add workshopType to each session for unified display
|
||||
const allSwotSessions = withWorkshopType(swotSessions, 'swot');
|
||||
const allMotivatorSessions = withWorkshopType(motivatorSessions, 'motivators');
|
||||
const allYearReviewSessions = withWorkshopType(yearReviewSessions, 'year-review');
|
||||
const allWeeklyCheckInSessions = withWorkshopType(weeklyCheckInSessions, 'weekly-checkin');
|
||||
const allWeatherSessions = withWorkshopType(weatherSessions, 'weather');
|
||||
|
||||
// Combine and sort by updatedAt
|
||||
const allSessions = [
|
||||
|
||||
@@ -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 { getWeatherSessionById } from '@/services/weather';
|
||||
import { getUserTeams } from '@/services/teams';
|
||||
import { WeatherBoard, WeatherLiveWrapper, WeatherInfoPanel } from '@/components/weather';
|
||||
@@ -33,8 +34,8 @@ export default async function WeatherSessionPage({ params }: WeatherSessionPageP
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||
<Link href="/sessions?tab=weather" className="hover:text-foreground">
|
||||
Météo
|
||||
<Link href={getSessionsTabUrl('weather')} className="hover:text-foreground">
|
||||
{getWorkshop('weather').labelShort}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{session.title}</span>
|
||||
|
||||
@@ -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 { getWeeklyCheckInSessionById } from '@/services/weekly-checkin';
|
||||
import { getUserOKRsForPeriod } from '@/services/okrs';
|
||||
import { getCurrentQuarterPeriod } from '@/lib/okr-utils';
|
||||
@@ -44,8 +45,8 @@ export default async function WeeklyCheckInSessionPage({ params }: WeeklyCheckIn
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||
<Link href="/sessions?tab=weekly-checkin" className="hover:text-foreground">
|
||||
Weekly Check-in
|
||||
<Link href={getSessionsTabUrl('weekly-checkin')} className="hover:text-foreground">
|
||||
{getWorkshop('weekly-checkin').label}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{session.title}</span>
|
||||
|
||||
@@ -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 */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 text-sm text-muted mb-2">
|
||||
<Link href="/sessions?tab=year-review" className="hover:text-foreground">
|
||||
Year Review
|
||||
<Link href={getSessionsTabUrl('year-review')} className="hover:text-foreground">
|
||||
{getWorkshop('year-review').label}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{session.title}</span>
|
||||
|
||||
Reference in New Issue
Block a user