Collapse completed to-dos in group session dialog

- Move completed drafts out of the active list to reduce clutter.
- Add a collapsible “Completed” section with per-row expansion controls.
This commit is contained in:
Brian Fertig 2026-09-21 16:18:09 -06:00
parent ddba881380
commit 661a1bfab6
1 changed files with 83 additions and 2 deletions

View File

@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Pencil, Plus, Sparkles, Trash2, X, XIcon } from "lucide-react";
import { ChevronDown, ChevronRight, Pencil, Plus, Sparkles, Trash2, X, XIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
@ -108,6 +108,8 @@ export function GroupSessionDialog({
const [aiOpen, setAiOpen] = useState(false);
const [todoAiOpen, setTodoAiOpen] = useState(false);
const [savingAll, setSavingAll] = useState(false);
const [completedOpen, setCompletedOpen] = useState(false);
const [expandedCompletedIds, setExpandedCompletedIds] = useState<Set<string>>(new Set());
const rowFlushersRef = useRef(new Map<string, () => Promise<void>>());
const dirtyRowIdsRef = useRef(new Set<string>());
@ -150,6 +152,9 @@ export function GroupSessionDialog({
setTodoDrafts((prev) => reconcileTodoDrafts(prev, group.todos, dirtyRowIdsRef.current));
}, [group.todos, open]);
const activeTodoDrafts = todoDrafts.filter((d) => !d.completed);
const completedTodoDrafts = todoDrafts.filter((d) => d.completed);
function updateTodoDraft(id: string, patch: Partial<TodoDraft>) {
setTodoDrafts((prev) => prev.map((d) => (d.id === id ? { ...d, ...patch } : d)));
}
@ -176,6 +181,15 @@ export function GroupSessionDialog({
removeTodoDraftLocalOnly(tempIdToRemove);
}
function toggleCompletedExpanded(id: string) {
setExpandedCompletedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
}
function addEmptyTodoDraft() {
setTodoDrafts((prev) => [
...prev,
@ -231,7 +245,7 @@ export function GroupSessionDialog({
<div className="space-y-2">
<Label className="text-sm font-medium">To-dos</Label>
<div className="space-y-2">
{todoDrafts.map((draft) => (
{activeTodoDrafts.map((draft) => (
<SessionTodoRow
key={draft.id}
draft={draft}
@ -257,6 +271,60 @@ export function GroupSessionDialog({
</p>
)}
</div>
{completedTodoDrafts.length > 0 && (
<div className="space-y-2 rounded-md border">
<button
type="button"
onClick={() => setCompletedOpen((v) => !v)}
className="flex w-full items-center gap-2 p-3 text-left text-sm font-medium text-muted-foreground hover:text-foreground"
>
{completedOpen ? (
<ChevronDown className="size-4 shrink-0" />
) : (
<ChevronRight className="size-4 shrink-0" />
)}
Completed ({completedTodoDrafts.length})
</button>
{completedOpen && (
<div className="space-y-2 p-3 pt-0">
{completedTodoDrafts.map((draft) =>
expandedCompletedIds.has(draft.id) ? (
<SessionTodoRow
key={draft.id}
draft={draft}
groupId={group.id}
categoryId={group.categoryId}
accentColor={accentColor}
todoTextColor={todoTextColor}
isDark={isDark}
onChangeLocal={(patch) => updateTodoDraft(draft.id, patch)}
onRemoveLocalOnly={() => removeTodoDraftLocalOnly(draft.id)}
onCreated={() => handleAddTodoCreated(draft.id)}
onDirtyChange={(dirty) => setRowDirty(draft.id, dirty)}
registerFlush={registerRowFlush}
addTodo={addTodo}
editTodo={editTodo}
toggleTodoDone={toggleTodoDone}
removeTodo={removeTodo}
onCollapse={() => toggleCompletedExpanded(draft.id)}
/>
) : (
<button
key={draft.id}
type="button"
onClick={() => toggleCompletedExpanded(draft.id)}
className="flex w-full items-center gap-2 rounded-md border border-dashed p-2 text-left text-sm text-muted-foreground hover:bg-muted/50"
>
<ChevronRight className="size-3.5 shrink-0" />
<span className="flex-1 truncate line-through">{draft.title}</span>
</button>
)
)}
</div>
)}
</div>
)}
</div>
</div>
@ -304,6 +372,7 @@ function SessionTodoRow({
editTodo,
toggleTodoDone,
removeTodo,
onCollapse,
}: {
draft: TodoDraft;
groupId: string;
@ -320,6 +389,7 @@ function SessionTodoRow({
editTodo: ReturnType<typeof useBoard>["editTodo"];
toggleTodoDone: ReturnType<typeof useBoard>["toggleTodoDone"];
removeTodo: ReturnType<typeof useBoard>["removeTodo"];
onCollapse?: () => void;
}) {
const [editingDetails, setEditingDetails] = useState(false);
// Guards against creating the same new to-do twice if title/details both
@ -435,6 +505,17 @@ function SessionTodoRow({
style={draft.completed ? undefined : { color: todoTextColor }}
className={cn("flex-1", draft.completed && "text-muted-foreground line-through")}
/>
{onCollapse && (
<Button
variant="ghost"
size="icon"
className="shrink-0"
onClick={onCollapse}
aria-label={`Collapse "${draft.title || "to-do"}"`}
>
<ChevronDown className="size-4" />
</Button>
)}
<Button
variant="ghost"
size="icon"