diff --git a/dev.db b/dev.db index 13481d7..06fbd62 100644 Binary files a/dev.db and b/dev.db differ diff --git a/src/actions/moving-motivators.ts b/src/actions/moving-motivators.ts index 3ebff87..139e8c2 100644 --- a/src/actions/moving-motivators.ts +++ b/src/actions/moving-motivators.ts @@ -49,6 +49,7 @@ export async function updateMotivatorSession( revalidatePath(`/motivators/${sessionId}`); revalidatePath('/motivators'); + revalidatePath('/sessions'); // Also revalidate unified workshops page return { success: true }; } catch (error) { console.error('Error updating motivator session:', error); @@ -65,6 +66,7 @@ export async function deleteMotivatorSession(sessionId: string) { try { await motivatorsService.deleteMotivatorSession(sessionId, authSession.user.id); revalidatePath('/motivators'); + revalidatePath('/sessions'); // Also revalidate unified workshops page return { success: true }; } catch (error) { console.error('Error deleting motivator session:', error); diff --git a/src/actions/session.ts b/src/actions/session.ts index 9108296..73ec155 100644 --- a/src/actions/session.ts +++ b/src/actions/session.ts @@ -70,6 +70,46 @@ export async function updateSessionCollaborator(sessionId: string, collaborator: } } +export async function updateSwotSession( + sessionId: string, + data: { title?: string; collaborator?: string } +) { + const session = await auth(); + if (!session?.user?.id) { + return { success: false, error: 'Non autorisé' }; + } + + if (data.title !== undefined && !data.title.trim()) { + return { success: false, error: 'Le titre ne peut pas être vide' }; + } + + if (data.collaborator !== undefined && !data.collaborator.trim()) { + return { success: false, error: 'Le nom du collaborateur ne peut pas être vide' }; + } + + try { + const updateData: { title?: string; collaborator?: string } = {}; + if (data.title) updateData.title = data.title.trim(); + if (data.collaborator) updateData.collaborator = data.collaborator.trim(); + + const result = await sessionsService.updateSession(sessionId, session.user.id, updateData); + + if (result.count === 0) { + return { success: false, error: 'Session non trouvée ou non autorisé' }; + } + + // Emit event for real-time sync + await sessionsService.createSessionEvent(sessionId, session.user.id, 'SESSION_UPDATED', updateData); + + revalidatePath(`/sessions/${sessionId}`); + revalidatePath('/sessions'); + return { success: true }; + } catch (error) { + console.error('Error updating session:', error); + return { success: false, error: 'Erreur lors de la mise à jour' }; + } +} + export async function deleteSwotSession(sessionId: string) { const session = await auth(); if (!session?.user?.id) { diff --git a/src/app/sessions/WorkshopTabs.tsx b/src/app/sessions/WorkshopTabs.tsx index 4742598..9f3bda6 100644 --- a/src/app/sessions/WorkshopTabs.tsx +++ b/src/app/sessions/WorkshopTabs.tsx @@ -2,11 +2,11 @@ import { useState, useTransition } from 'react'; import Link from 'next/link'; -import { Card, Badge, Button, Modal, ModalFooter } from '@/components/ui'; -import { deleteSwotSession } from '@/actions/session'; -import { deleteMotivatorSession } from '@/actions/moving-motivators'; +import { Card, Badge, Button, Modal, ModalFooter, Input } from '@/components/ui'; +import { deleteSwotSession, updateSwotSession } from '@/actions/session'; +import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving-motivators'; -type WorkshopType = 'all' | 'swot' | 'motivators'; +type WorkshopType = 'all' | 'swot' | 'motivators' | 'byPerson'; interface ShareUser { id: string; @@ -53,6 +53,38 @@ interface WorkshopTabsProps { motivatorSessions: MotivatorSession[]; } +// Helper to get participant name from any session +function getParticipant(session: AnySession): string { + return session.workshopType === 'swot' + ? (session as SwotSession).collaborator + : (session as MotivatorSession).participant; +} + +// Group sessions by participant +function groupByPerson(sessions: AnySession[]): Map { + const grouped = new Map(); + + sessions.forEach((session) => { + const participant = getParticipant(session).trim().toLowerCase(); + const displayName = getParticipant(session).trim(); + + // Use normalized key but store with original display name + const existing = grouped.get(participant); + if (existing) { + existing.push(session); + } else { + grouped.set(participant, [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 }: WorkshopTabsProps) { const [activeTab, setActiveTab] = useState('all'); @@ -61,9 +93,9 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ); - // Filter based on active tab + // Filter based on active tab (for non-byPerson tabs) const filteredSessions = - activeTab === 'all' + activeTab === 'all' || activeTab === 'byPerson' ? allSessions : activeTab === 'swot' ? swotSessions @@ -73,10 +105,16 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr const ownedSessions = filteredSessions.filter((s) => s.isOwner); const sharedSessions = filteredSessions.filter((s) => !s.isOwner); + // 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 (
{/* Tabs */} -
+
setActiveTab('all')} @@ -84,6 +122,13 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr label="Tous" count={allSessions.length} /> + setActiveTab('byPerson')} + icon="👥" + label="Par personne" + count={sessionsByPerson.size} + /> setActiveTab('swot')} @@ -101,7 +146,38 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
{/* Sessions */} - {filteredSessions.length === 0 ? ( + {activeTab === 'byPerson' ? ( + // By Person View + sortedPersons.length === 0 ? ( +
+ Aucun atelier pour le moment +
+ ) : ( +
+ {sortedPersons.map(([personKey, sessions]) => { + const displayName = getParticipant(sessions[0]); + return ( +
+

+ + {displayName.charAt(0).toUpperCase()} + + {displayName} + + {sessions.length} + +

+
+ {sessions.map((s) => ( + + ))} +
+
+ ); + })} +
+ ) + ) : filteredSessions.length === 0 ? (
Aucun atelier de ce type pour le moment
@@ -175,7 +251,16 @@ function TabButton({ function SessionCard({ session }: { session: AnySession }) { 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 as MotivatorSession).participant + ); const isSwot = session.workshopType === 'swot'; const href = isSwot ? `/sessions/${session.id}` : `/motivators/${session.id}`; @@ -199,6 +284,27 @@ function SessionCard({ session }: { session: AnySession }) { }); }; + const handleEdit = () => { + startTransition(async () => { + const result = isSwot + ? await updateSwotSession(session.id, { title: editTitle, collaborator: editParticipant }) + : 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); + }; + return ( <>
@@ -289,24 +395,96 @@ function SessionCard({ session }: { session: AnySession }) { - {/* Delete button - only for owner */} + {/* Action buttons - only for owner */} {session.isOwner && ( - +
+ + +
)}
+ {/* Edit modal */} + setShowEditModal(false)} + title="Modifier l'atelier" + size="sm" + > +
{ + e.preventDefault(); + handleEdit(); + }} + className="space-y-4" + > +
+ + setEditTitle(e.target.value)} + placeholder="Titre de l'atelier" + required + /> +
+
+ + setEditParticipant(e.target.value)} + placeholder={isSwot ? 'Nom du collaborateur' : 'Nom du participant'} + required + /> +
+ + + + +
+
+ {/* Delete confirmation modal */}