feat: enhance session management by resolving collaborators to users and integrating CollaboratorDisplay component across motivators and sessions pages
This commit is contained in:
@@ -3,7 +3,7 @@ import Link from 'next/link';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { getMotivatorSessionById } from '@/services/moving-motivators';
|
||||
import { MotivatorBoard, MotivatorLiveWrapper } from '@/components/moving-motivators';
|
||||
import { Badge } from '@/components/ui';
|
||||
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
||||
import { EditableMotivatorTitle } from './EditableTitle';
|
||||
|
||||
interface MotivatorSessionPageProps {
|
||||
@@ -48,9 +48,13 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
||||
initialTitle={session.title}
|
||||
isOwner={session.isOwner}
|
||||
/>
|
||||
<p className="mt-1 text-lg text-muted">
|
||||
👤 {session.participant}
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={session.resolvedParticipant}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="primary">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { getMotivatorSessionsByUserId } from '@/services/moving-motivators';
|
||||
import { Card, CardContent, Badge, Button } from '@/components/ui';
|
||||
import { Card, CardContent, Badge, Button, CollaboratorDisplay } from '@/components/ui';
|
||||
|
||||
export default async function MotivatorsPage() {
|
||||
const session = await auth();
|
||||
@@ -95,7 +95,9 @@ function SessionCard({ session: s }: { session: SessionWithMeta }) {
|
||||
<h3 className="font-semibold text-foreground line-clamp-1">
|
||||
{s.title}
|
||||
</h3>
|
||||
<p className="text-sm text-muted">{s.participant}</p>
|
||||
<div className="mt-1">
|
||||
<CollaboratorDisplay collaborator={s.resolvedParticipant} size="sm" />
|
||||
</div>
|
||||
{!s.isOwner && (
|
||||
<p className="text-xs text-muted mt-1">
|
||||
Par {s.user.name || s.user.email}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useTransition } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Card, Badge, Button, Modal, ModalFooter, Input } from '@/components/ui';
|
||||
import { Card, Badge, Button, Modal, ModalFooter, Input, CollaboratorDisplay } from '@/components/ui';
|
||||
import { deleteSwotSession, updateSwotSession } from '@/actions/session';
|
||||
import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving-motivators';
|
||||
|
||||
@@ -20,10 +20,20 @@ interface Share {
|
||||
user: ShareUser;
|
||||
}
|
||||
|
||||
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';
|
||||
@@ -37,6 +47,7 @@ interface MotivatorSession {
|
||||
id: string;
|
||||
title: string;
|
||||
participant: string;
|
||||
resolvedParticipant: ResolvedCollaborator;
|
||||
updatedAt: Date;
|
||||
isOwner: boolean;
|
||||
role: 'OWNER' | 'VIEWER' | 'EDITOR';
|
||||
@@ -60,20 +71,45 @@ function getParticipant(session: AnySession): string {
|
||||
: (session as MotivatorSession).participant;
|
||||
}
|
||||
|
||||
// Group sessions by participant
|
||||
// Helper to get resolved collaborator from any session
|
||||
function getResolvedCollaborator(session: AnySession): ResolvedCollaborator {
|
||||
return session.workshopType === 'swot'
|
||||
? (session as SwotSession).resolvedCollaborator
|
||||
: (session as MotivatorSession).resolvedParticipant;
|
||||
}
|
||||
|
||||
// Get display name for grouping - prefer matched user name
|
||||
function getDisplayName(session: AnySession): string {
|
||||
const resolved = getResolvedCollaborator(session);
|
||||
if (resolved.matchedUser?.name) {
|
||||
return resolved.matchedUser.name;
|
||||
}
|
||||
return resolved.raw;
|
||||
}
|
||||
|
||||
// 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 participant = getParticipant(session).trim().toLowerCase();
|
||||
const displayName = getParticipant(session).trim();
|
||||
const key = getGroupKey(session);
|
||||
|
||||
// Use normalized key but store with original display name
|
||||
const existing = grouped.get(participant);
|
||||
const existing = grouped.get(key);
|
||||
if (existing) {
|
||||
existing.push(session);
|
||||
} else {
|
||||
grouped.set(participant, [session]);
|
||||
grouped.set(key, [session]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -155,16 +191,13 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{sortedPersons.map(([personKey, sessions]) => {
|
||||
const displayName = getParticipant(sessions[0]);
|
||||
const resolved = getResolvedCollaborator(sessions[0]);
|
||||
return (
|
||||
<section key={personKey}>
|
||||
<h2 className="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
|
||||
<span className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-primary text-sm">
|
||||
{displayName.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
{displayName}
|
||||
<Badge variant="primary" className="ml-2">
|
||||
{sessions.length}
|
||||
<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">
|
||||
@@ -336,12 +369,15 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
</div>
|
||||
|
||||
{/* Participant + Owner info */}
|
||||
<p className="text-sm text-muted mb-3 line-clamp-1">
|
||||
👤 {participant}
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={getResolvedCollaborator(session)}
|
||||
size="sm"
|
||||
/>
|
||||
{!session.isOwner && (
|
||||
<span className="text-xs"> · par {session.user.name || session.user.email}</span>
|
||||
<span className="text-xs text-muted">· par {session.user.name || session.user.email}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Footer: Stats + Avatars + Date */}
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getSessionById } from '@/services/sessions';
|
||||
import { SwotBoard } from '@/components/swot/SwotBoard';
|
||||
import { SessionLiveWrapper } from '@/components/collaboration';
|
||||
import { EditableTitle } from '@/components/session';
|
||||
import { Badge } from '@/components/ui';
|
||||
import { Badge, CollaboratorDisplay } from '@/components/ui';
|
||||
|
||||
interface SessionPageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
@@ -49,9 +49,13 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
||||
initialTitle={session.title}
|
||||
isOwner={session.isOwner}
|
||||
/>
|
||||
<p className="mt-1 text-lg text-muted">
|
||||
👤 {session.collaborator}
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={session.resolvedCollaborator}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="primary">{session.items.length} items</Badge>
|
||||
|
||||
Reference in New Issue
Block a user