Files
vibe-kanban/frontend/src/components/tasks/TaskKanbanBoard.tsx
Louis Knight-Webb d2e3cbd4f6 Add task search (vibe-kanban) (#48)
* Perfect! I've successfully added search functionality to the TaskKanbanBoard component. The implementation includes:

1. **Search input field** at the top of the kanban board
2. **Real-time filtering** that searches both task titles and descriptions
3. **Case-insensitive search** for better user experience
4. **Clean integration** with the existing kanban board structure

The search functionality works by filtering tasks before they're grouped by status, so only matching tasks will appear on the board across all columns.

* Perfect! I've successfully moved the search input to the header, positioning it to the left of the "Add Task" button. The improvements include:

1. **Better positioning**: Search input is now in the header alongside the "Add Task" button
2. **Improved layout**: The search input has a fixed width (w-64) and is properly spaced
3. **Clean integration**: The search functionality is maintained while improving the visual hierarchy
4. **Responsive design**: The search input is grouped with the "Add Task" button for better visual balance

The search functionality remains the same - it filters tasks by title and description in real-time as you type.

* Prettier
2025-07-02 12:09:09 +01:00

117 lines
3.0 KiB
TypeScript

import {
KanbanProvider,
KanbanBoard,
KanbanHeader,
KanbanCards,
type DragEndEvent,
} from '@/components/ui/shadcn-io/kanban';
import { TaskCard } from './TaskCard';
import type { TaskStatus, TaskWithAttemptStatus } from 'shared/types';
type Task = TaskWithAttemptStatus;
interface TaskKanbanBoardProps {
tasks: Task[];
searchQuery?: string;
onDragEnd: (event: DragEndEvent) => void;
onEditTask: (task: Task) => void;
onDeleteTask: (taskId: string) => void;
onViewTaskDetails: (task: Task) => void;
}
const allTaskStatuses: TaskStatus[] = [
'todo',
'inprogress',
'inreview',
'done',
'cancelled',
];
const statusLabels: Record<TaskStatus, string> = {
todo: 'To Do',
inprogress: 'In Progress',
inreview: 'In Review',
done: 'Done',
cancelled: 'Cancelled',
};
const statusBoardColors: Record<TaskStatus, string> = {
todo: 'hsl(var(--neutral))',
inprogress: 'hsl(var(--info))',
inreview: 'hsl(var(--warning))',
done: 'hsl(var(--success))',
cancelled: 'hsl(var(--destructive))',
};
export function TaskKanbanBoard({
tasks,
searchQuery = '',
onDragEnd,
onEditTask,
onDeleteTask,
onViewTaskDetails,
}: TaskKanbanBoardProps) {
const filterTasks = (tasks: Task[]) => {
if (!searchQuery.trim()) {
return tasks;
}
const query = searchQuery.toLowerCase();
return tasks.filter(
(task) =>
task.title.toLowerCase().includes(query) ||
(task.description && task.description.toLowerCase().includes(query))
);
};
const groupTasksByStatus = () => {
const groups: Record<TaskStatus, Task[]> = {} as Record<TaskStatus, Task[]>;
// Initialize groups for all possible statuses
allTaskStatuses.forEach((status) => {
groups[status] = [];
});
const filteredTasks = filterTasks(tasks);
filteredTasks.forEach((task) => {
// Convert old capitalized status to lowercase if needed
const normalizedStatus = task.status.toLowerCase() as TaskStatus;
if (groups[normalizedStatus]) {
groups[normalizedStatus].push(task);
} else {
// Default to todo if status doesn't match any expected value
groups['todo'].push(task);
}
});
return groups;
};
return (
<KanbanProvider onDragEnd={onDragEnd}>
{Object.entries(groupTasksByStatus()).map(([status, statusTasks]) => (
<KanbanBoard key={status} id={status as TaskStatus}>
<KanbanHeader
name={statusLabels[status as TaskStatus]}
color={statusBoardColors[status as TaskStatus]}
/>
<KanbanCards>
{statusTasks.map((task, index) => (
<TaskCard
key={task.id}
task={task}
index={index}
status={status}
onEdit={onEditTask}
onDelete={onDeleteTask}
onViewDetails={onViewTaskDetails}
/>
))}
</KanbanCards>
</KanbanBoard>
))}
</KanbanProvider>
);
}