Files
workshop-manager/src/app/sessions/WorkshopTabs.tsx
2026-02-18 08:39:15 +01:00

880 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useTransition, useRef } from 'react';
import Link from 'next/link';
import { useSearchParams, useRouter } from 'next/navigation';
import {
Card,
Badge,
Button,
Modal,
ModalFooter,
Input,
CollaboratorDisplay,
} from '@/components/ui';
import { deleteSwotSession, updateSwotSession } from '@/actions/session';
import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving-motivators';
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';
import { useClickOutside } from '@/hooks/useClickOutside';
import type { Share } from '@/lib/share-utils';
const TYPE_TABS = [
{ value: 'all' as const, icon: '📋', label: 'Tous' },
{ value: 'team' as const, icon: '🏢', label: 'Équipe' },
...WORKSHOPS.map((w) => ({ value: w.id, icon: w.icon, label: w.labelShort })),
];
interface ResolvedCollaborator {
raw: string;
matchedUser: {
id: string;
email: string;
name: string | null;
} | null;
}
interface SwotSession {
id: string;
title: string;
collaborator: string;
resolvedCollaborator: ResolvedCollaborator;
updatedAt: Date;
isOwner: boolean;
role: 'OWNER' | 'VIEWER' | 'EDITOR';
user: { id: string; name: string | null; email: string };
shares: Share[];
_count: { items: number; actions: number };
workshopType: 'swot';
isTeamCollab?: true;
canEdit?: boolean;
}
interface MotivatorSession {
id: string;
title: string;
participant: string;
resolvedParticipant: ResolvedCollaborator;
updatedAt: Date;
isOwner: boolean;
role: 'OWNER' | 'VIEWER' | 'EDITOR';
user: { id: string; name: string | null; email: string };
shares: Share[];
_count: { cards: number };
workshopType: 'motivators';
isTeamCollab?: true;
canEdit?: boolean;
}
interface YearReviewSession {
id: string;
title: string;
participant: string;
resolvedParticipant: ResolvedCollaborator;
year: number;
updatedAt: Date;
isOwner: boolean;
role: 'OWNER' | 'VIEWER' | 'EDITOR';
user: { id: string; name: string | null; email: string };
shares: Share[];
_count: { items: number };
workshopType: 'year-review';
isTeamCollab?: true;
canEdit?: boolean;
}
interface WeeklyCheckInSession {
id: string;
title: string;
participant: string;
resolvedParticipant: ResolvedCollaborator;
date: Date;
updatedAt: Date;
isOwner: boolean;
role: 'OWNER' | 'VIEWER' | 'EDITOR';
user: { id: string; name: string | null; email: string };
shares: Share[];
_count: { items: number };
workshopType: 'weekly-checkin';
isTeamCollab?: true;
canEdit?: boolean;
}
interface WeatherSession {
id: string;
title: string;
date: Date;
updatedAt: Date;
isOwner: boolean;
role: 'OWNER' | 'VIEWER' | 'EDITOR';
user: { id: string; name: string | null; email: string };
shares: Share[];
_count: { entries: number };
workshopType: 'weather';
isTeamCollab?: true;
canEdit?: boolean;
}
type AnySession = SwotSession | MotivatorSession | YearReviewSession | WeeklyCheckInSession | WeatherSession;
interface WorkshopTabsProps {
swotSessions: SwotSession[];
motivatorSessions: MotivatorSession[];
yearReviewSessions: YearReviewSession[];
weeklyCheckInSessions: WeeklyCheckInSession[];
weatherSessions: WeatherSession[];
teamCollabSessions?: (AnySession & { isTeamCollab?: true })[];
}
// Helper to get resolved collaborator from any session
function getResolvedCollaborator(session: AnySession): ResolvedCollaborator {
if (session.workshopType === 'swot') {
return (session as SwotSession).resolvedCollaborator;
} else if (session.workshopType === 'year-review') {
return (session as YearReviewSession).resolvedParticipant;
} else if (session.workshopType === 'weekly-checkin') {
return (session as WeeklyCheckInSession).resolvedParticipant;
} else if (session.workshopType === 'weather') {
// For weather sessions, use the owner as the "participant" since it's a personal weather
const weatherSession = session as WeatherSession;
return {
raw: weatherSession.user.name || weatherSession.user.email,
matchedUser: {
id: weatherSession.user.id,
email: weatherSession.user.email,
name: weatherSession.user.name,
},
};
} else {
return (session as MotivatorSession).resolvedParticipant;
}
}
// Get grouping key - use matched user ID if available, otherwise normalized raw string
function getGroupKey(session: AnySession): string {
const resolved = getResolvedCollaborator(session);
// If we have a matched user, use their ID as key (ensures same person = same group)
if (resolved.matchedUser) {
return `user:${resolved.matchedUser.id}`;
}
// Otherwise, normalize the raw string
return `raw:${resolved.raw.trim().toLowerCase()}`;
}
// Group sessions by participant (using matched user ID when available)
function groupByPerson(sessions: AnySession[]): Map<string, AnySession[]> {
const grouped = new Map<string, AnySession[]>();
sessions.forEach((session) => {
const key = getGroupKey(session);
const existing = grouped.get(key);
if (existing) {
existing.push(session);
} else {
grouped.set(key, [session]);
}
});
// Sort sessions within each group by date
grouped.forEach((sessions) => {
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
});
return grouped;
}
export function WorkshopTabs({
swotSessions,
motivatorSessions,
yearReviewSessions,
weeklyCheckInSessions,
weatherSessions,
teamCollabSessions = [],
}: 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');
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()}` : ''}`);
};
// Combine and sort all sessions (exclude team collab from main list - they're shown separately)
const allSessions: AnySession[] = [
...swotSessions,
...motivatorSessions,
...yearReviewSessions,
...weeklyCheckInSessions,
...weatherSessions,
].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
// Filter based on active tab (for non-byPerson tabs)
const filteredSessions =
activeTab === 'all' || activeTab === 'byPerson'
? allSessions
: activeTab === 'team'
? teamCollabSessions
: activeTab === 'swot'
? swotSessions
: activeTab === 'motivators'
? motivatorSessions
: activeTab === 'year-review'
? yearReviewSessions
: activeTab === 'weekly-checkin'
? weeklyCheckInSessions
: weatherSessions;
// Separate by ownership (for non-team tab: owned, shared, teamCollab)
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 : activeTab === 'team' ? teamCollabSessions : [];
// Group by person (all sessions - owned and shared)
const sessionsByPerson = groupByPerson(allSessions);
const sortedPersons = Array.from(sessionsByPerson.entries()).sort((a, b) =>
a[0].localeCompare(b[0], 'fr')
);
return (
<div className="space-y-6">
{/* Tabs */}
<div className="flex gap-2 border-b border-border pb-4 flex-wrap items-center">
<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}
/>
)}
<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,
team: teamCollabSessions.length,
}}
/>
</div>
{/* Sessions */}
{activeTab === 'byPerson' ? (
// By Person View
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}>
<h2 className="text-lg font-semibold text-foreground mb-4 flex items-center gap-3">
<CollaboratorDisplay collaborator={resolved} size="md" />
<Badge variant="primary">
{sessions.length} atelier{sessions.length > 1 ? 's' : ''}
</Badge>
</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{sessions.map((s) => (
<SessionCard key={s.id} session={s} />
))}
</div>
</section>
);
})}
</div>
)
) : activeTab === 'team' ? (
teamCollabSessions.length === 0 ? (
<div className="text-center py-12 text-muted">
Aucun atelier de vos collaborateurs d&apos;équipe (non partagés avec vous)
</div>
) : (
<div className="space-y-8">
<section>
<h2 className="text-lg font-semibold text-muted mb-4">
🏢 Ateliers de l&apos;équipe non partagés ({teamCollabSessions.length})
</h2>
<p className="text-sm text-muted mb-4">
En tant qu&apos;admin d&apos;équipe, vous voyez les ateliers de vos collaborateurs qui ne vous sont pas encore partagés.
</p>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{teamCollabSessions.map((s) => (
<SessionCard key={s.id} session={s} isTeamCollab />
))}
</div>
</section>
</div>
)
) : filteredSessions.length === 0 ? (
<div className="text-center py-12 text-muted">Aucun atelier de ce type pour le moment</div>
) : (
<div className="space-y-8">
{/* My Sessions */}
{ownedSessions.length > 0 && (
<section>
<h2 className="text-lg font-semibold text-foreground mb-4">
📁 Mes ateliers ({ownedSessions.length})
</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{ownedSessions.map((s) => (
<SessionCard key={s.id} session={s} />
))}
</div>
</section>
)}
{/* Shared Sessions */}
{sharedSessions.length > 0 && (
<section>
<h2 className="text-lg font-semibold text-foreground mb-4">
🤝 Partagés avec moi ({sharedSessions.length})
</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{sharedSessions.map((s) => (
<SessionCard key={s.id} session={s} />
))}
</div>
</section>
)}
{/* Team collab sessions (non-shared) - grayed out, admin view only */}
{activeTab === 'all' && teamCollabFiltered.length > 0 && (
<section>
<h2 className="text-lg font-semibold text-muted mb-4">
🏢 Équipe non partagés ({teamCollabFiltered.length})
</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{teamCollabFiltered.map((s) => (
<SessionCard key={s.id} session={s} isTeamCollab />
))}
</div>
</section>
)}
</div>
)}
</div>
);
}
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);
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-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,
icon,
label,
count,
}: {
active: boolean;
onClick: () => void;
icon: string;
label: string;
count: number;
}) {
return (
<button
type="button"
onClick={onClick}
className={`
flex items-center gap-2 px-3 py-2 rounded-lg font-medium text-sm transition-colors
${
active
? 'bg-primary text-primary-foreground'
: 'text-muted hover:bg-card-hover hover:text-foreground'
}
`}
>
<span>{icon}</span>
<span>{label}</span>
<Badge variant={active ? 'default' : 'primary'} className="ml-1 text-xs">
{count}
</Badge>
</button>
);
}
function SessionCard({ session, isTeamCollab = false }: { session: AnySession; isTeamCollab?: boolean }) {
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [isPending, startTransition] = useTransition();
// Edit form state
const [editTitle, setEditTitle] = useState(session.title);
const [editParticipant, setEditParticipant] = useState(
session.workshopType === 'swot'
? (session as SwotSession).collaborator
: session.workshopType === 'year-review'
? (session as YearReviewSession).participant
: session.workshopType === 'weather'
? ''
: (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 = getSessionPath(session.workshopType as WorkshopTypeId, session.id);
const participant = isSwot
? (session as SwotSession).collaborator
: isYearReview
? (session as YearReviewSession).participant
: isWeeklyCheckIn
? (session as WeeklyCheckInSession).participant
: isWeather
? (session as WeatherSession).user.name || (session as WeatherSession).user.email
: (session as MotivatorSession).participant;
const accentColor = workshop.accentColor;
const handleDelete = () => {
startTransition(async () => {
const result = isSwot
? await deleteSwotSession(session.id)
: isYearReview
? await deleteYearReviewSession(session.id)
: isWeeklyCheckIn
? await deleteWeeklyCheckInSession(session.id)
: isWeather
? await deleteWeatherSession(session.id)
: await deleteMotivatorSession(session.id);
if (result.success) {
setShowDeleteModal(false);
} else {
console.error('Error deleting session:', result.error);
}
});
};
const handleEdit = () => {
startTransition(async () => {
const result = isSwot
? await updateSwotSession(session.id, { title: editTitle, collaborator: editParticipant })
: isYearReview
? await updateYearReviewSession(session.id, {
title: editTitle,
participant: editParticipant,
})
: isWeeklyCheckIn
? await updateWeeklyCheckInSession(session.id, {
title: editTitle,
participant: editParticipant,
})
: isWeather
? await updateWeatherSession(session.id, { title: editTitle })
: await updateMotivatorSession(session.id, {
title: editTitle,
participant: editParticipant,
});
if (result.success) {
setShowEditModal(false);
} else {
console.error('Error updating session:', result.error);
}
});
};
const openEditModal = () => {
// Reset form values when opening
setEditTitle(session.title);
setEditParticipant(participant);
setShowEditModal(true);
};
const editParticipantLabel = workshop.participantLabel;
const cardContent = (
<Card hover={!isTeamCollab} className={`h-full p-4 relative overflow-hidden ${isTeamCollab ? 'opacity-60' : ''}`}>
{/* Accent bar */}
<div
className="absolute top-0 left-0 right-0 h-1"
style={{ backgroundColor: accentColor }}
/>
{/* Header: Icon + Title + Role badge */}
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">{workshop.icon}</span>
<h3 className="font-semibold text-foreground line-clamp-1 flex-1">{session.title}</h3>
{!session.isOwner && (
<span
className="text-xs px-1.5 py-0.5 rounded"
style={{
backgroundColor:
session.role === 'EDITOR' ? 'rgba(6,182,212,0.1)' : 'rgba(234,179,8,0.1)',
color: session.role === 'EDITOR' ? '#06b6d4' : '#eab308',
}}
>
{session.role === 'EDITOR' ? '✏️' : '👁️'}
</span>
)}
</div>
{/* Participant + Owner info */}
<div className="mb-3 flex items-center gap-2">
<CollaboratorDisplay collaborator={getResolvedCollaborator(session)} size="sm" />
{!session.isOwner && (
<span className="text-xs text-muted">
· par {session.user.name || session.user.email}
</span>
)}
</div>
{/* Footer: Stats + Avatars + Date */}
<div className="flex items-center justify-between text-xs">
{/* Stats */}
<div className="flex items-center gap-2 text-muted">
{isSwot ? (
<>
<span>{(session as SwotSession)._count.items} items</span>
<span>·</span>
<span>{(session as SwotSession)._count.actions} actions</span>
</>
) : isYearReview ? (
<>
<span>{(session as YearReviewSession)._count.items} items</span>
<span>·</span>
<span>Année {(session as YearReviewSession).year}</span>
</>
) : isWeeklyCheckIn ? (
<>
<span>{(session as WeeklyCheckInSession)._count.items} items</span>
<span>·</span>
<span>
{new Date((session as WeeklyCheckInSession).date).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short',
})}
</span>
</>
) : isWeather ? (
<>
<span>{(session as WeatherSession)._count.entries} membres</span>
<span>·</span>
<span>
{new Date((session as WeatherSession).date).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short',
})}
</span>
</>
) : (
<span>{(session as MotivatorSession)._count.cards}/10</span>
)}
</div>
{/* Date */}
<span className="text-muted">
{new Date(session.updatedAt).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short',
})}
</span>
</div>
{/* Shared with */}
{session.isOwner && session.shares.length > 0 && (
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-border">
<span className="text-[10px] text-muted uppercase tracking-wide">Partagé</span>
<div className="flex flex-wrap gap-1.5">
{session.shares.slice(0, 3).map((share) => (
<div
key={share.id}
className="flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-primary/10 text-[10px] text-primary"
title={share.role === 'EDITOR' ? 'Éditeur' : 'Lecteur'}
>
<span className="font-medium">
{share.user.name?.split(' ')[0] || share.user.email.split('@')[0]}
</span>
<span>{share.role === 'EDITOR' ? '✏️' : '👁️'}</span>
</div>
))}
{session.shares.length > 3 && (
<span className="text-[10px] text-muted">+{session.shares.length - 3}</span>
)}
</div>
</div>
)}
</Card>
);
return (
<>
<div className="relative group">
<Link
href={href}
className={isTeamCollab ? 'cursor-pointer' : ''}
title={isTeamCollab ? "Atelier de l'équipe éditable en tant qu'admin" : undefined}
>
{cardContent}
</Link>
{/* Edit: owner, EDITOR, or team admin | Delete: owner or team admin only (not EDITOR) */}
{(session.isOwner || session.role === 'EDITOR' || session.isTeamCollab) && (
<div className="absolute top-3 right-3 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
openEditModal();
}}
className="p-1.5 rounded-lg bg-primary/10 text-primary hover:bg-primary/20"
title="Modifier"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</button>
{(session.isOwner || session.isTeamCollab) && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setShowDeleteModal(true);
}}
className="p-1.5 rounded-lg bg-destructive/10 text-destructive hover:bg-destructive/20"
title="Supprimer"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
)}
</div>
)}
</div>
{/* Edit modal */}
<Modal
isOpen={showEditModal}
onClose={() => setShowEditModal(false)}
title="Modifier l'atelier"
size="sm"
>
<form
onSubmit={(e) => {
e.preventDefault();
handleEdit();
}}
className="space-y-4"
>
<div>
<label htmlFor="edit-title" className="block text-sm font-medium text-foreground mb-1">
Titre
</label>
<Input
id="edit-title"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
placeholder="Titre de l'atelier"
required
/>
</div>
<div>
<label
htmlFor="edit-participant"
className="block text-sm font-medium text-foreground mb-1"
>
{editParticipantLabel}
</label>
{!isWeather && (
<Input
id="edit-participant"
value={editParticipant}
onChange={(e) => setEditParticipant(e.target.value)}
placeholder={isSwot ? 'Nom du collaborateur' : 'Nom du participant'}
required
/>
)}
</div>
<ModalFooter>
<Button
type="button"
variant="ghost"
onClick={() => setShowEditModal(false)}
disabled={isPending}
>
Annuler
</Button>
<Button
type="submit"
disabled={isPending || !editTitle.trim() || (!isWeather && !editParticipant.trim())}
>
{isPending ? 'Enregistrement...' : 'Enregistrer'}
</Button>
</ModalFooter>
</form>
</Modal>
{/* Delete confirmation modal */}
<Modal
isOpen={showDeleteModal}
onClose={() => setShowDeleteModal(false)}
title="Supprimer l'atelier"
size="sm"
>
<div className="space-y-4">
<p className="text-muted">
Êtes-vous sûr de vouloir supprimer l&apos;atelier{' '}
<strong className="text-foreground">&quot;{session.title}&quot;</strong> ?
</p>
<p className="text-sm text-destructive">
Cette action est irréversible. Toutes les données seront perdues.
</p>
<ModalFooter>
<Button variant="ghost" onClick={() => setShowDeleteModal(false)} disabled={isPending}>
Annuler
</Button>
<Button variant="destructive" onClick={handleDelete} disabled={isPending}>
{isPending ? 'Suppression...' : 'Supprimer'}
</Button>
</ModalFooter>
</div>
</Modal>
</>
);
}