48 lines
1.9 KiB
TypeScript
48 lines
1.9 KiB
TypeScript
// SPEC: PJ2026-010404 project management draft-2026-06-25-p0-mdtodo-web-active-editing-hwpod-source.
|
|
// 职责:根据节点自身及全部后代推导只用于展示的 TaskTree 父级时间范围。
|
|
|
|
export type TaskTreeTimedNode = {
|
|
id: string;
|
|
parentId: string | null;
|
|
startAt: string | null;
|
|
dueAt: string | null;
|
|
};
|
|
|
|
export type TaskTreeTimeRange = Pick<TaskTreeTimedNode, "startAt" | "dueAt">;
|
|
|
|
export function deriveEffectiveTaskTimeRanges(tasks: TaskTreeTimedNode[]): Map<string, TaskTreeTimeRange> {
|
|
const tasksById = new Map(tasks.map((task) => [task.id, task]));
|
|
const childrenByParent = new Map<string, TaskTreeTimedNode[]>();
|
|
for (const task of tasks) {
|
|
if (!task.parentId || !tasksById.has(task.parentId)) continue;
|
|
childrenByParent.set(task.parentId, [...(childrenByParent.get(task.parentId) ?? []), task]);
|
|
}
|
|
|
|
const ranges = new Map<string, TaskTreeTimeRange>();
|
|
const resolving = new Set<string>();
|
|
const resolve = (task: TaskTreeTimedNode): TaskTreeTimeRange => {
|
|
const cached = ranges.get(task.id);
|
|
if (cached) return cached;
|
|
if (resolving.has(task.id)) return { startAt: task.startAt, dueAt: task.dueAt };
|
|
resolving.add(task.id);
|
|
const values = [task.startAt, task.dueAt];
|
|
for (const child of childrenByParent.get(task.id) ?? []) {
|
|
const childRange = resolve(child);
|
|
values.push(childRange.startAt, childRange.dueAt);
|
|
}
|
|
const timestamps = values
|
|
.filter((value): value is string => Boolean(value))
|
|
.map((value) => new Date(value).getTime())
|
|
.filter(Number.isFinite);
|
|
const range = timestamps.length > 0
|
|
? { startAt: new Date(Math.min(...timestamps)).toISOString(), dueAt: new Date(Math.max(...timestamps)).toISOString() }
|
|
: { startAt: null, dueAt: null };
|
|
resolving.delete(task.id);
|
|
ranges.set(task.id, range);
|
|
return range;
|
|
};
|
|
|
|
for (const task of tasks) resolve(task);
|
|
return ranges;
|
|
}
|