feat: add task creation functionality to Kanban components

- Integrated `onCreateTask` prop into `PrioritySwimlanesBoard`, `SwimlanesBoard`, and `SwimlanesBase` for task creation support.
- Implemented quick add feature in `DroppableColumn` for streamlined task addition.
- Added modal for complete task creation in `SwimlanesBase`, enhancing user experience.
- Updated relevant components to handle loading state during task creation.
This commit is contained in:
Julien Froidefond
2025-09-15 11:16:32 +02:00
parent 1a21f9b88b
commit dce11e0569
4 changed files with 112 additions and 9 deletions

View File

@@ -84,22 +84,26 @@ export function KanbanBoardContainer() {
kanbanFilters.swimlanesMode === 'priority' ? (
<PrioritySwimlanesBoard
tasks={filteredTasks}
onCreateTask={createTask}
onDeleteTask={deleteTask}
onEditTask={handleEditTask}
onUpdateTitle={handleUpdateTitle}
onUpdateStatus={handleUpdateStatus}
compactView={kanbanFilters.compactView}
visibleStatuses={visibleStatuses}
loading={loading}
/>
) : (
<SwimlanesBoard
tasks={filteredTasks}
onCreateTask={createTask}
onDeleteTask={deleteTask}
onEditTask={handleEditTask}
onUpdateTitle={handleUpdateTitle}
onUpdateStatus={handleUpdateStatus}
compactView={kanbanFilters.compactView}
visibleStatuses={visibleStatuses}
loading={loading}
/>
)
) : (

View File

@@ -1,28 +1,33 @@
'use client';
import { Task, TaskStatus } from '@/lib/types';
import { CreateTaskData } from '@/clients/tasks-client';
import { useMemo } from 'react';
import { getAllPriorities } from '@/lib/status-config';
import { SwimlanesBase, SwimlaneData } from './SwimlanesBase';
interface PrioritySwimlanesoardProps {
tasks: Task[];
onCreateTask?: (data: CreateTaskData) => Promise<Task | null>;
onDeleteTask?: (taskId: string) => Promise<void>;
onEditTask?: (task: Task) => void;
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
compactView?: boolean;
visibleStatuses?: TaskStatus[];
loading?: boolean;
}
export function PrioritySwimlanesBoard({
tasks,
onCreateTask,
onDeleteTask,
onEditTask,
onUpdateTitle,
onUpdateStatus,
compactView = false,
visibleStatuses
visibleStatuses,
loading = false
}: PrioritySwimlanesoardProps) {
// Grouper les tâches par priorités et créer les données de swimlanes
@@ -57,12 +62,14 @@ export function PrioritySwimlanesBoard({
tasks={tasks}
title="Swimlanes par Priorité"
swimlanes={swimlanesData}
onCreateTask={onCreateTask}
onDeleteTask={onDeleteTask}
onEditTask={onEditTask}
onUpdateTitle={onUpdateTitle}
onUpdateStatus={onUpdateStatus}
compactView={compactView}
visibleStatuses={visibleStatuses}
loading={loading}
/>
);
}

View File

@@ -2,6 +2,10 @@
import { Task, TaskStatus } from '@/lib/types';
import { TaskCard } from './TaskCard';
import { QuickAddTask } from './QuickAddTask';
import { CreateTaskForm } from '@/components/forms/CreateTaskForm';
import { Button } from '@/components/ui/Button';
import { CreateTaskData } from '@/clients/tasks-client';
import { useState } from 'react';
import { useColumnVisibility } from '@/hooks/useColumnVisibility';
import { getAllStatuses } from '@/lib/status-config';
@@ -28,7 +32,10 @@ function DroppableColumn({
onDeleteTask,
onEditTask,
onUpdateTitle,
compactView
compactView,
onCreateTask,
showQuickAdd,
onToggleQuickAdd
}: {
status: TaskStatus;
tasks: Task[];
@@ -36,13 +43,16 @@ function DroppableColumn({
onEditTask?: (task: Task) => void;
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
compactView: boolean;
onCreateTask?: (data: CreateTaskData) => Promise<void>;
showQuickAdd?: boolean;
onToggleQuickAdd?: () => void;
}) {
const { setNodeRef } = useDroppable({
id: status,
});
return (
<div ref={setNodeRef} className="min-h-[100px]">
<div ref={setNodeRef} className="min-h-[100px] space-y-2">
<SortableContext items={tasks.map(t => t.id)} strategy={verticalListSortingStrategy}>
<div className="space-y-1">
{tasks.map(task => (
@@ -57,6 +67,29 @@ function DroppableColumn({
))}
</div>
</SortableContext>
{/* QuickAdd pour cette colonne */}
{onCreateTask && (
<div className="mt-2">
{showQuickAdd ? (
<QuickAddTask
status={status}
onSubmit={onCreateTask}
onCancel={onToggleQuickAdd || (() => {})}
/>
) : (
<button
onClick={onToggleQuickAdd}
className="w-full p-2 flex justify-center transition-colors"
title="Ajouter une tâche"
>
<div className="w-5 h-5 rounded-full bg-slate-800/30 hover:bg-slate-700/50 flex items-center justify-center text-slate-600 hover:text-slate-300 transition-all text-sm">
+
</div>
</button>
)}
</div>
)}
</div>
);
}
@@ -74,27 +107,33 @@ interface SwimlanesBaseProps {
tasks: Task[];
title: string;
swimlanes: SwimlaneData[];
onCreateTask?: (data: CreateTaskData) => Promise<Task | null>;
onDeleteTask?: (taskId: string) => Promise<void>;
onEditTask?: (task: Task) => void;
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
compactView?: boolean;
visibleStatuses?: TaskStatus[];
loading?: boolean;
}
export function SwimlanesBase({
tasks,
title,
swimlanes,
onCreateTask,
onDeleteTask,
onEditTask,
onUpdateTitle,
onUpdateStatus,
compactView = false,
visibleStatuses
visibleStatuses,
loading = false
}: SwimlanesBaseProps) {
const [activeTask, setActiveTask] = useState<Task | null>(null);
const [collapsedSwimlanes, setCollapsedSwimlanes] = useState<Set<string>>(new Set());
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [showQuickAdd, setShowQuickAdd] = useState<{ [key: string]: boolean }>({});
// Gestion de la visibilité des colonnes
const { getVisibleStatuses } = useColumnVisibility();
@@ -144,6 +183,25 @@ export function SwimlanesBase({
setCollapsedSwimlanes(newCollapsed);
};
// Handlers pour la création de tâches
const handleCreateTask = async (data: CreateTaskData) => {
if (onCreateTask) {
await onCreateTask(data);
setIsCreateModalOpen(false);
}
};
const handleQuickAdd = async (data: CreateTaskData, columnId: string) => {
if (onCreateTask) {
await onCreateTask(data);
setShowQuickAdd(prev => ({ ...prev, [columnId]: false }));
}
};
const toggleQuickAdd = (columnId: string) => {
setShowQuickAdd(prev => ({ ...prev, [columnId]: !prev[columnId] }));
};
return (
<DndContext
sensors={sensors}
@@ -154,9 +212,21 @@ export function SwimlanesBase({
<div className="flex flex-col h-full bg-slate-950">
{/* Header */}
<div className="flex-shrink-0 px-6 py-4 border-b border-slate-700/50">
<h2 className="text-lg font-mono font-bold text-slate-200 uppercase tracking-wider">
{title}
</h2>
<div className="flex items-center justify-between">
<h2 className="text-lg font-mono font-bold text-slate-200 uppercase tracking-wider">
{title}
</h2>
{onCreateTask && (
<Button
variant="primary"
onClick={() => setIsCreateModalOpen(true)}
disabled={loading}
>
+ Nouvelle tâche
</Button>
)}
</div>
</div>
@@ -221,15 +291,20 @@ export function SwimlanesBase({
{statusesToShow.map(status => {
const statusTasks = swimlane.tasks.filter(task => task.status === status);
const columnId = `${swimlane.key}-${status}`;
return (
<DroppableColumn
key={`${swimlane.key}-${status}`}
key={columnId}
status={status}
tasks={statusTasks}
onDeleteTask={onDeleteTask}
onEditTask={onEditTask}
onUpdateTitle={onUpdateTitle}
compactView={compactView}
onCreateTask={onCreateTask ? (data) => handleQuickAdd(data, columnId) : undefined}
showQuickAdd={showQuickAdd[columnId] || false}
onToggleQuickAdd={() => toggleQuickAdd(columnId)}
/>
);
})}
@@ -251,6 +326,16 @@ export function SwimlanesBase({
/>
)}
</DragOverlay>
{/* Modal de création complète */}
{onCreateTask && (
<CreateTaskForm
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
onSubmit={handleCreateTask}
loading={loading}
/>
)}
</DndContext>
);
}

View File

@@ -1,28 +1,33 @@
'use client';
import { Task, TaskStatus } from '@/lib/types';
import { CreateTaskData } from '@/clients/tasks-client';
import { useMemo } from 'react';
import { useTasksContext } from '@/contexts/TasksContext';
import { SwimlanesBase, SwimlaneData } from './SwimlanesBase';
interface SwimlanesboardProps {
tasks: Task[];
onCreateTask?: (data: CreateTaskData) => Promise<Task | null>;
onDeleteTask?: (taskId: string) => Promise<void>;
onEditTask?: (task: Task) => void;
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
compactView?: boolean;
visibleStatuses?: TaskStatus[];
loading?: boolean;
}
export function SwimlanesBoard({
tasks,
onCreateTask,
onDeleteTask,
onEditTask,
onUpdateTitle,
onUpdateStatus,
compactView = false,
visibleStatuses
visibleStatuses,
loading = false
}: SwimlanesboardProps) {
const { tags: availableTags } = useTasksContext();
@@ -77,12 +82,14 @@ export function SwimlanesBoard({
tasks={tasks}
title="Swimlanes par Tag"
swimlanes={swimlanesData}
onCreateTask={onCreateTask}
onDeleteTask={onDeleteTask}
onEditTask={onEditTask}
onUpdateTitle={onUpdateTitle}
onUpdateStatus={onUpdateStatus}
compactView={compactView}
visibleStatuses={visibleStatuses}
loading={loading}
/>
);
}