feat: add task buttons to Kanban headers (#863)

* wip: add task

* add handler

* i18n

* add button styles

---------

Co-authored-by: Louis Knight-Webb <louis@bloop.ai>
This commit is contained in:
Gabriel Gordon-Hall
2025-09-26 16:34:52 +01:00
committed by GitHub
parent e3727e249d
commit dd877eaa51
6 changed files with 57 additions and 10 deletions

View File

@@ -22,6 +22,7 @@ interface TaskKanbanBoardProps {
onDuplicateTask?: (task: Task) => void;
onViewTaskDetails: (task: Task) => void;
selectedTask?: Task;
onCreateTask?: () => void;
}
function TaskKanbanBoard({
@@ -32,6 +33,7 @@ function TaskKanbanBoard({
onDuplicateTask,
onViewTaskDetails,
selectedTask,
onCreateTask,
}: TaskKanbanBoardProps) {
return (
<KanbanProvider onDragEnd={onDragEnd}>
@@ -40,6 +42,7 @@ function TaskKanbanBoard({
<KanbanHeader
name={statusLabels[status as TaskStatus]}
color={statusBoardColors[status as TaskStatus]}
onAddTask={onCreateTask}
/>
<KanbanCards>
{statusTasks.map((task, index) => (

View File

@@ -1,6 +1,12 @@
'use client';
import { Card } from '@/components/ui/card';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
import type { DragEndEvent, Modifier } from '@dnd-kit/core';
import {
@@ -13,9 +19,12 @@ import {
useSensors,
} from '@dnd-kit/core';
import { type ReactNode, type Ref, type KeyboardEvent } from 'react';
import { useTranslation } from 'react-i18next';
import { Plus } from 'lucide-react';
import type { ClientRect } from '@dnd-kit/core';
import type { Transform } from '@dnd-kit/utilities';
import { Button } from '../../button';
export type { DragEndEvent } from '@dnd-kit/core';
export type Status = {
@@ -140,15 +149,20 @@ export type KanbanHeaderProps =
name: Status['name'];
color: Status['color'];
className?: string;
onAddTask?: () => void;
};
export const KanbanHeader = (props: KanbanHeaderProps) =>
'children' in props ? (
props.children
) : (
export const KanbanHeader = (props: KanbanHeaderProps) => {
const { t } = useTranslation('tasks');
if ('children' in props) {
return props.children;
}
return (
<Card
className={cn(
'sticky top-0 z-20 flex shrink-0 items-center gap-2 p-3 border-b border-dashed',
'sticky top-0 z-20 flex shrink-0 items-center gap-2 p-3 border-b border-dashed flex gap-2',
'bg-background',
props.className
)}
@@ -156,13 +170,32 @@ export const KanbanHeader = (props: KanbanHeaderProps) =>
backgroundImage: `linear-gradient(hsl(var(${props.color}) / 0.03), hsl(var(${props.color}) / 0.03))`,
}}
>
<div
className="h-2 w-2 rounded-full"
style={{ backgroundColor: `hsl(var(${props.color}))` }}
/>
<p className="m-0 text-sm">{props.name}</p>
<span className="flex-1 flex items-center gap-2">
<div
className="h-2 w-2 rounded-full"
style={{ backgroundColor: `hsl(var(${props.color}))` }}
/>
<p className="m-0 text-sm">{props.name}</p>
</span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
className="m-0 p-0 h-0 text-foreground/50 hover:text-foreground"
onClick={props.onAddTask}
aria-label={t('actions.addTask')}
>
<Plus className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="top">{t('actions.addTask')}</TooltipContent>
</Tooltip>
</TooltipProvider>
</Card>
);
};
function restrictToBoundingRectWithRightPadding(
transform: Transform,

View File

@@ -4,5 +4,8 @@
"noTasks": "No tasks found for this project.",
"createFirst": "Create First Task",
"noSearchResults": "No tasks match your search."
},
"actions": {
"addTask": "Add task"
}
}

View File

@@ -4,5 +4,8 @@
"noTasks": "No se encontraron tareas para este proyecto.",
"createFirst": "Crear Primera Tarea",
"noSearchResults": "Ninguna tarea coincide con tu búsqueda."
},
"actions": {
"addTask": "Agregar tarea"
}
}

View File

@@ -4,5 +4,8 @@
"noTasks": "このプロジェクトにタスクが見つかりません。",
"createFirst": "最初のタスクを作成",
"noSearchResults": "検索条件に一致するタスクがありません。"
},
"actions": {
"addTask": "タスクを追加"
}
}

View File

@@ -61,6 +61,7 @@ export function ProjectTasks() {
const [project, setProject] = useState<Project | null>(null);
const [error, setError] = useState<string | null>(null);
// Helper functions to open task forms
const handleCreateTask = () => {
if (project?.id) {
@@ -540,6 +541,7 @@ export function ProjectTasks() {
onDuplicateTask={handleDuplicateTaskCallback}
onViewTaskDetails={handleViewTaskDetails}
selectedTask={selectedTask || undefined}
onCreateTask={handleCreateNewTask}
/>
</div>
)}