Persist sessions view mode in localStorage
This commit is contained in:
@@ -1,16 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import { CollaboratorDisplay } from '@/components/ui';
|
||||
import { type WorkshopTabType, WORKSHOPS, VALID_TAB_PARAMS } from '@/lib/workshops';
|
||||
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,
|
||||
type CardView,
|
||||
type SortCol,
|
||||
type WorkshopTabsProps,
|
||||
type AnySession,
|
||||
TABLE_COLS,
|
||||
SORT_COLUMNS,
|
||||
TYPE_TABS,
|
||||
} from './workshop-session-types';
|
||||
import {
|
||||
getResolvedCollaborator, groupByPerson, getMonthGroup, sortSessions,
|
||||
getResolvedCollaborator,
|
||||
groupByPerson,
|
||||
getMonthGroup,
|
||||
sortSessions,
|
||||
} from './workshop-session-helpers';
|
||||
import { SessionCard } from './SessionCard';
|
||||
|
||||
@@ -33,7 +41,13 @@ function SectionHeader({ label, count }: { label: string; count: number }) {
|
||||
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">
|
||||
<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>
|
||||
@@ -72,27 +86,61 @@ function ViewToggle({ view, setView }: { view: CardView; setView: (v: CardView)
|
||||
|
||||
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',
|
||||
{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" />
|
||||
<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" />
|
||||
{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',
|
||||
{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" />
|
||||
<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">
|
||||
{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" />
|
||||
@@ -108,11 +156,23 @@ function ViewToggle({ view, setView }: { view: CardView; setView: (v: CardView)
|
||||
|
||||
// ─── TabButton ────────────────────────────────────────────────────────────────
|
||||
|
||||
function TabButton({ active, onClick, icon, label, count }: {
|
||||
active: boolean; onClick: () => void; icon: string; label: string; count: number;
|
||||
function TabButton({
|
||||
active,
|
||||
onClick,
|
||||
icon,
|
||||
label,
|
||||
count,
|
||||
}: {
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
icon: string;
|
||||
label: string;
|
||||
count: number;
|
||||
}) {
|
||||
return (
|
||||
<button type="button" onClick={onClick}
|
||||
<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'
|
||||
@@ -121,7 +181,9 @@ function TabButton({ active, onClick, icon, label, count }: {
|
||||
>
|
||||
<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'}`}>
|
||||
<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>
|
||||
@@ -131,10 +193,17 @@ function TabButton({ active, onClick, icon, label, count }: {
|
||||
// ─── TypeFilterDropdown ───────────────────────────────────────────────────────
|
||||
|
||||
function TypeFilterDropdown({
|
||||
activeTab, setActiveTab, open, onOpenChange, counts,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
open,
|
||||
onOpenChange,
|
||||
counts,
|
||||
}: {
|
||||
activeTab: WorkshopTabType; setActiveTab: (t: WorkshopTabType) => void;
|
||||
open: boolean; onOpenChange: (v: boolean) => void; counts: Record<string, number>;
|
||||
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];
|
||||
@@ -156,25 +225,55 @@ function TypeFilterDropdown({
|
||||
>
|
||||
<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'}`}>
|
||||
<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">
|
||||
<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
|
||||
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
|
||||
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>
|
||||
@@ -186,16 +285,25 @@ function TypeFilterDropdown({
|
||||
// ─── SessionsGrid ─────────────────────────────────────────────────────────────
|
||||
|
||||
function SessionsGrid({
|
||||
sessions, view, isTeamCollab = false,
|
||||
sessions,
|
||||
view,
|
||||
isTeamCollab = false,
|
||||
}: {
|
||||
sessions: AnySession[]; view: CardView; isTeamCollab?: boolean;
|
||||
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 }}>
|
||||
<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 key={col.key} className="px-4 py-2.5">
|
||||
{col.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{sessions.map((s) => (
|
||||
@@ -205,7 +313,11 @@ function SessionsGrid({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={view === 'list' ? 'flex flex-col gap-2' : 'grid gap-4 md:grid-cols-2 lg:grid-cols-3'}>
|
||||
<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} />
|
||||
))}
|
||||
@@ -216,7 +328,10 @@ function SessionsGrid({
|
||||
// ─── SortableTableView ────────────────────────────────────────────────────────
|
||||
|
||||
function SortableTableView({
|
||||
sessions, sortCol, sortDir, onSort,
|
||||
sessions,
|
||||
sortCol,
|
||||
sortDir,
|
||||
onSort,
|
||||
}: {
|
||||
sessions: AnySession[];
|
||||
sortCol: SortCol;
|
||||
@@ -228,7 +343,10 @@ function SortableTableView({
|
||||
}
|
||||
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 }}>
|
||||
<div
|
||||
className="grid bg-card-hover/60 border-b border-border"
|
||||
style={{ gridTemplateColumns: TABLE_COLS }}
|
||||
>
|
||||
{SORT_COLUMNS.map((col) => (
|
||||
<button
|
||||
key={col.key}
|
||||
@@ -258,20 +376,39 @@ function SortableTableView({
|
||||
// ─── WorkshopTabs ─────────────────────────────────────────────────────────────
|
||||
|
||||
export function WorkshopTabs({
|
||||
swotSessions, motivatorSessions, yearReviewSessions,
|
||||
weeklyCheckInSessions, weatherSessions, gifMoodSessions,
|
||||
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>('grid');
|
||||
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'); }
|
||||
else {
|
||||
setSortCol(col);
|
||||
setSortDir('asc');
|
||||
}
|
||||
};
|
||||
|
||||
const tabParam = searchParams.get('tab');
|
||||
@@ -288,19 +425,30 @@ export function WorkshopTabs({
|
||||
};
|
||||
|
||||
const allSessions: AnySession[] = [
|
||||
...swotSessions, ...motivatorSessions, ...yearReviewSessions,
|
||||
...weeklyCheckInSessions, ...weatherSessions, ...gifMoodSessions,
|
||||
...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;
|
||||
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(
|
||||
@@ -309,11 +457,14 @@ export function WorkshopTabs({
|
||||
const teamCollabFiltered = activeTab === 'all' ? teamCollabSessions : [];
|
||||
|
||||
const sessionsByPerson = groupByPerson(allSessions);
|
||||
const sortedPersons = Array.from(sessionsByPerson.entries()).sort((a, b) => a[0].localeCompare(b[0], 'fr'));
|
||||
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 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);
|
||||
@@ -326,7 +477,8 @@ export function WorkshopTabs({
|
||||
cardView === 'table' && activeTab !== 'byPerson'
|
||||
? sortSessions(
|
||||
activeTab === 'all' ? [...filteredSessions, ...teamCollabSessions] : filteredSessions,
|
||||
sortCol, sortDir,
|
||||
sortCol,
|
||||
sortDir
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -334,19 +486,42 @@ export function WorkshopTabs({
|
||||
<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} />
|
||||
<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} />
|
||||
<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}
|
||||
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,
|
||||
swot: swotSessions.length,
|
||||
motivators: motivatorSessions.length,
|
||||
'year-review': yearReviewSessions.length,
|
||||
'weekly-checkin': weeklyCheckInSessions.length,
|
||||
weather: weatherSessions.length,
|
||||
'gif-mood': gifMoodSessions.length,
|
||||
team: teamCollabSessions.length,
|
||||
}}
|
||||
/>
|
||||
@@ -355,8 +530,12 @@ export function WorkshopTabs({
|
||||
|
||||
{/* ── Vue Tableau flat (colonnes triables) ──────────────────── */}
|
||||
{cardView === 'table' && activeTab !== 'byPerson' ? (
|
||||
<SortableTableView sessions={flatTableSessions} sortCol={sortCol} sortDir={sortDir} onSort={handleSort} />
|
||||
|
||||
<SortableTableView
|
||||
sessions={flatTableSessions}
|
||||
sortCol={sortCol}
|
||||
sortDir={sortDir}
|
||||
onSort={handleSort}
|
||||
/>
|
||||
) : cardView === 'timeline' && activeTab !== 'byPerson' ? (
|
||||
/* ── Vue Timeline ────────────────────────────────────────── */
|
||||
byMonth.size === 0 ? (
|
||||
@@ -367,19 +546,25 @@ export function WorkshopTabs({
|
||||
<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>
|
||||
<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" />
|
||||
<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 ? (
|
||||
@@ -396,21 +581,28 @@ export function WorkshopTabs({
|
||||
{sessions.length} atelier{sessions.length > 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<SessionsGrid sessions={sessions} view={cardView === 'timeline' ? 'list' : cardView} />
|
||||
<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="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} />
|
||||
<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.
|
||||
@@ -419,10 +611,8 @@ export function WorkshopTabs({
|
||||
</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">
|
||||
|
||||
Reference in New Issue
Block a user