2025-12-08 08:49:59 +01:00

126 lines
3.3 KiB
TypeScript

"use client";
import React, { useState } from "react";
import {
DndContext,
DragEndEvent,
DragOverlay,
DragStartEvent,
PointerSensor,
useSensor,
useSensors,
closestCenter,
} from "@dnd-kit/core";
import { Box, Card, CardContent, Typography } from "@mui/material";
import { MatcherBoardProps } from "../types";
import { DroppableColumn } from "./DroppableColumn";
import { findActiveItem, isItemInSource, addItemIfNotPresent } from "../utils";
import "./MatcherBoard.scss";
export default function MatcherBoard({
sourceItems: initialSourceItems,
targetItems: initialTargetItems,
config,
onMatch,
}: MatcherBoardProps) {
const [sourceItems, setSourceItems] = useState(initialSourceItems);
const [targetItems, setTargetItems] = useState(initialTargetItems);
const [activeId, setActiveId] = useState<string | null>(null);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
})
);
const handleDragStart = (event: DragStartEvent) => {
setActiveId(String(event.active.id));
};
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
setActiveId(null);
if (!over) return;
const activeId = String(active.id);
const overId = String(over.id);
const activeItem = findActiveItem(sourceItems, targetItems, activeId);
if (!activeItem) return;
const activeIsSource = isItemInSource(sourceItems, activeId);
const droppedOnSource = overId === "source";
const droppedOnTarget = overId === "target";
// If dragging from source to target column
if (activeIsSource && droppedOnTarget) {
setSourceItems(prev => prev.filter(item => item.id !== activeId));
setTargetItems(prev => addItemIfNotPresent(prev, activeItem));
if (onMatch) {
onMatch(activeId, "target");
}
} else if (!activeIsSource && droppedOnSource) {
setTargetItems(prev => prev.filter(item => item.id !== activeId));
setSourceItems(prev => addItemIfNotPresent(prev, activeItem));
}
};
const activeItem = activeId
? findActiveItem(sourceItems, targetItems, activeId)
: null;
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<Box
className="matcher-board"
sx={{
display: "flex",
gap: 3,
p: 3,
width: "100%",
justifyContent: "center",
}}
>
<DroppableColumn
id="source"
title={config.sourceLabel}
items={sourceItems}
activeId={activeId}
/>
<DroppableColumn
id="target"
title={config.targetLabel}
items={targetItems}
activeId={activeId}
/>
</Box>
<DragOverlay>
{activeItem ? (
<Card
sx={{
width: 250,
boxShadow: 6,
transform: "rotate(5deg)",
}}
>
<CardContent sx={{ p: 1.5, "&:last-child": { pb: 1.5 } }}>
<Typography variant="body2" fontWeight={500}>
{activeItem.name}
</Typography>
</CardContent>
</Card>
) : null}
</DragOverlay>
</DndContext>
);
}