Files
workshop-manager/src/components/year-review/YearReviewBoard.tsx
Julien Froidefond 56a9c2c3be
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m7s
feat: implement Year Review feature with session management, item categorization, and real-time collaboration
2025-12-16 08:55:13 +01:00

95 lines
3.5 KiB
TypeScript

'use client';
import { useTransition } from 'react';
import { DragDropContext, Droppable, Draggable, DropResult } from '@hello-pangea/dnd';
import type { YearReviewItem, YearReviewCategory } from '@prisma/client';
import { YearReviewSection } from './YearReviewSection';
import { YearReviewCard } from './YearReviewCard';
import { moveYearReviewItem, reorderYearReviewItems } from '@/actions/year-review';
import { YEAR_REVIEW_SECTIONS } from '@/lib/types';
interface YearReviewBoardProps {
sessionId: string;
items: YearReviewItem[];
}
export function YearReviewBoard({ sessionId, items }: YearReviewBoardProps) {
const [isPending, startTransition] = useTransition();
const itemsByCategory = YEAR_REVIEW_SECTIONS.reduce(
(acc, section) => {
acc[section.category] = items
.filter((item) => item.category === section.category)
.sort((a, b) => a.order - b.order);
return acc;
},
{} as Record<YearReviewCategory, YearReviewItem[]>
);
function handleDragEnd(result: DropResult) {
if (!result.destination) return;
const { source, destination, draggableId } = result;
const sourceCategory = source.droppableId as YearReviewCategory;
const destCategory = destination.droppableId as YearReviewCategory;
// If same position, do nothing
if (sourceCategory === destCategory && source.index === destination.index) {
return;
}
startTransition(async () => {
if (sourceCategory === destCategory) {
// Same category - just reorder
const categoryItems = itemsByCategory[sourceCategory];
const itemIds = categoryItems.map((item) => item.id);
const [removed] = itemIds.splice(source.index, 1);
itemIds.splice(destination.index, 0, removed);
await reorderYearReviewItems(sessionId, sourceCategory, itemIds);
} else {
// Different category - move item
await moveYearReviewItem(draggableId, sessionId, destCategory, destination.index);
}
});
}
return (
<div className={`space-y-6 ${isPending ? 'opacity-70 pointer-events-none' : ''}`}>
{/* Year Review Sections */}
<DragDropContext onDragEnd={handleDragEnd}>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
{YEAR_REVIEW_SECTIONS.map((section) => (
<Droppable key={section.category} droppableId={section.category}>
{(provided, snapshot) => (
<YearReviewSection
category={section.category}
sessionId={sessionId}
isDraggingOver={snapshot.isDraggingOver}
ref={provided.innerRef}
{...provided.droppableProps}
>
{itemsByCategory[section.category].map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(dragProvided, dragSnapshot) => (
<YearReviewCard
item={item}
sessionId={sessionId}
isDragging={dragSnapshot.isDragging}
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
/>
)}
</Draggable>
))}
{provided.placeholder}
</YearReviewSection>
)}
</Droppable>
))}
</div>
</DragDropContext>
</div>
);
}