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:
parent
ddba881380
commit
661a1bfab6
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue