Add ability to delete tasks and improve task card interactions
Refactors the KanbanBoard and TaskCard components to include a task deletion functionality and enhance user interaction with task details through a dropdown menu. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/P2PZNJ9
This commit is contained in:
@@ -19,8 +19,8 @@ localPort = 35345
|
|||||||
externalPort = 3002
|
externalPort = 3002
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 40125
|
localPort = 40265
|
||||||
externalPort = 3001
|
externalPort = 3003
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 41353
|
localPort = 41353
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ interface KanbanBoardProps {
|
|||||||
onTaskStatusChange?: (taskId: string, newStatus: Task['status']) => void;
|
onTaskStatusChange?: (taskId: string, newStatus: Task['status']) => void;
|
||||||
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
||||||
onTaskClick?: (task: Task) => void;
|
onTaskClick?: (task: Task) => void;
|
||||||
|
onTaskDelete?: (taskId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewMode = 'traditional' | 'weekly' | 'monthly';
|
type ViewMode = 'traditional' | 'weekly' | 'monthly';
|
||||||
|
|
||||||
export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, onTaskClick }: KanbanBoardProps) {
|
export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, onTaskClick, onTaskDelete }: KanbanBoardProps) {
|
||||||
const [viewMode, setViewMode] = useState<ViewMode>('traditional');
|
const [viewMode, setViewMode] = useState<ViewMode>('traditional');
|
||||||
const [currentDate, setCurrentDate] = useState(new Date());
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
const [draggedTask, setDraggedTask] = useState<string | null>(null);
|
const [draggedTask, setDraggedTask] = useState<string | null>(null);
|
||||||
@@ -199,6 +200,14 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
onTaskClick?.(task);
|
onTaskClick?.(task);
|
||||||
console.log(`Task clicked from board: ${task.title}`);
|
console.log(`Task clicked from board: ${task.title}`);
|
||||||
}}
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
onTaskDelete?.(task.id);
|
||||||
|
console.log(`Delete task from board: ${task.title}`);
|
||||||
|
}}
|
||||||
|
onStatusChange={(newStatus) => {
|
||||||
|
onTaskStatusChange?.(task.id, newStatus);
|
||||||
|
console.log(`Task ${task.title} status changed to ${newStatus}`);
|
||||||
|
}}
|
||||||
isDragging={draggedTask === task.id}
|
isDragging={draggedTask === task.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -261,6 +270,14 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
onTaskClick?.(task);
|
onTaskClick?.(task);
|
||||||
console.log(`Task clicked from board: ${task.title}`);
|
console.log(`Task clicked from board: ${task.title}`);
|
||||||
}}
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
onTaskDelete?.(task.id);
|
||||||
|
console.log(`Delete task from board: ${task.title}`);
|
||||||
|
}}
|
||||||
|
onStatusChange={(newStatus) => {
|
||||||
|
onTaskStatusChange?.(task.id, newStatus);
|
||||||
|
console.log(`Task ${task.title} status changed to ${newStatus}`);
|
||||||
|
}}
|
||||||
isDragging={draggedTask === task.id}
|
isDragging={draggedTask === task.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -323,6 +340,14 @@ export default function KanbanBoard({ tasks, onTaskStatusChange, onTaskUpdate, o
|
|||||||
onTaskClick?.(task);
|
onTaskClick?.(task);
|
||||||
console.log(`Task clicked from board: ${task.title}`);
|
console.log(`Task clicked from board: ${task.title}`);
|
||||||
}}
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
onTaskDelete?.(task.id);
|
||||||
|
console.log(`Delete task from board: ${task.title}`);
|
||||||
|
}}
|
||||||
|
onStatusChange={(newStatus) => {
|
||||||
|
onTaskStatusChange?.(task.id, newStatus);
|
||||||
|
console.log(`Task ${task.title} status changed to ${newStatus}`);
|
||||||
|
}}
|
||||||
isDragging={draggedTask === task.id}
|
isDragging={draggedTask === task.id}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Clock, Calendar, Play, Pause, MoreHorizontal } from "lucide-react";
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Clock, Calendar, Play, Pause, MoreHorizontal, Edit, Trash2, Timer, CheckCircle } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Task, Label } from '@shared/schema';
|
import { Task, Label } from '@shared/schema';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
@@ -11,11 +18,12 @@ interface TaskCardProps {
|
|||||||
onPlay?: () => void;
|
onPlay?: () => void;
|
||||||
onPause?: () => void;
|
onPause?: () => void;
|
||||||
onEdit?: () => void;
|
onEdit?: () => void;
|
||||||
|
onDelete?: () => void;
|
||||||
onStatusChange?: (status: Task['status']) => void;
|
onStatusChange?: (status: Task['status']) => void;
|
||||||
isDragging?: boolean;
|
isDragging?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TaskCard({ task, onPlay, onPause, onEdit, onStatusChange, isDragging }: TaskCardProps) {
|
export default function TaskCard({ task, onPlay, onPause, onEdit, onDelete, onStatusChange, isDragging }: TaskCardProps) {
|
||||||
const [isTimerRunning, setIsTimerRunning] = useState(task.isTracking);
|
const [isTimerRunning, setIsTimerRunning] = useState(task.isTracking);
|
||||||
|
|
||||||
// Fetch labels to get the label color
|
// Fetch labels to get the label color
|
||||||
@@ -122,19 +130,118 @@ export default function TaskCard({ task, onPlay, onPause, onEdit, onStatusChange
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Button
|
<DropdownMenu>
|
||||||
size="icon"
|
<DropdownMenuTrigger asChild>
|
||||||
variant="ghost"
|
<Button
|
||||||
onClick={(e) => {
|
size="icon"
|
||||||
e.stopPropagation();
|
variant="ghost"
|
||||||
onEdit?.();
|
onClick={(e) => e.stopPropagation()}
|
||||||
console.log(`Edit task: ${task.title}`);
|
data-testid={`button-menu-${task.id}`}
|
||||||
}}
|
className="w-6 h-6"
|
||||||
data-testid={`button-edit-${task.id}`}
|
>
|
||||||
className="w-6 h-6"
|
<MoreHorizontal className="w-3 h-3" />
|
||||||
>
|
</Button>
|
||||||
<MoreHorizontal className="w-3 h-3" />
|
</DropdownMenuTrigger>
|
||||||
</Button>
|
<DropdownMenuContent align="end" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEdit?.();
|
||||||
|
console.log(`Edit task: ${task.title}`);
|
||||||
|
}}
|
||||||
|
data-testid={`menu-edit-${task.id}`}
|
||||||
|
>
|
||||||
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
|
Edit Task
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleToggleTimer();
|
||||||
|
}}
|
||||||
|
data-testid={`menu-timer-${task.id}`}
|
||||||
|
>
|
||||||
|
<Timer className="w-4 h-4 mr-2" />
|
||||||
|
{isTimerRunning ? 'Stop Timer' : 'Start Timer'}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
{task.status !== 'done' && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onStatusChange?.('done');
|
||||||
|
console.log(`Mark task as done: ${task.title}`);
|
||||||
|
}}
|
||||||
|
data-testid={`menu-complete-${task.id}`}
|
||||||
|
>
|
||||||
|
<CheckCircle className="w-4 h-4 mr-2" />
|
||||||
|
Mark as Done
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.status === 'todo' && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onStatusChange?.('inProgress');
|
||||||
|
console.log(`Start working on: ${task.title}`);
|
||||||
|
}}
|
||||||
|
data-testid={`menu-start-${task.id}`}
|
||||||
|
>
|
||||||
|
<Play className="w-4 h-4 mr-2" />
|
||||||
|
Start Working
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.status === 'inProgress' && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onStatusChange?.('todo');
|
||||||
|
console.log(`Move back to todo: ${task.title}`);
|
||||||
|
}}
|
||||||
|
data-testid={`menu-todo-${task.id}`}
|
||||||
|
>
|
||||||
|
<Clock className="w-4 h-4 mr-2" />
|
||||||
|
Move to Todo
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{task.status === 'done' && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onStatusChange?.('inProgress');
|
||||||
|
console.log(`Reopen task: ${task.title}`);
|
||||||
|
}}
|
||||||
|
data-testid={`menu-reopen-${task.id}`}
|
||||||
|
>
|
||||||
|
<Play className="w-4 h-4 mr-2" />
|
||||||
|
Reopen Task
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
{onDelete && (
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete();
|
||||||
|
console.log(`Delete task: ${task.title}`);
|
||||||
|
}}
|
||||||
|
className="text-destructive"
|
||||||
|
data-testid={`menu-delete-${task.id}`}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
|
Delete Task
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
Reference in New Issue
Block a user