@tuturuuu/utils 0.0.3 → 0.6.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/CHANGELOG.md +313 -0
- package/biome.json +5 -0
- package/jsr.json +8 -8
- package/package.json +63 -32
- package/src/__tests__/ai-temp-auth.test.ts +309 -0
- package/src/__tests__/api-proxy-guard.test.ts +1451 -0
- package/src/__tests__/app-url.test.ts +270 -0
- package/src/__tests__/avatar-url.test.ts +97 -0
- package/src/__tests__/color-helper.test.ts +179 -0
- package/src/__tests__/constants.test.ts +351 -0
- package/src/__tests__/crypto.test.ts +107 -0
- package/src/__tests__/date-helper.test.ts +408 -0
- package/src/__tests__/fixtures/task-description-full-featured.json +456 -0
- package/src/__tests__/format.test.ts +317 -0
- package/src/__tests__/html-sanitizer.test.ts +360 -0
- package/src/__tests__/interest-calculator.test.ts +336 -0
- package/src/__tests__/interest-detector.test.ts +222 -0
- package/src/__tests__/label-colors.test.ts +241 -0
- package/src/__tests__/name-helper.test.ts +158 -0
- package/src/__tests__/node-diff.test.ts +576 -0
- package/src/__tests__/notification-service.test.ts +210 -0
- package/src/__tests__/onboarding-helper.test.ts +331 -0
- package/src/__tests__/path-helper.test.ts +152 -0
- package/src/__tests__/permissions.test.tsx +81 -0
- package/src/__tests__/request-emoji-limit.test.ts +172 -0
- package/src/__tests__/search-helper.test.ts +51 -0
- package/src/__tests__/storage-display-name.test.ts +37 -0
- package/src/__tests__/storage-path.test.ts +238 -0
- package/src/__tests__/tag-utils.test.ts +205 -0
- package/src/__tests__/task-description-yjs-state.test.ts +581 -0
- package/src/__tests__/task-helper-board-api-routing.test.ts +94 -0
- package/src/__tests__/task-helper-create-task.test.ts +129 -0
- package/src/__tests__/task-helpers.test.ts +464 -0
- package/src/__tests__/task-overrides.test.ts +305 -0
- package/src/__tests__/task-reorder-cache.test.ts +74 -0
- package/src/__tests__/task-sort-keys.test.ts +36 -0
- package/src/__tests__/task-transformers.test.ts +62 -0
- package/src/__tests__/text-helper.test.ts +776 -0
- package/src/__tests__/time-helper.test.ts +70 -0
- package/src/__tests__/time-tracker-period.test.ts +55 -0
- package/src/__tests__/timezone.test.ts +117 -0
- package/src/__tests__/upstash-rest.test.ts +77 -0
- package/src/__tests__/uuid-helper.test.ts +133 -0
- package/src/__tests__/workspace-helper.test.ts +859 -0
- package/src/__tests__/workspace-limits.test.ts +255 -0
- package/src/__tests__/yjs-helper.test.ts +581 -0
- package/src/abuse-protection/__tests__/backend-rate-limit.test.ts +113 -0
- package/src/abuse-protection/__tests__/edge.test.ts +136 -0
- package/src/abuse-protection/__tests__/index.test.ts +562 -0
- package/src/abuse-protection/__tests__/reputation.test.ts +192 -0
- package/src/abuse-protection/backend-rate-limit.ts +44 -0
- package/src/abuse-protection/constants.ts +117 -0
- package/src/abuse-protection/edge.ts +223 -0
- package/src/abuse-protection/index.ts +1545 -0
- package/src/abuse-protection/reputation.ts +587 -0
- package/src/abuse-protection/types.ts +97 -0
- package/src/abuse-protection/user-agent.ts +124 -0
- package/src/abuse-protection/user-suspension.ts +231 -0
- package/src/ai-temp-auth.ts +315 -0
- package/src/api-proxy-guard.ts +965 -0
- package/src/app-url.ts +96 -0
- package/src/avatar-url.ts +64 -0
- package/src/break-duration.ts +84 -0
- package/src/calendar-auth-token.test.ts +37 -0
- package/src/calendar-auth-token.ts +19 -0
- package/src/calendar-sync-coordination.md +197 -0
- package/src/calendar-utils.test.ts +169 -0
- package/src/calendar-utils.ts +91 -0
- package/src/color-helper.ts +110 -0
- package/src/common/nextjs.tsx +99 -0
- package/src/common/scan.tsx +15 -0
- package/src/configs/reports.ts +160 -0
- package/src/constants.ts +85 -0
- package/src/crypto.ts +21 -0
- package/src/currencies.ts +97 -0
- package/src/date-helper.ts +313 -0
- package/src/editor/convert-to-task.ts +264 -0
- package/src/editor/index.ts +5 -0
- package/src/email/__tests__/client.test.ts +141 -0
- package/src/email/__tests__/validation.test.ts +46 -0
- package/src/email/client.ts +92 -0
- package/src/email/server.ts +128 -0
- package/src/email/validation.ts +11 -0
- package/src/encryption/__tests__/calendar-events.test.ts +411 -0
- package/src/encryption/__tests__/configuration.test.ts +114 -0
- package/src/encryption/__tests__/field-encryption.test.ts +232 -0
- package/src/encryption/__tests__/key-generation.test.ts +30 -0
- package/src/encryption/__tests__/performance-edge-cases.test.ts +187 -0
- package/src/encryption/__tests__/test-helpers.ts +22 -0
- package/src/encryption/__tests__/workspace-key-encryption.test.ts +129 -0
- package/src/encryption/encryption-service.ts +343 -0
- package/src/encryption/index.ts +25 -0
- package/src/encryption/types.ts +57 -0
- package/src/exchange-rates.ts +49 -0
- package/src/feature-flags/__tests__/feature-flags.test.ts +302 -0
- package/src/feature-flags/core.ts +322 -0
- package/src/feature-flags/data.ts +16 -0
- package/src/feature-flags/default.ts +18 -0
- package/src/feature-flags/index.ts +7 -0
- package/src/feature-flags/requestable-features.ts +79 -0
- package/src/feature-flags/types.ts +4 -0
- package/src/fetcher.ts +2 -0
- package/src/finance/index.ts +4 -0
- package/src/finance/interest-calculator.ts +456 -0
- package/src/finance/interest-detector.ts +141 -0
- package/src/finance/transform-invoice-results.ts +219 -0
- package/src/finance/wallet-permissions.test.ts +169 -0
- package/src/finance/wallet-permissions.ts +82 -0
- package/src/format.ts +120 -1
- package/src/generated/platform-build-metadata.ts +11 -0
- package/src/hooks/use-platform.ts +64 -0
- package/src/html-sanitizer.ts +155 -0
- package/src/internal-domains.ts +497 -0
- package/src/keyboard-preset.ts +109 -0
- package/src/label-colors.ts +213 -0
- package/src/launchable-apps.test.ts +126 -0
- package/src/launchable-apps.ts +490 -0
- package/src/name-helper.ts +269 -0
- package/src/next-config.test.ts +234 -0
- package/src/next-config.ts +203 -0
- package/src/node-diff.ts +375 -0
- package/src/notification-service.ts +379 -0
- package/src/nova/scores/__tests__/calculate.test.ts +254 -0
- package/src/nova/scores/calculate.ts +132 -0
- package/src/nova/submissions/check-permission.ts +132 -0
- package/src/onboarding-helper.ts +213 -0
- package/src/path-helper.ts +93 -0
- package/src/permissions.tsx +1170 -0
- package/src/plan-helpers.test.ts +188 -0
- package/src/plan-helpers.ts +80 -0
- package/src/platform-release.test.ts +74 -0
- package/src/platform-release.ts +155 -0
- package/src/portless.ts +124 -0
- package/src/priority-styles.ts +42 -0
- package/src/request-emoji-limit.ts +335 -0
- package/src/search-helper.ts +18 -0
- package/src/search.test.ts +89 -0
- package/src/search.ts +355 -0
- package/src/storage-display-name.ts +30 -0
- package/src/storage-path.ts +147 -0
- package/src/tag-utils.ts +159 -0
- package/src/task/reorder.ts +245 -0
- package/src/task/transformers.ts +149 -0
- package/src/task-date-timezone.ts +133 -0
- package/src/task-description-content.ts +240 -0
- package/src/task-helper/board.ts +193 -0
- package/src/task-helper/bulk-actions.ts +564 -0
- package/src/task-helper/personal-external-staging.ts +21 -0
- package/src/task-helper/recycle-bin.ts +202 -0
- package/src/task-helper/relationships.ts +346 -0
- package/src/task-helper/shared.ts +109 -0
- package/src/task-helper/sort-keys.ts +337 -0
- package/src/task-helper/task-hooks-basic.ts +342 -0
- package/src/task-helper/task-hooks-move.ts +264 -0
- package/src/task-helper/task-operations.ts +278 -0
- package/src/task-helper.ts +12 -0
- package/src/task-helpers.ts +241 -0
- package/src/task-list-status.ts +62 -0
- package/src/task-overrides.ts +82 -0
- package/src/task-snapshot.ts +374 -0
- package/src/text-diff.ts +81 -0
- package/src/text-helper.ts +537 -0
- package/src/time-helper.ts +63 -0
- package/src/time-tracker-period.ts +73 -0
- package/src/timeblock-helper.ts +418 -0
- package/src/timezone.ts +190 -0
- package/src/timezones.json +1271 -0
- package/src/upstash-rest.ts +56 -0
- package/src/user-helper.ts +296 -0
- package/src/uuid-helper.ts +11 -0
- package/src/workspace-handle.ts +10 -0
- package/src/workspace-helper.ts +1408 -0
- package/src/workspace-limits.ts +68 -0
- package/src/yjs-helper.ts +217 -0
- package/src/yjs-task-description.ts +81 -0
- package/tsconfig.json +3 -5
- package/tsconfig.typecheck.json +33 -0
- package/vitest.config.ts +36 -0
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -2
- package/dist/index.mjs.map +0 -1
- package/eslint.config.mjs +0 -20
- package/rollup.config.js +0 -41
- package/src/index.ts +0 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { moveWorkspaceTask } from '@tuturuuu/internal-api/tasks';
|
|
3
|
+
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
4
|
+
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
5
|
+
import { isTaskBoardCompletedStatus } from '../task-list-status';
|
|
6
|
+
|
|
7
|
+
import { getBrowserApiOptions } from './shared';
|
|
8
|
+
import { moveTask } from './task-operations';
|
|
9
|
+
|
|
10
|
+
function applyOptimisticMoveToList(
|
|
11
|
+
task: Task,
|
|
12
|
+
newListId: string,
|
|
13
|
+
list?: TaskList
|
|
14
|
+
): Task {
|
|
15
|
+
const now = new Date().toISOString();
|
|
16
|
+
const targetIsCompleted = isTaskBoardCompletedStatus(list?.status);
|
|
17
|
+
const targetIsClosed = list?.status === 'closed';
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
...task,
|
|
21
|
+
list_id: newListId,
|
|
22
|
+
completed: targetIsCompleted,
|
|
23
|
+
completed_at: targetIsCompleted ? (task.completed_at ?? now) : null,
|
|
24
|
+
closed_at: targetIsClosed ? (task.closed_at ?? now) : null,
|
|
25
|
+
} as Task;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useMoveTask(boardId: string, wsId?: string) {
|
|
29
|
+
const queryClient = useQueryClient();
|
|
30
|
+
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: async ({
|
|
33
|
+
taskId,
|
|
34
|
+
newListId,
|
|
35
|
+
}: {
|
|
36
|
+
taskId: string;
|
|
37
|
+
newListId: string;
|
|
38
|
+
}) => {
|
|
39
|
+
if (!wsId) {
|
|
40
|
+
console.error('Workspace ID missing for moveTask');
|
|
41
|
+
throw new Error('Workspace ID is required to move tasks');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const baseUrl =
|
|
45
|
+
typeof window !== 'undefined' ? window.location.origin : undefined;
|
|
46
|
+
const result = await moveTask(wsId, taskId, newListId, {
|
|
47
|
+
baseUrl: baseUrl ?? undefined,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
},
|
|
52
|
+
onMutate: async ({ taskId, newListId }) => {
|
|
53
|
+
await queryClient.cancelQueries({ queryKey: ['tasks', boardId] });
|
|
54
|
+
|
|
55
|
+
const previousTasks = queryClient.getQueryData(['tasks', boardId]);
|
|
56
|
+
|
|
57
|
+
queryClient.setQueryData(
|
|
58
|
+
['tasks', boardId],
|
|
59
|
+
(old: Task[] | undefined) => {
|
|
60
|
+
if (!old) return old;
|
|
61
|
+
return old.map((task) => {
|
|
62
|
+
if (task.id === taskId) {
|
|
63
|
+
const targetList = queryClient.getQueryData([
|
|
64
|
+
'task_lists',
|
|
65
|
+
boardId,
|
|
66
|
+
]) as TaskList[] | undefined;
|
|
67
|
+
const list = targetList?.find((l) => l.id === newListId);
|
|
68
|
+
return applyOptimisticMoveToList(task, newListId, list);
|
|
69
|
+
}
|
|
70
|
+
return task;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return { previousTasks };
|
|
76
|
+
},
|
|
77
|
+
onError: (err, _variables, context) => {
|
|
78
|
+
if (context?.previousTasks) {
|
|
79
|
+
queryClient.setQueryData(['tasks', boardId], context.previousTasks);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.error('Failed to move task:', err);
|
|
83
|
+
},
|
|
84
|
+
onSuccess: (updatedTask) => {
|
|
85
|
+
queryClient.setQueryData(
|
|
86
|
+
['tasks', boardId],
|
|
87
|
+
(old: Task[] | undefined) => {
|
|
88
|
+
if (!old) return old;
|
|
89
|
+
return old.map((task) =>
|
|
90
|
+
task.id === updatedTask.id ? updatedTask : task
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function useMoveTaskToBoard(currentBoardId: string, wsId?: string) {
|
|
99
|
+
const queryClient = useQueryClient();
|
|
100
|
+
|
|
101
|
+
return useMutation({
|
|
102
|
+
mutationFn: async ({
|
|
103
|
+
taskId,
|
|
104
|
+
newListId,
|
|
105
|
+
targetBoardId,
|
|
106
|
+
}: {
|
|
107
|
+
taskId: string;
|
|
108
|
+
newListId: string;
|
|
109
|
+
targetBoardId?: string;
|
|
110
|
+
}) => {
|
|
111
|
+
if (!wsId) {
|
|
112
|
+
throw new Error('Workspace ID is required to move tasks');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const result = await moveWorkspaceTask(
|
|
116
|
+
wsId,
|
|
117
|
+
taskId,
|
|
118
|
+
{
|
|
119
|
+
list_id: newListId,
|
|
120
|
+
target_board_id: targetBoardId,
|
|
121
|
+
},
|
|
122
|
+
getBrowserApiOptions()
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return result;
|
|
126
|
+
},
|
|
127
|
+
onMutate: async ({ taskId, newListId, targetBoardId }) => {
|
|
128
|
+
await queryClient.cancelQueries({ queryKey: ['tasks', currentBoardId] });
|
|
129
|
+
if (targetBoardId && targetBoardId !== currentBoardId) {
|
|
130
|
+
await queryClient.cancelQueries({ queryKey: ['tasks', targetBoardId] });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const previousCurrentBoardTasks = queryClient.getQueryData([
|
|
134
|
+
'tasks',
|
|
135
|
+
currentBoardId,
|
|
136
|
+
]);
|
|
137
|
+
const previousTargetBoardTasks =
|
|
138
|
+
targetBoardId && targetBoardId !== currentBoardId
|
|
139
|
+
? queryClient.getQueryData(['tasks', targetBoardId])
|
|
140
|
+
: null;
|
|
141
|
+
|
|
142
|
+
if (targetBoardId && targetBoardId !== currentBoardId) {
|
|
143
|
+
queryClient.setQueryData(
|
|
144
|
+
['tasks', currentBoardId],
|
|
145
|
+
(old: Task[] | undefined) => {
|
|
146
|
+
if (!old) return old;
|
|
147
|
+
return old.filter((task) => task.id !== taskId);
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
queryClient.setQueryData(
|
|
152
|
+
['tasks', targetBoardId],
|
|
153
|
+
(old: Task[] | undefined) => {
|
|
154
|
+
if (!old) return old;
|
|
155
|
+
|
|
156
|
+
const currentBoardTasks = previousCurrentBoardTasks as
|
|
157
|
+
| Task[]
|
|
158
|
+
| undefined;
|
|
159
|
+
const taskToMove = currentBoardTasks?.find((t) => t.id === taskId);
|
|
160
|
+
|
|
161
|
+
if (!taskToMove) return old;
|
|
162
|
+
|
|
163
|
+
const targetList = queryClient.getQueryData([
|
|
164
|
+
'task_lists',
|
|
165
|
+
targetBoardId,
|
|
166
|
+
]) as TaskList[] | undefined;
|
|
167
|
+
const list = targetList?.find((l) => l.id === newListId);
|
|
168
|
+
|
|
169
|
+
const updatedTask = applyOptimisticMoveToList(
|
|
170
|
+
taskToMove,
|
|
171
|
+
newListId,
|
|
172
|
+
list
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return [...old, updatedTask];
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
} else {
|
|
179
|
+
queryClient.setQueryData(
|
|
180
|
+
['tasks', currentBoardId],
|
|
181
|
+
(old: Task[] | undefined) => {
|
|
182
|
+
if (!old) return old;
|
|
183
|
+
return old.map((task) => {
|
|
184
|
+
if (task.id === taskId) {
|
|
185
|
+
const targetList = queryClient.getQueryData([
|
|
186
|
+
'task_lists',
|
|
187
|
+
currentBoardId,
|
|
188
|
+
]) as TaskList[] | undefined;
|
|
189
|
+
const list = targetList?.find((l) => l.id === newListId);
|
|
190
|
+
return applyOptimisticMoveToList(task, newListId, list);
|
|
191
|
+
}
|
|
192
|
+
return task;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
previousCurrentBoardTasks,
|
|
200
|
+
previousTargetBoardTasks,
|
|
201
|
+
targetBoardId: targetBoardId || currentBoardId,
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
onError: (err, _variables, context) => {
|
|
205
|
+
if (context?.previousCurrentBoardTasks) {
|
|
206
|
+
queryClient.setQueryData(
|
|
207
|
+
['tasks', currentBoardId],
|
|
208
|
+
context.previousCurrentBoardTasks
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
context?.previousTargetBoardTasks &&
|
|
214
|
+
context.targetBoardId !== currentBoardId
|
|
215
|
+
) {
|
|
216
|
+
queryClient.setQueryData(
|
|
217
|
+
['tasks', context.targetBoardId],
|
|
218
|
+
context.previousTargetBoardTasks
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.error('Failed to move task to board:', err);
|
|
223
|
+
},
|
|
224
|
+
onSuccess: (result) => {
|
|
225
|
+
if (result.movedToDifferentBoard) {
|
|
226
|
+
queryClient.setQueryData(
|
|
227
|
+
['tasks', result.sourceBoardId],
|
|
228
|
+
(old: Task[] | undefined) => {
|
|
229
|
+
if (!old) return old;
|
|
230
|
+
return old.filter((task) => task.id !== result.task.id);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
queryClient.setQueryData(
|
|
235
|
+
['tasks', result.targetBoardId],
|
|
236
|
+
(old: Task[] | undefined) => {
|
|
237
|
+
if (!old) return [result.task];
|
|
238
|
+
|
|
239
|
+
const existingIndex = old.findIndex(
|
|
240
|
+
(task) => task.id === result.task.id
|
|
241
|
+
);
|
|
242
|
+
if (existingIndex >= 0) {
|
|
243
|
+
const updated = [...old];
|
|
244
|
+
updated[existingIndex] = result.task;
|
|
245
|
+
return updated;
|
|
246
|
+
} else {
|
|
247
|
+
return [...old, result.task];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
queryClient.setQueryData(
|
|
253
|
+
['tasks', currentBoardId],
|
|
254
|
+
(old: Task[] | undefined) => {
|
|
255
|
+
if (!old) return old;
|
|
256
|
+
return old.map((task) =>
|
|
257
|
+
task.id === result.task.id ? result.task : task
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
2
|
+
import type { InternalApiClientOptions } from '@tuturuuu/internal-api/client';
|
|
3
|
+
import {
|
|
4
|
+
createWorkspaceTask,
|
|
5
|
+
getWorkspaceTask,
|
|
6
|
+
listWorkspaceBoardsWithLists,
|
|
7
|
+
moveWorkspaceTask,
|
|
8
|
+
triggerWorkspaceTaskEmbedding,
|
|
9
|
+
updateWorkspaceTask,
|
|
10
|
+
} from '@tuturuuu/internal-api/tasks';
|
|
11
|
+
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
12
|
+
import { isTaskBoardCompletedStatus } from '../task-list-status';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
getBrowserApiOptions,
|
|
16
|
+
getMutationApiOptions,
|
|
17
|
+
toWorkspaceTaskUpdatePayload,
|
|
18
|
+
} from './shared';
|
|
19
|
+
|
|
20
|
+
export async function getTaskAssignees(wsId: string, taskId: string) {
|
|
21
|
+
const { task } = await getWorkspaceTask(wsId, taskId, getBrowserApiOptions());
|
|
22
|
+
return task.assignees ?? [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function buildTaskArchivedStatusUpdates(
|
|
26
|
+
task: {
|
|
27
|
+
completed?: boolean | null;
|
|
28
|
+
completed_at?: string | null;
|
|
29
|
+
closed_at?: string | null;
|
|
30
|
+
},
|
|
31
|
+
listStatus: string | null | undefined
|
|
32
|
+
) {
|
|
33
|
+
const updates: {
|
|
34
|
+
completed?: boolean;
|
|
35
|
+
completed_at?: string | null;
|
|
36
|
+
closed_at?: string | null;
|
|
37
|
+
} = {};
|
|
38
|
+
|
|
39
|
+
if (isTaskBoardCompletedStatus(listStatus)) {
|
|
40
|
+
if (task.completed !== true || !task.completed_at || task.closed_at) {
|
|
41
|
+
updates.completed = true;
|
|
42
|
+
updates.completed_at = task.completed_at ?? new Date().toISOString();
|
|
43
|
+
updates.closed_at = null;
|
|
44
|
+
}
|
|
45
|
+
} else if (listStatus === 'closed') {
|
|
46
|
+
if (task.completed !== false || !task.closed_at || task.completed_at) {
|
|
47
|
+
updates.completed = false;
|
|
48
|
+
updates.closed_at = new Date().toISOString();
|
|
49
|
+
updates.completed_at = null;
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
if (task.completed !== false) {
|
|
53
|
+
updates.completed = false;
|
|
54
|
+
}
|
|
55
|
+
if (task.completed_at) {
|
|
56
|
+
updates.completed_at = null;
|
|
57
|
+
}
|
|
58
|
+
if (task.closed_at) {
|
|
59
|
+
updates.closed_at = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return updates;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function createTask(
|
|
67
|
+
wsId: string,
|
|
68
|
+
listId: string,
|
|
69
|
+
task: Partial<Task> & {
|
|
70
|
+
description_yjs_state?: number[];
|
|
71
|
+
label_ids?: string[];
|
|
72
|
+
assignee_ids?: string[];
|
|
73
|
+
project_ids?: string[];
|
|
74
|
+
}
|
|
75
|
+
) {
|
|
76
|
+
if (!task.name || task.name.trim().length === 0) {
|
|
77
|
+
throw new Error('Task name is required');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!listId) {
|
|
81
|
+
throw new Error('List ID is required');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!wsId) {
|
|
85
|
+
throw new Error('Workspace ID is required');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const schedulingInput = task as Partial<{
|
|
89
|
+
total_duration: number | null;
|
|
90
|
+
is_splittable: boolean | null;
|
|
91
|
+
min_split_duration_minutes: number | null;
|
|
92
|
+
max_split_duration_minutes: number | null;
|
|
93
|
+
calendar_hours: Task['calendar_hours'];
|
|
94
|
+
auto_schedule: boolean | null;
|
|
95
|
+
}>;
|
|
96
|
+
|
|
97
|
+
const options = await getMutationApiOptions();
|
|
98
|
+
const { task: createdTask } = await createWorkspaceTask(
|
|
99
|
+
wsId,
|
|
100
|
+
{
|
|
101
|
+
name: task.name.trim(),
|
|
102
|
+
description: task.description || null,
|
|
103
|
+
description_yjs_state: task.description_yjs_state ?? null,
|
|
104
|
+
listId,
|
|
105
|
+
priority: task.priority || null,
|
|
106
|
+
start_date: task.start_date || null,
|
|
107
|
+
end_date: task.end_date || null,
|
|
108
|
+
estimation_points: task.estimation_points ?? null,
|
|
109
|
+
label_ids: task.label_ids ?? [],
|
|
110
|
+
assignee_ids: task.assignee_ids ?? [],
|
|
111
|
+
project_ids: task.project_ids ?? [],
|
|
112
|
+
total_duration: schedulingInput.total_duration ?? null,
|
|
113
|
+
is_splittable: schedulingInput.is_splittable ?? null,
|
|
114
|
+
min_split_duration_minutes:
|
|
115
|
+
schedulingInput.min_split_duration_minutes ?? null,
|
|
116
|
+
max_split_duration_minutes:
|
|
117
|
+
schedulingInput.max_split_duration_minutes ?? null,
|
|
118
|
+
calendar_hours: schedulingInput.calendar_hours ?? null,
|
|
119
|
+
auto_schedule: schedulingInput.auto_schedule ?? null,
|
|
120
|
+
},
|
|
121
|
+
options
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return createdTask as Task;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function updateTask(
|
|
128
|
+
wsId: string,
|
|
129
|
+
taskId: string,
|
|
130
|
+
task: Partial<Task> & { completed?: boolean }
|
|
131
|
+
) {
|
|
132
|
+
if (!wsId) {
|
|
133
|
+
throw new Error('Workspace ID is required');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const options = await getMutationApiOptions();
|
|
137
|
+
const { task: data } = await updateWorkspaceTask(
|
|
138
|
+
wsId,
|
|
139
|
+
taskId,
|
|
140
|
+
toWorkspaceTaskUpdatePayload(task),
|
|
141
|
+
options
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if ((task.name !== undefined || task.description !== undefined) && data) {
|
|
145
|
+
triggerWorkspaceTaskEmbedding(wsId, taskId, options).catch(
|
|
146
|
+
(err: unknown) => {
|
|
147
|
+
console.error('Failed to regenerate embedding:', err);
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return data as Task;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function invalidateTaskCaches(
|
|
156
|
+
queryClient: QueryClient,
|
|
157
|
+
boardId?: string
|
|
158
|
+
) {
|
|
159
|
+
const promises: Promise<void>[] = [];
|
|
160
|
+
|
|
161
|
+
if (boardId) {
|
|
162
|
+
promises.push(
|
|
163
|
+
queryClient.invalidateQueries({ queryKey: ['tasks', boardId] })
|
|
164
|
+
);
|
|
165
|
+
promises.push(
|
|
166
|
+
queryClient.invalidateQueries({ queryKey: ['task_lists', boardId] })
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
promises.push(
|
|
171
|
+
queryClient.invalidateQueries({ queryKey: ['time-tracking-data'] })
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
await Promise.all(promises);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function syncTaskArchivedStatus(
|
|
178
|
+
wsId: string,
|
|
179
|
+
taskId: string,
|
|
180
|
+
listId: string,
|
|
181
|
+
options?: InternalApiClientOptions
|
|
182
|
+
) {
|
|
183
|
+
const clientOptions = options ?? getBrowserApiOptions();
|
|
184
|
+
const [{ boards }, { task }] = await Promise.all([
|
|
185
|
+
listWorkspaceBoardsWithLists(wsId, clientOptions),
|
|
186
|
+
getWorkspaceTask(wsId, taskId, clientOptions),
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
const list = boards
|
|
190
|
+
.flatMap((board) => board.task_lists)
|
|
191
|
+
.find((entry) => entry.id === listId);
|
|
192
|
+
|
|
193
|
+
if (!list) {
|
|
194
|
+
console.error('Error fetching list status: list not found', { listId });
|
|
195
|
+
throw new Error(`Task list not found: ${listId}`);
|
|
196
|
+
}
|
|
197
|
+
const updates = buildTaskArchivedStatusUpdates(task, list.status);
|
|
198
|
+
|
|
199
|
+
if (Object.keys(updates).length === 0) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const mutationOptions = options ?? (await getMutationApiOptions());
|
|
205
|
+
await updateWorkspaceTask(wsId, taskId, updates, mutationOptions);
|
|
206
|
+
} catch (updateError) {
|
|
207
|
+
console.error('Error syncing task archived status:', updateError);
|
|
208
|
+
throw updateError;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function moveTask(
|
|
213
|
+
wsId: string,
|
|
214
|
+
taskId: string,
|
|
215
|
+
newListId: string,
|
|
216
|
+
options?: InternalApiClientOptions
|
|
217
|
+
) {
|
|
218
|
+
const clientOptions = options ?? getBrowserApiOptions();
|
|
219
|
+
const [{ boards }, { task }] = await Promise.all([
|
|
220
|
+
listWorkspaceBoardsWithLists(wsId, clientOptions),
|
|
221
|
+
getWorkspaceTask(wsId, taskId, clientOptions),
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
const targetList = boards
|
|
225
|
+
.flatMap((board) => board.task_lists)
|
|
226
|
+
.find((list) => list.id === newListId);
|
|
227
|
+
|
|
228
|
+
if (!targetList) {
|
|
229
|
+
throw new Error(`Task list not found: ${newListId}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const updates = buildTaskArchivedStatusUpdates(task, targetList.status);
|
|
233
|
+
const mutationOptions = options ?? (await getMutationApiOptions());
|
|
234
|
+
|
|
235
|
+
await updateWorkspaceTask(
|
|
236
|
+
wsId,
|
|
237
|
+
taskId,
|
|
238
|
+
{
|
|
239
|
+
list_id: newListId,
|
|
240
|
+
...updates,
|
|
241
|
+
},
|
|
242
|
+
mutationOptions
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const { task: updatedTask } = await getWorkspaceTask(
|
|
246
|
+
wsId,
|
|
247
|
+
taskId,
|
|
248
|
+
clientOptions
|
|
249
|
+
);
|
|
250
|
+
return updatedTask as Task;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function moveTaskToBoard(
|
|
254
|
+
wsId: string,
|
|
255
|
+
taskId: string,
|
|
256
|
+
newListId: string,
|
|
257
|
+
targetBoardId?: string
|
|
258
|
+
) {
|
|
259
|
+
if (!wsId) {
|
|
260
|
+
throw new Error('Workspace ID is required');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const options = await getMutationApiOptions();
|
|
264
|
+
const result = await moveWorkspaceTask(
|
|
265
|
+
wsId,
|
|
266
|
+
taskId,
|
|
267
|
+
{
|
|
268
|
+
list_id: newListId,
|
|
269
|
+
target_board_id: targetBoardId,
|
|
270
|
+
},
|
|
271
|
+
options
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
...result,
|
|
276
|
+
task: result.task as Task,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { reorderTask, useReorderTask } from './task/reorder';
|
|
2
|
+
export { transformAssignees, transformTaskRecord } from './task/transformers';
|
|
3
|
+
export * from './task-helper/board';
|
|
4
|
+
export * from './task-helper/bulk-actions';
|
|
5
|
+
export * from './task-helper/personal-external-staging';
|
|
6
|
+
export * from './task-helper/recycle-bin';
|
|
7
|
+
export * from './task-helper/relationships';
|
|
8
|
+
export { getTicketIdentifier } from './task-helper/shared';
|
|
9
|
+
export * from './task-helper/sort-keys';
|
|
10
|
+
export * from './task-helper/task-hooks-basic';
|
|
11
|
+
export * from './task-helper/task-hooks-move';
|
|
12
|
+
export * from './task-helper/task-operations';
|