95 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|