+
+
{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 */