126 lines
3.3 KiB
TypeScript
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>
|
|
);
|
|
}
|