Fix: Full N-depth recursive subtask tree rendering in main list, universal custom context menu on all subtask levels, and bidirectional realtime sync

This commit is contained in:
Wonhee Han
2026-08-20 14:45:55 +09:00
parent 81ddb9cba7
commit cadb2c78cf
4 changed files with 255 additions and 323 deletions
+72 -20
View File
@@ -20,7 +20,7 @@ export interface MockTask {
createdAt: string;
updatedAt: string;
children: MockTask[];
tags: { tag: { id: string; name: string; color: string } }[];
tags?: { tag: { id: string; name: string; color: string } }[];
}
const STORAGE_KEY_LISTS = "checkflow_demo_lists";
@@ -52,7 +52,7 @@ export const INITIAL_DEMO_TASKS: MockTask[] = [
listId: "list-1",
parentId: "task-1",
title: "Configure .env environment variables",
note: null,
note: "Database URL, NextAuth secret, and port settings",
completed: true,
completedAt: new Date().toISOString(),
dueDate: null,
@@ -60,7 +60,24 @@ export const INITIAL_DEMO_TASKS: MockTask[] = [
sortOrder: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
children: [],
children: [
{
id: "sub-1-1-1",
listId: "list-1",
parentId: "sub-1-1",
title: "Database connection string verification",
note: null,
completed: false,
completedAt: null,
dueDate: null,
priority: 2,
sortOrder: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
children: [],
tags: [],
},
],
tags: [],
},
{
@@ -87,7 +104,7 @@ export const INITIAL_DEMO_TASKS: MockTask[] = [
listId: "list-1",
parentId: null,
title: "Import existing tasks from TickTick",
note: "1. Export CSV/ICS in TickTick Settings\n2. Click 'Import Tasks' in CheckFlow Sidebar\n3. Select target project",
note: "1. Export CSV/ICS in TickTick Settings\n2. Click user profile at bottom-left\n3. Select 'Import Tasks'",
completed: false,
completedAt: null,
dueDate: null,
@@ -98,22 +115,6 @@ export const INITIAL_DEMO_TASKS: MockTask[] = [
children: [],
tags: [],
},
{
id: "task-3",
listId: "list-2",
parentId: null,
title: "Install CheckFlow PWA on mobile home screen",
note: "Open in Chrome/Samsung Internet -> Menu -> **Install app** or **Add to Home Screen**",
completed: true,
completedAt: new Date().toISOString(),
dueDate: new Date().toISOString(),
priority: 1,
sortOrder: 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
children: [],
tags: [],
},
];
export function getDemoStore() {
@@ -147,4 +148,55 @@ export function saveDemoStore(lists: MockList[], tasks: MockTask[]) {
} catch (e) {
console.error("Failed to save demo storage", e);
}
}
// Recursive tree helpers for N-depth sub-tasks
export function updateTaskInTree(tree: MockTask[], updated: MockTask): MockTask[] {
return tree.map((node) => {
if (node.id === updated.id) {
return { ...updated, children: updated.children || node.children || [] };
}
if (node.children && node.children.length > 0) {
return { ...node, children: updateTaskInTree(node.children, updated) };
}
return node;
});
}
export function deleteTaskInTree(tree: MockTask[], idToDelete: string): MockTask[] {
return tree
.filter((node) => node.id !== idToDelete)
.map((node) => {
if (node.children && node.children.length > 0) {
return { ...node, children: deleteTaskInTree(node.children, idToDelete) };
}
return node;
});
}
export function addTaskToTree(tree: MockTask[], parentId: string | null, newTask: MockTask): MockTask[] {
if (!parentId) {
return [...tree, newTask];
}
return tree.map((node) => {
if (node.id === parentId) {
const currentChildren = node.children || [];
return { ...node, children: [...currentChildren, newTask] };
}
if (node.children && node.children.length > 0) {
return { ...node, children: addTaskToTree(node.children, parentId, newTask) };
}
return node;
});
}
export function findTaskInTree(tree: MockTask[], id: string): MockTask | null {
for (const node of tree) {
if (node.id === id) return node;
if (node.children && node.children.length > 0) {
const found = findTaskInTree(node.children, id);
if (found) return found;
}
}
return null;
}