171 lines
5.6 KiB
TypeScript
171 lines
5.6 KiB
TypeScript
'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>
|
|
);
|
|
}
|
|
|