From 621559ee22ba7d95843269235165d175f6b55121 Mon Sep 17 00:00:00 2001 From: Hammer Date: Wed, 28 Jan 2026 18:54:09 +0000 Subject: [PATCH] Improve board view: section management, completed column, better styling --- dist/index.html | 4 +- src/pages/Board.tsx | 376 +++++++++++++++++++++++++++++++++++++----- src/pages/Project.tsx | 2 + 3 files changed, 339 insertions(+), 43 deletions(-) diff --git a/dist/index.html b/dist/index.html index 1b7f52f..f80dc07 100644 --- a/dist/index.html +++ b/dist/index.html @@ -6,8 +6,8 @@ Todo App - - + +
diff --git a/src/pages/Board.tsx b/src/pages/Board.tsx index a7d1b25..7e71706 100644 --- a/src/pages/Board.tsx +++ b/src/pages/Board.tsx @@ -1,56 +1,70 @@ -import { Calendar, Flag, Plus } from 'lucide-react'; +import { useState, useRef, useEffect } from 'react'; +import { Calendar, Pencil, Plus, Trash2, Check, X, CheckCircle2 } from 'lucide-react'; import type { Task, Project, Section } from '@/types'; import { cn, formatDate, isOverdue, getPriorityColor } from '@/lib/utils'; import { useTasksStore } from '@/stores/tasks'; import { AddTask } from '@/components/AddTask'; +import { api } from '@/lib/api'; interface BoardViewProps { project: Project; sections: Section[]; tasks: Task[]; + completedTasks: Task[]; isLoading: boolean; + onSectionsChange: (sections: Section[]) => void; } -function TaskCard({ task }: { task: Task }) { +function TaskCard({ task, muted }: { task: Task; muted?: boolean }) { const { toggleComplete, setSelectedTask } = useTasksStore(); const overdue = !task.isCompleted && isOverdue(task.dueDate); - + return (
setSelectedTask(task)} >
- {/* Priority indicator */}
-

+

{task.title}

- + {task.description && (

{task.description}

)} - +
{task.dueDate && ( - + {formatDate(task.dueDate)} )} - + {task.assignee && ( void; + onDelete?: () => void; +}) { + const [editing, setEditing] = useState(false); + const [value, setValue] = useState(title); + const inputRef = useRef(null); + + useEffect(() => { + if (editing && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [editing]); + + const submit = () => { + const trimmed = value.trim(); + if (trimmed && trimmed !== title && onRename) { + onRename(trimmed); + } else { + setValue(title); + } + setEditing(false); + }; + + const handleDelete = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onDelete && confirm(`Delete section "${title}"? Tasks will become unsectioned.`)) { + onDelete(); + } + }; + + if (editing) { + return ( +
+ setValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') submit(); + if (e.key === 'Escape') { setValue(title); setEditing(false); } + }} + onBlur={submit} + /> +
+ ); + } + + return ( +
+

+ {title} + {count} +

+ {sectionId && ( +
+ + +
+ )} +
+ ); +} + function BoardColumn({ title, tasks, projectId, sectionId, + onRename, + onDelete, + muted, + showAddTask, }: { title: string; tasks: Task[]; projectId: string; sectionId?: string; + onRename?: (newName: string) => void; + onDelete?: () => void; + muted?: boolean; + showAddTask?: boolean; }) { return ( -
- {/* Column header */} -
-

- {title} - {tasks.length} -

-
- - {/* Tasks */} +
+ +
{tasks.map((task) => ( - + ))}
- - {/* Add task */} -
- + + {showAddTask !== false && ( +
+ +
+ )} +
+ ); +} + +function AddSectionColumn({ + projectId, + onAdd, +}: { + projectId: string; + onAdd: (section: Section) => void; +}) { + const [adding, setAdding] = useState(false); + const [name, setName] = useState(''); + const [submitting, setSubmitting] = useState(false); + const inputRef = useRef(null); + + useEffect(() => { + if (adding && inputRef.current) { + inputRef.current.focus(); + } + }, [adding]); + + const submit = async () => { + const trimmed = name.trim(); + if (!trimmed || submitting) return; + setSubmitting(true); + try { + const section = await api.createSection(projectId, { name: trimmed }); + onAdd(section); + setName(''); + setAdding(false); + } catch (err) { + console.error('Failed to create section:', err); + } finally { + setSubmitting(false); + } + }; + + if (!adding) { + return ( +
+ +
+ ); + } + + return ( +
+ setName(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') submit(); + if (e.key === 'Escape') { setName(''); setAdding(false); } + }} + disabled={submitting} + /> +
+ +
); } -export function BoardView({ project, sections, tasks, isLoading }: BoardViewProps) { +export function BoardView({ + project, + sections, + tasks, + completedTasks, + isLoading, + onSectionsChange, +}: BoardViewProps) { + const [showCompleted, setShowCompleted] = useState(true); + if (isLoading) { return (
Loading tasks...
); } - + const unsectionedTasks = tasks.filter((t) => !t.sectionId); - const columns = [ - { title: 'No Section', tasks: unsectionedTasks, sectionId: undefined }, - ...sections.map((section) => ({ + + const handleRename = async (sectionId: string, newName: string) => { + try { + await api.updateSection(project.id, sectionId, { name: newName }); + onSectionsChange( + sections.map((s) => (s.id === sectionId ? { ...s, name: newName } : s)) + ); + } catch (err) { + console.error('Failed to rename section:', err); + } + }; + + const handleDelete = async (sectionId: string) => { + try { + await api.deleteSection(project.id, sectionId); + onSectionsChange(sections.filter((s) => s.id !== sectionId)); + } catch (err) { + console.error('Failed to delete section:', err); + } + }; + + const handleAdd = (section: Section) => { + onSectionsChange([...sections, section]); + }; + + const columns: { + key: string; + title: string; + tasks: Task[]; + sectionId?: string; + muted?: boolean; + showAddTask?: boolean; + onRename?: (name: string) => void; + onDelete?: () => void; + }[] = []; + + // Only show "No Section" column if there are unsectioned tasks + if (unsectionedTasks.length > 0) { + columns.push({ + key: 'unsectioned', + title: 'No Section', + tasks: unsectionedTasks, + sectionId: undefined, + }); + } + + // Section columns + for (const section of sections) { + columns.push({ + key: section.id, title: section.name, tasks: tasks.filter((t) => t.sectionId === section.id), sectionId: section.id, - })), - ]; - + onRename: (name: string) => handleRename(section.id, name), + onDelete: () => handleDelete(section.id), + }); + } + return ( -
+
{columns.map((col) => ( ))} + + {/* Done column */} + {completedTasks.length > 0 && ( +
+
+

+ + Done + {completedTasks.length} +

+ +
+ {showCompleted && ( +
+ {completedTasks.map((task) => ( + + ))} +
+ )} +
+ )} + + {/* Add section button */} +
); } diff --git a/src/pages/Project.tsx b/src/pages/Project.tsx index f2ee530..512d618 100644 --- a/src/pages/Project.tsx +++ b/src/pages/Project.tsx @@ -97,7 +97,9 @@ export function ProjectPage() { project={project} sections={sections} tasks={tasks} + completedTasks={completedTasks} isLoading={isLoading} + onSectionsChange={setSections} /> ) : ( /* List view */