chore: mark completion of sessions and SWOT components in devbook.md; add @hello-pangea/dnd dependency for drag & drop functionality

This commit is contained in:
Julien Froidefond
2025-11-27 13:15:56 +01:00
parent 27e409fb76
commit 628d64a5c6
12 changed files with 1398 additions and 45 deletions

View File

@@ -0,0 +1,170 @@
'use client';
import { useState, useTransition } from 'react';
import {
DragDropContext,
Droppable,
Draggable,
DropResult,
} from '@hello-pangea/dnd';
import type { SwotItem, Action, ActionLink, SwotCategory } from '@prisma/client';
import { SwotQuadrant } from './SwotQuadrant';
import { SwotCard } from './SwotCard';
import { ActionPanel } from './ActionPanel';
import { moveSwotItem } from '@/actions/swot';
type ActionWithLinks = Action & {
links: (ActionLink & { swotItem: SwotItem })[];
};
interface SwotBoardProps {
sessionId: string;
items: SwotItem[];
actions: ActionWithLinks[];
}
const QUADRANTS: { category: SwotCategory; title: string; icon: string }[] = [
{ category: 'STRENGTH', title: 'Forces', icon: '💪' },
{ category: 'WEAKNESS', title: 'Faiblesses', icon: '⚠️' },
{ category: 'OPPORTUNITY', title: 'Opportunités', icon: '🚀' },
{ category: 'THREAT', title: 'Menaces', icon: '🛡️' },
];
export function SwotBoard({ sessionId, items, actions }: SwotBoardProps) {
const [isPending, startTransition] = useTransition();
const [linkMode, setLinkMode] = useState(false);
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const [highlightedItems, setHighlightedItems] = useState<string[]>([]);
const itemsByCategory = QUADRANTS.reduce(
(acc, q) => {
acc[q.category] = items
.filter((item) => item.category === q.category)
.sort((a, b) => a.order - b.order);
return acc;
},
{} as Record<SwotCategory, SwotItem[]>
);
function handleDragEnd(result: DropResult) {
if (!result.destination) return;
const { source, destination, draggableId } = result;
const sourceCategory = source.droppableId as SwotCategory;
const destCategory = destination.droppableId as SwotCategory;
// If same position, do nothing
if (sourceCategory === destCategory && source.index === destination.index) {
return;
}
startTransition(async () => {
await moveSwotItem(draggableId, sessionId, destCategory, destination.index);
});
}
function toggleItemSelection(itemId: string) {
if (!linkMode) return;
setSelectedItems((prev) =>
prev.includes(itemId)
? prev.filter((id) => id !== itemId)
: [...prev, itemId]
);
}
function handleActionHover(linkedItemIds: string[]) {
setHighlightedItems(linkedItemIds);
}
function handleActionLeave() {
setHighlightedItems([]);
}
function exitLinkMode() {
setLinkMode(false);
setSelectedItems([]);
}
return (
<div className={`space-y-6 ${isPending ? 'opacity-70 pointer-events-none' : ''}`}>
{/* Link Mode Banner */}
{linkMode && (
<div className="flex items-center justify-between rounded-lg border border-primary/30 bg-primary/10 p-4">
<div className="flex items-center gap-3">
<span className="text-2xl">🔗</span>
<div>
<p className="font-medium text-foreground">Mode Liaison</p>
<p className="text-sm text-muted">
Sélectionnez les items à lier ({selectedItems.length} sélectionné
{selectedItems.length > 1 ? 's' : ''})
</p>
</div>
</div>
<button
onClick={exitLinkMode}
className="rounded-lg border border-border bg-card px-4 py-2 text-sm font-medium hover:bg-card-hover"
>
Annuler
</button>
</div>
)}
{/* SWOT Matrix */}
<DragDropContext onDragEnd={handleDragEnd}>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
{QUADRANTS.map((quadrant) => (
<Droppable key={quadrant.category} droppableId={quadrant.category}>
{(provided, snapshot) => (
<SwotQuadrant
category={quadrant.category}
title={quadrant.title}
icon={quadrant.icon}
sessionId={sessionId}
isDraggingOver={snapshot.isDraggingOver}
ref={provided.innerRef}
{...provided.droppableProps}
>
{itemsByCategory[quadrant.category].map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(dragProvided, dragSnapshot) => (
<SwotCard
item={item}
sessionId={sessionId}
isSelected={selectedItems.includes(item.id)}
isHighlighted={highlightedItems.includes(item.id)}
isDragging={dragSnapshot.isDragging}
linkMode={linkMode}
onSelect={() => toggleItemSelection(item.id)}
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
/>
)}
</Draggable>
))}
{provided.placeholder}
</SwotQuadrant>
)}
</Droppable>
))}
</div>
</DragDropContext>
{/* Actions Panel */}
<ActionPanel
sessionId={sessionId}
actions={actions}
allItems={items}
linkMode={linkMode}
selectedItems={selectedItems}
onEnterLinkMode={() => setLinkMode(true)}
onExitLinkMode={exitLinkMode}
onClearSelection={() => setSelectedItems([])}
onActionHover={handleActionHover}
onActionLeave={handleActionLeave}
/>
</div>
);
}