feat: replace individual workshop buttons with a dropdown for creating new workshops in SessionsPage and update WorkshopTabs for improved tab management

This commit is contained in:
Julien Froidefond
2026-02-17 09:30:46 +01:00
parent e8282bb118
commit a8f53bfe2a
4 changed files with 215 additions and 105 deletions

View File

@@ -0,0 +1,58 @@
'use client';
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' },
];
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.href}
href={w.href}
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>
</Link>
))}
</div>
)}
</div>
);
}

View File

@@ -30,6 +30,15 @@ const VALID_TABS: WorkshopType[] = [
'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' },
];
interface ShareUser {
id: string;
name: string | null;
@@ -199,6 +208,7 @@ export function WorkshopTabs({
}: WorkshopTabsProps) {
const searchParams = useSearchParams();
const router = useRouter();
const [typeDropdownOpen, setTypeDropdownOpen] = useState(false);
// Get tab from URL or default to 'all'
const tabParam = searchParams.get('tab');
@@ -251,7 +261,7 @@ export function WorkshopTabs({
return (
<div className="space-y-6">
{/* 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
active={activeTab === 'all'}
onClick={() => setActiveTab('all')}
@@ -266,40 +276,18 @@ export function WorkshopTabs({
label="Par personne"
count={sessionsByPerson.size}
/>
<TabButton
active={activeTab === 'swot'}
onClick={() => setActiveTab('swot')}
icon="📊"
label="SWOT"
count={swotSessions.length}
/>
<TabButton
active={activeTab === 'motivators'}
onClick={() => setActiveTab('motivators')}
icon="🎯"
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}
<TypeFilterDropdown
activeTab={activeTab}
setActiveTab={setActiveTab}
open={typeDropdownOpen}
onOpenChange={setTypeDropdownOpen}
counts={{
swot: swotSessions.length,
motivators: motivatorSessions.length,
'year-review': yearReviewSessions.length,
'weekly-checkin': weeklyCheckInSessions.length,
weather: weatherSessions.length,
}}
/>
</div>
@@ -367,6 +355,92 @@ export function WorkshopTabs({
);
}
function TypeFilterDropdown({
activeTab,
setActiveTab,
open,
onOpenChange,
counts,
}: {
activeTab: WorkshopType;
setActiveTab: (t: WorkshopType) => 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({
active,
onClick,
@@ -382,9 +456,10 @@ function TabButton({
}) {
return (
<button
type="button"
onClick={onClick}
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
? 'bg-primary text-primary-foreground'
@@ -394,7 +469,7 @@ function TabButton({
>
<span>{icon}</span>
<span>{label}</span>
<Badge variant={active ? 'default' : 'primary'} className="ml-1">
<Badge variant={active ? 'default' : 'primary'} className="ml-1 text-xs">
{count}
</Badge>
</button>

View File

@@ -1,13 +1,13 @@
import { Suspense } from 'react';
import Link from 'next/link';
import { auth } from '@/lib/auth';
import { getSessionsByUserId } from '@/services/sessions';
import { getMotivatorSessionsByUserId } from '@/services/moving-motivators';
import { getYearReviewSessionsByUserId } from '@/services/year-review';
import { getWeeklyCheckInSessionsByUserId } from '@/services/weekly-checkin';
import { getWeatherSessionsByUserId } from '@/services/weather';
import { Card, Button } from '@/components/ui';
import { Card } from '@/components/ui';
import { WorkshopTabs } from './WorkshopTabs';
import { NewWorkshopDropdown } from './NewWorkshopDropdown';
function WorkshopTabsSkeleton() {
return (
@@ -90,38 +90,7 @@ export default async function SessionsPage() {
<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>
</div>
<div className="flex gap-2">
<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>
<NewWorkshopDropdown />
</div>
{/* Content */}
@@ -136,37 +105,8 @@ export default async function SessionsPage() {
découvrir les motivations, un Year Review pour faire le bilan de l&apos;année, ou un
Weekly Check-in pour le suivi hebdomadaire.
</p>
<div className="flex gap-3 justify-center flex-wrap">
<Link href="/sessions/new">
<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 className="flex justify-center">
<NewWorkshopDropdown />
</div>
</Card>
) : (