refactor(dashboard): move drag-and-drop source logic into KanbanCard component
This commit is contained in:
parent
a9919a045c
commit
1033f99d3f
2 changed files with 28 additions and 41 deletions
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @file dashboard/KanbanCard.tsx
|
||||
* @description Client component rendering a single kanban card with support for priority indicators, due date alerts, team avatars, and a mobile status dropdown.
|
||||
* @description Client component rendering a single kanban card with native drag-and-drop source handlers, dynamic priority styling, and mobile status dropdown.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
|
@ -54,6 +54,17 @@ export default function KanbanCard({ task, onStatusChange }: KanbanCardProps) {
|
|||
(a) => a !== task.creator,
|
||||
);
|
||||
|
||||
/**
|
||||
* Initiates the drag action on a task card, storing its ID and current status in the data transfer payload.
|
||||
*
|
||||
* @param {React.DragEvent<HTMLDivElement>} e - The drag event object.
|
||||
*/
|
||||
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.dataTransfer.setData("text/plain", task.id);
|
||||
e.dataTransfer.setData("sourceStatus", task.status);
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles shifting the task to a new status category.
|
||||
*
|
||||
|
|
@ -69,18 +80,20 @@ export default function KanbanCard({ task, onStatusChange }: KanbanCardProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-3">
|
||||
<div
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
className="group relative bg-card/40 border border-border/80 hover:border-primary/60 p-4 rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 space-y-3 cursor-grab active:cursor-grabbing hover:-translate-y-0.5 flex flex-col h-full"
|
||||
>
|
||||
{/* --- Card Header (Title & Priority) --- */}
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
{/* Title */}
|
||||
<h3 className="font-semibold leading-snug group-hover/card:text-primary transition-colors line-clamp-2">
|
||||
{task.title}
|
||||
</h3>
|
||||
|
||||
{/* Priority Badge */}
|
||||
{task.priority && (
|
||||
<span
|
||||
className={`gap-1.5 px-2.5 py-1 rounded-lg text-xs font-bold border-2 ${
|
||||
className={`gap-1.5 px-2.5 py-1 rounded-lg text-xs font-bold border-2 shrink-0 ${
|
||||
task.priority === "high"
|
||||
? "bg-rose-500/10 text-rose-500 border-rose-500/20"
|
||||
: task.priority === "medium"
|
||||
|
|
@ -98,9 +111,9 @@ export default function KanbanCard({ task, onStatusChange }: KanbanCardProps) {
|
|||
{task.description}
|
||||
</p>
|
||||
|
||||
{/* --- Card Footer (Date, Mobile Action Menu, Avatars) --- */}
|
||||
{/* --- Card Footer --- */}
|
||||
<div className="flex items-center justify-between pt-3 mt-auto text-xs border-t border-border">
|
||||
{/* Left Side: Date Badge */}
|
||||
{/* Date Badge */}
|
||||
{task.dueDate && (
|
||||
<div
|
||||
className={`group/date relative inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-semibold border border-border transition-all duration-200 ease-out cursor-default ${
|
||||
|
|
@ -116,8 +129,6 @@ export default function KanbanCard({ task, onStatusChange }: KanbanCardProps) {
|
|||
month: "short",
|
||||
})}
|
||||
</span>
|
||||
|
||||
{/* Time displayed when hovering over the badge */}
|
||||
<span className="grid grid-cols-[0fr] group-hover/date:grid-cols-[1fr] transition-all duration-200 ease-out">
|
||||
<span className="overflow-hidden whitespace-nowrap opacity-0 group-hover/date:opacity-100 transition-opacity duration-200">
|
||||
{new Date(task.dueDate).toLocaleTimeString("de-DE", {
|
||||
|
|
@ -129,9 +140,8 @@ export default function KanbanCard({ task, onStatusChange }: KanbanCardProps) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Right Side: Avatars & Mobile Switcher */}
|
||||
{/* Avatars & Mobile Switcher */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Avatars */}
|
||||
{(task.creator || filteredAssignees.length > 0) && (
|
||||
<div className="flex items-center shrink-0 -space-x-2.5 group/avatars hover:space-x-0.5 transition-all duration-300">
|
||||
{filteredAssignees.map((assignee, index) => (
|
||||
|
|
@ -171,7 +181,6 @@ export default function KanbanCard({ task, onStatusChange }: KanbanCardProps) {
|
|||
<MoreHorizontal size={14} />
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
<div
|
||||
className={`absolute right-0 bottom-8 z-50 w-50 bg-card border border-border rounded-xl shadow-2xl p-1.5 space-y-1 text-xs origin-bottom-right transition-all duration-200 ease-out ${
|
||||
showMobileActions
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @file dashboard/KanbanColumn.tsx
|
||||
* @description Client component rendering an single kanban column container supporting drag-and-drop task reordering, status updates, and interactive card layouts.
|
||||
* @description Client component rendering a single kanban column container supporting drag-and-drop drop targets and status updates.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
|
@ -58,6 +58,7 @@ export default function KanbanColumn(props: KanbanColumnProps) {
|
|||
*/
|
||||
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
setIsDraggingOver(true);
|
||||
};
|
||||
|
||||
|
|
@ -68,22 +69,6 @@ export default function KanbanColumn(props: KanbanColumnProps) {
|
|||
setIsDraggingOver(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates the drag action on a task card, storing its ID and current status in the data transfer payload.
|
||||
*
|
||||
* @param {React.DragEvent<HTMLDivElement>} e - The drag event object.
|
||||
* @param {string} taskId - The identifier of the task being dragged.
|
||||
* @param {TaskStatus} currentStatus - The original status category of the task.
|
||||
*/
|
||||
const handleDragStart = (
|
||||
e: React.DragEvent<HTMLDivElement>,
|
||||
taskId: string,
|
||||
currentStatus: TaskStatus,
|
||||
) => {
|
||||
e.dataTransfer.setData("text/plain", taskId);
|
||||
e.dataTransfer.setData("sourceStatus", currentStatus);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles dropping a task card onto the column, triggering status updates if valid.
|
||||
*
|
||||
|
|
@ -118,7 +103,7 @@ export default function KanbanColumn(props: KanbanColumnProps) {
|
|||
<span
|
||||
className={`w-2.5 h-2.5 rounded-full shadow-sm ${indicatorColor}`}
|
||||
/>
|
||||
<h2 className="font-bold text-xs uppercase tracking-wider ">
|
||||
<h2 className="font-bold text-xs uppercase tracking-wider">
|
||||
{props.title}
|
||||
</h2>
|
||||
<span className="text-xs bg-card text-foreground-muted px-2 py-0.5 rounded-full font-semibold border border-border/60">
|
||||
|
|
@ -138,18 +123,11 @@ export default function KanbanColumn(props: KanbanColumnProps) {
|
|||
</div>
|
||||
) : (
|
||||
props.tasks.map((task) => (
|
||||
<div
|
||||
<KanbanCard
|
||||
key={task.id}
|
||||
draggable
|
||||
onDragStart={(e) => handleDragStart(e, task.id, task.status)}
|
||||
className="group relative bg-card/40 border border-border/80 hover:border-primary/60 p-4 rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 space-y-3 cursor-grab active:cursor-grabbing hover:-translate-y-0.5"
|
||||
>
|
||||
<KanbanCard
|
||||
key={task.id}
|
||||
task={task}
|
||||
onStatusChange={updateTaskStatus}
|
||||
/>
|
||||
</div>
|
||||
task={task}
|
||||
onStatusChange={updateTaskStatus}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue