diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e767e64fef..b187b52d30 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono, Figtree } from "next/font/google"; import "./globals.css"; +import { ThemeProvider } from "@/components/theme-provider"; const figtree = Figtree({subsets:['latin'],variable:'--font-sans'}); @@ -25,11 +26,11 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + {children} ); diff --git a/apps/web/components/kanban-board.tsx b/apps/web/components/kanban-board.tsx index 9c40fdccca..596337ace0 100644 --- a/apps/web/components/kanban-board.tsx +++ b/apps/web/components/kanban-board.tsx @@ -10,6 +10,7 @@ import { } from '@dnd-kit/core'; import { KanbanColumn, Column } from './kanban-column'; import { Task } from './kanban-card'; +import { ThemeToggle } from './theme-toggle'; type ColumnId = 'todo' | 'in-progress' | 'in-review' | 'done'; @@ -52,11 +53,12 @@ export function KanbanBoard() { ); }; - const handleAddTask = (columnId: string, title: string) => { + const handleAddTask = (columnId: string, title: string, description?: string) => { const newTask: Task = { id: crypto.randomUUID(), title, status: columnId, + description, }; setTasks((tasks) => [...tasks, newTask]); }; @@ -66,19 +68,25 @@ export function KanbanBoard() { }; return ( - -
-
- {COLUMNS.map((column) => ( - - ))} +
+
+

Kanban Board

+ +
+ +
+
+ {COLUMNS.map((column) => ( + + ))} +
-
- + +
); } diff --git a/apps/web/components/kanban-card.tsx b/apps/web/components/kanban-card.tsx index cf777e4b2f..4fe1ba04c0 100644 --- a/apps/web/components/kanban-card.tsx +++ b/apps/web/components/kanban-card.tsx @@ -9,6 +9,7 @@ export interface Task { id: string; title: string; status: string; + description?: string; } interface KanbanCardProps { @@ -30,13 +31,16 @@ export function KanbanCard({ task }: KanbanCardProps) { style={style} className={cn( 'cursor-grab active:cursor-grabbing mb-2 transition-opacity', - isDragging && 'opacity-50' + isDragging && 'opacity-50 z-50' )} {...listeners} {...attributes} > -

{task.title}

+

{task.title}

+ {task.description && ( +

{task.description}

+ )}
); diff --git a/apps/web/components/kanban-column.tsx b/apps/web/components/kanban-column.tsx index c709bcfb9d..3bc5eb31bd 100644 --- a/apps/web/components/kanban-column.tsx +++ b/apps/web/components/kanban-column.tsx @@ -4,10 +4,10 @@ import { useDroppable } from '@dnd-kit/core'; import { KanbanCard, Task } from './kanban-card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { cn } from '@/lib/utils'; import { IconPlus } from '@tabler/icons-react'; -import { useState, KeyboardEvent } from 'react'; +import { useState } from 'react'; +import { TaskCreateDialog } from './task-create-dialog'; export interface Column { id: string; @@ -18,7 +18,7 @@ export interface Column { interface KanbanColumnProps { column: Column; tasks: Task[]; - onAddTask: (columnId: string, title: string) => void; + onAddTask: (columnId: string, title: string, description?: string) => void; } export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) { @@ -26,28 +26,10 @@ export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) { id: column.id, }); - const [isAdding, setIsAdding] = useState(false); - const [newTaskTitle, setNewTaskTitle] = useState(''); + const [isDialogOpen, setIsDialogOpen] = useState(false); - const handleAddClick = () => { - setIsAdding(true); - }; - - const handleSubmit = () => { - if (newTaskTitle.trim()) { - onAddTask(column.id, newTaskTitle.trim()); - setNewTaskTitle(''); - setIsAdding(false); - } - }; - - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter') { - handleSubmit(); - } else if (e.key === 'Escape') { - setNewTaskTitle(''); - setIsAdding(false); - } + const handleDialogSubmit = (title: string, description: string) => { + onAddTask(column.id, title, description); }; return ( @@ -71,30 +53,21 @@ export function KanbanColumn({ column, tasks, onAddTask }: KanbanColumnProps) { variant="ghost" size="sm" className="h-8 w-8 p-0" - onClick={handleAddClick} + onClick={() => setIsDialogOpen(true)} >
- {/* Add Task Input */} - {isAdding && ( -
- setNewTaskTitle(e.target.value)} - onKeyDown={handleKeyDown} - onBlur={handleSubmit} - className="text-sm" - /> -
- )} + {/* Tasks */}
- {tasks.length === 0 && !isAdding ? ( + {tasks.length === 0 ? (

No tasks yet

) : ( tasks.map((task) => ) diff --git a/apps/web/components/task-create-dialog.tsx b/apps/web/components/task-create-dialog.tsx new file mode 100644 index 0000000000..cdcead3033 --- /dev/null +++ b/apps/web/components/task-create-dialog.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { useState, FormEvent } from 'react'; +import { + AlertDialog, + AlertDialogContent, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogFooter, + AlertDialogCancel, +} from '@/components/ui/alert-dialog'; +import { Field, FieldLabel } from '@/components/ui/field'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Button } from '@/components/ui/button'; + +interface TaskCreateDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSubmit: (title: string, description: string) => void; +} + +export function TaskCreateDialog({ + open, + onOpenChange, + onSubmit, +}: TaskCreateDialogProps) { + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (title.trim()) { + onSubmit(title.trim(), description.trim()); + setTitle(''); + setDescription(''); + onOpenChange(false); + } + }; + + const handleCancel = () => { + setTitle(''); + setDescription(''); + onOpenChange(false); + }; + + return ( + + + + Create Task + +
+ + Title + setTitle(e.target.value)} + /> + + + Description +