feat: implement Year Review feature with session management, item categorization, and real-time collaboration
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m7s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m7s
This commit is contained in:
@@ -14,10 +14,11 @@ import {
|
||||
} from '@/components/ui';
|
||||
import { deleteSwotSession, updateSwotSession } from '@/actions/session';
|
||||
import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving-motivators';
|
||||
import { deleteYearReviewSession, updateYearReviewSession } from '@/actions/year-review';
|
||||
|
||||
type WorkshopType = 'all' | 'swot' | 'motivators' | 'byPerson';
|
||||
type WorkshopType = 'all' | 'swot' | 'motivators' | 'year-review' | 'byPerson';
|
||||
|
||||
const VALID_TABS: WorkshopType[] = ['all', 'swot', 'motivators', 'byPerson'];
|
||||
const VALID_TABS: WorkshopType[] = ['all', 'swot', 'motivators', 'year-review', 'byPerson'];
|
||||
|
||||
interface ShareUser {
|
||||
id: string;
|
||||
@@ -68,34 +69,38 @@ interface MotivatorSession {
|
||||
workshopType: 'motivators';
|
||||
}
|
||||
|
||||
type AnySession = SwotSession | MotivatorSession;
|
||||
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';
|
||||
}
|
||||
|
||||
type AnySession = SwotSession | MotivatorSession | YearReviewSession;
|
||||
|
||||
interface WorkshopTabsProps {
|
||||
swotSessions: SwotSession[];
|
||||
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;
|
||||
yearReviewSessions: YearReviewSession[];
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (session.workshopType === 'swot') {
|
||||
return (session as SwotSession).resolvedCollaborator;
|
||||
} else if (session.workshopType === 'year-review') {
|
||||
return (session as YearReviewSession).resolvedParticipant;
|
||||
} else {
|
||||
return (session as MotivatorSession).resolvedParticipant;
|
||||
}
|
||||
return resolved.raw;
|
||||
}
|
||||
|
||||
// Get grouping key - use matched user ID if available, otherwise normalized raw string
|
||||
@@ -132,7 +137,11 @@ function groupByPerson(sessions: AnySession[]): Map<string, AnySession[]> {
|
||||
return grouped;
|
||||
}
|
||||
|
||||
export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsProps) {
|
||||
export function WorkshopTabs({
|
||||
swotSessions,
|
||||
motivatorSessions,
|
||||
yearReviewSessions,
|
||||
}: WorkshopTabsProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -152,9 +161,11 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
};
|
||||
|
||||
// Combine and sort all sessions
|
||||
const allSessions: AnySession[] = [...swotSessions, ...motivatorSessions].sort(
|
||||
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
);
|
||||
const allSessions: AnySession[] = [
|
||||
...swotSessions,
|
||||
...motivatorSessions,
|
||||
...yearReviewSessions,
|
||||
].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
||||
|
||||
// Filter based on active tab (for non-byPerson tabs)
|
||||
const filteredSessions =
|
||||
@@ -162,7 +173,9 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
? allSessions
|
||||
: activeTab === 'swot'
|
||||
? swotSessions
|
||||
: motivatorSessions;
|
||||
: activeTab === 'motivators'
|
||||
? motivatorSessions
|
||||
: yearReviewSessions;
|
||||
|
||||
// Separate by ownership
|
||||
const ownedSessions = filteredSessions.filter((s) => s.isOwner);
|
||||
@@ -206,6 +219,13 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
label="Moving Motivators"
|
||||
count={motivatorSessions.length}
|
||||
/>
|
||||
<TabButton
|
||||
active={activeTab === 'year-review'}
|
||||
onClick={() => setActiveTab('year-review')}
|
||||
icon="📅"
|
||||
label="Year Review"
|
||||
count={yearReviewSessions.length}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Sessions */}
|
||||
@@ -316,22 +336,33 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
const [editParticipant, setEditParticipant] = useState(
|
||||
session.workshopType === 'swot'
|
||||
? (session as SwotSession).collaborator
|
||||
: (session as MotivatorSession).participant
|
||||
: session.workshopType === 'year-review'
|
||||
? (session as YearReviewSession).participant
|
||||
: (session as MotivatorSession).participant
|
||||
);
|
||||
|
||||
const isSwot = session.workshopType === 'swot';
|
||||
const href = isSwot ? `/sessions/${session.id}` : `/motivators/${session.id}`;
|
||||
const icon = isSwot ? '📊' : '🎯';
|
||||
const isYearReview = session.workshopType === 'year-review';
|
||||
const href = isSwot
|
||||
? `/sessions/${session.id}`
|
||||
: isYearReview
|
||||
? `/year-review/${session.id}`
|
||||
: `/motivators/${session.id}`;
|
||||
const icon = isSwot ? '📊' : isYearReview ? '📅' : '🎯';
|
||||
const participant = isSwot
|
||||
? (session as SwotSession).collaborator
|
||||
: (session as MotivatorSession).participant;
|
||||
const accentColor = isSwot ? '#06b6d4' : '#8b5cf6';
|
||||
: isYearReview
|
||||
? (session as YearReviewSession).participant
|
||||
: (session as MotivatorSession).participant;
|
||||
const accentColor = isSwot ? '#06b6d4' : isYearReview ? '#f59e0b' : '#8b5cf6';
|
||||
|
||||
const handleDelete = () => {
|
||||
startTransition(async () => {
|
||||
const result = isSwot
|
||||
? await deleteSwotSession(session.id)
|
||||
: await deleteMotivatorSession(session.id);
|
||||
: isYearReview
|
||||
? await deleteYearReviewSession(session.id)
|
||||
: await deleteMotivatorSession(session.id);
|
||||
|
||||
if (result.success) {
|
||||
setShowDeleteModal(false);
|
||||
@@ -345,10 +376,15 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
startTransition(async () => {
|
||||
const result = isSwot
|
||||
? await updateSwotSession(session.id, { title: editTitle, collaborator: editParticipant })
|
||||
: await updateMotivatorSession(session.id, {
|
||||
title: editTitle,
|
||||
participant: editParticipant,
|
||||
});
|
||||
: isYearReview
|
||||
? await updateYearReviewSession(session.id, {
|
||||
title: editTitle,
|
||||
participant: editParticipant,
|
||||
})
|
||||
: await updateMotivatorSession(session.id, {
|
||||
title: editTitle,
|
||||
participant: editParticipant,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
setShowEditModal(false);
|
||||
@@ -414,6 +450,12 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
<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>
|
||||
</>
|
||||
) : (
|
||||
<span>{(session as MotivatorSession)._count.cards}/10</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user