diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx
index fe2d730..76cff55 100644
--- a/src/components/layout/AppShell.tsx
+++ b/src/components/layout/AppShell.tsx
@@ -410,13 +410,13 @@ export function AppShell({ user, isDemo = false }: AppShellProps) {
}
};
- // Restore from Trash
+ // Restore from Trash (Restores task and all its nested subtasks)
const handleRestoreTask = (id: string) => {
if (isDemo) {
const store = getDemoStore();
const newTasks = restoreTaskInTree(store.tasks, id);
saveDemoStore(store.lists, newTasks);
- setTasks((prev) => prev.filter((t) => t.id !== id));
+ setTasks(getAllTrashTasks(newTasks) as Task[]);
refresh();
}
};
@@ -427,7 +427,7 @@ export function AppShell({ user, isDemo = false }: AppShellProps) {
const store = getDemoStore();
const newTasks = deleteTaskInTree(store.tasks, id);
saveDemoStore(store.lists, newTasks);
- setTasks((prev) => prev.filter((t) => t.id !== id));
+ setTasks(getAllTrashTasks(newTasks) as Task[]);
refresh();
}
};
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 89646ac..9a43d2a 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 } from "@/lib/mockData";
+import { getCustomTags, MockTag, getAllTrashTasks, getDemoStore, getTagTaskCount } 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 } }
@@ -607,16 +607,22 @@ export function Sidebar({
{tags.length > 0 && (
🏷️ {t("tags") || "Tags"}
- {tags.map((tag) => (
-
onTagSelect(selectedTag === tag.name ? null : tag.name)}
- >
- #
- {tag.name}
-
- ))}
+ {tags.map((tag) => {
+ const tagCount = isDemo && typeof window !== "undefined"
+ ? getTagTaskCount(getDemoStore().tasks, tag.name)
+ : 0;
+ return (
+
onTagSelect(selectedTag === tag.name ? null : tag.name)}
+ >
+ #
+ {tag.name}
+ {tagCount > 0 && {tagCount}}
+
+ );
+ })}
)}
diff --git a/src/components/tasks/TaskDetail.tsx b/src/components/tasks/TaskDetail.tsx
index 7035dc7..b7b325e 100644
--- a/src/components/tasks/TaskDetail.tsx
+++ b/src/components/tasks/TaskDetail.tsx
@@ -160,8 +160,8 @@ export function TaskDetail({
const updatedTask: Task = {
...task,
...data,
- children: subtasks,
- tags,
+ children: (data.children as Task[]) || subtasks,
+ tags: (data.tags as { tag: Tag }[]) || tags,
updatedAt: new Date().toISOString(),
} as Task;
if (onDemoUpdateTask) onDemoUpdateTask(updatedTask);
@@ -251,7 +251,12 @@ export function TaskDetail({
name: trimmed,
color: ["#4B7BF5", "#10B981", "#EF4444", "#F59E0B", "#8B5CF6"][Math.floor(Math.random() * 5)],
};
- setAllAvailableTags((p) => [...p, newTag]);
+ const updatedAvailable = [...allAvailableTags, newTag];
+ setAllAvailableTags(updatedAvailable);
+ if (typeof window !== "undefined") {
+ const { saveCustomTags } = require("@/lib/mockData");
+ saveCustomTags(updatedAvailable);
+ }
toggleTag(newTag);
setNewTagName("");
};
diff --git a/src/lib/mockData.ts b/src/lib/mockData.ts
index b6683c1..9c5e8fc 100644
--- a/src/lib/mockData.ts
+++ b/src/lib/mockData.ts
@@ -280,16 +280,16 @@ 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 unmarkDeleted = (node: MockTask): MockTask => ({
+ const markRestored = (node: MockTask): MockTask => ({
...node,
isDeleted: false,
deletedAt: null,
- children: node.children ? node.children.map(unmarkDeleted) : [],
+ children: node.children ? node.children.map(markRestored) : [],
});
return tree.map((node) => {
if (node.id === id) {
- return unmarkDeleted(node);
+ return markRestored(node);
}
if (node.children && node.children.length > 0) {
return { ...node, children: restoreTaskInTree(node.children, id) };
@@ -348,25 +348,24 @@ export function findTaskInTree(tree: MockTask[], id: string): MockTask | null {
return null;
}
-// Get all active tasks or all trash tasks flattened (strictly deduplicated)
+// Get all top-level trash tasks preserving intact nested sub-tasks hierarchy
export function getAllTrashTasks(tree: MockTask[]): MockTask[] {
- const map = new Map();
+ const result: MockTask[] = [];
function collect(nodes: MockTask[]) {
for (const node of nodes) {
if (node.isDeleted) {
- if (!map.has(node.id)) {
- map.set(node.id, { ...node, children: [] });
- }
- }
- if (node.children && node.children.length > 0) {
+ // Collect top-level deleted item with its full subtasks tree intact
+ result.push(node);
+ } else if (node.children && node.children.length > 0) {
+ // If parent is not deleted, check if any subtask was individually deleted
collect(node.children);
}
}
}
collect(tree);
- return Array.from(map.values());
+ return result;
}
// Filter tasks by custom tag
@@ -381,4 +380,9 @@ export function filterTasksByTag(tree: MockTask[], tagName: string): MockTask[]
}
}
return matched;
-}
\ No newline at end of file
+}
+
+// Get count of active tasks for a tag
+export function getTagTaskCount(tree: MockTask[], tagName: string): number {
+ return filterTasksByTag(tree, tagName).filter((t) => !t.completed).length;
+}