@tuturuuu/ui 0.9.0 → 0.10.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 +29 -0
- package/package.json +6 -5
- package/src/components/ui/custom/__tests__/settings-dialog-search.test.ts +78 -0
- package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +27 -1
- package/src/components/ui/custom/nav-link.test.tsx +165 -0
- package/src/components/ui/custom/nav-link.tsx +69 -11
- package/src/components/ui/custom/navigation.tsx +1 -0
- package/src/components/ui/custom/settings/task-settings.tsx +104 -0
- package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
- package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
- package/src/components/ui/custom/settings-dialog-search.ts +75 -0
- package/src/components/ui/custom/settings-dialog-shell.tsx +63 -27
- package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
- package/src/components/ui/custom/workspace-select.tsx +17 -16
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +16 -0
- package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
- package/src/components/ui/tu-do/boards/board-share-dialog.tsx +4 -328
- package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +50 -37
- package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
- package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +263 -21
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +133 -14
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +112 -54
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +8 -2
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +29 -14
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +24 -1
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +7 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +7 -1
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +80 -8
- package/src/components/ui/tu-do/boards/boardId/task-list.tsx +15 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
- package/src/components/ui/tu-do/boards/form.tsx +1 -1
- package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
- package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
- package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
- package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
- package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
- package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
- package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
- package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
- package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
- package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
- package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +116 -1
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +38 -0
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +128 -7
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +222 -9
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +21 -0
- package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
- package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
- package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
- package/src/components/ui/tu-do/shared/board-client.tsx +14 -4
- package/src/components/ui/tu-do/shared/board-header.tsx +8 -1
- package/src/components/ui/tu-do/shared/board-switcher.tsx +70 -38
- package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
- package/src/components/ui/tu-do/shared/board-views.tsx +49 -69
- package/src/components/ui/tu-do/shared/list-view.tsx +21 -3
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +4 -4
- package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
- package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
- package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +9 -0
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
- package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
- package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
- package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
- package/src/hooks/useBoardPresence.ts +364 -0
- package/src/hooks/useBoardRealtimeEventHandler.ts +34 -90
- package/src/lib/workspace-actions.ts +2 -6
|
@@ -5,6 +5,7 @@ import type { RealtimePresenceState } from '@tuturuuu/supabase/next/realtime';
|
|
|
5
5
|
import type { User } from '@tuturuuu/types/primitives/User';
|
|
6
6
|
import { Avatar, AvatarFallback, AvatarImage } from '@tuturuuu/ui/avatar';
|
|
7
7
|
import { Button } from '@tuturuuu/ui/button';
|
|
8
|
+
import { useBoardPresence } from '@tuturuuu/ui/hooks/useBoardPresence';
|
|
8
9
|
import type { UserPresenceState } from '@tuturuuu/ui/hooks/usePresence';
|
|
9
10
|
import { Popover, PopoverContent, PopoverTrigger } from '@tuturuuu/ui/popover';
|
|
10
11
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
|
|
@@ -105,12 +106,17 @@ export function BoardUserPresenceAvatarsComponent({
|
|
|
105
106
|
onListStatusFilterChange: (filter: ListStatusFilter) => void;
|
|
106
107
|
}) {
|
|
107
108
|
const wsPresence = useOptionalWorkspacePresenceContext();
|
|
109
|
+
const useWorkspacePresence = wsPresence?.realtimeEnabled === true;
|
|
110
|
+
const boardPresence = useBoardPresence(boardId, {
|
|
111
|
+
enabled: !useWorkspacePresence,
|
|
112
|
+
});
|
|
113
|
+
const activePresence = useWorkspacePresence ? wsPresence : boardPresence;
|
|
108
114
|
const { openTaskById } = useTaskDialog();
|
|
109
115
|
const { state: dialogState } = useTaskDialogContext();
|
|
110
116
|
|
|
111
117
|
// Extract stable function refs to avoid re-running effects on every context change
|
|
112
|
-
const
|
|
113
|
-
const
|
|
118
|
+
const updateLocation = activePresence?.updateLocation;
|
|
119
|
+
const updateMetadata = activePresence?.updateMetadata;
|
|
114
120
|
|
|
115
121
|
// Derive dialog props — only track task presence when editing a real task
|
|
116
122
|
const dialogOpen = dialogState.isOpen;
|
|
@@ -124,26 +130,26 @@ export function BoardUserPresenceAvatarsComponent({
|
|
|
124
130
|
// When the dialog is open with a real task → track { board, boardId, taskId }
|
|
125
131
|
// Otherwise → track { board, boardId } (board-level only)
|
|
126
132
|
useEffect(() => {
|
|
127
|
-
if (!
|
|
133
|
+
if (!updateLocation || !boardId) return;
|
|
128
134
|
if (dialogOpen && dialogTaskId) {
|
|
129
|
-
|
|
135
|
+
updateLocation({ type: 'board', boardId, taskId: dialogTaskId });
|
|
130
136
|
} else {
|
|
131
|
-
|
|
137
|
+
updateLocation({ type: 'board', boardId });
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
return () => {
|
|
135
|
-
|
|
141
|
+
updateLocation({ type: 'other' });
|
|
136
142
|
};
|
|
137
|
-
}, [
|
|
143
|
+
}, [updateLocation, boardId, dialogOpen, dialogTaskId]);
|
|
138
144
|
|
|
139
145
|
// Update metadata (filters) separately so it doesn't overwrite task-level location
|
|
140
146
|
useEffect(() => {
|
|
141
|
-
if (!
|
|
142
|
-
|
|
143
|
-
}, [
|
|
147
|
+
if (!updateMetadata || !currentMetadata) return;
|
|
148
|
+
updateMetadata(currentMetadata as Record<string, any>);
|
|
149
|
+
}, [updateMetadata, currentMetadata]);
|
|
144
150
|
|
|
145
|
-
const boardViewers =
|
|
146
|
-
const currentUserId =
|
|
151
|
+
const boardViewers = activePresence?.getBoardViewers(boardId) ?? [];
|
|
152
|
+
const currentUserId = activePresence?.currentUserId;
|
|
147
153
|
|
|
148
154
|
const presenceState: RealtimePresenceState<UserPresenceState> =
|
|
149
155
|
useMemo(() => {
|
|
@@ -94,17 +94,6 @@ const DEFAULT_TASK_FILTERS: TaskFilters = {
|
|
|
94
94
|
sourceBoardIds: [],
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
function hasAssignedExternalTasks(tasks: Task[], boardId: string) {
|
|
98
|
-
const externalStagingListId = getPersonalExternalStagingListId(boardId);
|
|
99
|
-
|
|
100
|
-
return tasks.some(
|
|
101
|
-
(task) =>
|
|
102
|
-
task.is_personal_external === true ||
|
|
103
|
-
task.list_id === externalStagingListId ||
|
|
104
|
-
Boolean(task.source_workspace_id)
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
97
|
function getClosedTaskListCollapsedStorageKey(boardId: string, listId: string) {
|
|
109
98
|
return `${CLOSED_TASK_LIST_COLLAPSED_STORAGE_PREFIX}:${boardId}:${listId}`;
|
|
110
99
|
}
|
|
@@ -246,13 +235,18 @@ function sortLocalTasks(tasks: Task[], sortBy: TaskFilters['sortBy']) {
|
|
|
246
235
|
interface Props {
|
|
247
236
|
workspace: Workspace;
|
|
248
237
|
workspaceTier?: WorkspaceProductTier | null;
|
|
249
|
-
board: WorkspaceTaskBoard
|
|
238
|
+
board: WorkspaceTaskBoard & {
|
|
239
|
+
access_type?: 'member' | 'guest';
|
|
240
|
+
guest_permission?: 'view' | 'edit' | null;
|
|
241
|
+
has_guest_access?: boolean;
|
|
242
|
+
};
|
|
250
243
|
tasks: Task[];
|
|
251
244
|
lists: TaskList[];
|
|
252
245
|
workspaceLabels: WorkspaceLabel[];
|
|
253
246
|
availableViews?: ViewType[];
|
|
254
247
|
canManageBoard?: boolean;
|
|
255
248
|
currentUserId?: string;
|
|
249
|
+
defaultView?: ViewType;
|
|
256
250
|
idleBottomIsland?: ReactNode;
|
|
257
251
|
publicHeaderPrefix?: ReactNode;
|
|
258
252
|
publicView?: boolean;
|
|
@@ -268,6 +262,7 @@ export function BoardViews({
|
|
|
268
262
|
availableViews,
|
|
269
263
|
canManageBoard = true,
|
|
270
264
|
currentUserId,
|
|
265
|
+
defaultView,
|
|
271
266
|
idleBottomIsland,
|
|
272
267
|
publicHeaderPrefix,
|
|
273
268
|
publicView = false,
|
|
@@ -297,6 +292,18 @@ export function BoardViews({
|
|
|
297
292
|
useState(false);
|
|
298
293
|
const { createTask } = useTaskDialog();
|
|
299
294
|
const localTaskState = readOnly || publicView;
|
|
295
|
+
const boardAssigneesEnabled =
|
|
296
|
+
!workspace.personal ||
|
|
297
|
+
board.access_type === 'guest' ||
|
|
298
|
+
board.has_guest_access === true;
|
|
299
|
+
const assigneeMemberSource: 'workspace' | 'board' | 'workspace-and-board' =
|
|
300
|
+
board.access_type === 'guest'
|
|
301
|
+
? 'board'
|
|
302
|
+
: board.has_guest_access === true
|
|
303
|
+
? workspace.personal
|
|
304
|
+
? 'board'
|
|
305
|
+
: 'workspace-and-board'
|
|
306
|
+
: 'workspace';
|
|
300
307
|
const { data: pinnedSpecialListsRaw } = useUserWorkspaceConfig(
|
|
301
308
|
effectiveWorkspaceId,
|
|
302
309
|
TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID,
|
|
@@ -452,6 +459,7 @@ export function BoardViews({
|
|
|
452
459
|
const result = await listWorkspaceTasks(effectiveWorkspaceId, {
|
|
453
460
|
...taskQueryOptions,
|
|
454
461
|
boardId: board.id,
|
|
462
|
+
includeRelationshipSummary: false,
|
|
455
463
|
listStatuses: listStatusesForQuery,
|
|
456
464
|
limit: 200,
|
|
457
465
|
});
|
|
@@ -545,12 +553,15 @@ export function BoardViews({
|
|
|
545
553
|
: (new URLSearchParams(window.location.search).get(
|
|
546
554
|
'view'
|
|
547
555
|
) as ViewType | null);
|
|
548
|
-
const
|
|
556
|
+
const fallbackView = enabledViews?.[0] ?? 'kanban';
|
|
557
|
+
const routeDefaultView =
|
|
558
|
+
defaultView && viewIsEnabled(defaultView) ? defaultView : null;
|
|
559
|
+
const effectiveDefaultView = routeDefaultView ?? fallbackView;
|
|
549
560
|
const initialView =
|
|
550
561
|
requestedView && viewIsEnabled(requestedView) ? requestedView : null;
|
|
551
562
|
|
|
552
563
|
if (!savedConfig) {
|
|
553
|
-
setCurrentView(initialView ??
|
|
564
|
+
setCurrentView(initialView ?? effectiveDefaultView);
|
|
554
565
|
setFilters(DEFAULT_TASK_FILTERS);
|
|
555
566
|
setListStatusFilter('all');
|
|
556
567
|
return;
|
|
@@ -558,16 +569,17 @@ export function BoardViews({
|
|
|
558
569
|
|
|
559
570
|
setCurrentView(
|
|
560
571
|
initialView ??
|
|
572
|
+
routeDefaultView ??
|
|
561
573
|
(viewIsEnabled(savedConfig.currentView)
|
|
562
574
|
? savedConfig.currentView
|
|
563
|
-
:
|
|
575
|
+
: effectiveDefaultView)
|
|
564
576
|
);
|
|
565
577
|
setFilters({
|
|
566
578
|
...DEFAULT_TASK_FILTERS,
|
|
567
579
|
...savedConfig.filters,
|
|
568
580
|
});
|
|
569
581
|
setListStatusFilter(savedConfig.listStatusFilter);
|
|
570
|
-
}, [board.id, enabledViews, viewIsEnabled]);
|
|
582
|
+
}, [board.id, defaultView, enabledViews, viewIsEnabled]);
|
|
571
583
|
|
|
572
584
|
useEffect(() => {
|
|
573
585
|
if (!workspace.personal || typeof window === 'undefined') {
|
|
@@ -581,15 +593,11 @@ export function BoardViews({
|
|
|
581
593
|
const storedPreference =
|
|
582
594
|
storedValue === null ? null : storedValue === 'true';
|
|
583
595
|
|
|
584
|
-
setExternalTasksCollapsed(
|
|
585
|
-
|
|
586
|
-
);
|
|
587
|
-
}, [board.id, tasks, workspace.personal]);
|
|
596
|
+
setExternalTasksCollapsed(storedPreference ?? true);
|
|
597
|
+
}, [board.id, workspace.personal]);
|
|
588
598
|
|
|
589
599
|
const handleExternalTasksCollapsedChange = useCallback(
|
|
590
600
|
(collapsed: boolean) => {
|
|
591
|
-
if (collapsed && specialTaskListPins.external_tasks) return;
|
|
592
|
-
|
|
593
601
|
setExternalTasksCollapsed(collapsed);
|
|
594
602
|
|
|
595
603
|
if (!workspace.personal || typeof window === 'undefined') return;
|
|
@@ -599,7 +607,7 @@ export function BoardViews({
|
|
|
599
607
|
String(collapsed)
|
|
600
608
|
);
|
|
601
609
|
},
|
|
602
|
-
[board.id,
|
|
610
|
+
[board.id, workspace.personal]
|
|
603
611
|
);
|
|
604
612
|
|
|
605
613
|
useEffect(() => {
|
|
@@ -635,16 +643,6 @@ export function BoardViews({
|
|
|
635
643
|
|
|
636
644
|
const handleTaskListCollapsedChange = useCallback(
|
|
637
645
|
(listId: string, collapsed: boolean) => {
|
|
638
|
-
if (
|
|
639
|
-
collapsed &&
|
|
640
|
-
specialTaskListPins.closed_tasks &&
|
|
641
|
-
boardLists.some(
|
|
642
|
-
(list) => list.id === listId && list.status === 'closed'
|
|
643
|
-
)
|
|
644
|
-
) {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
646
|
setClosedTaskListsCollapsed((previous) => ({
|
|
649
647
|
...previous,
|
|
650
648
|
[listId]: collapsed,
|
|
@@ -657,7 +655,7 @@ export function BoardViews({
|
|
|
657
655
|
String(collapsed)
|
|
658
656
|
);
|
|
659
657
|
},
|
|
660
|
-
[board.id
|
|
658
|
+
[board.id]
|
|
661
659
|
);
|
|
662
660
|
|
|
663
661
|
useEffect(() => {
|
|
@@ -674,7 +672,7 @@ export function BoardViews({
|
|
|
674
672
|
|
|
675
673
|
next[section] =
|
|
676
674
|
storedValue === null
|
|
677
|
-
? (previous[section] ??
|
|
675
|
+
? (previous[section] ?? true)
|
|
678
676
|
: storedValue === 'true';
|
|
679
677
|
}
|
|
680
678
|
|
|
@@ -684,14 +682,6 @@ export function BoardViews({
|
|
|
684
682
|
|
|
685
683
|
const handleDeadlineSectionCollapsedChange = useCallback(
|
|
686
684
|
(section: KanbanDeadlineSection, collapsed: boolean) => {
|
|
687
|
-
if (
|
|
688
|
-
collapsed &&
|
|
689
|
-
((section === 'overdue' && specialTaskListPins.overdue) ||
|
|
690
|
-
(section === 'upcoming' && specialTaskListPins.upcoming))
|
|
691
|
-
) {
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
685
|
setDeadlineSectionsCollapsed((previous) => ({
|
|
696
686
|
...previous,
|
|
697
687
|
[section]: collapsed,
|
|
@@ -704,7 +694,7 @@ export function BoardViews({
|
|
|
704
694
|
String(collapsed)
|
|
705
695
|
);
|
|
706
696
|
},
|
|
707
|
-
[board.id
|
|
697
|
+
[board.id]
|
|
708
698
|
);
|
|
709
699
|
|
|
710
700
|
const externalStagingList = useMemo<TaskList | null>(() => {
|
|
@@ -722,15 +712,12 @@ export function BoardViews({
|
|
|
722
712
|
color: 'CYAN',
|
|
723
713
|
position: Number.MIN_SAFE_INTEGER,
|
|
724
714
|
is_external_staging: true,
|
|
725
|
-
is_external_collapsed:
|
|
726
|
-
? false
|
|
727
|
-
: externalTasksCollapsed,
|
|
715
|
+
is_external_collapsed: externalTasksCollapsed,
|
|
728
716
|
};
|
|
729
717
|
}, [
|
|
730
718
|
board.created_at,
|
|
731
719
|
board.id,
|
|
732
720
|
externalTasksCollapsed,
|
|
733
|
-
specialTaskListPins.external_tasks,
|
|
734
721
|
tTasks,
|
|
735
722
|
workspace.personal,
|
|
736
723
|
]);
|
|
@@ -742,38 +729,22 @@ export function BoardViews({
|
|
|
742
729
|
list.status === 'closed'
|
|
743
730
|
? {
|
|
744
731
|
...list,
|
|
745
|
-
is_collapsed:
|
|
746
|
-
? false
|
|
747
|
-
: (closedTaskListsCollapsed[list.id] ?? true),
|
|
732
|
+
is_collapsed: closedTaskListsCollapsed[list.id] ?? true,
|
|
748
733
|
}
|
|
749
734
|
: list
|
|
750
735
|
);
|
|
751
736
|
return externalStagingList
|
|
752
737
|
? [externalStagingList, ...realLists]
|
|
753
738
|
: realLists;
|
|
754
|
-
}, [
|
|
755
|
-
boardLists,
|
|
756
|
-
closedTaskListsCollapsed,
|
|
757
|
-
externalStagingList,
|
|
758
|
-
specialTaskListPins.closed_tasks,
|
|
759
|
-
]);
|
|
739
|
+
}, [boardLists, closedTaskListsCollapsed, externalStagingList]);
|
|
760
740
|
|
|
761
741
|
const effectiveDeadlineSectionsCollapsed =
|
|
762
742
|
useMemo<KanbanDeadlineCollapsedState>(
|
|
763
743
|
() => ({
|
|
764
|
-
overdue:
|
|
765
|
-
|
|
766
|
-
: deadlineSectionsCollapsed.overdue,
|
|
767
|
-
upcoming: specialTaskListPins.upcoming
|
|
768
|
-
? false
|
|
769
|
-
: deadlineSectionsCollapsed.upcoming,
|
|
744
|
+
overdue: deadlineSectionsCollapsed.overdue,
|
|
745
|
+
upcoming: deadlineSectionsCollapsed.upcoming,
|
|
770
746
|
}),
|
|
771
|
-
[
|
|
772
|
-
deadlineSectionsCollapsed.overdue,
|
|
773
|
-
deadlineSectionsCollapsed.upcoming,
|
|
774
|
-
specialTaskListPins.overdue,
|
|
775
|
-
specialTaskListPins.upcoming,
|
|
776
|
-
]
|
|
747
|
+
[deadlineSectionsCollapsed.overdue, deadlineSectionsCollapsed.upcoming]
|
|
777
748
|
);
|
|
778
749
|
|
|
779
750
|
const { data: filteredListCounts, isFetching: isFilteredListCountsFetching } =
|
|
@@ -1049,6 +1020,8 @@ export function BoardViews({
|
|
|
1049
1020
|
specialTaskListPins={specialTaskListPins}
|
|
1050
1021
|
onSpecialTaskListPinnedChange={handleSpecialTaskListPinnedChange}
|
|
1051
1022
|
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
1023
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1024
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
1052
1025
|
readOnly={readOnly}
|
|
1053
1026
|
/>
|
|
1054
1027
|
);
|
|
@@ -1082,6 +1055,8 @@ export function BoardViews({
|
|
|
1082
1055
|
tasks={effectiveTasks}
|
|
1083
1056
|
lists={filteredLists}
|
|
1084
1057
|
isPersonalWorkspace={workspace.personal}
|
|
1058
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1059
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
1085
1060
|
preserveTaskOrder={!!filters.sortBy}
|
|
1086
1061
|
searchQuery={filters.searchQuery}
|
|
1087
1062
|
readOnly={readOnly}
|
|
@@ -1094,6 +1069,9 @@ export function BoardViews({
|
|
|
1094
1069
|
boardId={board.id}
|
|
1095
1070
|
tasks={effectiveTasks}
|
|
1096
1071
|
lists={filteredLists}
|
|
1072
|
+
isPersonalWorkspace={workspace.personal}
|
|
1073
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1074
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
1097
1075
|
onTaskPartialUpdate={handleTaskPartialUpdate}
|
|
1098
1076
|
/>
|
|
1099
1077
|
);
|
|
@@ -1182,6 +1160,8 @@ export function BoardViews({
|
|
|
1182
1160
|
specialTaskListPins={specialTaskListPins}
|
|
1183
1161
|
onSpecialTaskListPinnedChange={handleSpecialTaskListPinnedChange}
|
|
1184
1162
|
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
1163
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1164
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
1185
1165
|
readOnly={readOnly}
|
|
1186
1166
|
/>
|
|
1187
1167
|
);
|
|
@@ -61,6 +61,7 @@ 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 type { TaskCardAssigneeMemberSource } from '../boards/boardId/task-card/task-card';
|
|
64
65
|
import {
|
|
65
66
|
getTaskCardHydratingOpenOptions,
|
|
66
67
|
isExternalTaskSnapshot,
|
|
@@ -85,6 +86,8 @@ interface Props {
|
|
|
85
86
|
tasks: Task[];
|
|
86
87
|
lists: TaskList[];
|
|
87
88
|
isPersonalWorkspace?: boolean;
|
|
89
|
+
canUseBoardAssignees?: boolean;
|
|
90
|
+
assigneeMemberSource?: TaskCardAssigneeMemberSource;
|
|
88
91
|
preserveTaskOrder?: boolean;
|
|
89
92
|
searchQuery?: string;
|
|
90
93
|
weekStartsOn?: 0 | 1 | 6;
|
|
@@ -118,6 +121,7 @@ function ReadOnlyListView({
|
|
|
118
121
|
tasks,
|
|
119
122
|
lists,
|
|
120
123
|
isPersonalWorkspace = false,
|
|
124
|
+
canUseBoardAssignees,
|
|
121
125
|
preserveTaskOrder = false,
|
|
122
126
|
searchQuery,
|
|
123
127
|
}: Props) {
|
|
@@ -127,6 +131,7 @@ function ReadOnlyListView({
|
|
|
127
131
|
const dateLocale = locale === 'vi' ? vi : enUS;
|
|
128
132
|
const [sortField, setSortField] = useState<ListViewSortField>('created_at');
|
|
129
133
|
const [sortOrder, setSortOrder] = useState<ListViewSortOrder>('desc');
|
|
134
|
+
const showAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
|
|
130
135
|
const listsById = useMemo(
|
|
131
136
|
() => new Map(lists.map((list) => [list.id, list])),
|
|
132
137
|
[lists]
|
|
@@ -227,7 +232,7 @@ function ReadOnlyListView({
|
|
|
227
232
|
{getSortIcon('end_date')}
|
|
228
233
|
</Button>
|
|
229
234
|
</TableHead>
|
|
230
|
-
{
|
|
235
|
+
{showAssignees && (
|
|
231
236
|
<TableHead className="h-9 w-32 px-2">
|
|
232
237
|
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
233
238
|
{tc('assignee')}
|
|
@@ -300,7 +305,7 @@ function ReadOnlyListView({
|
|
|
300
305
|
</span>
|
|
301
306
|
)}
|
|
302
307
|
</TableCell>
|
|
303
|
-
{
|
|
308
|
+
{showAssignees && (
|
|
304
309
|
<TableCell className="px-2 py-2">
|
|
305
310
|
{task.assignees && task.assignees.length > 0 && (
|
|
306
311
|
<div className="flex flex-wrap gap-1">
|
|
@@ -337,6 +342,8 @@ function InteractiveListView({
|
|
|
337
342
|
tasks,
|
|
338
343
|
lists,
|
|
339
344
|
isPersonalWorkspace = false,
|
|
345
|
+
canUseBoardAssignees,
|
|
346
|
+
assigneeMemberSource,
|
|
340
347
|
preserveTaskOrder = false,
|
|
341
348
|
searchQuery,
|
|
342
349
|
weekStartsOn = 0,
|
|
@@ -360,6 +367,9 @@ function InteractiveListView({
|
|
|
360
367
|
const previousWorkspaceIdRef = useRef(workspaceId);
|
|
361
368
|
const previousBoardIdRef = useRef(boardId);
|
|
362
369
|
const { openTask, openTaskById } = useTaskDialog();
|
|
370
|
+
const showAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
|
|
371
|
+
const effectiveAssigneeMemberSource =
|
|
372
|
+
assigneeMemberSource ?? (isPersonalWorkspace ? 'board' : 'workspace');
|
|
363
373
|
|
|
364
374
|
// Infinite scroll
|
|
365
375
|
const [displayCount, setDisplayCount] = useState(50);
|
|
@@ -372,7 +382,7 @@ function InteractiveListView({
|
|
|
372
382
|
priority: true,
|
|
373
383
|
start_date: false,
|
|
374
384
|
end_date: true,
|
|
375
|
-
assignees:
|
|
385
|
+
assignees: showAssignees,
|
|
376
386
|
actions: true,
|
|
377
387
|
});
|
|
378
388
|
|
|
@@ -516,6 +526,10 @@ function InteractiveListView({
|
|
|
516
526
|
availableLists: lists,
|
|
517
527
|
effectiveWorkspaceId: workspaceId,
|
|
518
528
|
isPersonalWorkspace,
|
|
529
|
+
canUseBoardAssignees: task.source_workspace_id ? true : showAssignees,
|
|
530
|
+
assigneeMemberSource: task.source_workspace_id
|
|
531
|
+
? 'workspace'
|
|
532
|
+
: effectiveAssigneeMemberSource,
|
|
519
533
|
})
|
|
520
534
|
);
|
|
521
535
|
return;
|
|
@@ -524,6 +538,8 @@ function InteractiveListView({
|
|
|
524
538
|
openTask(task, boardId, lists, false, {
|
|
525
539
|
taskWsId: workspaceId,
|
|
526
540
|
taskWorkspacePersonal: isPersonalWorkspace,
|
|
541
|
+
canUseBoardAssignees: showAssignees,
|
|
542
|
+
assigneeMemberSource: effectiveAssigneeMemberSource,
|
|
527
543
|
});
|
|
528
544
|
}
|
|
529
545
|
|
|
@@ -972,6 +988,8 @@ function InteractiveListView({
|
|
|
972
988
|
workspaceId={workspaceId}
|
|
973
989
|
lists={lists}
|
|
974
990
|
isPersonalWorkspace={isPersonalWorkspace}
|
|
991
|
+
canUseBoardAssignees={showAssignees}
|
|
992
|
+
assigneeMemberSource={effectiveAssigneeMemberSource}
|
|
975
993
|
onUpdate={() => {
|
|
976
994
|
void queryClient.invalidateQueries({
|
|
977
995
|
queryKey: ['tasks', boardId],
|
|
@@ -14,15 +14,15 @@ export function TaskBoardLoadingState({
|
|
|
14
14
|
<div
|
|
15
15
|
aria-busy="true"
|
|
16
16
|
className={cn(
|
|
17
|
-
'
|
|
17
|
+
'overflow-hidden bg-transparent',
|
|
18
18
|
root
|
|
19
|
-
? '-m-4 h-[calc(100dvh+2rem)] min-h-[calc(32rem+2rem)]'
|
|
20
|
-
: 'h-[calc(100dvh-1rem)] min-h-[32rem]',
|
|
19
|
+
? '-m-4 h-[calc(100dvh+2rem)] min-h-[calc(32rem+2rem)] w-[calc(100%+2rem)] min-w-[calc(100%+2rem)]'
|
|
20
|
+
: 'h-[calc(100dvh-1rem)] min-h-[32rem] w-full',
|
|
21
21
|
className
|
|
22
22
|
)}
|
|
23
23
|
data-testid="task-board-loading-state"
|
|
24
24
|
>
|
|
25
|
-
<KanbanSkeleton />
|
|
25
|
+
<KanbanSkeleton root={root} />
|
|
26
26
|
</div>
|
|
27
27
|
);
|
|
28
28
|
}
|