642 lines
23 KiB
TypeScript
642 lines
23 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useRef, useState } from 'react';
|
||
import { useSearchParams, useRouter } from 'next/navigation';
|
||
import { CollaboratorDisplay } from '@/components/ui';
|
||
import { type WorkshopTabType, VALID_TAB_PARAMS } from '@/lib/workshops';
|
||
import { useClickOutside } from '@/hooks/useClickOutside';
|
||
import {
|
||
type CardView,
|
||
type SortCol,
|
||
type WorkshopTabsProps,
|
||
type AnySession,
|
||
TABLE_COLS,
|
||
SORT_COLUMNS,
|
||
TYPE_TABS,
|
||
} from './workshop-session-types';
|
||
import {
|
||
getResolvedCollaborator,
|
||
groupByPerson,
|
||
getMonthGroup,
|
||
sortSessions,
|
||
} from './workshop-session-helpers';
|
||
import { SessionCard } from './SessionCard';
|
||
|
||
// ─── SectionHeader ────────────────────────────────────────────────────────────
|
||
|
||
function SectionHeader({ label, count }: { label: string; count: number }) {
|
||
return (
|
||
<div className="flex items-center gap-3 mb-5">
|
||
<div className="w-1 h-5 rounded-full bg-primary flex-shrink-0" />
|
||
<h2 className="text-sm font-semibold text-foreground">{label}</h2>
|
||
<span className="inline-flex items-center justify-center h-5 min-w-[20px] px-1.5 rounded-full bg-primary/10 text-primary text-[11px] font-semibold">
|
||
{count}
|
||
</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── SortIcon ─────────────────────────────────────────────────────────────────
|
||
|
||
function SortIcon({ active, dir }: { active: boolean; dir: 'asc' | 'desc' }) {
|
||
if (!active) {
|
||
return (
|
||
<svg
|
||
width="10"
|
||
height="10"
|
||
viewBox="0 0 10 10"
|
||
fill="currentColor"
|
||
className="opacity-30 flex-shrink-0"
|
||
>
|
||
<path d="M5 1.5L8 5H2L5 1.5Z" />
|
||
<path d="M5 8.5L2 5H8L5 8.5Z" />
|
||
</svg>
|
||
);
|
||
}
|
||
if (dir === 'asc') {
|
||
return (
|
||
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor" className="flex-shrink-0">
|
||
<path d="M5 1.5L8 6H2L5 1.5Z" />
|
||
</svg>
|
||
);
|
||
}
|
||
return (
|
||
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor" className="flex-shrink-0">
|
||
<path d="M5 8.5L2 4H8L5 8.5Z" />
|
||
</svg>
|
||
);
|
||
}
|
||
|
||
// ─── ViewToggle ───────────────────────────────────────────────────────────────
|
||
|
||
function ViewToggle({ view, setView }: { view: CardView; setView: (v: CardView) => void }) {
|
||
const btn = (v: CardView, label: string, icon: React.ReactNode) => (
|
||
<button
|
||
key={v}
|
||
type="button"
|
||
title={label}
|
||
onClick={() => setView(v)}
|
||
className={`p-1.5 rounded transition-colors ${
|
||
view === v ? 'bg-primary text-primary-foreground' : 'text-muted hover:text-foreground'
|
||
}`}
|
||
>
|
||
{icon}
|
||
</button>
|
||
);
|
||
|
||
return (
|
||
<div className="flex items-center gap-0.5 p-0.5 bg-card border border-border rounded-lg ml-auto flex-shrink-0 shadow-sm">
|
||
{btn(
|
||
'grid',
|
||
'Grille',
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
|
||
<rect x="0" y="0" width="4" height="4" rx="0.5" />
|
||
<rect x="5" y="0" width="4" height="4" rx="0.5" />
|
||
<rect x="10" y="0" width="4" height="4" rx="0.5" />
|
||
<rect x="0" y="5" width="4" height="4" rx="0.5" />
|
||
<rect x="5" y="5" width="4" height="4" rx="0.5" />
|
||
<rect x="10" y="5" width="4" height="4" rx="0.5" />
|
||
<rect x="0" y="10" width="4" height="4" rx="0.5" />
|
||
<rect x="5" y="10" width="4" height="4" rx="0.5" />
|
||
<rect x="10" y="10" width="4" height="4" rx="0.5" />
|
||
</svg>
|
||
)}
|
||
{btn(
|
||
'list',
|
||
'Liste',
|
||
<svg
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 14 14"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="1.5"
|
||
strokeLinecap="round"
|
||
>
|
||
<line x1="0" y1="2.5" x2="14" y2="2.5" />
|
||
<line x1="0" y1="7" x2="14" y2="7" />
|
||
<line x1="0" y1="11.5" x2="14" y2="11.5" />
|
||
</svg>
|
||
)}
|
||
{btn(
|
||
'table',
|
||
'Tableau',
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
|
||
<rect x="0" y="0" width="14" height="3.5" rx="0.5" />
|
||
<rect x="0" y="5" width="6" height="2.5" rx="0.5" />
|
||
<rect x="8" y="5" width="6" height="2.5" rx="0.5" />
|
||
<rect x="0" y="9.5" width="6" height="2.5" rx="0.5" />
|
||
<rect x="8" y="9.5" width="6" height="2.5" rx="0.5" />
|
||
</svg>
|
||
)}
|
||
{btn(
|
||
'timeline',
|
||
'Chronologique',
|
||
<svg
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 14 14"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="1.5"
|
||
strokeLinecap="round"
|
||
>
|
||
<line x1="3" y1="0" x2="3" y2="14" />
|
||
<circle cx="3" cy="2.5" r="1.5" fill="currentColor" stroke="none" />
|
||
<line x1="5" y1="2.5" x2="14" y2="2.5" />
|
||
<circle cx="3" cy="7" r="1.5" fill="currentColor" stroke="none" />
|
||
<line x1="5" y1="7" x2="14" y2="7" />
|
||
<circle cx="3" cy="11.5" r="1.5" fill="currentColor" stroke="none" />
|
||
<line x1="5" y1="11.5" x2="14" y2="11.5" />
|
||
</svg>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── TabButton ────────────────────────────────────────────────────────────────
|
||
|
||
function TabButton({
|
||
active,
|
||
onClick,
|
||
icon,
|
||
label,
|
||
count,
|
||
}: {
|
||
active: boolean;
|
||
onClick: () => void;
|
||
icon: string;
|
||
label: string;
|
||
count: number;
|
||
}) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
onClick={onClick}
|
||
className={`flex items-center gap-1.5 px-3.5 py-1.5 rounded-full font-medium text-sm transition-all duration-150 shadow-sm ${
|
||
active
|
||
? 'bg-primary text-primary-foreground shadow-md'
|
||
: 'bg-card text-foreground/70 border border-border hover:text-foreground hover:bg-card-hover'
|
||
}`}
|
||
>
|
||
<span>{icon}</span>
|
||
<span>{label}</span>
|
||
<span
|
||
className={`text-[11px] font-semibold px-1.5 py-0.5 rounded-full ${active ? 'bg-white/20 text-white' : 'bg-primary/10 text-primary'}`}
|
||
>
|
||
{count}
|
||
</span>
|
||
</button>
|
||
);
|
||
}
|
||
|
||
// ─── TypeFilterDropdown ───────────────────────────────────────────────────────
|
||
|
||
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' && t.value !== 'team');
|
||
const current = TYPE_TABS.find((t) => t.value === activeTab) ?? TYPE_TABS[0];
|
||
const isTypeSelected = activeTab !== 'all' && activeTab !== 'byPerson' && activeTab !== 'team';
|
||
const totalCount = typeTabs.reduce((s, t) => s + (counts[t.value] ?? 0), 0);
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
useClickOutside(containerRef, () => onOpenChange(false), open);
|
||
|
||
return (
|
||
<div ref={containerRef} className="relative">
|
||
<button
|
||
type="button"
|
||
onClick={() => onOpenChange(!open)}
|
||
className={`flex items-center gap-1.5 px-3.5 py-1.5 rounded-full font-medium text-sm transition-all duration-150 shadow-sm ${
|
||
isTypeSelected
|
||
? 'bg-primary text-primary-foreground shadow-md'
|
||
: 'bg-card text-foreground/70 border border-border hover:text-foreground hover:bg-card-hover'
|
||
}`}
|
||
>
|
||
<span>{isTypeSelected ? current.icon : '🔖'}</span>
|
||
<span>{isTypeSelected ? current.label : 'Type'}</span>
|
||
<span
|
||
className={`text-[11px] font-semibold px-1.5 py-0.5 rounded-full ${isTypeSelected ? 'bg-white/20 text-white' : 'bg-primary/10 text-primary'}`}
|
||
>
|
||
{isTypeSelected ? (counts[activeTab] ?? 0) : totalCount}
|
||
</span>
|
||
<svg
|
||
className={`h-3.5 w-3.5 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-48 rounded-xl border border-border bg-card py-1.5 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 transition-colors"
|
||
>
|
||
<span className="flex items-center gap-2">
|
||
<span>📋</span>
|
||
<span>Tous les types</span>
|
||
</span>
|
||
<span className="text-[11px] font-semibold px-1.5 py-0.5 rounded-full bg-primary/10 text-primary">
|
||
{totalCount}
|
||
</span>
|
||
</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 transition-colors"
|
||
>
|
||
<span className="flex items-center gap-2">
|
||
<span>{t.icon}</span>
|
||
<span>{t.label}</span>
|
||
</span>
|
||
<span className="text-[11px] font-semibold px-1.5 py-0.5 rounded-full bg-primary/10 text-primary">
|
||
{counts[t.value] ?? 0}
|
||
</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── SessionsGrid ─────────────────────────────────────────────────────────────
|
||
|
||
function SessionsGrid({
|
||
sessions,
|
||
view,
|
||
isTeamCollab = false,
|
||
}: {
|
||
sessions: AnySession[];
|
||
view: CardView;
|
||
isTeamCollab?: boolean;
|
||
}) {
|
||
if (view === 'table') {
|
||
return (
|
||
<div className="rounded-xl border border-border overflow-hidden overflow-x-auto bg-card">
|
||
<div
|
||
className="grid text-[11px] font-semibold text-muted uppercase tracking-wider bg-card-hover/60 border-b border-border"
|
||
style={{ gridTemplateColumns: TABLE_COLS }}
|
||
>
|
||
{SORT_COLUMNS.map((col) => (
|
||
<div key={col.key} className="px-4 py-2.5">
|
||
{col.label}
|
||
</div>
|
||
))}
|
||
</div>
|
||
{sessions.map((s) => (
|
||
<SessionCard key={s.id} session={s} isTeamCollab={isTeamCollab} view="table" />
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<div
|
||
className={
|
||
view === 'list' ? 'flex flex-col gap-2' : 'grid gap-4 md:grid-cols-2 lg:grid-cols-3'
|
||
}
|
||
>
|
||
{sessions.map((s) => (
|
||
<SessionCard key={s.id} session={s} isTeamCollab={isTeamCollab} view={view} />
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── SortableTableView ────────────────────────────────────────────────────────
|
||
|
||
function SortableTableView({
|
||
sessions,
|
||
sortCol,
|
||
sortDir,
|
||
onSort,
|
||
}: {
|
||
sessions: AnySession[];
|
||
sortCol: SortCol;
|
||
sortDir: 'asc' | 'desc';
|
||
onSort: (col: SortCol) => void;
|
||
}) {
|
||
if (sessions.length === 0) {
|
||
return <div className="text-center py-12 text-muted">Aucun atelier pour le moment</div>;
|
||
}
|
||
return (
|
||
<div className="rounded-xl border border-border overflow-hidden overflow-x-auto bg-card">
|
||
<div
|
||
className="grid bg-card-hover/60 border-b border-border"
|
||
style={{ gridTemplateColumns: TABLE_COLS }}
|
||
>
|
||
{SORT_COLUMNS.map((col) => (
|
||
<button
|
||
key={col.key}
|
||
type="button"
|
||
onClick={() => onSort(col.key)}
|
||
className={`flex items-center gap-1.5 px-4 py-2.5 text-left text-[11px] font-semibold uppercase tracking-wider transition-colors hover:text-foreground ${
|
||
sortCol === col.key ? 'text-primary' : 'text-muted'
|
||
}`}
|
||
>
|
||
{col.label}
|
||
<SortIcon active={sortCol === col.key} dir={sortDir} />
|
||
</button>
|
||
))}
|
||
</div>
|
||
{sessions.map((s) => (
|
||
<SessionCard
|
||
key={s.id}
|
||
session={s}
|
||
view="table"
|
||
isTeamCollab={(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab}
|
||
/>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── WorkshopTabs ─────────────────────────────────────────────────────────────
|
||
|
||
export function WorkshopTabs({
|
||
swotSessions,
|
||
motivatorSessions,
|
||
yearReviewSessions,
|
||
weeklyCheckInSessions,
|
||
weatherSessions,
|
||
gifMoodSessions,
|
||
teamCollabSessions = [],
|
||
}: WorkshopTabsProps) {
|
||
const CARD_VIEW_STORAGE_KEY = 'sessions:cardView';
|
||
const isCardView = (value: string): value is CardView =>
|
||
value === 'grid' || value === 'list' || value === 'table' || value === 'timeline';
|
||
|
||
const searchParams = useSearchParams();
|
||
const router = useRouter();
|
||
const [typeDropdownOpen, setTypeDropdownOpen] = useState(false);
|
||
const [cardView, setCardView] = useState<CardView>(() => {
|
||
if (typeof window === 'undefined') return 'grid';
|
||
const storedView = localStorage.getItem(CARD_VIEW_STORAGE_KEY);
|
||
return storedView && isCardView(storedView) ? storedView : 'grid';
|
||
});
|
||
const [sortCol, setSortCol] = useState<SortCol>('date');
|
||
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc');
|
||
|
||
useEffect(() => {
|
||
localStorage.setItem(CARD_VIEW_STORAGE_KEY, cardView);
|
||
}, [cardView]);
|
||
|
||
const handleSort = (col: SortCol) => {
|
||
if (sortCol === col) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
|
||
else {
|
||
setSortCol(col);
|
||
setSortDir('asc');
|
||
}
|
||
};
|
||
|
||
const tabParam = searchParams.get('tab');
|
||
const activeTab: WorkshopTabType =
|
||
tabParam && VALID_TAB_PARAMS.includes(tabParam as WorkshopTabType)
|
||
? (tabParam as WorkshopTabType)
|
||
: 'all';
|
||
|
||
const setActiveTab = (tab: WorkshopTabType) => {
|
||
const params = new URLSearchParams(searchParams.toString());
|
||
if (tab === 'all') params.delete('tab');
|
||
else params.set('tab', tab);
|
||
router.push(`/sessions${params.toString() ? `?${params.toString()}` : ''}`);
|
||
};
|
||
|
||
const allSessions: AnySession[] = [
|
||
...swotSessions,
|
||
...motivatorSessions,
|
||
...yearReviewSessions,
|
||
...weeklyCheckInSessions,
|
||
...weatherSessions,
|
||
...gifMoodSessions,
|
||
].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
||
|
||
const filteredSessions: AnySession[] =
|
||
activeTab === 'all' || activeTab === 'byPerson'
|
||
? allSessions
|
||
: activeTab === 'team'
|
||
? teamCollabSessions
|
||
: activeTab === 'swot'
|
||
? swotSessions
|
||
: activeTab === 'motivators'
|
||
? motivatorSessions
|
||
: activeTab === 'year-review'
|
||
? yearReviewSessions
|
||
: activeTab === 'weekly-checkin'
|
||
? weeklyCheckInSessions
|
||
: activeTab === 'gif-mood'
|
||
? gifMoodSessions
|
||
: weatherSessions;
|
||
|
||
const ownedSessions = filteredSessions.filter((s) => s.isOwner);
|
||
const sharedSessions = filteredSessions.filter(
|
||
(s) => !s.isOwner && !(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab
|
||
);
|
||
const teamCollabFiltered = activeTab === 'all' ? teamCollabSessions : [];
|
||
|
||
const sessionsByPerson = groupByPerson(allSessions);
|
||
const sortedPersons = Array.from(sessionsByPerson.entries()).sort((a, b) =>
|
||
a[0].localeCompare(b[0], 'fr')
|
||
);
|
||
|
||
// Timeline grouping
|
||
const timelineSessions = [
|
||
...(activeTab === 'all' ? [...filteredSessions, ...teamCollabSessions] : filteredSessions),
|
||
].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
||
const byMonth = new Map<string, AnySession[]>();
|
||
timelineSessions.forEach((s) => {
|
||
const key = getMonthGroup(s.updatedAt);
|
||
if (!byMonth.has(key)) byMonth.set(key, []);
|
||
byMonth.get(key)!.push(s);
|
||
});
|
||
|
||
// Flat sorted sessions for the sortable table view
|
||
const flatTableSessions =
|
||
cardView === 'table' && activeTab !== 'byPerson'
|
||
? sortSessions(
|
||
activeTab === 'all' ? [...filteredSessions, ...teamCollabSessions] : filteredSessions,
|
||
sortCol,
|
||
sortDir
|
||
)
|
||
: [];
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
{/* Tabs + vue toggle */}
|
||
<div className="flex gap-1.5 items-center flex-wrap">
|
||
<TabButton
|
||
active={activeTab === 'all'}
|
||
onClick={() => setActiveTab('all')}
|
||
icon="📋"
|
||
label="Tous"
|
||
count={allSessions.length}
|
||
/>
|
||
<TabButton
|
||
active={activeTab === 'byPerson'}
|
||
onClick={() => setActiveTab('byPerson')}
|
||
icon="👥"
|
||
label="Par personne"
|
||
count={sessionsByPerson.size}
|
||
/>
|
||
{teamCollabSessions.length > 0 && (
|
||
<TabButton
|
||
active={activeTab === 'team'}
|
||
onClick={() => setActiveTab('team')}
|
||
icon="🏢"
|
||
label="Équipe"
|
||
count={teamCollabSessions.length}
|
||
/>
|
||
)}
|
||
<div className="h-5 w-px bg-border mx-0.5 self-center" />
|
||
<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,
|
||
'gif-mood': gifMoodSessions.length,
|
||
team: teamCollabSessions.length,
|
||
}}
|
||
/>
|
||
<ViewToggle view={cardView} setView={setCardView} />
|
||
</div>
|
||
|
||
{/* ── Vue Tableau flat (colonnes triables) ──────────────────── */}
|
||
{cardView === 'table' && activeTab !== 'byPerson' ? (
|
||
<SortableTableView
|
||
sessions={flatTableSessions}
|
||
sortCol={sortCol}
|
||
sortDir={sortDir}
|
||
onSort={handleSort}
|
||
/>
|
||
) : cardView === 'timeline' && activeTab !== 'byPerson' ? (
|
||
/* ── Vue Timeline ────────────────────────────────────────── */
|
||
byMonth.size === 0 ? (
|
||
<div className="text-center py-12 text-muted">Aucun atelier pour le moment</div>
|
||
) : (
|
||
<div className="space-y-8">
|
||
{Array.from(byMonth.entries()).map(([period, sessions]) => (
|
||
<section key={period}>
|
||
<div className="flex items-center gap-3 mb-4">
|
||
<div className="h-px flex-1 bg-border" />
|
||
<span className="text-xs font-semibold text-muted uppercase tracking-widest px-2 capitalize">
|
||
{period}
|
||
</span>
|
||
<div className="h-px flex-1 bg-border" />
|
||
</div>
|
||
<div className="flex flex-col gap-2">
|
||
{sessions.map((s) => (
|
||
<SessionCard
|
||
key={s.id}
|
||
session={s}
|
||
isTeamCollab={(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab}
|
||
view="list"
|
||
/>
|
||
))}
|
||
</div>
|
||
</section>
|
||
))}
|
||
</div>
|
||
)
|
||
) : activeTab === 'byPerson' ? (
|
||
/* ── Vue Par personne ───────────────────────────────────── */
|
||
sortedPersons.length === 0 ? (
|
||
<div className="text-center py-12 text-muted">Aucun atelier pour le moment</div>
|
||
) : (
|
||
<div className="space-y-8">
|
||
{sortedPersons.map(([personKey, sessions]) => {
|
||
const resolved = getResolvedCollaborator(sessions[0]);
|
||
return (
|
||
<section key={personKey}>
|
||
<div className="flex items-center gap-3 mb-5">
|
||
<CollaboratorDisplay collaborator={resolved} size="md" />
|
||
<span className="inline-flex items-center justify-center h-5 min-w-[20px] px-1.5 rounded-full bg-primary/10 text-primary text-[11px] font-semibold">
|
||
{sessions.length} atelier{sessions.length > 1 ? 's' : ''}
|
||
</span>
|
||
</div>
|
||
<SessionsGrid
|
||
sessions={sessions}
|
||
view={cardView === 'timeline' ? 'list' : cardView}
|
||
/>
|
||
</section>
|
||
);
|
||
})}
|
||
</div>
|
||
)
|
||
) : activeTab === 'team' ? (
|
||
/* ── Vue Équipe ─────────────────────────────────────────── */
|
||
teamCollabSessions.length === 0 ? (
|
||
<div className="text-center py-12 text-muted">
|
||
Aucun atelier de vos collaborateurs (non partagés)
|
||
</div>
|
||
) : (
|
||
<div className="space-y-8">
|
||
<section>
|
||
<SectionHeader
|
||
label="Ateliers de l'équipe – non partagés"
|
||
count={teamCollabSessions.length}
|
||
/>
|
||
<p className="text-sm text-muted mb-5 -mt-2">
|
||
En tant qu'admin d'équipe, vous voyez les ateliers de vos collaborateurs
|
||
qui ne vous sont pas encore partagés.
|
||
</p>
|
||
<SessionsGrid sessions={teamCollabSessions} view={cardView} isTeamCollab />
|
||
</section>
|
||
</div>
|
||
)
|
||
) : filteredSessions.length === 0 ? (
|
||
<div className="text-center py-12 text-muted">Aucun atelier de ce type pour le moment</div>
|
||
) : (
|
||
/* ── Vue normale (tous / par type) ─────────────────────── */
|
||
<div className="space-y-10">
|
||
{ownedSessions.length > 0 && (
|
||
<section>
|
||
<SectionHeader label="Mes ateliers" count={ownedSessions.length} />
|
||
<SessionsGrid sessions={ownedSessions} view={cardView} />
|
||
</section>
|
||
)}
|
||
{sharedSessions.length > 0 && (
|
||
<section>
|
||
<SectionHeader label="Partagés avec moi" count={sharedSessions.length} />
|
||
<SessionsGrid sessions={sharedSessions} view={cardView} />
|
||
</section>
|
||
)}
|
||
{activeTab === 'all' && teamCollabFiltered.length > 0 && (
|
||
<section>
|
||
<SectionHeader label="Équipe – non partagés" count={teamCollabFiltered.length} />
|
||
<SessionsGrid sessions={teamCollabFiltered} view={cardView} isTeamCollab />
|
||
</section>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|