diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx
index 76cff55..1af09ef 100644
--- a/src/components/layout/AppShell.tsx
+++ b/src/components/layout/AppShell.tsx
@@ -20,6 +20,7 @@ import {
findTaskInTree,
getAllTrashTasks,
filterTasksByTag,
+ filterActiveTree,
} from "@/lib/mockData";
interface AppShellProps {
@@ -126,10 +127,10 @@ export function AppShell({ user, isDemo = false }: AppShellProps) {
} else if (selectedTag) {
setTasks(filterTasksByTag(store.tasks, selectedTag) as Task[]);
} else if (selectedListId) {
- const listTasks = (store.tasks as Task[]).filter(
- (t) => !t.isDeleted && t.listId === selectedListId && (showCompleted ? true : !t.completed)
+ const listTasks = filterActiveTree(store.tasks as MockTask[], showCompleted).filter(
+ (t) => t.listId === selectedListId
);
- setTasks(listTasks);
+ setTasks(listTasks as Task[]);
} else {
setTasks([]);
}
@@ -337,7 +338,13 @@ export function AppShell({ user, isDemo = false }: AppShellProps) {
const store = getDemoStore();
const newTasks = moveToTrashInTree(store.tasks, task.id);
saveDemoStore(store.lists, newTasks);
- setTasks((prev) => prev.filter((t) => t.id !== task.id));
+ if (isTrashActive) {
+ setTasks(getAllTrashTasks(newTasks) as Task[]);
+ } else if (selectedListId) {
+ setTasks(filterActiveTree(newTasks, showCompleted).filter((t) => t.listId === selectedListId) as Task[]);
+ } else if (selectedTag) {
+ setTasks(filterTasksByTag(newTasks, selectedTag) as Task[]);
+ }
if (selectedTask?.id === task.id) setSelectedTask(null);
} else {
setTasks((prev) => prev.filter((t) => t.id !== task.id));
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 9a43d2a..9fa7221 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -7,7 +7,7 @@ import { useTheme } from "@/app/providers";
import { LanguageSelector } from "@/components/ui/LanguageSelector";
import { ContextMenu, MenuItem } from "@/components/ui/ContextMenu";
import { SettingsModal } from "@/components/settings/SettingsModal";
-import { getCustomTags, MockTag, getAllTrashTasks, getDemoStore, getTagTaskCount } from "@/lib/mockData";
+import { getCustomTags, MockTag, getAllTrashTasks, getDemoStore, getTagTaskCount, filterActiveTree, MockTask } from "@/lib/mockData";
interface User { id: string; name?: string | null; email?: string | null }
interface List { id: string; name: string; color: string; icon: string; _count?: { tasks: number } }
@@ -578,8 +578,8 @@ export function Sidebar({
? (() => {
if (typeof window === "undefined") return "";
const store = getDemoStore();
- const cnt = store.tasks.filter((t) => t.listId === list.id && !t.isDeleted && !t.completed).length;
- return cnt > 0 ? cnt : "";
+ const active = filterActiveTree(store.tasks as MockTask[], false).filter((t) => t.listId === list.id);
+ return active.length > 0 ? active.length : "";
})()
: list._count?.tasks || ""}
@@ -646,7 +646,7 @@ export function Sidebar({
? getAllTrashTasks(getDemoStore().tasks).length
: (externalTrashCount !== undefined ? externalTrashCount : internalTrashCount);
return count > 0 ? (
- {count}
+ {count}
) : null;
})()}
diff --git a/src/lib/mockData.ts b/src/lib/mockData.ts
index 9c5e8fc..c62bdd8 100644
--- a/src/lib/mockData.ts
+++ b/src/lib/mockData.ts
@@ -278,24 +278,56 @@ export function moveToTrashInTree(tree: MockTask[], id: string): MockTask[] {
});
}
-// Restore from Trash (recursively unmark node and its children)
-export function restoreTaskInTree(tree: MockTask[], id: string): MockTask[] {
- const markRestored = (node: MockTask): MockTask => ({
+// Restore task (and all its subtasks) from Trash, ensuring ancestor parents are also un-deleted
+export function restoreTaskInTree(tree: MockTask[], targetId: string): MockTask[] {
+ const unmarkNodeAndChildren = (node: MockTask): MockTask => ({
...node,
isDeleted: false,
deletedAt: null,
- children: node.children ? node.children.map(markRestored) : [],
+ children: node.children ? node.children.map(unmarkNodeAndChildren) : [],
});
- return tree.map((node) => {
- if (node.id === id) {
- return markRestored(node);
+ function processNodes(nodes: MockTask[]): { updated: MockTask[]; found: boolean } {
+ let foundInThisLevel = false;
+ const updated = nodes.map((node) => {
+ if (node.id === targetId) {
+ foundInThisLevel = true;
+ return unmarkNodeAndChildren(node);
+ }
+ if (node.children && node.children.length > 0) {
+ const res = processNodes(node.children);
+ if (res.found) {
+ foundInThisLevel = true;
+ // If a child was restored, ancestor parent must also be restored
+ return {
+ ...node,
+ isDeleted: false,
+ deletedAt: null,
+ children: res.updated,
+ };
+ }
+ return { ...node, children: res.updated };
+ }
+ return node;
+ });
+ return { updated, found: foundInThisLevel };
+ }
+
+ return processNodes(tree).updated;
+}
+
+// Recursively filter active tree (stripping isDeleted tasks at all depths)
+export function filterActiveTree(nodes: MockTask[], showCompleted = true): MockTask[] {
+ const result: MockTask[] = [];
+ for (const node of nodes) {
+ if (!node.isDeleted) {
+ if (showCompleted || !node.completed) {
+ const cleanChildren = node.children ? filterActiveTree(node.children, showCompleted) : [];
+ result.push({ ...node, children: cleanChildren });
+ }
}
- if (node.children && node.children.length > 0) {
- return { ...node, children: restoreTaskInTree(node.children, id) };
- }
- return node;
- });
+ }
+ return result;
}
// Permanent delete