@tuturuuu/ui 0.1.0 → 0.3.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 +71 -0
- package/package.json +82 -70
- package/src/components/ui/__tests__/avatar.test.tsx +8 -5
- package/src/components/ui/calendar-app/components/calendar-connections-compact.tsx +414 -0
- package/src/components/ui/calendar-app/components/calendar-connections-manager.tsx +5 -1
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +529 -0
- package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +26 -1429
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +711 -0
- package/src/components/ui/chart.test.tsx +29 -0
- package/src/components/ui/chart.tsx +12 -3
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +43 -13
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +138 -74
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +70 -0
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +60 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +13 -5
- package/src/components/ui/chat/chat-sidebar-panel.test.tsx +110 -0
- package/src/components/ui/chat/chat-sidebar-panel.tsx +13 -3
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +24 -1
- package/src/components/ui/custom/__tests__/tuturuuu-logo.test.ts +12 -3
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +39 -0
- package/src/components/ui/custom/common-footer.tsx +16 -1
- package/src/components/ui/custom/production-indicator.tsx +1 -1
- package/src/components/ui/custom/settings/sidebar-settings.tsx +1 -1
- package/src/components/ui/custom/settings/task-settings.tsx +18 -0
- package/src/components/ui/custom/settings-dialog-shell.tsx +38 -23
- package/src/components/ui/custom/sidebar-context-compile-graph.test.ts +60 -0
- package/src/components/ui/custom/sidebar-context.tsx +61 -61
- package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +123 -0
- package/src/components/ui/custom/tuturuuu-logo-urls.ts +6 -0
- package/src/components/ui/custom/tuturuuu-logo.tsx +25 -7
- package/src/components/ui/custom/workspace-select-helpers.ts +20 -0
- package/src/components/ui/custom/workspace-select.tsx +33 -12
- package/src/components/ui/finance/invoices/components/invoice-checkout-summary.tsx +7 -1
- package/src/components/ui/finance/invoices/components/invoice-payment-settings.tsx +3 -0
- package/src/components/ui/finance/invoices/components/invoice-products-permission-warning.tsx +58 -0
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +12 -20
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +10 -9
- package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +10 -5
- package/src/components/ui/finance/invoices/hooks.ts +75 -20
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +137 -0
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +86 -37
- package/src/components/ui/finance/invoices/product-selection.test.tsx +8 -26
- package/src/components/ui/finance/invoices/product-selection.tsx +2 -10
- package/src/components/ui/finance/invoices/standard-invoice.tsx +88 -26
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +154 -46
- package/src/components/ui/finance/invoices/utils.test.ts +50 -0
- package/src/components/ui/finance/invoices/utils.ts +75 -17
- package/src/components/ui/finance/shared/finance-display-amount.tsx +3 -1
- package/src/components/ui/finance/shared/finance-permission-warning-dialog.test.tsx +34 -0
- package/src/components/ui/finance/shared/finance-permission-warning-dialog.tsx +157 -0
- package/src/components/ui/finance/transactions/form-basic-tab.tsx +8 -0
- package/src/components/ui/finance/transactions/form-more-tab.tsx +8 -0
- package/src/components/ui/finance/transactions/form-types.ts +2 -0
- package/src/components/ui/finance/transactions/form.test.tsx +43 -0
- package/src/components/ui/finance/transactions/form.tsx +60 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +27 -0
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +13 -1
- package/src/components/ui/finance/transactions/transactions-infinite-page.tsx +4 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +23 -1
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +5 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +9 -1
- package/src/components/ui/legacy/calendar/event-modal.tsx +146 -2
- package/src/components/ui/legacy/calendar/event-preview-popover.tsx +200 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +76 -0
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +13 -1
- package/src/components/ui/legacy/meet/page.test.ts +180 -0
- package/src/components/ui/legacy/meet/page.tsx +87 -39
- package/src/components/ui/legacy/meet/planId/page.tsx +10 -4
- package/src/components/ui/text-editor/__tests__/task-mention-chip.test.tsx +203 -6
- package/src/components/ui/text-editor/task-mention-chip.tsx +29 -7
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +79 -25
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-external-workspaces.test.tsx +392 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.test.tsx +57 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.tsx +106 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +106 -161
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-assignees.ts +96 -150
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-labels.ts +63 -79
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-projects.ts +64 -83
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +115 -155
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-utils.ts +319 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +8 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +63 -37
- package/src/components/ui/tu-do/boards/boardId/kanban/kanban-column-collapse.ts +16 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +46 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +5 -3
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +19 -7
- package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +181 -2
- package/src/components/ui/tu-do/boards/boardId/menus/index.ts +1 -0
- package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +463 -0
- package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-utils.ts +109 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +4 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardCheckbox.tsx +6 -3
- package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardDates.tsx +26 -9
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-checkbox-style.ts +39 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.test.ts +43 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +33 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.test.ts +31 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.test.tsx +124 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.tsx +88 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +151 -76
- package/src/components/ui/tu-do/boards/boardId/task-card/task-scheduling-badge.tsx +174 -0
- package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +34 -13
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +54 -1
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +158 -0
- package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +5 -2
- package/src/components/ui/tu-do/shared/board-client.tsx +12 -2
- package/src/components/ui/tu-do/shared/board-views.tsx +195 -328
- package/src/components/ui/tu-do/shared/list-view.tsx +18 -8
- package/src/components/ui/tu-do/shared/task-due-date-visibility.test.ts +72 -0
- package/src/components/ui/tu-do/shared/task-due-date-visibility.ts +38 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-realtime-sync.test.tsx +37 -9
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +6 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +89 -70
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +2 -2
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +33 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +74 -3
- package/src/hooks/__tests__/use-task-actions.test.tsx +118 -0
- package/src/hooks/__tests__/use-user-config.test.tsx +65 -0
- package/src/hooks/__tests__/use-workspace-presence.test.tsx +1 -1
- package/src/hooks/use-calendar-sync.tsx +22 -277
- package/src/hooks/use-calendar.tsx +95 -525
- package/src/hooks/use-task-actions.ts +43 -117
- package/src/hooks/use-user-config.ts +1 -1
- package/src/hooks/use-workspace-config.ts +6 -2
- package/src/hooks/use-workspace-presence.ts +1 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-bar.tsx +0 -94
|
@@ -102,15 +102,20 @@ vi.mock('../../sonner', () => ({
|
|
|
102
102
|
},
|
|
103
103
|
}));
|
|
104
104
|
|
|
105
|
-
function
|
|
106
|
-
|
|
105
|
+
function createTestQueryClient() {
|
|
106
|
+
return new QueryClient({
|
|
107
107
|
defaultOptions: {
|
|
108
108
|
queries: {
|
|
109
109
|
retry: false,
|
|
110
110
|
},
|
|
111
111
|
},
|
|
112
112
|
});
|
|
113
|
+
}
|
|
113
114
|
|
|
115
|
+
function renderWithQueryClient(
|
|
116
|
+
children: ReactNode,
|
|
117
|
+
queryClient = createTestQueryClient()
|
|
118
|
+
) {
|
|
114
119
|
return render(
|
|
115
120
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
116
121
|
);
|
|
@@ -217,7 +222,112 @@ describe('TaskMentionChip', () => {
|
|
|
217
222
|
expect(screen.getByText('Complete Team Charter V1.0')).toBeInTheDocument();
|
|
218
223
|
});
|
|
219
224
|
|
|
220
|
-
it('
|
|
225
|
+
it('does not reuse stale mention fallback cache across board contexts', async () => {
|
|
226
|
+
const boardOneTask = {
|
|
227
|
+
assignees: [],
|
|
228
|
+
board_id: 'board-1',
|
|
229
|
+
display_number: 3,
|
|
230
|
+
id: 'board-one-task-id',
|
|
231
|
+
labels: [],
|
|
232
|
+
list_id: 'list-1',
|
|
233
|
+
name: 'Board one task',
|
|
234
|
+
projects: [],
|
|
235
|
+
ticket_prefix: null,
|
|
236
|
+
};
|
|
237
|
+
const boardTwoTask = {
|
|
238
|
+
assignees: [],
|
|
239
|
+
board_id: 'board-2',
|
|
240
|
+
display_number: 4,
|
|
241
|
+
id: 'board-two-task-id',
|
|
242
|
+
labels: [],
|
|
243
|
+
list_id: 'list-2',
|
|
244
|
+
name: 'Board two task',
|
|
245
|
+
projects: [],
|
|
246
|
+
ticket_prefix: null,
|
|
247
|
+
};
|
|
248
|
+
const queryClient = createTestQueryClient();
|
|
249
|
+
const firstResolved = vi.fn();
|
|
250
|
+
const secondResolved = vi.fn();
|
|
251
|
+
|
|
252
|
+
mocks.getCurrentUserTask.mockImplementation(async (taskId: string) => {
|
|
253
|
+
if (taskId === 'stale-task-id') {
|
|
254
|
+
throw new Error('Task not found');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const task =
|
|
258
|
+
taskId === boardOneTask.id
|
|
259
|
+
? boardOneTask
|
|
260
|
+
: taskId === boardTwoTask.id
|
|
261
|
+
? boardTwoTask
|
|
262
|
+
: null;
|
|
263
|
+
|
|
264
|
+
if (!task) {
|
|
265
|
+
throw new Error('Task not found');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
availableLists: [],
|
|
270
|
+
task,
|
|
271
|
+
taskWorkspacePersonal: false,
|
|
272
|
+
taskWorkspaceTier: 'FREE',
|
|
273
|
+
taskWsId: 'ws-1',
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
mocks.listWorkspaceTasks.mockImplementation(
|
|
277
|
+
async (_wsId: string, params: { boardId?: string }) => ({
|
|
278
|
+
tasks: params.boardId === 'board-2' ? [boardTwoTask] : [boardOneTask],
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const firstRender = renderWithQueryClient(
|
|
283
|
+
<TaskMentionChip
|
|
284
|
+
entityId="stale-task-id"
|
|
285
|
+
displayNumber="3"
|
|
286
|
+
subtitle="Board one task"
|
|
287
|
+
onResolvedTaskMention={firstResolved}
|
|
288
|
+
/>,
|
|
289
|
+
queryClient
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
await waitFor(() => {
|
|
293
|
+
expect(firstResolved).toHaveBeenCalledWith(
|
|
294
|
+
expect.objectContaining({ entityId: boardOneTask.id })
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
firstRender.unmount();
|
|
299
|
+
window.history.pushState({}, '', '/ws-1/tasks/boards/board-2');
|
|
300
|
+
|
|
301
|
+
renderWithQueryClient(
|
|
302
|
+
<TaskMentionChip
|
|
303
|
+
entityId="stale-task-id"
|
|
304
|
+
displayNumber="4"
|
|
305
|
+
subtitle="Board two task"
|
|
306
|
+
onResolvedTaskMention={secondResolved}
|
|
307
|
+
/>,
|
|
308
|
+
queryClient
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
await waitFor(() => {
|
|
312
|
+
expect(mocks.listWorkspaceTasks).toHaveBeenCalledWith(
|
|
313
|
+
'ws-1',
|
|
314
|
+
expect.objectContaining({
|
|
315
|
+
boardId: 'board-2',
|
|
316
|
+
identifier: '4',
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
await waitFor(() => {
|
|
321
|
+
expect(secondResolved).toHaveBeenCalledWith(
|
|
322
|
+
expect.objectContaining({ entityId: boardTwoTask.id })
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
expect(secondResolved).not.toHaveBeenCalledWith(
|
|
326
|
+
expect.objectContaining({ entityId: boardOneTask.id })
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('ignores untrusted mention workspace when repairing stale task mentions', async () => {
|
|
221
331
|
const resolvedTask = {
|
|
222
332
|
assignees: [],
|
|
223
333
|
board_id: 'board-1',
|
|
@@ -242,7 +352,7 @@ describe('TaskMentionChip', () => {
|
|
|
242
352
|
task: resolvedTask,
|
|
243
353
|
taskWorkspacePersonal: false,
|
|
244
354
|
taskWorkspaceTier: 'FREE',
|
|
245
|
-
taskWsId: '
|
|
355
|
+
taskWsId: 'route-ws',
|
|
246
356
|
};
|
|
247
357
|
});
|
|
248
358
|
mocks.listWorkspaceTasks.mockResolvedValue({ tasks: [resolvedTask] });
|
|
@@ -259,7 +369,7 @@ describe('TaskMentionChip', () => {
|
|
|
259
369
|
|
|
260
370
|
await waitFor(() => {
|
|
261
371
|
expect(mocks.listWorkspaceTasks).toHaveBeenCalledWith(
|
|
262
|
-
'
|
|
372
|
+
'route-ws',
|
|
263
373
|
expect.objectContaining({
|
|
264
374
|
boardId: 'board-1',
|
|
265
375
|
identifier: '7',
|
|
@@ -271,9 +381,96 @@ describe('TaskMentionChip', () => {
|
|
|
271
381
|
expect(onResolvedTaskMention).toHaveBeenCalledWith(
|
|
272
382
|
expect.objectContaining({
|
|
273
383
|
entityId: 'source-task-id',
|
|
274
|
-
workspaceId: '
|
|
384
|
+
workspaceId: 'route-ws',
|
|
275
385
|
})
|
|
276
386
|
);
|
|
277
387
|
});
|
|
278
388
|
});
|
|
389
|
+
|
|
390
|
+
it('does not rewrite stale mentions to tasks from another workspace', async () => {
|
|
391
|
+
const resolvedTask = {
|
|
392
|
+
assignees: [],
|
|
393
|
+
board_id: 'board-1',
|
|
394
|
+
display_number: 8,
|
|
395
|
+
id: 'source-task-id',
|
|
396
|
+
labels: [],
|
|
397
|
+
list_id: 'list-1',
|
|
398
|
+
name: 'Source workspace task',
|
|
399
|
+
projects: [],
|
|
400
|
+
ticket_prefix: null,
|
|
401
|
+
};
|
|
402
|
+
const onResolvedTaskMention = vi.fn();
|
|
403
|
+
|
|
404
|
+
window.history.pushState({}, '', '/route-ws/tasks/boards/board-1');
|
|
405
|
+
mocks.getCurrentUserTask.mockImplementation(async (taskId: string) => {
|
|
406
|
+
if (taskId === 'stale-task-id') {
|
|
407
|
+
throw new Error('Task not found');
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
availableLists: [],
|
|
412
|
+
task: resolvedTask,
|
|
413
|
+
taskWorkspacePersonal: false,
|
|
414
|
+
taskWorkspaceTier: 'FREE',
|
|
415
|
+
taskWsId: 'source-ws',
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
mocks.listWorkspaceTasks.mockResolvedValue({ tasks: [resolvedTask] });
|
|
419
|
+
|
|
420
|
+
renderWithQueryClient(
|
|
421
|
+
<TaskMentionChip
|
|
422
|
+
entityId="stale-task-id"
|
|
423
|
+
displayNumber="8"
|
|
424
|
+
subtitle="Source workspace task"
|
|
425
|
+
onResolvedTaskMention={onResolvedTaskMention}
|
|
426
|
+
/>
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
await waitFor(() => {
|
|
430
|
+
expect(mocks.getCurrentUserTask).toHaveBeenCalledWith('source-task-id');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
expect(onResolvedTaskMention).not.toHaveBeenCalled();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('uses the server-returned workspace for resolved cross-workspace task mentions', async () => {
|
|
437
|
+
const resolvedTask = {
|
|
438
|
+
assignees: [],
|
|
439
|
+
board_id: 'source-board',
|
|
440
|
+
display_number: 9,
|
|
441
|
+
id: 'source-task-id',
|
|
442
|
+
labels: [],
|
|
443
|
+
list_id: 'list-1',
|
|
444
|
+
name: 'Trusted cross workspace task',
|
|
445
|
+
projects: [],
|
|
446
|
+
ticket_prefix: null,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
window.history.pushState({}, '', '/route-ws/tasks/boards/board-1');
|
|
450
|
+
mocks.getCurrentUserTask.mockResolvedValue({
|
|
451
|
+
availableLists: [],
|
|
452
|
+
task: resolvedTask,
|
|
453
|
+
taskWorkspacePersonal: false,
|
|
454
|
+
taskWorkspaceTier: 'FREE',
|
|
455
|
+
taskWsId: 'source-ws',
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
renderWithQueryClient(
|
|
459
|
+
<TaskMentionChip
|
|
460
|
+
entityId="source-task-id"
|
|
461
|
+
displayNumber="9"
|
|
462
|
+
subtitle="Trusted cross workspace task"
|
|
463
|
+
workspaceId="source-ws"
|
|
464
|
+
/>
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
await waitFor(() => {
|
|
468
|
+
expect(mocks.getCurrentUserTask).toHaveBeenCalledWith('source-task-id');
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
expect(mocks.listWorkspaceTasks).not.toHaveBeenCalled();
|
|
472
|
+
expect(
|
|
473
|
+
screen.getByText('Trusted cross workspace task')
|
|
474
|
+
).toBeInTheDocument();
|
|
475
|
+
});
|
|
279
476
|
});
|
|
@@ -186,7 +186,6 @@ export function TaskMentionChip({
|
|
|
186
186
|
displayNumber,
|
|
187
187
|
avatarUrl,
|
|
188
188
|
subtitle,
|
|
189
|
-
workspaceId,
|
|
190
189
|
className,
|
|
191
190
|
editor: editorProp,
|
|
192
191
|
onResolvedTaskMention,
|
|
@@ -213,8 +212,19 @@ export function TaskMentionChip({
|
|
|
213
212
|
|
|
214
213
|
return getRouteTaskBoardIdFromPathname(window.location.pathname);
|
|
215
214
|
}, []);
|
|
216
|
-
const
|
|
217
|
-
const
|
|
215
|
+
const resolutionWorkspaceId = routeWsId;
|
|
216
|
+
const taskMentionQueryKey = useMemo(
|
|
217
|
+
() => [
|
|
218
|
+
'task',
|
|
219
|
+
'mention',
|
|
220
|
+
entityId,
|
|
221
|
+
resolutionWorkspaceId ?? null,
|
|
222
|
+
routeBoardId ?? null,
|
|
223
|
+
displayNumber.trim(),
|
|
224
|
+
subtitle?.trim() ?? null,
|
|
225
|
+
],
|
|
226
|
+
[displayNumber, entityId, resolutionWorkspaceId, routeBoardId, subtitle]
|
|
227
|
+
);
|
|
218
228
|
const isDark = useDomResolvedTheme() === 'dark';
|
|
219
229
|
|
|
220
230
|
// Dialog states
|
|
@@ -230,7 +240,7 @@ export function TaskMentionChip({
|
|
|
230
240
|
isLoading: taskLoading,
|
|
231
241
|
error: taskError,
|
|
232
242
|
} = useQuery({
|
|
233
|
-
queryKey:
|
|
243
|
+
queryKey: taskMentionQueryKey,
|
|
234
244
|
queryFn: async () => {
|
|
235
245
|
return resolveTaskMentionPayload({
|
|
236
246
|
displayNumber,
|
|
@@ -249,9 +259,19 @@ export function TaskMentionChip({
|
|
|
249
259
|
const resolvedTaskId = task?.id ?? entityId;
|
|
250
260
|
const taskWorkspaceId = taskPayload?.taskWsId;
|
|
251
261
|
const taskWorkspacePersonal = taskPayload?.taskWorkspacePersonal;
|
|
252
|
-
const canonicalWorkspaceId =
|
|
253
|
-
mentionWorkspaceId ?? taskWorkspaceId ?? routeWsId;
|
|
262
|
+
const canonicalWorkspaceId = taskWorkspaceId ?? routeWsId;
|
|
254
263
|
const boardWorkspaceId = canonicalWorkspaceId;
|
|
264
|
+
const resolvedTaskBelongsToRouteWorkspace = useMemo(() => {
|
|
265
|
+
if (!routeWsId || !taskWorkspaceId) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (taskWorkspaceId === routeWsId) {
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return routeWsId === 'personal' && taskWorkspacePersonal === true;
|
|
274
|
+
}, [routeWsId, taskWorkspaceId, taskWorkspacePersonal]);
|
|
255
275
|
|
|
256
276
|
// Get board config - only fetch when menu opens and we have task data
|
|
257
277
|
const { data: boardConfig } = useBoardConfig(
|
|
@@ -394,6 +414,7 @@ export function TaskMentionChip({
|
|
|
394
414
|
}, [availableLists, task]);
|
|
395
415
|
|
|
396
416
|
const handleUpdate = useCallback(() => {
|
|
417
|
+
queryClient.invalidateQueries({ queryKey: ['task', 'mention'] });
|
|
397
418
|
queryClient.invalidateQueries({ queryKey: ['task', entityId] });
|
|
398
419
|
if (resolvedTaskId !== entityId) {
|
|
399
420
|
queryClient.invalidateQueries({ queryKey: ['task', resolvedTaskId] });
|
|
@@ -668,7 +689,7 @@ export function TaskMentionChip({
|
|
|
668
689
|
}, [task?.name, subtitle]);
|
|
669
690
|
|
|
670
691
|
useEffect(() => {
|
|
671
|
-
if (!task || task.id === entityId) {
|
|
692
|
+
if (!task || task.id === entityId || !resolvedTaskBelongsToRouteWorkspace) {
|
|
672
693
|
return;
|
|
673
694
|
}
|
|
674
695
|
|
|
@@ -697,6 +718,7 @@ export function TaskMentionChip({
|
|
|
697
718
|
entityId,
|
|
698
719
|
onResolvedTaskMention,
|
|
699
720
|
queryClient,
|
|
721
|
+
resolvedTaskBelongsToRouteWorkspace,
|
|
700
722
|
subtitle,
|
|
701
723
|
task,
|
|
702
724
|
taskPayload,
|
|
@@ -40,8 +40,13 @@ import {
|
|
|
40
40
|
type ListPaginationState,
|
|
41
41
|
useProgressiveLoader,
|
|
42
42
|
} from '../../shared/progressive-loader-context';
|
|
43
|
+
import { getListTextColorClass } from '../../utils/taskColorUtils';
|
|
43
44
|
import { normalizeBoardText } from './board-text-utils';
|
|
44
45
|
import type { DragPreviewPosition } from './kanban/dnd/use-kanban-dnd';
|
|
46
|
+
import {
|
|
47
|
+
isClosedTaskListColumnCollapsed,
|
|
48
|
+
isKanbanColumnCollapsed,
|
|
49
|
+
} from './kanban/kanban-column-collapse';
|
|
45
50
|
import { ListActions } from './list-actions';
|
|
46
51
|
import { statusIcons } from './status-section';
|
|
47
52
|
import type { TaskFilters } from './task-filter';
|
|
@@ -168,6 +173,7 @@ interface BoardColumnProps {
|
|
|
168
173
|
workspaceId?: string;
|
|
169
174
|
wsId: string;
|
|
170
175
|
onExternalTasksCollapsedChange?: (collapsed: boolean) => void;
|
|
176
|
+
onTaskListCollapsedChange?: (listId: string, collapsed: boolean) => void;
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
export function BoardColumn({
|
|
@@ -193,6 +199,7 @@ export function BoardColumn({
|
|
|
193
199
|
workspaceId,
|
|
194
200
|
wsId,
|
|
195
201
|
onExternalTasksCollapsedChange,
|
|
202
|
+
onTaskListCollapsedChange,
|
|
196
203
|
}: BoardColumnProps) {
|
|
197
204
|
const t = useTranslations('common');
|
|
198
205
|
const tTasks = useTranslations('ws-tasks');
|
|
@@ -211,6 +218,8 @@ export function BoardColumn({
|
|
|
211
218
|
const [externalSortBy, setExternalSortBy] = useState<ExternalTaskSortBy>(
|
|
212
219
|
DEFAULT_EXTERNAL_TASK_SORT_BY
|
|
213
220
|
);
|
|
221
|
+
const isClosedCollapsed = isClosedTaskListColumnCollapsed(column);
|
|
222
|
+
const isColumnCollapsed = isKanbanColumnCollapsed(column);
|
|
214
223
|
const hasActiveFilters =
|
|
215
224
|
!!filters &&
|
|
216
225
|
(filters.labels.length > 0 ||
|
|
@@ -291,7 +300,7 @@ export function BoardColumn({
|
|
|
291
300
|
useEffect(() => {
|
|
292
301
|
if (
|
|
293
302
|
!isExternalStaging ||
|
|
294
|
-
|
|
303
|
+
isColumnCollapsed ||
|
|
295
304
|
!listState ||
|
|
296
305
|
listState.isInitialLoad ||
|
|
297
306
|
listState.isLoading ||
|
|
@@ -303,7 +312,7 @@ export function BoardColumn({
|
|
|
303
312
|
loadColumnPage(0);
|
|
304
313
|
}, [
|
|
305
314
|
externalOptionsSignature,
|
|
306
|
-
|
|
315
|
+
isColumnCollapsed,
|
|
307
316
|
isExternalStaging,
|
|
308
317
|
listState,
|
|
309
318
|
listState?.isInitialLoad,
|
|
@@ -315,7 +324,7 @@ export function BoardColumn({
|
|
|
315
324
|
// cache was cleared, refetch page 0 for this list so cards reappear.
|
|
316
325
|
useEffect(() => {
|
|
317
326
|
if (
|
|
318
|
-
|
|
327
|
+
isColumnCollapsed ||
|
|
319
328
|
!listState ||
|
|
320
329
|
listState.isLoading ||
|
|
321
330
|
hasActiveFilters
|
|
@@ -334,7 +343,7 @@ export function BoardColumn({
|
|
|
334
343
|
recoveryRequestedRef.current = false;
|
|
335
344
|
}, [
|
|
336
345
|
hasActiveFilters,
|
|
337
|
-
|
|
346
|
+
isColumnCollapsed,
|
|
338
347
|
listState,
|
|
339
348
|
listState?.isLoading,
|
|
340
349
|
listState?.totalCount,
|
|
@@ -481,22 +490,45 @@ export function BoardColumn({
|
|
|
481
490
|
});
|
|
482
491
|
};
|
|
483
492
|
|
|
484
|
-
if (
|
|
493
|
+
if (isColumnCollapsed) {
|
|
494
|
+
const collapsedListName = translateListName(column.name);
|
|
495
|
+
const expandLabel = isExternalCollapsed
|
|
496
|
+
? tTasks('expand_external_tasks')
|
|
497
|
+
: tTasks('expand_task_list', { name: collapsedListName });
|
|
498
|
+
const collapsedTextColor = isExternalCollapsed
|
|
499
|
+
? 'text-dynamic-cyan'
|
|
500
|
+
: getListTextColorClass(column.color as SupportedColor);
|
|
501
|
+
|
|
485
502
|
return (
|
|
486
503
|
<Card
|
|
487
504
|
ref={composedRef}
|
|
488
505
|
style={style}
|
|
489
506
|
className={cn(
|
|
490
|
-
'group flex h-full w-14 shrink-0 snap-start flex-col items-center rounded-xl border border-
|
|
491
|
-
'touch-none select-none overflow-hidden hover:shadow-md'
|
|
507
|
+
'group flex h-full w-14 shrink-0 snap-start flex-col items-center rounded-xl border border-dashed transition-all duration-200',
|
|
508
|
+
'touch-none select-none overflow-hidden hover:shadow-md',
|
|
509
|
+
isExternalCollapsed
|
|
510
|
+
? 'border-dynamic-cyan/45 bg-dynamic-cyan/[0.035]'
|
|
511
|
+
: colorClass
|
|
492
512
|
)}
|
|
493
513
|
>
|
|
494
514
|
<button
|
|
495
515
|
type="button"
|
|
496
|
-
className=
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
516
|
+
className={cn(
|
|
517
|
+
'flex h-full w-full flex-col items-center gap-3 rounded-xl px-1 py-3 transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
518
|
+
isExternalCollapsed
|
|
519
|
+
? 'text-dynamic-cyan hover:bg-dynamic-cyan/10 focus-visible:ring-dynamic-cyan/40'
|
|
520
|
+
: `${collapsedTextColor} hover:bg-muted/40 focus-visible:ring-primary/40`
|
|
521
|
+
)}
|
|
522
|
+
title={expandLabel}
|
|
523
|
+
aria-label={expandLabel}
|
|
524
|
+
onClick={() => {
|
|
525
|
+
if (isExternalCollapsed) {
|
|
526
|
+
onExternalTasksCollapsedChange?.(false);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
onTaskListCollapsedChange?.(column.id, false);
|
|
531
|
+
}}
|
|
500
532
|
>
|
|
501
533
|
<ChevronRight className="h-4 w-4 shrink-0" />
|
|
502
534
|
<Badge
|
|
@@ -509,7 +541,7 @@ export function BoardColumn({
|
|
|
509
541
|
className="max-h-48 truncate font-medium text-[11px]"
|
|
510
542
|
style={{ writingMode: 'vertical-rl' }}
|
|
511
543
|
>
|
|
512
|
-
{
|
|
544
|
+
{collapsedListName}
|
|
513
545
|
</span>
|
|
514
546
|
</button>
|
|
515
547
|
</Card>
|
|
@@ -688,19 +720,41 @@ export function BoardColumn({
|
|
|
688
720
|
</Button>
|
|
689
721
|
</>
|
|
690
722
|
) : (
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
723
|
+
<>
|
|
724
|
+
{isClosedCollapsed || column.status === 'closed' ? (
|
|
725
|
+
<Button
|
|
726
|
+
type="button"
|
|
727
|
+
variant="ghost"
|
|
728
|
+
size="xs"
|
|
729
|
+
className={cn(
|
|
730
|
+
'h-7 w-7 p-0 hover:bg-muted/40',
|
|
731
|
+
getListTextColorClass(column.color as SupportedColor)
|
|
732
|
+
)}
|
|
733
|
+
title={tTasks('collapse_task_list', {
|
|
734
|
+
name: translateListName(column.name),
|
|
735
|
+
})}
|
|
736
|
+
aria-label={tTasks('collapse_task_list', {
|
|
737
|
+
name: translateListName(column.name),
|
|
738
|
+
})}
|
|
739
|
+
onClick={() => onTaskListCollapsedChange?.(column.id, true)}
|
|
740
|
+
>
|
|
741
|
+
<ChevronLeft className="h-3.5 w-3.5" />
|
|
742
|
+
</Button>
|
|
743
|
+
) : null}
|
|
744
|
+
<ListActions
|
|
745
|
+
listId={column.id}
|
|
746
|
+
listName={column.name}
|
|
747
|
+
listStatus={column.status}
|
|
748
|
+
listColor={column.color as SupportedColor}
|
|
749
|
+
tasks={tasks}
|
|
750
|
+
boardId={boardId}
|
|
751
|
+
wsId={wsId}
|
|
752
|
+
onUpdate={handleUpdate}
|
|
753
|
+
onSelectAll={handleSelectAll}
|
|
754
|
+
isEditOpen={isEditOpen}
|
|
755
|
+
onEditOpenChange={setIsEditOpen}
|
|
756
|
+
/>
|
|
757
|
+
</>
|
|
704
758
|
)}
|
|
705
759
|
</div>
|
|
706
760
|
</div>
|