@tuturuuu/ui 0.12.0 → 0.14.0
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 +28 -0
- package/package.json +4 -4
- package/src/components/ui/calendar-app/components/habits-panel.tsx +5 -2
- package/src/components/ui/calendar-app/components/priority-view.tsx +15 -7
- package/src/components/ui/calendar-app/components/scheduling-dialog.tsx +7 -3
- package/src/components/ui/calendar-app/components/task-scheduler-panel.tsx +7 -3
- package/src/components/ui/calendar-app/components/time-tracker/index.tsx +14 -9
- package/src/components/ui/custom/settings/task-settings.tsx +61 -2
- package/src/components/ui/custom/tables/data-table.tsx +58 -8
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +4 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.test.ts +118 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/auto-scroll.ts +144 -70
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +21 -10
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +6 -0
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +4 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +11 -1
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +7 -3
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.test.ts +113 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-resource-context.ts +50 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +65 -18
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +1 -1
- package/src/components/ui/tu-do/cycles/task-cycles-client.tsx +23 -14
- package/src/components/ui/tu-do/estimates/use-task-estimates.ts +9 -4
- package/src/components/ui/tu-do/habits/client.tsx +4 -1
- package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +6 -3
- package/src/components/ui/tu-do/my-tasks/task-list-with-completion.tsx +19 -11
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-query.ts +15 -6
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +18 -13
- package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +17 -10
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +70 -0
- package/src/components/ui/tu-do/shared/board-views.tsx +33 -3
- package/src/components/ui/tu-do/shared/fade-setting-initializer.tsx +3 -1
- package/src/components/ui/tu-do/shared/list-view.tsx +1 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +6 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-update-shared-task.test.ts +9 -5
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +13 -8
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-overrides.ts +21 -9
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-update-shared-task.ts +10 -5
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +4 -2
- package/src/components/ui/tu-do/shared/task-estimation-picker.tsx +5 -2
- package/src/components/ui/tu-do/shared/task-quick-create-target-list.ts +20 -0
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +1 -1
- package/src/hooks/__tests__/use-task-actions.test.tsx +51 -0
- package/src/hooks/task-actions-personal-external.ts +2 -3
- package/src/hooks/use-board-actions.ts +27 -17
- package/src/hooks/use-calendar.tsx +7 -3
- package/src/hooks/use-settings-dialog-shortcut.ts +51 -0
- package/src/hooks/use-user-config.ts +10 -2
- package/src/lib/task-personal-external.ts +49 -0
- package/src/lib/tasks-app-url.ts +85 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
2
|
+
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
3
|
+
|
|
4
|
+
export interface TaskCardResourceContextInput {
|
|
5
|
+
boardId: string;
|
|
6
|
+
pageWorkspaceId?: string;
|
|
7
|
+
propAvailableLists?: TaskList[];
|
|
8
|
+
task: Task;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TaskCardResourceContext {
|
|
12
|
+
boardViewableMembersBoardId: string;
|
|
13
|
+
boardViewableMembersWorkspaceId?: string;
|
|
14
|
+
effectiveWorkspaceId?: string;
|
|
15
|
+
initialAvailableLists?: TaskList[];
|
|
16
|
+
isSourceWorkspaceTask: boolean;
|
|
17
|
+
taskBoardId: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTaskCardResourceContext({
|
|
21
|
+
boardId,
|
|
22
|
+
pageWorkspaceId,
|
|
23
|
+
propAvailableLists,
|
|
24
|
+
task,
|
|
25
|
+
}: TaskCardResourceContextInput): TaskCardResourceContext {
|
|
26
|
+
const effectiveWorkspaceId = task.source_workspace_id ?? pageWorkspaceId;
|
|
27
|
+
const taskBoardId = task.source_board_id ?? boardId;
|
|
28
|
+
const isSourceWorkspaceTask = Boolean(
|
|
29
|
+
task.source_workspace_id || task.source_board_id
|
|
30
|
+
);
|
|
31
|
+
const boardViewableMembersWorkspaceId =
|
|
32
|
+
task.source_workspace_id ?? pageWorkspaceId;
|
|
33
|
+
const boardViewableMembersBoardId =
|
|
34
|
+
isSourceWorkspaceTask && task.source_board_id
|
|
35
|
+
? task.source_board_id
|
|
36
|
+
: boardId;
|
|
37
|
+
const initialAvailableLists =
|
|
38
|
+
isSourceWorkspaceTask && task.source_board_id
|
|
39
|
+
? undefined
|
|
40
|
+
: propAvailableLists;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
boardViewableMembersBoardId,
|
|
44
|
+
boardViewableMembersWorkspaceId,
|
|
45
|
+
effectiveWorkspaceId,
|
|
46
|
+
initialAvailableLists,
|
|
47
|
+
isSourceWorkspaceTask,
|
|
48
|
+
taskBoardId,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
listWorkspaceTaskBoardViewableMembers,
|
|
34
34
|
listWorkspaceTaskLists,
|
|
35
35
|
listWorkspaceTaskProjects,
|
|
36
|
+
listWorkspaceTaskProjectsByIds,
|
|
36
37
|
removeCurrentUserTaskPersonalPlacement,
|
|
37
38
|
} from '@tuturuuu/internal-api/tasks';
|
|
38
39
|
import type { SupportedColor } from '@tuturuuu/types/primitives/SupportedColors';
|
|
@@ -85,6 +86,7 @@ import React, {
|
|
|
85
86
|
useRef,
|
|
86
87
|
useState,
|
|
87
88
|
} from 'react';
|
|
89
|
+
import { isPersonalExternalOverlayTask } from '../../../../../../lib/task-personal-external';
|
|
88
90
|
import {
|
|
89
91
|
enqueueTaskCardRelationshipRequest,
|
|
90
92
|
useTaskCardRelationships,
|
|
@@ -153,6 +155,7 @@ import {
|
|
|
153
155
|
getTaskCardHydratingOpenOptions,
|
|
154
156
|
isExternalTaskSnapshot,
|
|
155
157
|
} from './task-card-open-options';
|
|
158
|
+
import { getTaskCardResourceContext } from './task-card-resource-context';
|
|
156
159
|
import { getTaskCardVisibilityState } from './task-card-visibility';
|
|
157
160
|
import { TaskSchedulingBadge } from './task-scheduling-badge';
|
|
158
161
|
|
|
@@ -390,7 +393,24 @@ function TaskCardInner({
|
|
|
390
393
|
// Use React Query hooks for shared data (cached across all task cards)
|
|
391
394
|
const workspaceContextWsId = workspaceId ?? wsId;
|
|
392
395
|
const { data: boardConfig } = useBoardConfig(boardId, workspaceContextWsId);
|
|
393
|
-
const
|
|
396
|
+
const pageWorkspaceId = workspaceId ?? boardConfig?.ws_id ?? wsId;
|
|
397
|
+
const {
|
|
398
|
+
boardViewableMembersBoardId,
|
|
399
|
+
boardViewableMembersWorkspaceId,
|
|
400
|
+
effectiveWorkspaceId,
|
|
401
|
+
initialAvailableLists,
|
|
402
|
+
isSourceWorkspaceTask,
|
|
403
|
+
taskBoardId,
|
|
404
|
+
} = getTaskCardResourceContext({
|
|
405
|
+
boardId,
|
|
406
|
+
pageWorkspaceId,
|
|
407
|
+
propAvailableLists,
|
|
408
|
+
task,
|
|
409
|
+
});
|
|
410
|
+
const taskProjectIds = useMemo(
|
|
411
|
+
() => task.projects?.map((project) => project.id).filter(Boolean) ?? [],
|
|
412
|
+
[task.projects]
|
|
413
|
+
);
|
|
394
414
|
const taskShareWsId = effectiveWorkspaceId;
|
|
395
415
|
const { data: workspaceLabels = [], isLoading: labelsLoading } =
|
|
396
416
|
useWorkspaceLabels(effectiveWorkspaceId);
|
|
@@ -431,12 +451,24 @@ function TaskCardInner({
|
|
|
431
451
|
// Fetch workspace projects
|
|
432
452
|
const { data: workspaceProjects = [], isLoading: projectsLoading } = useQuery(
|
|
433
453
|
{
|
|
434
|
-
queryKey: [
|
|
454
|
+
queryKey: [
|
|
455
|
+
'task_projects',
|
|
456
|
+
effectiveWorkspaceId,
|
|
457
|
+
isSourceWorkspaceTask ? taskProjectIds.join(',') : 'all',
|
|
458
|
+
],
|
|
435
459
|
queryFn: async () => {
|
|
436
460
|
if (!effectiveWorkspaceId) return [];
|
|
461
|
+
if (isSourceWorkspaceTask) {
|
|
462
|
+
return listWorkspaceTaskProjectsByIds(
|
|
463
|
+
effectiveWorkspaceId,
|
|
464
|
+
taskProjectIds
|
|
465
|
+
);
|
|
466
|
+
}
|
|
437
467
|
return listWorkspaceTaskProjects(effectiveWorkspaceId);
|
|
438
468
|
},
|
|
439
|
-
enabled:
|
|
469
|
+
enabled:
|
|
470
|
+
!!effectiveWorkspaceId &&
|
|
471
|
+
(!isSourceWorkspaceTask || taskProjectIds.length > 0),
|
|
440
472
|
staleTime: 5 * 60 * 1000, // 5 minutes - projects rarely change
|
|
441
473
|
}
|
|
442
474
|
);
|
|
@@ -470,27 +502,35 @@ function TaskCardInner({
|
|
|
470
502
|
enabled: !!effectiveWorkspaceId && shouldLoadWorkspaceMembers,
|
|
471
503
|
});
|
|
472
504
|
const boardViewableMembersQuery = useQuery({
|
|
473
|
-
queryKey: [
|
|
505
|
+
queryKey: [
|
|
506
|
+
'task-board-viewable-members',
|
|
507
|
+
boardViewableMembersWorkspaceId,
|
|
508
|
+
boardViewableMembersBoardId,
|
|
509
|
+
],
|
|
474
510
|
queryFn: async (): Promise<WorkspaceMember[]> => {
|
|
475
|
-
if (!
|
|
511
|
+
if (!boardViewableMembersWorkspaceId || !boardViewableMembersBoardId) {
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
476
514
|
|
|
477
515
|
const payload = await listWorkspaceTaskBoardViewableMembers(
|
|
478
|
-
|
|
479
|
-
|
|
516
|
+
boardViewableMembersWorkspaceId,
|
|
517
|
+
boardViewableMembersBoardId
|
|
480
518
|
);
|
|
481
519
|
const members = Array.isArray(payload?.members) ? payload.members : [];
|
|
482
520
|
|
|
483
521
|
return members.map((member) => ({
|
|
484
522
|
id: member.user_id,
|
|
485
523
|
user_id: member.user_id,
|
|
486
|
-
workspace_id:
|
|
524
|
+
workspace_id: boardViewableMembersWorkspaceId,
|
|
487
525
|
display_name: member.display_name ?? member.email ?? member.user_id,
|
|
488
526
|
email: member.email ?? undefined,
|
|
489
527
|
avatar_url: member.avatar_url ?? undefined,
|
|
490
528
|
}));
|
|
491
529
|
},
|
|
492
530
|
enabled:
|
|
493
|
-
!!
|
|
531
|
+
!!boardViewableMembersWorkspaceId &&
|
|
532
|
+
!!boardViewableMembersBoardId &&
|
|
533
|
+
shouldLoadBoardViewableMembers,
|
|
494
534
|
staleTime: 5 * 60 * 1000,
|
|
495
535
|
});
|
|
496
536
|
const normalWorkspaceMembers = normalMembersQuery.data ?? [];
|
|
@@ -717,15 +757,15 @@ function TaskCardInner({
|
|
|
717
757
|
|
|
718
758
|
// Fetch available task lists using React Query (same key as other components)
|
|
719
759
|
const { data: availableLists = [] } = useQuery({
|
|
720
|
-
queryKey: ['task_lists',
|
|
760
|
+
queryKey: ['task_lists', taskBoardId],
|
|
721
761
|
queryFn: async () => {
|
|
722
|
-
if (!effectiveWorkspaceId) {
|
|
762
|
+
if (!effectiveWorkspaceId || !taskBoardId) {
|
|
723
763
|
return [];
|
|
724
764
|
}
|
|
725
765
|
|
|
726
766
|
const { lists } = await listWorkspaceTaskLists(
|
|
727
767
|
effectiveWorkspaceId,
|
|
728
|
-
|
|
768
|
+
taskBoardId,
|
|
729
769
|
typeof window !== 'undefined'
|
|
730
770
|
? { baseUrl: window.location.origin }
|
|
731
771
|
: undefined
|
|
@@ -733,8 +773,8 @@ function TaskCardInner({
|
|
|
733
773
|
|
|
734
774
|
return lists.filter((list) => !list.deleted) as TaskList[];
|
|
735
775
|
},
|
|
736
|
-
enabled: !
|
|
737
|
-
initialData:
|
|
776
|
+
enabled: !initialAvailableLists && !!effectiveWorkspaceId && !!taskBoardId,
|
|
777
|
+
initialData: initialAvailableLists,
|
|
738
778
|
staleTime: 60 * 1000, // 1 minute - lists change less frequently
|
|
739
779
|
});
|
|
740
780
|
|
|
@@ -852,6 +892,7 @@ function TaskCardInner({
|
|
|
852
892
|
// Check if task is optimistically added (pending realtime confirmation)
|
|
853
893
|
const isOptimistic = '_isOptimistic' in task && task._isOptimistic === true;
|
|
854
894
|
const isPersonalExternalTask = isExternalTaskSnapshot(task);
|
|
895
|
+
const isPersonalExternalOverlay = isPersonalExternalOverlayTask(task);
|
|
855
896
|
const sourceBoardUrl =
|
|
856
897
|
task.source_workspace_id && task.source_board_id
|
|
857
898
|
? `/${task.source_workspace_id}${tasksHref(`/boards/${task.source_board_id}`)}`
|
|
@@ -1098,7 +1139,7 @@ function TaskCardInner({
|
|
|
1098
1139
|
? true
|
|
1099
1140
|
: shouldUseBoardAssignees,
|
|
1100
1141
|
assigneeMemberSource: task.source_workspace_id
|
|
1101
|
-
? '
|
|
1142
|
+
? 'board'
|
|
1102
1143
|
: effectiveAssigneeMemberSource,
|
|
1103
1144
|
effectiveWorkspaceId,
|
|
1104
1145
|
isPersonalWorkspace,
|
|
@@ -1201,7 +1242,13 @@ function TaskCardInner({
|
|
|
1201
1242
|
const handleAddSubtask = () => {
|
|
1202
1243
|
setMenuOpen(false);
|
|
1203
1244
|
// Open subtask creation dialog - the relationship will be created when the user saves
|
|
1204
|
-
createSubtask(
|
|
1245
|
+
createSubtask(
|
|
1246
|
+
task.id,
|
|
1247
|
+
task.name,
|
|
1248
|
+
taskBoardId,
|
|
1249
|
+
task.list_id,
|
|
1250
|
+
availableLists
|
|
1251
|
+
);
|
|
1205
1252
|
};
|
|
1206
1253
|
|
|
1207
1254
|
const handleMoveToExternalStaging = async () => {
|
|
@@ -2303,7 +2350,7 @@ function TaskCardInner({
|
|
|
2303
2350
|
/>
|
|
2304
2351
|
)}
|
|
2305
2352
|
|
|
2306
|
-
{
|
|
2353
|
+
{isPersonalExternalOverlay && (
|
|
2307
2354
|
<>
|
|
2308
2355
|
{task.personal_board_id && task.personal_list_id && (
|
|
2309
2356
|
<DropdownMenuItem
|
|
@@ -2715,7 +2762,7 @@ function TaskCardInner({
|
|
|
2715
2762
|
<CreateListDialog
|
|
2716
2763
|
open={isCreateListDialogOpen}
|
|
2717
2764
|
onOpenChange={setIsCreateListDialogOpen}
|
|
2718
|
-
boardId={
|
|
2765
|
+
boardId={taskBoardId}
|
|
2719
2766
|
wsId={effectiveWorkspaceId}
|
|
2720
2767
|
initialStatus="active"
|
|
2721
2768
|
onSuccess={(listId) => {
|
|
@@ -47,6 +47,7 @@ import { toast } from '@tuturuuu/ui/sonner';
|
|
|
47
47
|
import { Textarea } from '@tuturuuu/ui/textarea';
|
|
48
48
|
import dayjs from 'dayjs';
|
|
49
49
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
50
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
50
51
|
|
|
51
52
|
type CycleStatus = 'planned' | 'active' | 'completed' | 'cancelled';
|
|
52
53
|
|
|
@@ -128,7 +129,9 @@ export function TaskCyclesClient({
|
|
|
128
129
|
} = useQuery<TaskCycle[]>({
|
|
129
130
|
queryKey: ['workspace', wsId, 'task-cycles'],
|
|
130
131
|
queryFn: async () => {
|
|
131
|
-
const response = await fetch(
|
|
132
|
+
const response = await fetch(
|
|
133
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles`)
|
|
134
|
+
);
|
|
132
135
|
if (!response.ok) throw new Error('Failed to fetch cycles');
|
|
133
136
|
return response.json();
|
|
134
137
|
},
|
|
@@ -149,17 +152,21 @@ export function TaskCyclesClient({
|
|
|
149
152
|
start_date?: string | null;
|
|
150
153
|
end_date?: string | null;
|
|
151
154
|
}) => {
|
|
152
|
-
const response = await fetch(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
155
|
+
const response = await fetch(
|
|
156
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles`),
|
|
157
|
+
{
|
|
158
|
+
method: 'POST',
|
|
159
|
+
credentials: 'include',
|
|
160
|
+
headers: { 'Content-Type': 'application/json' },
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
name,
|
|
163
|
+
description,
|
|
164
|
+
status,
|
|
165
|
+
start_date,
|
|
166
|
+
end_date,
|
|
167
|
+
}),
|
|
168
|
+
}
|
|
169
|
+
);
|
|
163
170
|
if (!response.ok) {
|
|
164
171
|
const data = await response.json();
|
|
165
172
|
throw new Error(data.error || 'Failed to create cycle');
|
|
@@ -196,9 +203,10 @@ export function TaskCyclesClient({
|
|
|
196
203
|
end_date?: string | null;
|
|
197
204
|
}) => {
|
|
198
205
|
const response = await fetch(
|
|
199
|
-
`/api/v1/workspaces/${wsId}/task-cycles/${id}
|
|
206
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles/${id}`),
|
|
200
207
|
{
|
|
201
208
|
method: 'PUT',
|
|
209
|
+
credentials: 'include',
|
|
202
210
|
headers: { 'Content-Type': 'application/json' },
|
|
203
211
|
body: JSON.stringify({
|
|
204
212
|
name,
|
|
@@ -232,9 +240,10 @@ export function TaskCyclesClient({
|
|
|
232
240
|
const deleteMutation = useMutation({
|
|
233
241
|
mutationFn: async (id: string) => {
|
|
234
242
|
const response = await fetch(
|
|
235
|
-
`/api/v1/workspaces/${wsId}/task-cycles/${id}
|
|
243
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/task-cycles/${id}`),
|
|
236
244
|
{
|
|
237
245
|
method: 'DELETE',
|
|
246
|
+
credentials: 'include',
|
|
238
247
|
}
|
|
239
248
|
);
|
|
240
249
|
if (!response.ok) {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import type { WorkspaceTaskBoard } from '@tuturuuu/types';
|
|
5
5
|
import { useTranslations } from 'next-intl';
|
|
6
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
6
7
|
|
|
7
8
|
export interface TaskEstimateBoardsResponse {
|
|
8
9
|
boards: Partial<WorkspaceTaskBoard>[];
|
|
@@ -34,9 +35,12 @@ export const taskEstimateBoardKeys = {
|
|
|
34
35
|
export async function fetchTaskEstimateBoards(
|
|
35
36
|
wsId: string
|
|
36
37
|
): Promise<TaskEstimateBoardsResponse> {
|
|
37
|
-
const response = await fetch(
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const response = await fetch(
|
|
39
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/boards/estimation`),
|
|
40
|
+
{
|
|
41
|
+
cache: 'no-store',
|
|
42
|
+
}
|
|
43
|
+
);
|
|
40
44
|
|
|
41
45
|
if (!response.ok) {
|
|
42
46
|
throw new Error('Failed to fetch task estimate boards');
|
|
@@ -56,9 +60,10 @@ async function updateTaskEstimateBoard(
|
|
|
56
60
|
}: UpdateTaskEstimateBoardInput
|
|
57
61
|
) {
|
|
58
62
|
const response = await fetch(
|
|
59
|
-
`/api/v1/workspaces/${wsId}/boards/${boardId}/estimation
|
|
63
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/boards/${boardId}/estimation`),
|
|
60
64
|
{
|
|
61
65
|
cache: 'no-store',
|
|
66
|
+
credentials: 'include',
|
|
62
67
|
method: 'PATCH',
|
|
63
68
|
headers: {
|
|
64
69
|
'Content-Type': 'application/json',
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { Skeleton } from '@tuturuuu/ui/skeleton';
|
|
15
15
|
import { toast } from '@tuturuuu/ui/sonner';
|
|
16
16
|
import { useState } from 'react';
|
|
17
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
17
18
|
import HabitCard from './habit-card';
|
|
18
19
|
import HabitFormDialog from './habit-form-dialog';
|
|
19
20
|
|
|
@@ -39,7 +40,9 @@ export default function HabitsClientPage({ wsId }: HabitsClientPageProps) {
|
|
|
39
40
|
} = useQuery({
|
|
40
41
|
queryKey: ['habits', wsId],
|
|
41
42
|
queryFn: async () => {
|
|
42
|
-
const response = await fetch(
|
|
43
|
+
const response = await fetch(
|
|
44
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/habits`)
|
|
45
|
+
);
|
|
43
46
|
if (!response.ok) {
|
|
44
47
|
throw new Error('Failed to fetch habits');
|
|
45
48
|
}
|
|
@@ -128,9 +128,10 @@ describe('useTaskContextActions', () => {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
131
|
-
|
|
131
|
+
`http://localhost:7809/api/v1/users/me/tasks/${mockTask.id}/overrides`,
|
|
132
132
|
{
|
|
133
133
|
method: 'PUT',
|
|
134
|
+
credentials: 'include',
|
|
134
135
|
headers: { 'Content-Type': 'application/json' },
|
|
135
136
|
body: JSON.stringify({ personally_unassigned: true }),
|
|
136
137
|
}
|
|
@@ -160,9 +161,10 @@ describe('useTaskContextActions', () => {
|
|
|
160
161
|
});
|
|
161
162
|
|
|
162
163
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
163
|
-
|
|
164
|
+
`http://localhost:7809/api/v1/users/me/tasks/${mockTask.id}/overrides`,
|
|
164
165
|
{
|
|
165
166
|
method: 'PUT',
|
|
167
|
+
credentials: 'include',
|
|
166
168
|
headers: { 'Content-Type': 'application/json' },
|
|
167
169
|
body: JSON.stringify({
|
|
168
170
|
personally_unassigned: false,
|
|
@@ -219,9 +221,10 @@ describe('useTaskContextActions', () => {
|
|
|
219
221
|
|
|
220
222
|
// Verify override cleanup
|
|
221
223
|
expect(global.fetch).toHaveBeenCalledWith(
|
|
222
|
-
|
|
224
|
+
`http://localhost:7809/api/v1/users/me/tasks/${taskWithOverrides.id}/overrides`,
|
|
223
225
|
expect.objectContaining({
|
|
224
226
|
method: 'PUT',
|
|
227
|
+
credentials: 'include',
|
|
225
228
|
headers: { 'Content-Type': 'application/json' },
|
|
226
229
|
})
|
|
227
230
|
);
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import Link from 'next/link';
|
|
35
35
|
import { useTranslations } from 'next-intl';
|
|
36
36
|
import { type MouseEvent, useState } from 'react';
|
|
37
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
37
38
|
import { useTasksHref } from '../tasks-route-context';
|
|
38
39
|
import { MyTaskContextMenu } from './my-task-context-menu';
|
|
39
40
|
import { PersonalPlacementDialog } from './personal-placement-dialog';
|
|
@@ -128,9 +129,10 @@ export default function TaskListWithCompletion({
|
|
|
128
129
|
setCompletingTasks((prev) => new Set(prev).add(task.id));
|
|
129
130
|
try {
|
|
130
131
|
const response = await fetch(
|
|
131
|
-
`/api/v1/users/me/tasks/${task.id}/overrides
|
|
132
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
132
133
|
{
|
|
133
134
|
method: 'PUT',
|
|
135
|
+
credentials: 'include',
|
|
134
136
|
headers: { 'Content-Type': 'application/json' },
|
|
135
137
|
body: JSON.stringify({ personally_unassigned: false }),
|
|
136
138
|
}
|
|
@@ -160,9 +162,10 @@ export default function TaskListWithCompletion({
|
|
|
160
162
|
|
|
161
163
|
try {
|
|
162
164
|
const response = await fetch(
|
|
163
|
-
`/api/v1/users/me/tasks/${task.id}/overrides
|
|
165
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
164
166
|
{
|
|
165
167
|
method: 'PUT',
|
|
168
|
+
credentials: 'include',
|
|
166
169
|
headers: { 'Content-Type': 'application/json' },
|
|
167
170
|
body: JSON.stringify({
|
|
168
171
|
completed_at: isAlreadyDone ? null : new Date().toISOString(),
|
|
@@ -234,14 +237,18 @@ export default function TaskListWithCompletion({
|
|
|
234
237
|
!isCompleted &&
|
|
235
238
|
(task.overrides?.completed_at || task.overrides?.personally_unassigned)
|
|
236
239
|
) {
|
|
237
|
-
await fetch(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
240
|
+
await fetch(
|
|
241
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
242
|
+
{
|
|
243
|
+
method: 'PUT',
|
|
244
|
+
credentials: 'include',
|
|
245
|
+
headers: { 'Content-Type': 'application/json' },
|
|
246
|
+
body: JSON.stringify({
|
|
247
|
+
completed_at: null,
|
|
248
|
+
personally_unassigned: false,
|
|
249
|
+
}),
|
|
250
|
+
}
|
|
251
|
+
).catch(() => {
|
|
245
252
|
// Non-critical cleanup — don't block the completion flow
|
|
246
253
|
});
|
|
247
254
|
}
|
|
@@ -275,9 +282,10 @@ export default function TaskListWithCompletion({
|
|
|
275
282
|
|
|
276
283
|
try {
|
|
277
284
|
const response = await fetch(
|
|
278
|
-
`/api/v1/users/me/tasks/${task.id}/overrides
|
|
285
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
279
286
|
{
|
|
280
287
|
method: 'PUT',
|
|
288
|
+
credentials: 'include',
|
|
281
289
|
headers: { 'Content-Type': 'application/json' },
|
|
282
290
|
body: JSON.stringify({ personally_unassigned: true }),
|
|
283
291
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useQuery,
|
|
7
7
|
} from '@tanstack/react-query';
|
|
8
8
|
import type { TaskWithRelations } from '@tuturuuu/types';
|
|
9
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
9
10
|
|
|
10
11
|
export interface MyTasksData {
|
|
11
12
|
overdue: TaskWithRelations[];
|
|
@@ -62,9 +63,13 @@ export function useMyTasksQuery(
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
const res = await fetch(
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const res = await fetch(
|
|
67
|
+
getTaskApiUrl(`/api/v1/users/me/tasks?${params}`),
|
|
68
|
+
{
|
|
69
|
+
cache: 'no-store',
|
|
70
|
+
credentials: 'include',
|
|
71
|
+
}
|
|
72
|
+
);
|
|
68
73
|
if (!res.ok) {
|
|
69
74
|
throw new Error('Failed to fetch tasks');
|
|
70
75
|
}
|
|
@@ -117,9 +122,13 @@ export function useCompletedTasksQuery(
|
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
const res = await fetch(
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
const res = await fetch(
|
|
126
|
+
getTaskApiUrl(`/api/v1/users/me/tasks?${params}`),
|
|
127
|
+
{
|
|
128
|
+
cache: 'no-store',
|
|
129
|
+
credentials: 'include',
|
|
130
|
+
}
|
|
131
|
+
);
|
|
123
132
|
if (!res.ok) {
|
|
124
133
|
throw new Error('Failed to fetch completed tasks');
|
|
125
134
|
}
|
|
@@ -25,6 +25,7 @@ import timezone from 'dayjs/plugin/timezone';
|
|
|
25
25
|
import utc from 'dayjs/plugin/utc';
|
|
26
26
|
import { useTranslations } from 'next-intl';
|
|
27
27
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
28
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
28
29
|
import { getRandomNewLabelColor } from '../utils/taskConstants';
|
|
29
30
|
import type { TaskOptions } from './command-bar';
|
|
30
31
|
import type { JournalTaskResponse } from './task-preview-dialog';
|
|
@@ -552,19 +553,23 @@ export function useMyTasksState({
|
|
|
552
553
|
}
|
|
553
554
|
>({
|
|
554
555
|
mutationFn: async (variables) => {
|
|
555
|
-
const response = await fetch(
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
556
|
+
const response = await fetch(
|
|
557
|
+
getTaskApiUrl(`/api/v1/workspaces/${wsId}/tasks/journal`),
|
|
558
|
+
{
|
|
559
|
+
method: 'POST',
|
|
560
|
+
credentials: 'include',
|
|
561
|
+
headers: { 'Content-Type': 'application/json' },
|
|
562
|
+
body: JSON.stringify({
|
|
563
|
+
entry: variables.entry,
|
|
564
|
+
previewOnly: true,
|
|
565
|
+
generateDescriptions: variables.generateDescriptions,
|
|
566
|
+
generatePriority: variables.generatePriority,
|
|
567
|
+
generateLabels: variables.generateLabels,
|
|
568
|
+
clientTimezone: variables.clientTimezone,
|
|
569
|
+
clientTimestamp: variables.clientTimestamp,
|
|
570
|
+
}),
|
|
571
|
+
}
|
|
572
|
+
);
|
|
568
573
|
|
|
569
574
|
const payload = await response.json().catch(() => null);
|
|
570
575
|
if (!response.ok) {
|
|
@@ -14,6 +14,7 @@ import { getActiveTaskUserBroadcast } from '@tuturuuu/ui/hooks/useTaskUserRealti
|
|
|
14
14
|
import { toast } from '@tuturuuu/ui/sonner';
|
|
15
15
|
import { useTranslations } from 'next-intl';
|
|
16
16
|
import { useCallback, useState } from 'react';
|
|
17
|
+
import { getTaskApiUrl } from '../../../../lib/tasks-app-url';
|
|
17
18
|
import { dispatchTaskSoundCue } from '../shared/task-sound-effects';
|
|
18
19
|
import {
|
|
19
20
|
MY_COMPLETED_TASKS_QUERY_KEY,
|
|
@@ -243,14 +244,18 @@ export function useTaskContextActions({
|
|
|
243
244
|
task.overrides?.completed_at ||
|
|
244
245
|
task.overrides?.personally_unassigned
|
|
245
246
|
) {
|
|
246
|
-
await fetch(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
247
|
+
await fetch(
|
|
248
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
249
|
+
{
|
|
250
|
+
method: 'PUT',
|
|
251
|
+
credentials: 'include',
|
|
252
|
+
headers: { 'Content-Type': 'application/json' },
|
|
253
|
+
body: JSON.stringify({
|
|
254
|
+
completed_at: null,
|
|
255
|
+
personally_unassigned: false,
|
|
256
|
+
}),
|
|
257
|
+
}
|
|
258
|
+
).catch(() => {
|
|
254
259
|
// Non-critical cleanup
|
|
255
260
|
});
|
|
256
261
|
}
|
|
@@ -278,9 +283,10 @@ export function useTaskContextActions({
|
|
|
278
283
|
setIsLoading(true);
|
|
279
284
|
try {
|
|
280
285
|
const response = await fetch(
|
|
281
|
-
`/api/v1/users/me/tasks/${task.id}/overrides
|
|
286
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
282
287
|
{
|
|
283
288
|
method: 'PUT',
|
|
289
|
+
credentials: 'include',
|
|
284
290
|
headers: { 'Content-Type': 'application/json' },
|
|
285
291
|
body: JSON.stringify({ personally_unassigned: true }),
|
|
286
292
|
}
|
|
@@ -301,9 +307,10 @@ export function useTaskContextActions({
|
|
|
301
307
|
setIsLoading(true);
|
|
302
308
|
try {
|
|
303
309
|
const response = await fetch(
|
|
304
|
-
`/api/v1/users/me/tasks/${task.id}/overrides
|
|
310
|
+
getTaskApiUrl(`/api/v1/users/me/tasks/${task.id}/overrides`),
|
|
305
311
|
{
|
|
306
312
|
method: 'PUT',
|
|
313
|
+
credentials: 'include',
|
|
307
314
|
headers: { 'Content-Type': 'application/json' },
|
|
308
315
|
body: JSON.stringify({
|
|
309
316
|
personally_unassigned: false,
|