@tuturuuu/ui 0.4.1 → 0.6.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 +43 -0
- package/package.json +41 -34
- package/src/components/ui/currency-input.tsx +65 -23
- package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
- package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
- package/src/components/ui/custom/combobox.test.tsx +141 -0
- package/src/components/ui/custom/combobox.tsx +105 -36
- package/src/components/ui/custom/settings/task-settings.tsx +126 -0
- package/src/components/ui/custom/settings/task-sound-settings.test.tsx +146 -0
- package/src/components/ui/custom/sidebar-context.tsx +68 -6
- package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
- package/src/components/ui/finance/finance-layout.tsx +2 -4
- package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
- package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
- package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
- package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
- package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
- package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
- package/src/components/ui/finance/transactions/form-types.ts +23 -0
- package/src/components/ui/finance/transactions/form.tsx +81 -22
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +29 -18
- package/src/components/ui/finance/transactions/transaction-card.tsx +75 -43
- package/src/components/ui/finance/transactions/transfer-merge.test.ts +90 -0
- package/src/components/ui/finance/transactions/transfer-merge.ts +52 -0
- package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +219 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-amount.tsx +32 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-delete-dialog.tsx +50 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-dialog.tsx +138 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +197 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +201 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +541 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +362 -0
- package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
- package/src/components/ui/finance/wallets/columns.test.ts +56 -0
- package/src/components/ui/finance/wallets/columns.tsx +196 -43
- package/src/components/ui/finance/wallets/form.test.tsx +79 -14
- package/src/components/ui/finance/wallets/form.tsx +41 -197
- package/src/components/ui/finance/wallets/query-invalidation.ts +3 -0
- package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
- package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
- package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
- package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +71 -5
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +52 -35
- package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
- package/src/components/ui/finance/wallets/wallets-page.test.tsx +117 -36
- package/src/components/ui/finance/wallets/wallets-page.tsx +40 -64
- package/src/components/ui/storefront/accent-button.tsx +33 -0
- package/src/components/ui/storefront/cart-summary.tsx +140 -0
- package/src/components/ui/storefront/empty-listings.tsx +32 -0
- package/src/components/ui/storefront/hero-panel.tsx +70 -0
- package/src/components/ui/storefront/image-panel.tsx +40 -0
- package/src/components/ui/storefront/index.ts +12 -0
- package/src/components/ui/storefront/listing-card.tsx +129 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
- package/src/components/ui/storefront/storefront-surface.tsx +235 -0
- package/src/components/ui/storefront/types.ts +99 -0
- package/src/components/ui/storefront/utils.ts +90 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-move.test.tsx +14 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +29 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
- package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
- package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
- package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +11 -0
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
- package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +124 -7
- package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
- package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
- package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
- package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
- package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
- package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
- package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +268 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +243 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +26 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +36 -20
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +41 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +157 -102
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.test.ts +84 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +5 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +300 -172
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +959 -340
- package/src/components/ui/tu-do/shared/task-sound-effects.test.ts +189 -0
- package/src/components/ui/tu-do/shared/task-sound-effects.tsx +468 -0
- package/src/hooks/__tests__/use-task-actions.test.tsx +61 -0
- package/src/hooks/use-task-actions.ts +45 -0
- package/src/hooks/useBoardRealtime.ts +54 -1
- package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
- package/src/hooks/useTaskUserRealtime.ts +338 -0
|
@@ -7,6 +7,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
7
7
|
import { ListView } from './list-view';
|
|
8
8
|
|
|
9
9
|
const openTaskMock = vi.hoisted(() => vi.fn());
|
|
10
|
+
const openTaskByIdMock = vi.hoisted(() => vi.fn());
|
|
10
11
|
|
|
11
12
|
vi.mock('next-intl', () => ({
|
|
12
13
|
useLocale: () => 'en',
|
|
@@ -24,6 +25,7 @@ vi.mock('next/image', () => ({
|
|
|
24
25
|
vi.mock('../hooks/useTaskDialog', () => ({
|
|
25
26
|
useTaskDialog: () => ({
|
|
26
27
|
openTask: openTaskMock,
|
|
28
|
+
openTaskById: openTaskByIdMock,
|
|
27
29
|
}),
|
|
28
30
|
}));
|
|
29
31
|
|
|
@@ -104,7 +106,7 @@ const tasks: Task[] = [
|
|
|
104
106
|
},
|
|
105
107
|
];
|
|
106
108
|
|
|
107
|
-
function renderListView() {
|
|
109
|
+
function renderListView(viewTasks = tasks) {
|
|
108
110
|
const queryClient = new QueryClient({
|
|
109
111
|
defaultOptions: {
|
|
110
112
|
queries: { retry: false },
|
|
@@ -116,8 +118,9 @@ function renderListView() {
|
|
|
116
118
|
<ListView
|
|
117
119
|
boardId="board-1"
|
|
118
120
|
lists={lists}
|
|
119
|
-
tasks={
|
|
121
|
+
tasks={viewTasks}
|
|
120
122
|
workspaceId="ws-1"
|
|
123
|
+
isPersonalWorkspace={true}
|
|
121
124
|
/>
|
|
122
125
|
</QueryClientProvider>
|
|
123
126
|
);
|
|
@@ -126,6 +129,7 @@ function renderListView() {
|
|
|
126
129
|
describe('ListView task context menu', () => {
|
|
127
130
|
beforeEach(() => {
|
|
128
131
|
openTaskMock.mockReset();
|
|
132
|
+
openTaskByIdMock.mockReset();
|
|
129
133
|
});
|
|
130
134
|
|
|
131
135
|
it('opens the shared task menu from row right-click and the compact menu button', () => {
|
|
@@ -147,4 +151,53 @@ describe('ListView task context menu', () => {
|
|
|
147
151
|
fireEvent.click(screen.getByTestId('mock-task-menu-trigger-task-1'));
|
|
148
152
|
expect(screen.getByTestId('mock-task-menu-task-1')).toBeInTheDocument();
|
|
149
153
|
});
|
|
154
|
+
|
|
155
|
+
it('opens external rows through the hydrating task-by-id path immediately', () => {
|
|
156
|
+
const externalTask: Task = {
|
|
157
|
+
...tasks[0]!,
|
|
158
|
+
id: 'external-task',
|
|
159
|
+
name: 'External task',
|
|
160
|
+
list_id: 'personal-list',
|
|
161
|
+
personal_board_id: 'board-1',
|
|
162
|
+
is_personal_external: true,
|
|
163
|
+
source_workspace_id: 'source-ws',
|
|
164
|
+
source_board_id: 'source-board',
|
|
165
|
+
source_board_name: 'Source board',
|
|
166
|
+
source_list_id: 'source-list',
|
|
167
|
+
source_list_name: 'Source list',
|
|
168
|
+
} satisfies Task;
|
|
169
|
+
|
|
170
|
+
renderListView([externalTask]);
|
|
171
|
+
|
|
172
|
+
fireEvent.click(screen.getByText('External task'));
|
|
173
|
+
|
|
174
|
+
expect(openTaskByIdMock).toHaveBeenCalledWith(
|
|
175
|
+
'external-task',
|
|
176
|
+
expect.objectContaining({
|
|
177
|
+
boardId: 'source-board',
|
|
178
|
+
taskWsId: 'source-ws',
|
|
179
|
+
taskWorkspacePersonal: false,
|
|
180
|
+
initialTask: expect.objectContaining({
|
|
181
|
+
id: 'external-task',
|
|
182
|
+
list_id: 'source-list',
|
|
183
|
+
name: 'External task',
|
|
184
|
+
}),
|
|
185
|
+
initialSharedContext: expect.objectContaining({
|
|
186
|
+
boardConfig: expect.objectContaining({
|
|
187
|
+
id: 'source-board',
|
|
188
|
+
name: 'Source board',
|
|
189
|
+
ws_id: 'source-ws',
|
|
190
|
+
}),
|
|
191
|
+
availableLists: [
|
|
192
|
+
expect.objectContaining({
|
|
193
|
+
id: 'source-list',
|
|
194
|
+
name: 'Source list',
|
|
195
|
+
board_id: 'source-board',
|
|
196
|
+
}),
|
|
197
|
+
],
|
|
198
|
+
}),
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
expect(openTaskMock).not.toHaveBeenCalled();
|
|
202
|
+
});
|
|
150
203
|
});
|
|
@@ -61,6 +61,10 @@ import { useLocale, useTranslations } from 'next-intl';
|
|
|
61
61
|
import { useTheme } from 'next-themes';
|
|
62
62
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
63
63
|
import { useBulkOperations } from '../boards/boardId/kanban/bulk/bulk-operations';
|
|
64
|
+
import {
|
|
65
|
+
getTaskCardHydratingOpenOptions,
|
|
66
|
+
isExternalTaskSnapshot,
|
|
67
|
+
} from '../boards/boardId/task-card/task-card-open-options';
|
|
64
68
|
import { useTaskDialog } from '../hooks/useTaskDialog';
|
|
65
69
|
import { computeAccessibleLabelStyles } from '../utils/label-colors';
|
|
66
70
|
import { useBoardBroadcast } from './board-broadcast-context';
|
|
@@ -129,7 +133,7 @@ export function ListView({
|
|
|
129
133
|
const [openTaskMenu, setOpenTaskMenu] = useState<TaskMenuState | null>(null);
|
|
130
134
|
const previousWorkspaceIdRef = useRef(workspaceId);
|
|
131
135
|
const previousBoardIdRef = useRef(boardId);
|
|
132
|
-
const { openTask } = useTaskDialog();
|
|
136
|
+
const { openTask, openTaskById } = useTaskDialog();
|
|
133
137
|
|
|
134
138
|
// Infinite scroll
|
|
135
139
|
const [displayCount, setDisplayCount] = useState(50);
|
|
@@ -277,21 +281,24 @@ export function ListView({
|
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
function openTaskFromRow(task: Task) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
284
|
+
if (isExternalTaskSnapshot(task)) {
|
|
285
|
+
void openTaskById(
|
|
286
|
+
task.id,
|
|
287
|
+
getTaskCardHydratingOpenOptions({
|
|
288
|
+
task,
|
|
289
|
+
boardId,
|
|
290
|
+
availableLists: lists,
|
|
291
|
+
effectiveWorkspaceId: workspaceId,
|
|
292
|
+
isPersonalWorkspace,
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
openTask(task, boardId, lists, false, {
|
|
299
|
+
taskWsId: workspaceId,
|
|
300
|
+
taskWorkspacePersonal: isPersonalWorkspace,
|
|
301
|
+
});
|
|
295
302
|
}
|
|
296
303
|
|
|
297
304
|
// Infinite scroll handler
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { getCurrentUserProfile } from '@tuturuuu/internal-api';
|
|
5
5
|
import { getWorkspaceTask } from '@tuturuuu/internal-api/tasks';
|
|
6
|
+
import { getUserConfig } from '@tuturuuu/internal-api/users';
|
|
6
7
|
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
7
8
|
import { useSearchParams } from 'next/navigation';
|
|
8
9
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
@@ -12,6 +13,10 @@ import {
|
|
|
12
13
|
WorkspacePresenceProvider,
|
|
13
14
|
} from '../providers/workspace-presence-provider';
|
|
14
15
|
import { dispatchRecentSidebarVisit } from './recent-sidebar-events';
|
|
16
|
+
import {
|
|
17
|
+
normalizeTaskDialogPresentation,
|
|
18
|
+
TASK_DIALOG_DEFAULT_PRESENTATION_CONFIG_ID,
|
|
19
|
+
} from './task-dialog-presentation';
|
|
15
20
|
import { TaskEditDialog } from './task-edit-dialog';
|
|
16
21
|
import {
|
|
17
22
|
dispatchTaskOpenResult,
|
|
@@ -146,19 +151,23 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
146
151
|
// Read draft mode preference from user config (same query key as useUserBooleanConfig)
|
|
147
152
|
const { data: draftModeRaw } = useQuery({
|
|
148
153
|
queryKey: ['user-config', 'TASK_DRAFT_MODE_ENABLED'],
|
|
149
|
-
queryFn: async () =>
|
|
150
|
-
|
|
151
|
-
'/api/v1/users/me/configs/TASK_DRAFT_MODE_ENABLED',
|
|
152
|
-
{ cache: 'no-store' }
|
|
153
|
-
);
|
|
154
|
-
if (!res.ok) return 'false';
|
|
155
|
-
const data = await res.json();
|
|
156
|
-
return (data.value as string) ?? 'false';
|
|
157
|
-
},
|
|
154
|
+
queryFn: async () =>
|
|
155
|
+
(await getUserConfig('TASK_DRAFT_MODE_ENABLED')).value ?? 'false',
|
|
158
156
|
staleTime: 5 * 60 * 1000,
|
|
159
157
|
});
|
|
160
158
|
const draftModeEnabled = draftModeRaw === 'true';
|
|
161
159
|
|
|
160
|
+
const { data: defaultPresentationRaw } = useQuery({
|
|
161
|
+
queryKey: ['user-config', TASK_DIALOG_DEFAULT_PRESENTATION_CONFIG_ID],
|
|
162
|
+
queryFn: async () =>
|
|
163
|
+
(await getUserConfig(TASK_DIALOG_DEFAULT_PRESENTATION_CONFIG_ID)).value ??
|
|
164
|
+
'compact',
|
|
165
|
+
staleTime: 5 * 60 * 1000,
|
|
166
|
+
});
|
|
167
|
+
const defaultPresentation = normalizeTaskDialogPresentation(
|
|
168
|
+
defaultPresentationRaw
|
|
169
|
+
);
|
|
170
|
+
|
|
162
171
|
const handleClose = () => {
|
|
163
172
|
triggerClose();
|
|
164
173
|
};
|
|
@@ -226,30 +235,12 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
226
235
|
|
|
227
236
|
const openTaskFromCurrentWorkspace = useCallback(
|
|
228
237
|
async (taskId: string) => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
...init,
|
|
234
|
-
cache: 'no-store',
|
|
235
|
-
}),
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const boardId = task.board_id;
|
|
239
|
-
if (!boardId) {
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
openTask(task as Task, boardId, undefined, false, {
|
|
244
|
-
taskWsId: wsId,
|
|
245
|
-
taskWorkspacePersonal: isPersonalWorkspace,
|
|
246
|
-
});
|
|
247
|
-
return true;
|
|
248
|
-
} catch {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
238
|
+
return openTaskById(taskId, {
|
|
239
|
+
taskWsId: wsId,
|
|
240
|
+
taskWorkspacePersonal: isPersonalWorkspace,
|
|
241
|
+
});
|
|
251
242
|
},
|
|
252
|
-
[isPersonalWorkspace,
|
|
243
|
+
[isPersonalWorkspace, openTaskById, wsId]
|
|
253
244
|
);
|
|
254
245
|
|
|
255
246
|
useEffect(() => {
|
|
@@ -269,40 +260,12 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
269
260
|
};
|
|
270
261
|
|
|
271
262
|
void (async () => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
...init,
|
|
280
|
-
cache: 'no-store',
|
|
281
|
-
}
|
|
282
|
-
),
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
const taskWithList = task as {
|
|
286
|
-
board_id?: string | null;
|
|
287
|
-
list?: {
|
|
288
|
-
board_id?: string | null;
|
|
289
|
-
} | null;
|
|
290
|
-
};
|
|
291
|
-
const boardId =
|
|
292
|
-
taskWithList.board_id || taskWithList.list?.board_id;
|
|
293
|
-
if (boardId) {
|
|
294
|
-
openTask(task as Task, boardId, undefined, false, {
|
|
295
|
-
taskWsId: requestedWsId,
|
|
296
|
-
});
|
|
297
|
-
emitOpenResult(true);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
} catch {
|
|
301
|
-
// Fall through to the generic current-user lookup below.
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const opened = await openTaskById(taskId);
|
|
263
|
+
const opened = await openTaskById(taskId, {
|
|
264
|
+
taskWsId: requestedWsId,
|
|
265
|
+
taskWorkspacePersonal: requestedWsId
|
|
266
|
+
? undefined
|
|
267
|
+
: isPersonalWorkspace,
|
|
268
|
+
});
|
|
306
269
|
emitOpenResult(opened);
|
|
307
270
|
})();
|
|
308
271
|
};
|
|
@@ -318,7 +281,7 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
318
281
|
handleTaskOpenRequest as EventListener
|
|
319
282
|
);
|
|
320
283
|
};
|
|
321
|
-
}, [
|
|
284
|
+
}, [isPersonalWorkspace, openTaskById]);
|
|
322
285
|
|
|
323
286
|
useEffect(() => {
|
|
324
287
|
const canonicalTaskId = searchParams.get('task');
|
|
@@ -348,7 +311,10 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
348
311
|
return;
|
|
349
312
|
}
|
|
350
313
|
|
|
351
|
-
void openTaskById(legacyTaskId
|
|
314
|
+
void openTaskById(legacyTaskId, {
|
|
315
|
+
taskWsId: wsId,
|
|
316
|
+
taskWorkspacePersonal: isPersonalWorkspace,
|
|
317
|
+
});
|
|
352
318
|
const nextSearchParams = new URLSearchParams(searchParams.toString());
|
|
353
319
|
nextSearchParams.delete('openTaskId');
|
|
354
320
|
|
|
@@ -358,7 +324,7 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
358
324
|
: window.location.pathname;
|
|
359
325
|
|
|
360
326
|
window.history.replaceState(window.history.state, '', nextUrl);
|
|
361
|
-
}, [openTaskById, searchParams]);
|
|
327
|
+
}, [isPersonalWorkspace, openTaskById, searchParams, wsId]);
|
|
362
328
|
|
|
363
329
|
// Open subtask creation dialog for the current task
|
|
364
330
|
const handleAddSubtask = useCallback(() => {
|
|
@@ -411,6 +377,31 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
411
377
|
const handleAddBlockedByTask = () => handleAddRelationship('blocked-by');
|
|
412
378
|
const handleAddRelatedTask = () => handleAddRelationship('related');
|
|
413
379
|
|
|
380
|
+
const handleRetryTaskLoad = useCallback(() => {
|
|
381
|
+
if (!state.task?.id) return;
|
|
382
|
+
|
|
383
|
+
void openTaskById(state.task.id, {
|
|
384
|
+
initialTask: state.task,
|
|
385
|
+
boardId: state.boardId,
|
|
386
|
+
availableLists: state.availableLists,
|
|
387
|
+
fakeTaskUrl: state.fakeTaskUrl,
|
|
388
|
+
taskWsId: state.taskWsId,
|
|
389
|
+
taskWorkspacePersonal: state.taskWorkspacePersonal,
|
|
390
|
+
taskWorkspaceTier: state.taskWorkspaceTier,
|
|
391
|
+
initialSharedContext: state.initialSharedContext,
|
|
392
|
+
});
|
|
393
|
+
}, [
|
|
394
|
+
openTaskById,
|
|
395
|
+
state.initialSharedContext,
|
|
396
|
+
state.availableLists,
|
|
397
|
+
state.boardId,
|
|
398
|
+
state.fakeTaskUrl,
|
|
399
|
+
state.task,
|
|
400
|
+
state.taskWorkspacePersonal,
|
|
401
|
+
state.taskWorkspaceTier,
|
|
402
|
+
state.taskWsId,
|
|
403
|
+
]);
|
|
404
|
+
|
|
414
405
|
// Track presence location when the dialog is open in edit mode.
|
|
415
406
|
// On kanban boards, BoardUserPresenceAvatarsComponent also calls updateLocation
|
|
416
407
|
// with the same args — this is idempotent (same location = no-op).
|
|
@@ -419,7 +410,14 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
419
410
|
const wsUpdateLocation = wsPresence?.updateLocation;
|
|
420
411
|
|
|
421
412
|
useEffect(() => {
|
|
422
|
-
if (
|
|
413
|
+
if (
|
|
414
|
+
!wsUpdateLocation ||
|
|
415
|
+
!state.isOpen ||
|
|
416
|
+
state.mode === 'create' ||
|
|
417
|
+
state.isHydratingTask ||
|
|
418
|
+
state.taskLoadError
|
|
419
|
+
)
|
|
420
|
+
return;
|
|
423
421
|
const taskId = state.task?.id;
|
|
424
422
|
const boardId = state.boardId;
|
|
425
423
|
if (!taskId || !boardId) return;
|
|
@@ -433,6 +431,8 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
433
431
|
wsUpdateLocation,
|
|
434
432
|
state.isOpen,
|
|
435
433
|
state.mode,
|
|
434
|
+
state.isHydratingTask,
|
|
435
|
+
state.taskLoadError,
|
|
436
436
|
state.task?.id,
|
|
437
437
|
state.boardId,
|
|
438
438
|
]);
|
|
@@ -441,7 +441,9 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
441
441
|
if (
|
|
442
442
|
typeof window === 'undefined' ||
|
|
443
443
|
!state.isOpen ||
|
|
444
|
-
state.mode === 'create'
|
|
444
|
+
state.mode === 'create' ||
|
|
445
|
+
state.isHydratingTask ||
|
|
446
|
+
state.taskLoadError
|
|
445
447
|
) {
|
|
446
448
|
return;
|
|
447
449
|
}
|
|
@@ -493,19 +495,24 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
493
495
|
}, [
|
|
494
496
|
state.boardId,
|
|
495
497
|
state.isOpen,
|
|
498
|
+
state.isHydratingTask,
|
|
496
499
|
state.mode,
|
|
497
500
|
state.task,
|
|
498
501
|
state.taskWsId,
|
|
499
502
|
state.taskWorkspacePersonal,
|
|
503
|
+
state.taskLoadError,
|
|
500
504
|
isPersonalWorkspace,
|
|
501
505
|
wsId,
|
|
502
506
|
]);
|
|
503
507
|
|
|
504
|
-
// Determine if the task needs its own presence provider (cross-workspace tasks)
|
|
508
|
+
// Determine if the task needs its own presence provider (cross-workspace tasks).
|
|
509
|
+
// Keep the provider shell mounted from the initial snapshot when taskWsId is
|
|
510
|
+
// already known, otherwise hydration can wrap the open dialog in a new
|
|
511
|
+
// provider and Radix replays the compact dialog entrance animation.
|
|
505
512
|
const needsOwnProvider =
|
|
506
|
-
state.realtimeEnabled
|
|
507
|
-
|
|
508
|
-
|
|
513
|
+
state.taskWsId && (!wsPresence?.realtimeEnabled || state.taskWsId !== wsId);
|
|
514
|
+
const ownProviderEnabled =
|
|
515
|
+
!!state.realtimeEnabled && !state.taskWorkspacePersonal;
|
|
509
516
|
|
|
510
517
|
if (!state.isOpen || !state.task) {
|
|
511
518
|
return null;
|
|
@@ -519,16 +526,25 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
519
526
|
boardId={state.boardId || ''}
|
|
520
527
|
isOpen={state.isOpen}
|
|
521
528
|
availableLists={state.availableLists}
|
|
529
|
+
sharedContext={
|
|
530
|
+
state.isHydratingTask || state.taskLoadError
|
|
531
|
+
? state.initialSharedContext
|
|
532
|
+
: undefined
|
|
533
|
+
}
|
|
522
534
|
filters={state.filters}
|
|
523
535
|
mode={state.mode}
|
|
524
536
|
collaborationMode={state.collaborationMode}
|
|
525
537
|
realtimeEnabled={state.realtimeEnabled}
|
|
538
|
+
isHydratingTask={state.isHydratingTask}
|
|
539
|
+
taskLoadError={state.taskLoadError}
|
|
540
|
+
taskHydrationVersion={state.taskHydrationVersion}
|
|
526
541
|
isPersonalWorkspace={isPersonalWorkspace}
|
|
527
542
|
parentTaskId={state.parentTaskId}
|
|
528
543
|
parentTaskName={state.parentTaskName}
|
|
529
544
|
pendingRelationship={state.pendingRelationship}
|
|
530
545
|
currentUser={currentUser || undefined}
|
|
531
546
|
draftModeEnabled={draftModeEnabled}
|
|
547
|
+
defaultPresentation={defaultPresentation}
|
|
532
548
|
draftId={state.draftId}
|
|
533
549
|
onClose={handleClose}
|
|
534
550
|
onUpdate={triggerUpdate}
|
|
@@ -538,6 +554,7 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
538
554
|
onAddBlockingTask={handleAddBlockingTask}
|
|
539
555
|
onAddBlockedByTask={handleAddBlockedByTask}
|
|
540
556
|
onAddRelatedTask={handleAddRelatedTask}
|
|
557
|
+
onRetryTaskLoad={handleRetryTaskLoad}
|
|
541
558
|
/>
|
|
542
559
|
);
|
|
543
560
|
|
|
@@ -546,7 +563,7 @@ export function TaskDialogManager({ wsId }: { wsId: string }) {
|
|
|
546
563
|
<WorkspacePresenceProvider
|
|
547
564
|
wsId={state.taskWsId}
|
|
548
565
|
tier={state.taskWorkspaceTier ?? null}
|
|
549
|
-
enabled={
|
|
566
|
+
enabled={ownProviderEnabled}
|
|
550
567
|
>
|
|
551
568
|
{dialog}
|
|
552
569
|
</WorkspacePresenceProvider>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const TASK_DIALOG_DEFAULT_PRESENTATION_CONFIG_ID =
|
|
2
|
+
'TASK_DIALOG_DEFAULT_PRESENTATION';
|
|
3
|
+
|
|
4
|
+
export type TaskDialogPresentation = 'compact' | 'fullscreen';
|
|
5
|
+
|
|
6
|
+
export function normalizeTaskDialogPresentation(
|
|
7
|
+
value: unknown,
|
|
8
|
+
fallback: TaskDialogPresentation = 'compact'
|
|
9
|
+
): TaskDialogPresentation {
|
|
10
|
+
return value === 'fullscreen' || value === 'compact' ? value : fallback;
|
|
11
|
+
}
|