refactor: improve team management, OKRs, and session components
This commit is contained in:
@@ -125,7 +125,12 @@ interface WeatherSession {
|
||||
canEdit?: boolean;
|
||||
}
|
||||
|
||||
type AnySession = SwotSession | MotivatorSession | YearReviewSession | WeeklyCheckInSession | WeatherSession;
|
||||
type AnySession =
|
||||
| SwotSession
|
||||
| MotivatorSession
|
||||
| YearReviewSession
|
||||
| WeeklyCheckInSession
|
||||
| WeatherSession;
|
||||
|
||||
interface WorkshopTabsProps {
|
||||
swotSessions: SwotSession[];
|
||||
@@ -239,18 +244,20 @@ export function WorkshopTabs({
|
||||
: activeTab === 'team'
|
||||
? teamCollabSessions
|
||||
: activeTab === 'swot'
|
||||
? swotSessions
|
||||
: activeTab === 'motivators'
|
||||
? motivatorSessions
|
||||
: activeTab === 'year-review'
|
||||
? yearReviewSessions
|
||||
: activeTab === 'weekly-checkin'
|
||||
? weeklyCheckInSessions
|
||||
: weatherSessions;
|
||||
? 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 sharedSessions = filteredSessions.filter(
|
||||
(s) => !s.isOwner && !(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab
|
||||
);
|
||||
const teamCollabFiltered =
|
||||
activeTab === 'all' ? teamCollabSessions : activeTab === 'team' ? teamCollabSessions : [];
|
||||
|
||||
@@ -342,7 +349,8 @@ export function WorkshopTabs({
|
||||
🏢 Ateliers de l'équipe – non partagés ({teamCollabSessions.length})
|
||||
</h2>
|
||||
<p className="text-sm text-muted mb-4">
|
||||
En tant qu'admin d'équipe, vous voyez les ateliers de vos collaborateurs qui ne vous sont pas encore partagés.
|
||||
En tant qu'admin d'é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) => (
|
||||
@@ -436,7 +444,7 @@ function TypeFilterDropdown({
|
||||
<span>{current.icon}</span>
|
||||
<span>{current.label}</span>
|
||||
<Badge variant={isTypeSelected ? 'default' : 'primary'} className="ml-1 text-xs">
|
||||
{isTypeSelected ? counts[activeTab] ?? 0 : totalCount}
|
||||
{isTypeSelected ? (counts[activeTab] ?? 0) : totalCount}
|
||||
</Badge>
|
||||
<svg
|
||||
className={`h-4 w-4 transition-transform ${open ? 'rotate-180' : ''}`}
|
||||
@@ -525,7 +533,13 @@ function TabButton({
|
||||
);
|
||||
}
|
||||
|
||||
function SessionCard({ session, isTeamCollab = false }: { session: AnySession; isTeamCollab?: boolean }) {
|
||||
function SessionCard({
|
||||
session,
|
||||
isTeamCollab = false,
|
||||
}: {
|
||||
session: AnySession;
|
||||
isTeamCollab?: boolean;
|
||||
}) {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
@@ -618,117 +632,117 @@ function SessionCard({ session, isTeamCollab = false }: { session: AnySession; i
|
||||
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 }}
|
||||
/>
|
||||
<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>
|
||||
{/* 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>
|
||||
{/* 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', {
|
||||
{/* 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>
|
||||
</div>
|
||||
</>
|
||||
) : 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>
|
||||
|
||||
{/* 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>
|
||||
{/* 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>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -764,24 +778,24 @@ function SessionCard({ session, isTeamCollab = false }: { session: AnySession; i
|
||||
</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>
|
||||
<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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user