@critical-path/react 0.13.11 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +213 -72
- package/dist/hooks.js.map +1 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +1 -0
- package/dist/provider.js.map +1 -1
- package/package.json +14 -6
- package/CHANGELOG.md +0 -613
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -25
- package/dist/index.test.js.map +0 -1
- package/src/hooks.ts +0 -1218
- package/src/index.test.ts +0 -45
- package/src/index.ts +0 -2
- package/src/provider.tsx +0 -36
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/src/hooks.ts
DELETED
|
@@ -1,1218 +0,0 @@
|
|
|
1
|
-
import type { CreateAttachmentBody, CreateCommentBody, CreateTaskBody, UpdateCommentBody, UpdateDeliverableBody, UpdateTaskBody } from '@critical-path/core/schemas';
|
|
2
|
-
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
3
|
-
import type {
|
|
4
|
-
Project,
|
|
5
|
-
Task,
|
|
6
|
-
TaskStatus,
|
|
7
|
-
Workflow,
|
|
8
|
-
Comment,
|
|
9
|
-
CommentReaction,
|
|
10
|
-
Attachment,
|
|
11
|
-
Deliverable,
|
|
12
|
-
DeliverableSummary,
|
|
13
|
-
CreateDeliverableInput,
|
|
14
|
-
StatusDefinition,
|
|
15
|
-
SemanticStatus,
|
|
16
|
-
CriticalPathAnalysis,
|
|
17
|
-
TimelineLadder,
|
|
18
|
-
TimelineLadderOptions,
|
|
19
|
-
TaskLadderView,
|
|
20
|
-
AbstractionLevel,
|
|
21
|
-
TaskMetrics,
|
|
22
|
-
TaskProgressHistory,
|
|
23
|
-
WorkloadDistribution,
|
|
24
|
-
WorkloadDistributionOptions,
|
|
25
|
-
WorkloadInterval,
|
|
26
|
-
WorkloadGroupBy,
|
|
27
|
-
WorkloadMetric
|
|
28
|
-
} from '@critical-path/core';
|
|
29
|
-
import { resolveStatusDefinition, isTempTaskId } from '@critical-path/core';
|
|
30
|
-
import { registerWebMcpTools } from '@critical-path/mcp/web';
|
|
31
|
-
import { useCriticalPathClient } from './provider.js';
|
|
32
|
-
|
|
33
|
-
export function useWorkflows() {
|
|
34
|
-
const client = useCriticalPathClient();
|
|
35
|
-
const [workflows, setWorkflows] = useState<Workflow[]>([]);
|
|
36
|
-
const [loading, setLoading] = useState(true);
|
|
37
|
-
const [error, setError] = useState<Error | null>(null);
|
|
38
|
-
|
|
39
|
-
const fetchWorkflows = useCallback(async () => {
|
|
40
|
-
try {
|
|
41
|
-
setLoading(true);
|
|
42
|
-
const data = await client.getWorkflows();
|
|
43
|
-
setWorkflows(data);
|
|
44
|
-
setError(null);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
47
|
-
} finally {
|
|
48
|
-
setLoading(false);
|
|
49
|
-
}
|
|
50
|
-
}, [client]);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
fetchWorkflows();
|
|
54
|
-
}, [fetchWorkflows]);
|
|
55
|
-
|
|
56
|
-
const createWorkflow = async (input: Omit<Workflow, 'id' | 'createdAt' | 'updatedAt'>) => {
|
|
57
|
-
try {
|
|
58
|
-
const created = await client.createWorkflow(input);
|
|
59
|
-
setWorkflows((prev) => [...prev, created]);
|
|
60
|
-
return created;
|
|
61
|
-
} catch (err) {
|
|
62
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
63
|
-
setError(errorObj);
|
|
64
|
-
throw errorObj;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const updateWorkflow = async (id: string, updates: Partial<Workflow>) => {
|
|
69
|
-
try {
|
|
70
|
-
const updated = await client.updateWorkflow(id, updates);
|
|
71
|
-
setWorkflows((prev) => prev.map((w) => (w.id === id ? updated : w)));
|
|
72
|
-
return updated;
|
|
73
|
-
} catch (err) {
|
|
74
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
75
|
-
setError(errorObj);
|
|
76
|
-
throw errorObj;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const deleteWorkflow = async (id: string) => {
|
|
81
|
-
try {
|
|
82
|
-
await client.deleteWorkflow(id);
|
|
83
|
-
setWorkflows((prev) => prev.filter((w) => w.id !== id));
|
|
84
|
-
} catch (err) {
|
|
85
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
86
|
-
setError(errorObj);
|
|
87
|
-
throw errorObj;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return { workflows, loading, error, refresh: fetchWorkflows, createWorkflow, updateWorkflow, deleteWorkflow };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function useTaskTransitions(taskId?: string) {
|
|
95
|
-
const client = useCriticalPathClient();
|
|
96
|
-
const [allowedTransitions, setAllowedTransitions] = useState<string[]>([]);
|
|
97
|
-
const [loading, setLoading] = useState(false);
|
|
98
|
-
const [error, setError] = useState<Error | null>(null);
|
|
99
|
-
|
|
100
|
-
const fetchTransitions = useCallback(async () => {
|
|
101
|
-
if (!taskId) {
|
|
102
|
-
setAllowedTransitions([]);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
setLoading(true);
|
|
107
|
-
const data = await client.getAllowedTaskTransitions(taskId);
|
|
108
|
-
setAllowedTransitions(data);
|
|
109
|
-
setError(null);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
-
} finally {
|
|
113
|
-
setLoading(false);
|
|
114
|
-
}
|
|
115
|
-
}, [client, taskId]);
|
|
116
|
-
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
fetchTransitions();
|
|
119
|
-
}, [fetchTransitions]);
|
|
120
|
-
|
|
121
|
-
return { allowedTransitions, loading, error, refresh: fetchTransitions };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function useProjects() {
|
|
125
|
-
const client = useCriticalPathClient();
|
|
126
|
-
const [projects, setProjects] = useState<Project[]>([]);
|
|
127
|
-
const [loading, setLoading] = useState(true);
|
|
128
|
-
const [error, setError] = useState<Error | null>(null);
|
|
129
|
-
|
|
130
|
-
const fetchProjects = useCallback(async () => {
|
|
131
|
-
try {
|
|
132
|
-
setLoading(true);
|
|
133
|
-
const data = await client.getProjects();
|
|
134
|
-
setProjects(data);
|
|
135
|
-
setError(null);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
138
|
-
} finally {
|
|
139
|
-
setLoading(false);
|
|
140
|
-
}
|
|
141
|
-
}, [client]);
|
|
142
|
-
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
fetchProjects();
|
|
145
|
-
}, [fetchProjects]);
|
|
146
|
-
|
|
147
|
-
const createProject = async (input: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>) => {
|
|
148
|
-
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
149
|
-
const now = new Date().toISOString();
|
|
150
|
-
const tempProject: Project = {
|
|
151
|
-
...input,
|
|
152
|
-
id: tempId,
|
|
153
|
-
createdAt: now,
|
|
154
|
-
updatedAt: now
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
setProjects((prev) => [...prev, tempProject]);
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const created = await client.createProject(input);
|
|
161
|
-
setProjects((prev) => prev.map((p) => (p.id === tempId ? created : p)));
|
|
162
|
-
return created;
|
|
163
|
-
} catch (err) {
|
|
164
|
-
setProjects((prev) => prev.filter((p) => p.id !== tempId));
|
|
165
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
166
|
-
setError(errorObj);
|
|
167
|
-
throw errorObj;
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
return { projects, loading, error, refresh: fetchProjects, createProject };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function useTasks(projectId?: string) {
|
|
175
|
-
const client = useCriticalPathClient();
|
|
176
|
-
const [tasks, setTasks] = useState<Task[]>([]);
|
|
177
|
-
const [loading, setLoading] = useState(true);
|
|
178
|
-
const [error, setError] = useState<Error | null>(null);
|
|
179
|
-
|
|
180
|
-
const fetchTasks = useCallback(async () => {
|
|
181
|
-
try {
|
|
182
|
-
setLoading(true);
|
|
183
|
-
const data = await client.getTasks(projectId);
|
|
184
|
-
setTasks(data);
|
|
185
|
-
setError(null);
|
|
186
|
-
} catch (err) {
|
|
187
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
188
|
-
} finally {
|
|
189
|
-
setLoading(false);
|
|
190
|
-
}
|
|
191
|
-
}, [client, projectId]);
|
|
192
|
-
|
|
193
|
-
const pendingCreationsRef = useRef<Map<string, Promise<Task>>>(new Map());
|
|
194
|
-
const tempToRealIdMapRef = useRef<Map<string, string>>(new Map());
|
|
195
|
-
|
|
196
|
-
useEffect(() => {
|
|
197
|
-
fetchTasks();
|
|
198
|
-
}, [fetchTasks]);
|
|
199
|
-
|
|
200
|
-
const createTask = async (input: CreateTaskBody) => {
|
|
201
|
-
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
202
|
-
const now = new Date().toISOString();
|
|
203
|
-
const tempTask: Task = {
|
|
204
|
-
...input,
|
|
205
|
-
// Placeholders until the server responds with the workflow's real defaults
|
|
206
|
-
status: input.status ?? 'todo',
|
|
207
|
-
priority: input.priority ?? 'medium',
|
|
208
|
-
id: tempId,
|
|
209
|
-
createdAt: now,
|
|
210
|
-
updatedAt: now
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
setTasks((prev) => [...prev, tempTask]);
|
|
214
|
-
|
|
215
|
-
const promise = client.createTask(input);
|
|
216
|
-
pendingCreationsRef.current.set(tempId, promise);
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const created = await promise;
|
|
220
|
-
tempToRealIdMapRef.current.set(tempId, created.id);
|
|
221
|
-
setTasks((prev) => prev.map((t) => (t.id === tempId ? created : t)));
|
|
222
|
-
return created;
|
|
223
|
-
} catch (err) {
|
|
224
|
-
setTasks((prev) => prev.filter((t) => t.id !== tempId));
|
|
225
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
226
|
-
setError(errorObj);
|
|
227
|
-
throw errorObj;
|
|
228
|
-
} finally {
|
|
229
|
-
pendingCreationsRef.current.delete(tempId);
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const updateTask = async (taskId: string, updates: UpdateTaskBody) => {
|
|
234
|
-
let targetId = taskId;
|
|
235
|
-
if (tempToRealIdMapRef.current.has(taskId)) {
|
|
236
|
-
targetId = tempToRealIdMapRef.current.get(taskId)!;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
let previousTask: Task | undefined;
|
|
240
|
-
|
|
241
|
-
// 1. Immediately apply optimistic updates to local tasks
|
|
242
|
-
setTasks((prev) => {
|
|
243
|
-
previousTask = prev.find((t) => t.id === targetId || t.id === taskId);
|
|
244
|
-
if (!previousTask) return prev;
|
|
245
|
-
return prev.map((t) =>
|
|
246
|
-
t.id === targetId || t.id === taskId
|
|
247
|
-
? { ...t, ...updates, updatedAt: new Date().toISOString() }
|
|
248
|
-
: t
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// 2. If it's a temporary task, await pending creation if in flight
|
|
253
|
-
if (isTempTaskId(targetId)) {
|
|
254
|
-
const pendingPromise =
|
|
255
|
-
pendingCreationsRef.current.get(targetId) ||
|
|
256
|
-
(pendingCreationsRef.current.size === 1
|
|
257
|
-
? Array.from(pendingCreationsRef.current.values())[0]
|
|
258
|
-
: undefined);
|
|
259
|
-
|
|
260
|
-
if (pendingPromise) {
|
|
261
|
-
try {
|
|
262
|
-
const created = await pendingPromise;
|
|
263
|
-
if (created && created.id) {
|
|
264
|
-
targetId = created.id;
|
|
265
|
-
tempToRealIdMapRef.current.set(taskId, targetId);
|
|
266
|
-
}
|
|
267
|
-
} catch {
|
|
268
|
-
throw new Error(`Failed to update task: task creation failed for "${taskId}"`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// If still a temp ID, the task exists purely in local state; avoid sending unresolvable ID to backend
|
|
274
|
-
if (isTempTaskId(targetId)) {
|
|
275
|
-
let resolvedTask: Task | undefined;
|
|
276
|
-
setTasks((prev) => {
|
|
277
|
-
resolvedTask = prev.find((t) => t.id === targetId || t.id === taskId);
|
|
278
|
-
return prev;
|
|
279
|
-
});
|
|
280
|
-
return (resolvedTask || { id: targetId, ...updates }) as Task;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
const updated = await client.updateTask(targetId, updates);
|
|
285
|
-
setTasks((prev) =>
|
|
286
|
-
prev.map((t) => (t.id === targetId || t.id === taskId ? updated : t))
|
|
287
|
-
);
|
|
288
|
-
return updated;
|
|
289
|
-
} catch (err) {
|
|
290
|
-
if (previousTask) {
|
|
291
|
-
const revertTask = previousTask;
|
|
292
|
-
setTasks((prev) =>
|
|
293
|
-
prev.map((t) => (t.id === targetId || t.id === taskId ? revertTask : t))
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
297
|
-
setError(errorObj);
|
|
298
|
-
throw errorObj;
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
const updateTaskStatus = async (taskId: string, status: TaskStatus) => {
|
|
303
|
-
return updateTask(taskId, { status });
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const deleteTask = async (taskId: string) => {
|
|
307
|
-
let targetId = taskId;
|
|
308
|
-
if (tempToRealIdMapRef.current.has(taskId)) {
|
|
309
|
-
targetId = tempToRealIdMapRef.current.get(taskId)!;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
let previousTask: Task | undefined;
|
|
313
|
-
let previousIndex = -1;
|
|
314
|
-
|
|
315
|
-
setTasks((prev) => {
|
|
316
|
-
previousIndex = prev.findIndex((t) => t.id === targetId || t.id === taskId);
|
|
317
|
-
if (previousIndex !== -1) {
|
|
318
|
-
previousTask = prev[previousIndex];
|
|
319
|
-
}
|
|
320
|
-
return prev.filter((t) => t.id !== targetId && t.id !== taskId);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
if (isTempTaskId(targetId)) {
|
|
324
|
-
const pendingPromise = pendingCreationsRef.current.get(targetId);
|
|
325
|
-
if (pendingPromise) {
|
|
326
|
-
try {
|
|
327
|
-
const created = await pendingPromise;
|
|
328
|
-
if (created && created.id) {
|
|
329
|
-
await client.deleteTask(created.id);
|
|
330
|
-
}
|
|
331
|
-
} catch {
|
|
332
|
-
// If creation failed anyway, task is already gone
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
await client.deleteTask(targetId);
|
|
340
|
-
} catch (err) {
|
|
341
|
-
if (previousTask && previousIndex !== -1) {
|
|
342
|
-
const restoreTask = previousTask;
|
|
343
|
-
const indexToInsert = previousIndex;
|
|
344
|
-
setTasks((prev) => {
|
|
345
|
-
const restored = [...prev];
|
|
346
|
-
restored.splice(indexToInsert, 0, restoreTask);
|
|
347
|
-
return restored;
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
351
|
-
setError(errorObj);
|
|
352
|
-
throw errorObj;
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
return { tasks, loading, error, refresh: fetchTasks, createTask, updateTask, updateTaskStatus, deleteTask };
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
export interface UseKanbanOptions {
|
|
360
|
-
groupBy?: 'workflow' | 'semantic';
|
|
361
|
-
customDefinitions?: StatusDefinition[];
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export function useKanban(projectId?: string, options?: UseKanbanOptions) {
|
|
365
|
-
const { tasks, loading, error, refresh, updateTaskStatus, createTask } = useTasks(projectId);
|
|
366
|
-
|
|
367
|
-
const columns = useMemo<Record<string, Task[]>>(() => {
|
|
368
|
-
if (options?.groupBy === 'semantic') {
|
|
369
|
-
const semanticCols: Record<SemanticStatus, Task[]> = {
|
|
370
|
-
not_started: [],
|
|
371
|
-
in_progress: [],
|
|
372
|
-
completed: [],
|
|
373
|
-
canceled: []
|
|
374
|
-
};
|
|
375
|
-
for (const task of tasks) {
|
|
376
|
-
const category = task.semanticStatus || resolveStatusDefinition(task.status, options?.customDefinitions).category;
|
|
377
|
-
if (semanticCols[category]) {
|
|
378
|
-
semanticCols[category].push(task);
|
|
379
|
-
} else {
|
|
380
|
-
semanticCols.not_started.push(task);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return semanticCols;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const workflowCols: Record<string, Task[]> = {};
|
|
387
|
-
if (options?.customDefinitions && options.customDefinitions.length > 0) {
|
|
388
|
-
for (const def of options.customDefinitions) {
|
|
389
|
-
workflowCols[def.key] = [];
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
workflowCols.backlog = [];
|
|
393
|
-
workflowCols.todo = [];
|
|
394
|
-
workflowCols.in_progress = [];
|
|
395
|
-
workflowCols.in_review = [];
|
|
396
|
-
workflowCols.done = [];
|
|
397
|
-
workflowCols.canceled = [];
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
for (const task of tasks) {
|
|
401
|
-
if (!workflowCols[task.status]) {
|
|
402
|
-
workflowCols[task.status] = [];
|
|
403
|
-
}
|
|
404
|
-
workflowCols[task.status].push(task);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return workflowCols;
|
|
408
|
-
}, [tasks, options?.groupBy, options?.customDefinitions]);
|
|
409
|
-
|
|
410
|
-
const moveTask = async (taskId: string, targetStatus: TaskStatus) => {
|
|
411
|
-
return updateTaskStatus(taskId, targetStatus);
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
return { columns, tasks, loading, error, refresh, moveTask, createTask };
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export interface ThreadedComment extends Comment {
|
|
418
|
-
replies: ThreadedComment[];
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
export function useComments(taskId: string) {
|
|
422
|
-
const client = useCriticalPathClient();
|
|
423
|
-
const [comments, setComments] = useState<Comment[]>([]);
|
|
424
|
-
const [loading, setLoading] = useState(true);
|
|
425
|
-
const [error, setError] = useState<Error | null>(null);
|
|
426
|
-
|
|
427
|
-
const fetchComments = useCallback(async () => {
|
|
428
|
-
if (!taskId) {
|
|
429
|
-
setComments([]);
|
|
430
|
-
setLoading(false);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
try {
|
|
434
|
-
setLoading(true);
|
|
435
|
-
const data = await client.getComments(taskId);
|
|
436
|
-
setComments(data);
|
|
437
|
-
setError(null);
|
|
438
|
-
} catch (err) {
|
|
439
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
440
|
-
} finally {
|
|
441
|
-
setLoading(false);
|
|
442
|
-
}
|
|
443
|
-
}, [client, taskId]);
|
|
444
|
-
|
|
445
|
-
useEffect(() => {
|
|
446
|
-
fetchComments();
|
|
447
|
-
}, [fetchComments]);
|
|
448
|
-
|
|
449
|
-
const addComment = async (input: Omit<CreateCommentBody, 'taskId'>) => {
|
|
450
|
-
try {
|
|
451
|
-
const created = await client.addComment({ ...input, taskId });
|
|
452
|
-
setComments((prev) => [...prev, created]);
|
|
453
|
-
return created;
|
|
454
|
-
} catch (err) {
|
|
455
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
456
|
-
setError(errorObj);
|
|
457
|
-
throw errorObj;
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
const updateComment = async (id: string, updates: UpdateCommentBody) => {
|
|
462
|
-
try {
|
|
463
|
-
const updated = await client.updateComment(id, updates);
|
|
464
|
-
setComments((prev) => prev.map((c) => (c.id === id ? updated : c)));
|
|
465
|
-
return updated;
|
|
466
|
-
} catch (err) {
|
|
467
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
468
|
-
setError(errorObj);
|
|
469
|
-
throw errorObj;
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
const deleteComment = async (id: string) => {
|
|
474
|
-
try {
|
|
475
|
-
await client.deleteComment(id);
|
|
476
|
-
setComments((prev) => prev.filter((c) => c.id !== id));
|
|
477
|
-
} catch (err) {
|
|
478
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
479
|
-
setError(errorObj);
|
|
480
|
-
throw errorObj;
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
const addReaction = async (
|
|
485
|
-
commentId: string,
|
|
486
|
-
emoji: string
|
|
487
|
-
) => {
|
|
488
|
-
try {
|
|
489
|
-
const payload = { emoji };
|
|
490
|
-
const updated = await client.addCommentReaction(commentId, payload);
|
|
491
|
-
setComments((prev) => prev.map((c) => (c.id === commentId ? updated : c)));
|
|
492
|
-
return updated;
|
|
493
|
-
} catch (err) {
|
|
494
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
495
|
-
setError(errorObj);
|
|
496
|
-
throw errorObj;
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const removeReaction = async (
|
|
501
|
-
commentId: string,
|
|
502
|
-
emoji: string
|
|
503
|
-
) => {
|
|
504
|
-
try {
|
|
505
|
-
const payload = { emoji };
|
|
506
|
-
const updated = await client.removeCommentReaction(commentId, payload);
|
|
507
|
-
setComments((prev) => prev.map((c) => (c.id === commentId ? updated : c)));
|
|
508
|
-
return updated;
|
|
509
|
-
} catch (err) {
|
|
510
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
511
|
-
setError(errorObj);
|
|
512
|
-
throw errorObj;
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
// Build tree of threaded comments
|
|
517
|
-
const threads = useMemo(() => {
|
|
518
|
-
const map = new Map<string, ThreadedComment>();
|
|
519
|
-
const roots: ThreadedComment[] = [];
|
|
520
|
-
|
|
521
|
-
// First pass: wrap comments
|
|
522
|
-
for (const c of comments) {
|
|
523
|
-
map.set(c.id, { ...c, replies: [] });
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Second pass: link replies to parents
|
|
527
|
-
for (const c of comments) {
|
|
528
|
-
const threaded = map.get(c.id)!;
|
|
529
|
-
if (c.parentId && map.has(c.parentId)) {
|
|
530
|
-
map.get(c.parentId)!.replies.push(threaded);
|
|
531
|
-
} else {
|
|
532
|
-
roots.push(threaded);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return roots;
|
|
537
|
-
}, [comments]);
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
comments,
|
|
541
|
-
threads,
|
|
542
|
-
loading,
|
|
543
|
-
error,
|
|
544
|
-
refresh: fetchComments,
|
|
545
|
-
addComment,
|
|
546
|
-
updateComment,
|
|
547
|
-
deleteComment,
|
|
548
|
-
addReaction,
|
|
549
|
-
removeReaction
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
export function useAttachments(filter?: { taskId?: string; projectId?: string; commentId?: string }) {
|
|
554
|
-
const client = useCriticalPathClient();
|
|
555
|
-
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
|
556
|
-
const [loading, setLoading] = useState(true);
|
|
557
|
-
const [error, setError] = useState<Error | null>(null);
|
|
558
|
-
|
|
559
|
-
const filterKey = JSON.stringify(filter || {});
|
|
560
|
-
|
|
561
|
-
const fetchAttachments = useCallback(async () => {
|
|
562
|
-
try {
|
|
563
|
-
setLoading(true);
|
|
564
|
-
const data = await client.getAttachments(filter);
|
|
565
|
-
setAttachments(data);
|
|
566
|
-
setError(null);
|
|
567
|
-
} catch (err) {
|
|
568
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
569
|
-
} finally {
|
|
570
|
-
setLoading(false);
|
|
571
|
-
}
|
|
572
|
-
}, [client, filterKey]);
|
|
573
|
-
|
|
574
|
-
useEffect(() => {
|
|
575
|
-
fetchAttachments();
|
|
576
|
-
}, [fetchAttachments]);
|
|
577
|
-
|
|
578
|
-
const createAttachment = async (input: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>) => {
|
|
579
|
-
try {
|
|
580
|
-
const created = await client.createAttachment(input);
|
|
581
|
-
setAttachments((prev) => [created, ...prev]);
|
|
582
|
-
return created;
|
|
583
|
-
} catch (err) {
|
|
584
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
585
|
-
setError(errorObj);
|
|
586
|
-
throw errorObj;
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
const deleteAttachment = async (id: string) => {
|
|
591
|
-
try {
|
|
592
|
-
await client.deleteAttachment(id);
|
|
593
|
-
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
594
|
-
} catch (err) {
|
|
595
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
596
|
-
setError(errorObj);
|
|
597
|
-
throw errorObj;
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
return {
|
|
602
|
-
attachments,
|
|
603
|
-
loading,
|
|
604
|
-
error,
|
|
605
|
-
refresh: fetchAttachments,
|
|
606
|
-
createAttachment,
|
|
607
|
-
deleteAttachment
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
export function useDeliverables(projectId?: string) {
|
|
612
|
-
const client = useCriticalPathClient();
|
|
613
|
-
const [deliverables, setDeliverables] = useState<Deliverable[]>([]);
|
|
614
|
-
const [loading, setLoading] = useState(true);
|
|
615
|
-
const [error, setError] = useState<Error | null>(null);
|
|
616
|
-
|
|
617
|
-
const fetchDeliverables = useCallback(async () => {
|
|
618
|
-
if (!projectId) {
|
|
619
|
-
setDeliverables([]);
|
|
620
|
-
setLoading(false);
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
try {
|
|
624
|
-
setLoading(true);
|
|
625
|
-
const data = await client.getDeliverables(projectId);
|
|
626
|
-
setDeliverables(data);
|
|
627
|
-
setError(null);
|
|
628
|
-
} catch (err) {
|
|
629
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
630
|
-
} finally {
|
|
631
|
-
setLoading(false);
|
|
632
|
-
}
|
|
633
|
-
}, [client, projectId]);
|
|
634
|
-
|
|
635
|
-
useEffect(() => {
|
|
636
|
-
fetchDeliverables();
|
|
637
|
-
}, [fetchDeliverables]);
|
|
638
|
-
|
|
639
|
-
const createDeliverable = async (input: CreateDeliverableInput) => {
|
|
640
|
-
try {
|
|
641
|
-
const created = await client.createDeliverable(input);
|
|
642
|
-
setDeliverables((prev) => [...prev, created]);
|
|
643
|
-
return created;
|
|
644
|
-
} catch (err) {
|
|
645
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
646
|
-
setError(errorObj);
|
|
647
|
-
throw errorObj;
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
const updateDeliverable = async (id: string, updates: UpdateDeliverableBody) => {
|
|
652
|
-
try {
|
|
653
|
-
const updated = await client.updateDeliverable(id, updates);
|
|
654
|
-
setDeliverables((prev) => prev.map((d) => (d.id === id ? updated : d)));
|
|
655
|
-
return updated;
|
|
656
|
-
} catch (err) {
|
|
657
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
658
|
-
setError(errorObj);
|
|
659
|
-
throw errorObj;
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
const deleteDeliverable = async (id: string) => {
|
|
664
|
-
try {
|
|
665
|
-
await client.deleteDeliverable(id);
|
|
666
|
-
setDeliverables((prev) => prev.filter((d) => d.id !== id));
|
|
667
|
-
} catch (err) {
|
|
668
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
669
|
-
setError(errorObj);
|
|
670
|
-
throw errorObj;
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
return {
|
|
675
|
-
deliverables,
|
|
676
|
-
loading,
|
|
677
|
-
error,
|
|
678
|
-
refresh: fetchDeliverables,
|
|
679
|
-
createDeliverable,
|
|
680
|
-
updateDeliverable,
|
|
681
|
-
deleteDeliverable
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
export function useDeliverableSummary(deliverableId?: string) {
|
|
686
|
-
const client = useCriticalPathClient();
|
|
687
|
-
const [summary, setSummary] = useState<DeliverableSummary | null>(null);
|
|
688
|
-
const [loading, setLoading] = useState(true);
|
|
689
|
-
const [error, setError] = useState<Error | null>(null);
|
|
690
|
-
|
|
691
|
-
const fetchSummary = useCallback(async () => {
|
|
692
|
-
if (!deliverableId) {
|
|
693
|
-
setSummary(null);
|
|
694
|
-
setLoading(false);
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
try {
|
|
698
|
-
setLoading(true);
|
|
699
|
-
const data = await client.getDeliverableSummary(deliverableId);
|
|
700
|
-
setSummary(data);
|
|
701
|
-
setError(null);
|
|
702
|
-
} catch (err) {
|
|
703
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
704
|
-
} finally {
|
|
705
|
-
setLoading(false);
|
|
706
|
-
}
|
|
707
|
-
}, [client, deliverableId]);
|
|
708
|
-
|
|
709
|
-
useEffect(() => {
|
|
710
|
-
fetchSummary();
|
|
711
|
-
}, [fetchSummary]);
|
|
712
|
-
|
|
713
|
-
return {
|
|
714
|
-
summary,
|
|
715
|
-
loading,
|
|
716
|
-
error,
|
|
717
|
-
refresh: fetchSummary
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
export interface UseWebMCPOptions {
|
|
722
|
-
projectId?: string;
|
|
723
|
-
tools?: string[];
|
|
724
|
-
enabled?: boolean;
|
|
725
|
-
onToolExecuted?: (toolName: string, input: any, result: any) => void;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
export function useWebMCP(options: UseWebMCPOptions = {}) {
|
|
729
|
-
const client = useCriticalPathClient();
|
|
730
|
-
const { projectId, tools, enabled = true, onToolExecuted } = options;
|
|
731
|
-
const [registered, setRegistered] = useState(false);
|
|
732
|
-
const [registeredTools, setRegisteredTools] = useState<string[]>([]);
|
|
733
|
-
const [error, setError] = useState<Error | null>(null);
|
|
734
|
-
|
|
735
|
-
useEffect(() => {
|
|
736
|
-
if (!enabled) {
|
|
737
|
-
setRegistered(false);
|
|
738
|
-
setRegisteredTools([]);
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
try {
|
|
743
|
-
const handle = registerWebMcpTools({
|
|
744
|
-
client,
|
|
745
|
-
projectId,
|
|
746
|
-
tools,
|
|
747
|
-
onToolExecuted
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
setRegistered(true);
|
|
751
|
-
setRegisteredTools(handle.getRegisteredTools().map((t) => t.name));
|
|
752
|
-
setError(null);
|
|
753
|
-
|
|
754
|
-
return () => {
|
|
755
|
-
handle.unregister();
|
|
756
|
-
setRegistered(false);
|
|
757
|
-
setRegisteredTools([]);
|
|
758
|
-
};
|
|
759
|
-
} catch (err) {
|
|
760
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
761
|
-
setRegistered(false);
|
|
762
|
-
}
|
|
763
|
-
}, [client, projectId, enabled, tools, onToolExecuted]);
|
|
764
|
-
|
|
765
|
-
return {
|
|
766
|
-
registered,
|
|
767
|
-
tools: registeredTools,
|
|
768
|
-
error
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
export interface ThreadedCommentWithAttachments extends Comment {
|
|
773
|
-
attachments: Attachment[];
|
|
774
|
-
replies: ThreadedCommentWithAttachments[];
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
export function useTaskActivity(taskId?: string) {
|
|
778
|
-
const client = useCriticalPathClient();
|
|
779
|
-
const [comments, setComments] = useState<Comment[]>([]);
|
|
780
|
-
const [attachments, setAttachments] = useState<Attachment[]>([]);
|
|
781
|
-
const [loading, setLoading] = useState(false);
|
|
782
|
-
const [error, setError] = useState<Error | null>(null);
|
|
783
|
-
|
|
784
|
-
const fetchActivity = useCallback(async () => {
|
|
785
|
-
if (!taskId) {
|
|
786
|
-
setComments([]);
|
|
787
|
-
setAttachments([]);
|
|
788
|
-
setLoading(false);
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
try {
|
|
792
|
-
setLoading(true);
|
|
793
|
-
const [fetchedComments, fetchedAttachments] = await Promise.all([
|
|
794
|
-
client.getComments(taskId),
|
|
795
|
-
client.getAttachments({ taskId })
|
|
796
|
-
]);
|
|
797
|
-
setComments(fetchedComments);
|
|
798
|
-
setAttachments(fetchedAttachments);
|
|
799
|
-
setError(null);
|
|
800
|
-
} catch (err) {
|
|
801
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
802
|
-
} finally {
|
|
803
|
-
setLoading(false);
|
|
804
|
-
}
|
|
805
|
-
}, [client, taskId]);
|
|
806
|
-
|
|
807
|
-
useEffect(() => {
|
|
808
|
-
fetchActivity();
|
|
809
|
-
}, [fetchActivity]);
|
|
810
|
-
|
|
811
|
-
const threads = useMemo<ThreadedCommentWithAttachments[]>(() => {
|
|
812
|
-
const map = new Map<string, ThreadedCommentWithAttachments>();
|
|
813
|
-
const roots: ThreadedCommentWithAttachments[] = [];
|
|
814
|
-
|
|
815
|
-
const attachmentsByComment = new Map<string, Attachment[]>();
|
|
816
|
-
for (const a of attachments) {
|
|
817
|
-
if (a.commentId) {
|
|
818
|
-
if (!attachmentsByComment.has(a.commentId)) {
|
|
819
|
-
attachmentsByComment.set(a.commentId, []);
|
|
820
|
-
}
|
|
821
|
-
attachmentsByComment.get(a.commentId)!.push(a);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
for (const c of comments) {
|
|
826
|
-
map.set(c.id, {
|
|
827
|
-
...c,
|
|
828
|
-
attachments: attachmentsByComment.get(c.id) || [],
|
|
829
|
-
replies: []
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
for (const c of comments) {
|
|
834
|
-
const threaded = map.get(c.id)!;
|
|
835
|
-
if (c.parentId && map.has(c.parentId)) {
|
|
836
|
-
map.get(c.parentId)!.replies.push(threaded);
|
|
837
|
-
} else {
|
|
838
|
-
roots.push(threaded);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return roots;
|
|
843
|
-
}, [comments, attachments]);
|
|
844
|
-
|
|
845
|
-
const standaloneAttachments = useMemo<Attachment[]>(() => {
|
|
846
|
-
return attachments.filter((a) => !a.commentId);
|
|
847
|
-
}, [attachments]);
|
|
848
|
-
|
|
849
|
-
const addComment = async (
|
|
850
|
-
input: Omit<CreateCommentBody, 'taskId'>,
|
|
851
|
-
attachmentInputs?: Array<Omit<CreateAttachmentBody, 'taskId' | 'commentId'>>
|
|
852
|
-
) => {
|
|
853
|
-
if (!taskId) {
|
|
854
|
-
throw new Error('useTaskActivity requires a taskId to add comments.');
|
|
855
|
-
}
|
|
856
|
-
try {
|
|
857
|
-
const comment = await client.addComment({ ...input, taskId });
|
|
858
|
-
let createdAttachments: Attachment[] = [];
|
|
859
|
-
|
|
860
|
-
if (attachmentInputs && attachmentInputs.length > 0) {
|
|
861
|
-
createdAttachments = await Promise.all(
|
|
862
|
-
attachmentInputs.map((att) =>
|
|
863
|
-
client.createAttachment({
|
|
864
|
-
...att,
|
|
865
|
-
taskId,
|
|
866
|
-
commentId: comment.id
|
|
867
|
-
})
|
|
868
|
-
)
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
setComments((prev) => [...prev, comment]);
|
|
873
|
-
if (createdAttachments.length > 0) {
|
|
874
|
-
setAttachments((prev) => [...prev, ...createdAttachments]);
|
|
875
|
-
}
|
|
876
|
-
return { comment, attachments: createdAttachments };
|
|
877
|
-
} catch (err) {
|
|
878
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
879
|
-
setError(errorObj);
|
|
880
|
-
throw errorObj;
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
|
|
884
|
-
const addAttachment = async (input: Omit<Attachment, 'id' | 'taskId' | 'createdAt' | 'updatedAt'>) => {
|
|
885
|
-
if (!taskId) {
|
|
886
|
-
throw new Error('useTaskActivity requires a taskId to add attachments.');
|
|
887
|
-
}
|
|
888
|
-
try {
|
|
889
|
-
const created = await client.createAttachment({ ...input, taskId });
|
|
890
|
-
setAttachments((prev) => [created, ...prev]);
|
|
891
|
-
return created;
|
|
892
|
-
} catch (err) {
|
|
893
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
894
|
-
setError(errorObj);
|
|
895
|
-
throw errorObj;
|
|
896
|
-
}
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
const deleteComment = async (id: string) => {
|
|
900
|
-
let previousComments: Comment[] = [];
|
|
901
|
-
setComments((prev) => {
|
|
902
|
-
previousComments = prev;
|
|
903
|
-
return prev.filter((c) => c.id !== id);
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
try {
|
|
907
|
-
await client.deleteComment(id);
|
|
908
|
-
} catch (err) {
|
|
909
|
-
setComments(previousComments);
|
|
910
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
911
|
-
setError(errorObj);
|
|
912
|
-
throw errorObj;
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
const deleteAttachment = async (id: string) => {
|
|
917
|
-
let previousAttachments: Attachment[] = [];
|
|
918
|
-
setAttachments((prev) => {
|
|
919
|
-
previousAttachments = prev;
|
|
920
|
-
return prev.filter((a) => a.id !== id);
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
try {
|
|
924
|
-
await client.deleteAttachment(id);
|
|
925
|
-
} catch (err) {
|
|
926
|
-
setAttachments(previousAttachments);
|
|
927
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
928
|
-
setError(errorObj);
|
|
929
|
-
throw errorObj;
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
const addReaction = async (
|
|
934
|
-
commentId: string,
|
|
935
|
-
emoji: string
|
|
936
|
-
) => {
|
|
937
|
-
try {
|
|
938
|
-
const payload = { emoji };
|
|
939
|
-
const updated = await client.addCommentReaction(commentId, payload);
|
|
940
|
-
setComments((prev) => prev.map((c) => (c.id === commentId ? updated : c)));
|
|
941
|
-
return updated;
|
|
942
|
-
} catch (err) {
|
|
943
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
944
|
-
setError(errorObj);
|
|
945
|
-
throw errorObj;
|
|
946
|
-
}
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
const removeReaction = async (
|
|
950
|
-
commentId: string,
|
|
951
|
-
emoji: string
|
|
952
|
-
) => {
|
|
953
|
-
try {
|
|
954
|
-
const payload = { emoji };
|
|
955
|
-
const updated = await client.removeCommentReaction(commentId, payload);
|
|
956
|
-
setComments((prev) => prev.map((c) => (c.id === commentId ? updated : c)));
|
|
957
|
-
return updated;
|
|
958
|
-
} catch (err) {
|
|
959
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
960
|
-
setError(errorObj);
|
|
961
|
-
throw errorObj;
|
|
962
|
-
}
|
|
963
|
-
};
|
|
964
|
-
|
|
965
|
-
return {
|
|
966
|
-
comments,
|
|
967
|
-
attachments,
|
|
968
|
-
threads,
|
|
969
|
-
standaloneAttachments,
|
|
970
|
-
loading,
|
|
971
|
-
error,
|
|
972
|
-
refresh: fetchActivity,
|
|
973
|
-
addComment,
|
|
974
|
-
addAttachment,
|
|
975
|
-
deleteComment,
|
|
976
|
-
deleteAttachment,
|
|
977
|
-
addReaction,
|
|
978
|
-
removeReaction
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
export function useCriticalPath(projectId: string | undefined) {
|
|
983
|
-
const client = useCriticalPathClient();
|
|
984
|
-
const [analysis, setAnalysis] = useState<CriticalPathAnalysis | null>(null);
|
|
985
|
-
const [loading, setLoading] = useState(true);
|
|
986
|
-
const [error, setError] = useState<Error | null>(null);
|
|
987
|
-
|
|
988
|
-
const fetchCriticalPath = useCallback(async () => {
|
|
989
|
-
if (!projectId) {
|
|
990
|
-
setAnalysis(null);
|
|
991
|
-
setLoading(false);
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
try {
|
|
995
|
-
setLoading(true);
|
|
996
|
-
const data = await client.calculateCriticalPath(projectId);
|
|
997
|
-
setAnalysis(data);
|
|
998
|
-
setError(null);
|
|
999
|
-
} catch (err) {
|
|
1000
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1001
|
-
} finally {
|
|
1002
|
-
setLoading(false);
|
|
1003
|
-
}
|
|
1004
|
-
}, [client, projectId]);
|
|
1005
|
-
|
|
1006
|
-
useEffect(() => {
|
|
1007
|
-
fetchCriticalPath();
|
|
1008
|
-
}, [fetchCriticalPath]);
|
|
1009
|
-
|
|
1010
|
-
return {
|
|
1011
|
-
analysis,
|
|
1012
|
-
loading,
|
|
1013
|
-
error,
|
|
1014
|
-
refresh: fetchCriticalPath
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
export function useTimelineLadder(
|
|
1019
|
-
projectId: string | undefined,
|
|
1020
|
-
initialOptions: TimelineLadderOptions = {}
|
|
1021
|
-
) {
|
|
1022
|
-
const client = useCriticalPathClient();
|
|
1023
|
-
const [level, setLevel] = useState<AbstractionLevel>(initialOptions.level || 'all');
|
|
1024
|
-
const [ladder, setLadder] = useState<TimelineLadder | null>(null);
|
|
1025
|
-
const [loading, setLoading] = useState(true);
|
|
1026
|
-
const [error, setError] = useState<Error | null>(null);
|
|
1027
|
-
|
|
1028
|
-
const fetchLadder = useCallback(async () => {
|
|
1029
|
-
if (!projectId) {
|
|
1030
|
-
setLadder(null);
|
|
1031
|
-
setLoading(false);
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
try {
|
|
1035
|
-
setLoading(true);
|
|
1036
|
-
const data = await client.getTimelineLadder(projectId, {
|
|
1037
|
-
...initialOptions,
|
|
1038
|
-
level
|
|
1039
|
-
});
|
|
1040
|
-
setLadder(data);
|
|
1041
|
-
setError(null);
|
|
1042
|
-
} catch (err) {
|
|
1043
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1044
|
-
} finally {
|
|
1045
|
-
setLoading(false);
|
|
1046
|
-
}
|
|
1047
|
-
}, [client, projectId, level, initialOptions.containerId, initialOptions.iterationId]);
|
|
1048
|
-
|
|
1049
|
-
useEffect(() => {
|
|
1050
|
-
fetchLadder();
|
|
1051
|
-
}, [fetchLadder]);
|
|
1052
|
-
|
|
1053
|
-
return {
|
|
1054
|
-
ladder,
|
|
1055
|
-
level,
|
|
1056
|
-
setLevel,
|
|
1057
|
-
macro: ladder?.macro ?? null,
|
|
1058
|
-
standard: ladder?.standard ?? null,
|
|
1059
|
-
concrete: ladder?.concrete ?? null,
|
|
1060
|
-
loading,
|
|
1061
|
-
error,
|
|
1062
|
-
refresh: fetchLadder
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
export function useTaskLadder(taskId: string | undefined) {
|
|
1067
|
-
const client = useCriticalPathClient();
|
|
1068
|
-
const [taskLadder, setTaskLadder] = useState<TaskLadderView | null>(null);
|
|
1069
|
-
const [loading, setLoading] = useState(true);
|
|
1070
|
-
const [error, setError] = useState<Error | null>(null);
|
|
1071
|
-
|
|
1072
|
-
const fetchTaskLadder = useCallback(async () => {
|
|
1073
|
-
if (!taskId) {
|
|
1074
|
-
setTaskLadder(null);
|
|
1075
|
-
setLoading(false);
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
try {
|
|
1079
|
-
setLoading(true);
|
|
1080
|
-
const data = await client.getTaskLadder(taskId);
|
|
1081
|
-
setTaskLadder(data);
|
|
1082
|
-
setError(null);
|
|
1083
|
-
} catch (err) {
|
|
1084
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1085
|
-
} finally {
|
|
1086
|
-
setLoading(false);
|
|
1087
|
-
}
|
|
1088
|
-
}, [client, taskId]);
|
|
1089
|
-
|
|
1090
|
-
useEffect(() => {
|
|
1091
|
-
fetchTaskLadder();
|
|
1092
|
-
}, [fetchTaskLadder]);
|
|
1093
|
-
|
|
1094
|
-
return {
|
|
1095
|
-
taskLadder,
|
|
1096
|
-
macroPhase: taskLadder?.macroPhase ?? null,
|
|
1097
|
-
standard: taskLadder?.standard ?? null,
|
|
1098
|
-
concrete: taskLadder?.concrete ?? null,
|
|
1099
|
-
metrics: taskLadder?.metrics ?? null,
|
|
1100
|
-
loading,
|
|
1101
|
-
error,
|
|
1102
|
-
refresh: fetchTaskLadder
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
export function useTaskMetrics(taskId: string | undefined) {
|
|
1107
|
-
const client = useCriticalPathClient();
|
|
1108
|
-
const [metrics, setMetrics] = useState<TaskMetrics | null>(null);
|
|
1109
|
-
const [history, setHistory] = useState<TaskProgressHistory | null>(null);
|
|
1110
|
-
const [loading, setLoading] = useState(true);
|
|
1111
|
-
const [error, setError] = useState<Error | null>(null);
|
|
1112
|
-
|
|
1113
|
-
const fetchMetrics = useCallback(async () => {
|
|
1114
|
-
if (!taskId) {
|
|
1115
|
-
setMetrics(null);
|
|
1116
|
-
setHistory(null);
|
|
1117
|
-
setLoading(false);
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
try {
|
|
1121
|
-
setLoading(true);
|
|
1122
|
-
const [m, h] = await Promise.all([
|
|
1123
|
-
client.getTaskMetrics(taskId),
|
|
1124
|
-
client.getTaskProgressHistory(taskId)
|
|
1125
|
-
]);
|
|
1126
|
-
setMetrics(m);
|
|
1127
|
-
setHistory(h);
|
|
1128
|
-
setError(null);
|
|
1129
|
-
} catch (err) {
|
|
1130
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1131
|
-
} finally {
|
|
1132
|
-
setLoading(false);
|
|
1133
|
-
}
|
|
1134
|
-
}, [client, taskId]);
|
|
1135
|
-
|
|
1136
|
-
useEffect(() => {
|
|
1137
|
-
fetchMetrics();
|
|
1138
|
-
}, [fetchMetrics]);
|
|
1139
|
-
|
|
1140
|
-
return {
|
|
1141
|
-
metrics,
|
|
1142
|
-
history,
|
|
1143
|
-
inferredActuals: metrics?.inferredActuals ?? null,
|
|
1144
|
-
realityDelta: metrics?.realityDelta ?? null,
|
|
1145
|
-
progress: metrics?.progress ?? null,
|
|
1146
|
-
evm: metrics?.evm ?? null,
|
|
1147
|
-
curveProfile: history?.curveProfile ?? null,
|
|
1148
|
-
loading,
|
|
1149
|
-
error,
|
|
1150
|
-
refresh: fetchMetrics
|
|
1151
|
-
};
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
export function useWorkloadDistribution(
|
|
1155
|
-
projectId?: string,
|
|
1156
|
-
initialOptions: WorkloadDistributionOptions = {}
|
|
1157
|
-
) {
|
|
1158
|
-
const client = useCriticalPathClient();
|
|
1159
|
-
const [interval, setInterval] = useState<WorkloadInterval>(initialOptions.interval || 'week');
|
|
1160
|
-
const [groupBy, setGroupBy] = useState<WorkloadGroupBy>(initialOptions.groupBy || 'assignee');
|
|
1161
|
-
const [metric, setMetric] = useState<WorkloadMetric>(initialOptions.metric || 'blended');
|
|
1162
|
-
const [workload, setWorkload] = useState<WorkloadDistribution | null>(null);
|
|
1163
|
-
const [loading, setLoading] = useState(true);
|
|
1164
|
-
const [error, setError] = useState<Error | null>(null);
|
|
1165
|
-
|
|
1166
|
-
const fetchWorkload = useCallback(async () => {
|
|
1167
|
-
try {
|
|
1168
|
-
setLoading(true);
|
|
1169
|
-
const data = await client.getWorkloadDistribution(projectId, {
|
|
1170
|
-
...initialOptions,
|
|
1171
|
-
interval,
|
|
1172
|
-
groupBy,
|
|
1173
|
-
metric
|
|
1174
|
-
});
|
|
1175
|
-
setWorkload(data);
|
|
1176
|
-
setError(null);
|
|
1177
|
-
} catch (err) {
|
|
1178
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1179
|
-
} finally {
|
|
1180
|
-
setLoading(false);
|
|
1181
|
-
}
|
|
1182
|
-
}, [
|
|
1183
|
-
client,
|
|
1184
|
-
projectId,
|
|
1185
|
-
interval,
|
|
1186
|
-
groupBy,
|
|
1187
|
-
metric,
|
|
1188
|
-
initialOptions.startDate,
|
|
1189
|
-
initialOptions.endDate,
|
|
1190
|
-
initialOptions.defaultWeeklyCapacityHours
|
|
1191
|
-
]);
|
|
1192
|
-
|
|
1193
|
-
useEffect(() => {
|
|
1194
|
-
fetchWorkload();
|
|
1195
|
-
}, [fetchWorkload]);
|
|
1196
|
-
|
|
1197
|
-
return {
|
|
1198
|
-
workload,
|
|
1199
|
-
interval,
|
|
1200
|
-
setInterval,
|
|
1201
|
-
groupBy,
|
|
1202
|
-
setGroupBy,
|
|
1203
|
-
metric,
|
|
1204
|
-
setMetric,
|
|
1205
|
-
buckets: workload?.buckets ?? [],
|
|
1206
|
-
seriesKeys: workload?.seriesKeys ?? [],
|
|
1207
|
-
seriesLabels: workload?.seriesLabels ?? {},
|
|
1208
|
-
totalHours: workload?.totalHours ?? 0,
|
|
1209
|
-
totalCapacity: workload?.totalCapacity ?? 0,
|
|
1210
|
-
averageUtilization: workload?.averageUtilization,
|
|
1211
|
-
loading,
|
|
1212
|
-
error,
|
|
1213
|
-
refresh: fetchWorkload
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|