@tuturuuu/ui 0.2.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 +53 -0
- package/package.json +79 -67
- 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/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/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/use-task-mutations.ts +6 -3
- 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
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
useHotkeySequence,
|
|
7
7
|
} from '@tanstack/react-hotkeys';
|
|
8
8
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type ListWorkspaceTasksOptions,
|
|
11
|
+
listWorkspaceTasks,
|
|
12
|
+
} from '@tuturuuu/internal-api/tasks';
|
|
10
13
|
import type {
|
|
11
14
|
Workspace,
|
|
12
15
|
WorkspaceProductTier,
|
|
@@ -14,18 +17,17 @@ import type {
|
|
|
14
17
|
} from '@tuturuuu/types';
|
|
15
18
|
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
16
19
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
17
|
-
import { useSemanticTaskSearch } from '@tuturuuu/ui/hooks/use-semantic-task-search';
|
|
18
20
|
import {
|
|
19
21
|
getPersonalExternalStagingListId,
|
|
20
22
|
type WorkspaceLabel,
|
|
21
23
|
} from '@tuturuuu/utils/task-helper';
|
|
22
24
|
import { useTranslations } from 'next-intl';
|
|
23
25
|
import {
|
|
26
|
+
type ReactNode,
|
|
24
27
|
useCallback,
|
|
25
28
|
useEffect,
|
|
26
29
|
useLayoutEffect,
|
|
27
30
|
useMemo,
|
|
28
|
-
useRef,
|
|
29
31
|
useState,
|
|
30
32
|
} from 'react';
|
|
31
33
|
import { KanbanBoard } from '../boards/boardId/kanban';
|
|
@@ -34,7 +36,6 @@ import { TimelineBoard } from '../boards/boardId/timeline-board';
|
|
|
34
36
|
import { useTaskDialog } from '../hooks/useTaskDialog';
|
|
35
37
|
import { BoardHeader, type ListStatusFilter } from '../shared/board-header';
|
|
36
38
|
import { ListView } from '../shared/list-view';
|
|
37
|
-
import { useProgressiveLoader } from '../shared/progressive-loader-context';
|
|
38
39
|
import { RecycleBinPanel } from '../shared/recycle-bin-panel';
|
|
39
40
|
import { loadBoardConfig } from './board-config-storage';
|
|
40
41
|
|
|
@@ -46,6 +47,8 @@ const HOTKEY_GO_TO_LIST: ['G', 'L'] = ['G', 'L'];
|
|
|
46
47
|
const HOTKEY_GO_TO_TIMELINE: ['G', 'T'] = ['G', 'T'];
|
|
47
48
|
const EXTERNAL_TASKS_COLLAPSED_STORAGE_PREFIX =
|
|
48
49
|
'personal-board-external-tasks-collapsed';
|
|
50
|
+
const CLOSED_TASK_LIST_COLLAPSED_STORAGE_PREFIX =
|
|
51
|
+
'task-board-closed-list-collapsed';
|
|
49
52
|
const DEFAULT_TASK_FILTERS: TaskFilters = {
|
|
50
53
|
labels: [],
|
|
51
54
|
assignees: [],
|
|
@@ -71,6 +74,10 @@ function hasAssignedExternalTasks(tasks: Task[], boardId: string) {
|
|
|
71
74
|
);
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
function getClosedTaskListCollapsedStorageKey(boardId: string, listId: string) {
|
|
78
|
+
return `${CLOSED_TASK_LIST_COLLAPSED_STORAGE_PREFIX}:${boardId}:${listId}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
interface Props {
|
|
75
82
|
workspace: Workspace;
|
|
76
83
|
workspaceTier?: WorkspaceProductTier | null;
|
|
@@ -80,6 +87,7 @@ interface Props {
|
|
|
80
87
|
workspaceLabels: WorkspaceLabel[];
|
|
81
88
|
canManageBoard?: boolean;
|
|
82
89
|
currentUserId?: string;
|
|
90
|
+
idleBottomIsland?: ReactNode;
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
export function BoardViews({
|
|
@@ -90,6 +98,7 @@ export function BoardViews({
|
|
|
90
98
|
lists,
|
|
91
99
|
canManageBoard = true,
|
|
92
100
|
currentUserId,
|
|
101
|
+
idleBottomIsland,
|
|
93
102
|
}: Props) {
|
|
94
103
|
const t = useTranslations('common');
|
|
95
104
|
const tTasks = useTranslations('ws-tasks');
|
|
@@ -98,6 +107,9 @@ export function BoardViews({
|
|
|
98
107
|
const effectiveWorkspaceId = board.ws_id ?? workspace.id;
|
|
99
108
|
const [currentView, setCurrentView] = useState<ViewType>('kanban');
|
|
100
109
|
const [externalTasksCollapsed, setExternalTasksCollapsed] = useState(false);
|
|
110
|
+
const [closedTaskListsCollapsed, setClosedTaskListsCollapsed] = useState<
|
|
111
|
+
Record<string, boolean>
|
|
112
|
+
>({});
|
|
101
113
|
const [filters, setFilters] = useState<TaskFilters>(DEFAULT_TASK_FILTERS);
|
|
102
114
|
const [listStatusFilter, setListStatusFilter] =
|
|
103
115
|
useState<ListStatusFilter>('all');
|
|
@@ -107,8 +119,9 @@ export function BoardViews({
|
|
|
107
119
|
>({});
|
|
108
120
|
const [recycleBinOpen, setRecycleBinOpen] = useState(false);
|
|
109
121
|
const [isMultiSelectMode, setIsMultiSelectMode] = useState(false);
|
|
122
|
+
const [kanbanBulkSelectionActive, setKanbanBulkSelectionActive] =
|
|
123
|
+
useState(false);
|
|
110
124
|
const { createTask } = useTaskDialog();
|
|
111
|
-
const { pagination, loadListPage } = useProgressiveLoader();
|
|
112
125
|
const sourceScope = filters.sourceScope ?? 'all_visible';
|
|
113
126
|
const sourceWorkspaceIds = filters.sourceWorkspaceIds ?? [];
|
|
114
127
|
const sourceBoardIds = filters.sourceBoardIds ?? [];
|
|
@@ -119,15 +132,79 @@ export function BoardViews({
|
|
|
119
132
|
() => (listStatusFilter === 'all' ? undefined : [listStatusFilter]),
|
|
120
133
|
[listStatusFilter]
|
|
121
134
|
);
|
|
122
|
-
const
|
|
135
|
+
const hasTaskFilters = useMemo(
|
|
136
|
+
() =>
|
|
137
|
+
filters.labels.length > 0 ||
|
|
138
|
+
filters.assignees.length > 0 ||
|
|
139
|
+
filters.projects.length > 0 ||
|
|
140
|
+
filters.priorities.length > 0 ||
|
|
141
|
+
!!filters.dueDateRange?.from ||
|
|
142
|
+
!!filters.dueDateRange?.to ||
|
|
143
|
+
typeof filters.estimationRange?.min === 'number' ||
|
|
144
|
+
typeof filters.estimationRange?.max === 'number' ||
|
|
145
|
+
!!filters.searchQuery?.trim() ||
|
|
146
|
+
filters.includeMyTasks ||
|
|
147
|
+
filters.includeUnassigned ||
|
|
148
|
+
sourceScope !== 'all_visible' ||
|
|
149
|
+
sourceWorkspaceIds.length > 0 ||
|
|
150
|
+
sourceBoardIds.length > 0,
|
|
151
|
+
[filters, sourceBoardIds.length, sourceScope, sourceWorkspaceIds.length]
|
|
152
|
+
);
|
|
153
|
+
const hasServerTaskQuery = hasTaskFilters || !!filters.sortBy;
|
|
154
|
+
const taskQueryOptions = useMemo<ListWorkspaceTasksOptions>(
|
|
155
|
+
() => ({
|
|
156
|
+
assignedToMe: filters.includeMyTasks || undefined,
|
|
157
|
+
assigneeIds: filters.includeMyTasks
|
|
158
|
+
? undefined
|
|
159
|
+
: filters.assignees.map((assignee) => assignee.id),
|
|
160
|
+
dueDateFrom: filters.dueDateRange?.from?.toISOString(),
|
|
161
|
+
dueDateTo: filters.dueDateRange?.to?.toISOString(),
|
|
162
|
+
estimationMax: filters.estimationRange?.max,
|
|
163
|
+
estimationMin: filters.estimationRange?.min,
|
|
164
|
+
includeUnassigned: filters.includeUnassigned || undefined,
|
|
165
|
+
labelIds: filters.labels.map((label) => label.id),
|
|
166
|
+
priorities: filters.priorities,
|
|
167
|
+
projectIds: filters.projects.map((project) => project.id),
|
|
168
|
+
q: filters.searchQuery?.trim() || undefined,
|
|
169
|
+
sortBy: filters.sortBy,
|
|
170
|
+
sourceBoardIds,
|
|
171
|
+
sourceScope,
|
|
172
|
+
sourceWorkspaceIds,
|
|
173
|
+
}),
|
|
174
|
+
[
|
|
175
|
+
filters.assignees,
|
|
176
|
+
filters.dueDateRange?.from,
|
|
177
|
+
filters.dueDateRange?.to,
|
|
178
|
+
filters.estimationRange?.max,
|
|
179
|
+
filters.estimationRange?.min,
|
|
180
|
+
filters.includeMyTasks,
|
|
181
|
+
filters.includeUnassigned,
|
|
182
|
+
filters.labels,
|
|
183
|
+
filters.priorities,
|
|
184
|
+
filters.projects,
|
|
185
|
+
filters.searchQuery,
|
|
186
|
+
filters.sortBy,
|
|
187
|
+
sourceBoardIds,
|
|
188
|
+
sourceScope,
|
|
189
|
+
sourceWorkspaceIds,
|
|
190
|
+
]
|
|
191
|
+
);
|
|
192
|
+
const taskFilterKey = useMemo(
|
|
123
193
|
() =>
|
|
124
194
|
JSON.stringify({
|
|
125
195
|
listStatusFilter,
|
|
196
|
+
query: taskQueryOptions,
|
|
126
197
|
scope: sourceScope,
|
|
127
198
|
sourceBoardIds: [...sourceBoardIds].sort(),
|
|
128
199
|
sourceWorkspaceIds: [...sourceWorkspaceIds].sort(),
|
|
129
200
|
}),
|
|
130
|
-
[
|
|
201
|
+
[
|
|
202
|
+
listStatusFilter,
|
|
203
|
+
sourceBoardIds,
|
|
204
|
+
sourceScope,
|
|
205
|
+
sourceWorkspaceIds,
|
|
206
|
+
taskQueryOptions,
|
|
207
|
+
]
|
|
131
208
|
);
|
|
132
209
|
const viewHotkeyLabels = useMemo(
|
|
133
210
|
() => ({
|
|
@@ -138,38 +215,28 @@ export function BoardViews({
|
|
|
138
215
|
[]
|
|
139
216
|
);
|
|
140
217
|
const shouldEagerLoadTasks =
|
|
141
|
-
currentView === 'list' ||
|
|
142
|
-
currentView === 'timeline' ||
|
|
143
|
-
sourceScope !== 'all_visible';
|
|
218
|
+
currentView === 'list' || currentView === 'timeline' || hasServerTaskQuery;
|
|
144
219
|
const fetchBoardTasks = useCallback(async () => {
|
|
145
220
|
const result = await listWorkspaceTasks(effectiveWorkspaceId, {
|
|
221
|
+
...taskQueryOptions,
|
|
146
222
|
boardId: board.id,
|
|
147
223
|
listStatuses: listStatusesForQuery,
|
|
148
|
-
|
|
149
|
-
sourceScope: sourceScope === 'all_visible' ? undefined : sourceScope,
|
|
150
|
-
sourceWorkspaceIds,
|
|
224
|
+
limit: 200,
|
|
151
225
|
});
|
|
152
226
|
return result.tasks;
|
|
153
|
-
}, [
|
|
154
|
-
board.id,
|
|
155
|
-
effectiveWorkspaceId,
|
|
156
|
-
listStatusesForQuery,
|
|
157
|
-
sourceBoardIds,
|
|
158
|
-
sourceScope,
|
|
159
|
-
sourceWorkspaceIds,
|
|
160
|
-
]);
|
|
227
|
+
}, [board.id, effectiveWorkspaceId, listStatusesForQuery, taskQueryOptions]);
|
|
161
228
|
|
|
162
229
|
const primeFullTaskCache = useCallback(
|
|
163
230
|
(nextView: ViewType) => {
|
|
164
231
|
if (nextView !== 'list' && nextView !== 'timeline') return;
|
|
165
232
|
|
|
166
233
|
void queryClient.prefetchQuery({
|
|
167
|
-
queryKey: ['tasks-full', board.id,
|
|
234
|
+
queryKey: ['tasks-full', board.id, taskFilterKey],
|
|
168
235
|
queryFn: fetchBoardTasks,
|
|
169
236
|
staleTime: 0,
|
|
170
237
|
});
|
|
171
238
|
},
|
|
172
|
-
[board.id, fetchBoardTasks, queryClient,
|
|
239
|
+
[board.id, fetchBoardTasks, queryClient, taskFilterKey]
|
|
173
240
|
);
|
|
174
241
|
|
|
175
242
|
const handleViewChange = useCallback(
|
|
@@ -180,8 +247,8 @@ export function BoardViews({
|
|
|
180
247
|
[primeFullTaskCache]
|
|
181
248
|
);
|
|
182
249
|
|
|
183
|
-
const { data: fullTasks = [] } = useQuery({
|
|
184
|
-
queryKey: ['tasks-full', board.id,
|
|
250
|
+
const { data: fullTasks = [], isFetching: isFullTasksFetching } = useQuery({
|
|
251
|
+
queryKey: ['tasks-full', board.id, taskFilterKey],
|
|
185
252
|
enabled: shouldEagerLoadTasks,
|
|
186
253
|
queryFn: fetchBoardTasks,
|
|
187
254
|
refetchOnMount: 'always',
|
|
@@ -253,22 +320,52 @@ export function BoardViews({
|
|
|
253
320
|
[board.id, workspace.personal]
|
|
254
321
|
);
|
|
255
322
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
const closedLists = boardLists.filter(
|
|
325
|
+
(list) => !list.deleted && list.status === 'closed'
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (closedLists.length === 0) {
|
|
329
|
+
setClosedTaskListsCollapsed({});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
setClosedTaskListsCollapsed((previous) => {
|
|
334
|
+
const next: Record<string, boolean> = {};
|
|
335
|
+
|
|
336
|
+
for (const list of closedLists) {
|
|
337
|
+
const storedValue =
|
|
338
|
+
typeof window === 'undefined'
|
|
339
|
+
? null
|
|
340
|
+
: window.localStorage.getItem(
|
|
341
|
+
getClosedTaskListCollapsedStorageKey(board.id, list.id)
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
next[list.id] =
|
|
345
|
+
storedValue === null
|
|
346
|
+
? (previous[list.id] ?? true)
|
|
347
|
+
: storedValue === 'true';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return next;
|
|
351
|
+
});
|
|
352
|
+
}, [board.id, boardLists]);
|
|
353
|
+
|
|
354
|
+
const handleTaskListCollapsedChange = useCallback(
|
|
355
|
+
(listId: string, collapsed: boolean) => {
|
|
356
|
+
setClosedTaskListsCollapsed((previous) => ({
|
|
357
|
+
...previous,
|
|
358
|
+
[listId]: collapsed,
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
if (typeof window === 'undefined') return;
|
|
362
|
+
|
|
363
|
+
window.localStorage.setItem(
|
|
364
|
+
getClosedTaskListCollapsedStorageKey(board.id, listId),
|
|
365
|
+
String(collapsed)
|
|
366
|
+
);
|
|
367
|
+
},
|
|
368
|
+
[board.id]
|
|
272
369
|
);
|
|
273
370
|
|
|
274
371
|
const externalStagingList = useMemo<TaskList | null>(() => {
|
|
@@ -297,63 +394,43 @@ export function BoardViews({
|
|
|
297
394
|
]);
|
|
298
395
|
|
|
299
396
|
const activeLists = useMemo(() => {
|
|
300
|
-
const realLists = boardLists
|
|
397
|
+
const realLists = boardLists
|
|
398
|
+
.filter((list) => !list.deleted)
|
|
399
|
+
.map((list) =>
|
|
400
|
+
list.status === 'closed'
|
|
401
|
+
? {
|
|
402
|
+
...list,
|
|
403
|
+
is_collapsed: closedTaskListsCollapsed[list.id] ?? true,
|
|
404
|
+
}
|
|
405
|
+
: list
|
|
406
|
+
);
|
|
301
407
|
return externalStagingList
|
|
302
408
|
? [externalStagingList, ...realLists]
|
|
303
409
|
: realLists;
|
|
304
|
-
}, [boardLists, externalStagingList]);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// Find the first list that still has more pages and isn't loading
|
|
320
|
-
for (const list of activeLists) {
|
|
321
|
-
const state = pagination[list.id];
|
|
322
|
-
if (state?.hasMore && !state.isLoading) {
|
|
323
|
-
autoLoadingRef.current = true;
|
|
324
|
-
loadListPage(list.id, state.page + 1).finally(() => {
|
|
325
|
-
autoLoadingRef.current = false;
|
|
410
|
+
}, [boardLists, closedTaskListsCollapsed, externalStagingList]);
|
|
411
|
+
|
|
412
|
+
const { data: filteredListCounts, isFetching: isFilteredListCountsFetching } =
|
|
413
|
+
useQuery({
|
|
414
|
+
queryKey: ['task-list-counts', board.id, taskFilterKey],
|
|
415
|
+
enabled: hasTaskFilters,
|
|
416
|
+
queryFn: async () => {
|
|
417
|
+
const result = await listWorkspaceTasks(effectiveWorkspaceId, {
|
|
418
|
+
...taskQueryOptions,
|
|
419
|
+
boardId: board.id,
|
|
420
|
+
includeListCounts: true,
|
|
421
|
+
includeRelationshipSummary: false,
|
|
422
|
+
limit: 0,
|
|
423
|
+
listStatuses: listStatusesForQuery,
|
|
326
424
|
});
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
currentView,
|
|
332
|
-
hasActiveFilters,
|
|
333
|
-
pagination,
|
|
334
|
-
activeLists,
|
|
335
|
-
loadListPage,
|
|
336
|
-
sourceScope,
|
|
337
|
-
]);
|
|
338
|
-
|
|
339
|
-
// Semantic search hook
|
|
340
|
-
const {
|
|
341
|
-
data: semanticSearchResults = [],
|
|
342
|
-
isLoading: isSearchLoading,
|
|
343
|
-
isFetching: isSearchFetching,
|
|
344
|
-
} = useSemanticTaskSearch({
|
|
345
|
-
wsId: effectiveWorkspaceId,
|
|
346
|
-
query: filters.searchQuery || '',
|
|
347
|
-
matchThreshold: 0.3,
|
|
348
|
-
matchCount: 50,
|
|
349
|
-
enabled: !!filters.searchQuery && filters.searchQuery.trim().length > 0,
|
|
350
|
-
});
|
|
425
|
+
return result.listCounts ?? [];
|
|
426
|
+
},
|
|
427
|
+
staleTime: 30_000,
|
|
428
|
+
});
|
|
351
429
|
|
|
352
430
|
// Filter lists based on selected status filter
|
|
353
|
-
const
|
|
354
|
-
if (listStatusFilter === 'all')
|
|
355
|
-
|
|
356
|
-
}
|
|
431
|
+
const statusFilteredLists = useMemo(() => {
|
|
432
|
+
if (listStatusFilter === 'all') return activeLists;
|
|
433
|
+
|
|
357
434
|
const stagingLists = activeLists.filter((list) => list.is_external_staging);
|
|
358
435
|
const realLists = activeLists.filter(
|
|
359
436
|
(list) => !list.is_external_staging && list.status === listStatusFilter
|
|
@@ -361,83 +438,26 @@ export function BoardViews({
|
|
|
361
438
|
return [...stagingLists, ...realLists];
|
|
362
439
|
}, [activeLists, listStatusFilter]);
|
|
363
440
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
(tasksToFilter: Task[]) => {
|
|
367
|
-
let result = tasksToFilter;
|
|
368
|
-
|
|
369
|
-
// Filter by labels
|
|
370
|
-
if (filters.labels.length > 0) {
|
|
371
|
-
result = result.filter((task) => {
|
|
372
|
-
if (!task.labels || task.labels.length === 0) return false;
|
|
373
|
-
return filters.labels.some((selectedLabel) =>
|
|
374
|
-
task.labels?.some((taskLabel) => taskLabel.id === selectedLabel.id)
|
|
375
|
-
);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Filter by assignees or "my tasks"
|
|
380
|
-
if (filters.includeMyTasks && currentUserId) {
|
|
381
|
-
result = result.filter((task) =>
|
|
382
|
-
task.assignees?.some((a) => a.id === currentUserId)
|
|
383
|
-
);
|
|
384
|
-
} else if (filters.assignees.length > 0) {
|
|
385
|
-
result = result.filter((task) =>
|
|
386
|
-
task.assignees?.some((a) =>
|
|
387
|
-
filters.assignees.some((fa) => fa.id === a.id)
|
|
388
|
-
)
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Filter by unassigned
|
|
393
|
-
if (filters.includeUnassigned) {
|
|
394
|
-
result = result.filter(
|
|
395
|
-
(task) => !task.assignees || task.assignees.length === 0
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Filter by projects
|
|
400
|
-
if (filters.projects.length > 0) {
|
|
401
|
-
result = result.filter((task) => {
|
|
402
|
-
// Check if task has projects relationship
|
|
403
|
-
if (!task.projects || task.projects.length === 0) return false;
|
|
404
|
-
return task.projects.some((pt: any) =>
|
|
405
|
-
filters.projects.some((p) => p.id === pt.id)
|
|
406
|
-
);
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Filter by priorities
|
|
411
|
-
if (filters.priorities.length > 0) {
|
|
412
|
-
result = result.filter((task) =>
|
|
413
|
-
task.priority ? filters.priorities.includes(task.priority) : false
|
|
414
|
-
);
|
|
415
|
-
}
|
|
441
|
+
const filteredLists = useMemo(() => {
|
|
442
|
+
if (!hasTaskFilters || !filteredListCounts) return statusFilteredLists;
|
|
416
443
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (!task.end_date) return false;
|
|
421
|
-
const taskDate = new Date(task.end_date);
|
|
422
|
-
const fromDate = filters.dueDateRange!.from!;
|
|
423
|
-
const toDate = filters.dueDateRange!.to;
|
|
424
|
-
return taskDate >= fromDate && (!toDate || taskDate <= toDate);
|
|
425
|
-
});
|
|
426
|
-
}
|
|
444
|
+
const countByListId = new Map(
|
|
445
|
+
filteredListCounts.map((entry) => [entry.list_id, entry.count] as const)
|
|
446
|
+
);
|
|
427
447
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
);
|
|
448
|
+
return statusFilteredLists.filter(
|
|
449
|
+
(list) => (countByListId.get(list.id) ?? 0) > 0
|
|
450
|
+
);
|
|
451
|
+
}, [filteredListCounts, hasTaskFilters, statusFilteredLists]);
|
|
432
452
|
|
|
433
453
|
const sourceTasks = useMemo(() => {
|
|
434
454
|
if (!shouldEagerLoadTasks) return tasks;
|
|
435
455
|
|
|
436
456
|
if (fullTasks.length === 0) {
|
|
437
|
-
return sourceScope
|
|
457
|
+
return hasServerTaskQuery || sourceScope !== 'all_visible' ? [] : tasks;
|
|
438
458
|
}
|
|
439
459
|
|
|
440
|
-
if (sourceScope !== 'all_visible') {
|
|
460
|
+
if (hasServerTaskQuery || sourceScope !== 'all_visible') {
|
|
441
461
|
return fullTasks;
|
|
442
462
|
}
|
|
443
463
|
|
|
@@ -455,44 +475,15 @@ export function BoardViews({
|
|
|
455
475
|
}
|
|
456
476
|
|
|
457
477
|
return merged;
|
|
458
|
-
}, [fullTasks, shouldEagerLoadTasks, sourceScope, tasks]);
|
|
478
|
+
}, [fullTasks, hasServerTaskQuery, shouldEagerLoadTasks, sourceScope, tasks]);
|
|
459
479
|
|
|
460
|
-
//
|
|
480
|
+
// Keep only tasks that belong to the server-visible lists/status scope.
|
|
461
481
|
const filteredTasks = useMemo(() => {
|
|
462
|
-
// First, filter by list status
|
|
463
482
|
const listIds = new Set(filteredLists.map((list) => list.id));
|
|
464
|
-
|
|
483
|
+
return isExternalSourceScope
|
|
465
484
|
? sourceTasks
|
|
466
485
|
: sourceTasks.filter((task) => listIds.has(task.list_id));
|
|
467
|
-
|
|
468
|
-
// If there's a search query, use semantic search results
|
|
469
|
-
if (filters.searchQuery && filters.searchQuery.trim().length > 0) {
|
|
470
|
-
// Create a map of task IDs to their search ranking
|
|
471
|
-
const searchRankMap = new Map(
|
|
472
|
-
semanticSearchResults.map((result, index) => [result.id, index])
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
// Filter to only include semantic search results
|
|
476
|
-
result = result.filter((task) => searchRankMap.has(task.id));
|
|
477
|
-
|
|
478
|
-
// Sort by search relevance (lower index = higher relevance)
|
|
479
|
-
result.sort((a, b) => {
|
|
480
|
-
const rankA = searchRankMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
481
|
-
const rankB = searchRankMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
482
|
-
return rankA - rankB;
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Apply other filters (labels, assignees, projects, priorities, due date)
|
|
487
|
-
return applyNonSearchFilters(result);
|
|
488
|
-
}, [
|
|
489
|
-
sourceTasks,
|
|
490
|
-
filters,
|
|
491
|
-
filteredLists,
|
|
492
|
-
isExternalSourceScope,
|
|
493
|
-
semanticSearchResults,
|
|
494
|
-
applyNonSearchFilters,
|
|
495
|
-
]);
|
|
486
|
+
}, [filteredLists, isExternalSourceScope, sourceTasks]);
|
|
496
487
|
|
|
497
488
|
// Apply optimistic overrides so views receive up-to-date edits (durations, name, dates) even before refetch.
|
|
498
489
|
const effectiveTasks = useMemo(() => {
|
|
@@ -508,141 +499,8 @@ export function BoardViews({
|
|
|
508
499
|
|
|
509
500
|
tasks = tasks.filter((task) => !task.deleted_at);
|
|
510
501
|
|
|
511
|
-
// Apply sorting - but NEVER sort done/closed tasks (they always sort by timestamps)
|
|
512
|
-
if (filters.sortBy) {
|
|
513
|
-
// Create a map of list_id to status
|
|
514
|
-
const listStatusMap = new Map(
|
|
515
|
-
activeLists.map((list) => [list.id, list.status])
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
// Separate tasks into sortable and completion (done/closed) tasks
|
|
519
|
-
const sortableTasks = tasks.filter((task) => {
|
|
520
|
-
const status = listStatusMap.get(task.list_id);
|
|
521
|
-
return status !== 'done' && status !== 'closed';
|
|
522
|
-
});
|
|
523
|
-
const completionTasks = tasks.filter((task) => {
|
|
524
|
-
const status = listStatusMap.get(task.list_id);
|
|
525
|
-
return status === 'done' || status === 'closed';
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
// Sort only the sortable tasks
|
|
529
|
-
const sorted = [...sortableTasks];
|
|
530
|
-
switch (filters.sortBy) {
|
|
531
|
-
case 'name-asc':
|
|
532
|
-
sorted.sort((a, b) => a.name.localeCompare(b.name));
|
|
533
|
-
break;
|
|
534
|
-
case 'name-desc':
|
|
535
|
-
sorted.sort((a, b) => b.name.localeCompare(a.name));
|
|
536
|
-
break;
|
|
537
|
-
case 'priority-high': {
|
|
538
|
-
const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 };
|
|
539
|
-
sorted.sort((a, b) => {
|
|
540
|
-
const priorityA = a.priority
|
|
541
|
-
? priorityOrder[a.priority]
|
|
542
|
-
: Number.MAX_SAFE_INTEGER;
|
|
543
|
-
const priorityB = b.priority
|
|
544
|
-
? priorityOrder[b.priority]
|
|
545
|
-
: Number.MAX_SAFE_INTEGER;
|
|
546
|
-
return priorityA - priorityB;
|
|
547
|
-
});
|
|
548
|
-
break;
|
|
549
|
-
}
|
|
550
|
-
case 'priority-low': {
|
|
551
|
-
const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 };
|
|
552
|
-
sorted.sort((a, b) => {
|
|
553
|
-
const priorityA = a.priority
|
|
554
|
-
? priorityOrder[a.priority]
|
|
555
|
-
: Number.MAX_SAFE_INTEGER;
|
|
556
|
-
const priorityB = b.priority
|
|
557
|
-
? priorityOrder[b.priority]
|
|
558
|
-
: Number.MAX_SAFE_INTEGER;
|
|
559
|
-
return priorityB - priorityA;
|
|
560
|
-
});
|
|
561
|
-
break;
|
|
562
|
-
}
|
|
563
|
-
case 'due-date-asc':
|
|
564
|
-
sorted.sort((a, b) => {
|
|
565
|
-
if (!a.end_date && !b.end_date) return 0;
|
|
566
|
-
if (!a.end_date) return 1;
|
|
567
|
-
if (!b.end_date) return -1;
|
|
568
|
-
return (
|
|
569
|
-
new Date(a.end_date).getTime() - new Date(b.end_date).getTime()
|
|
570
|
-
);
|
|
571
|
-
});
|
|
572
|
-
break;
|
|
573
|
-
case 'due-date-desc':
|
|
574
|
-
sorted.sort((a, b) => {
|
|
575
|
-
if (!a.end_date && !b.end_date) return 0;
|
|
576
|
-
if (!a.end_date) return -1;
|
|
577
|
-
if (!b.end_date) return 1;
|
|
578
|
-
return (
|
|
579
|
-
new Date(b.end_date).getTime() - new Date(a.end_date).getTime()
|
|
580
|
-
);
|
|
581
|
-
});
|
|
582
|
-
break;
|
|
583
|
-
case 'created-date-desc':
|
|
584
|
-
sorted.sort(
|
|
585
|
-
(a, b) =>
|
|
586
|
-
new Date(b.created_at).getTime() -
|
|
587
|
-
new Date(a.created_at).getTime()
|
|
588
|
-
);
|
|
589
|
-
break;
|
|
590
|
-
case 'created-date-asc':
|
|
591
|
-
sorted.sort(
|
|
592
|
-
(a, b) =>
|
|
593
|
-
new Date(a.created_at).getTime() -
|
|
594
|
-
new Date(b.created_at).getTime()
|
|
595
|
-
);
|
|
596
|
-
break;
|
|
597
|
-
case 'estimation-high':
|
|
598
|
-
sorted.sort((a, b) => {
|
|
599
|
-
const estA = a.estimation_points ?? Number.MIN_SAFE_INTEGER;
|
|
600
|
-
const estB = b.estimation_points ?? Number.MIN_SAFE_INTEGER;
|
|
601
|
-
return estB - estA;
|
|
602
|
-
});
|
|
603
|
-
break;
|
|
604
|
-
case 'estimation-low':
|
|
605
|
-
sorted.sort((a, b) => {
|
|
606
|
-
const estA = a.estimation_points ?? Number.MAX_SAFE_INTEGER;
|
|
607
|
-
const estB = b.estimation_points ?? Number.MAX_SAFE_INTEGER;
|
|
608
|
-
return estA - estB;
|
|
609
|
-
});
|
|
610
|
-
break;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Sort completion tasks by their respective timestamps
|
|
614
|
-
const sortedCompletionTasks = [...completionTasks].sort((a, b) => {
|
|
615
|
-
const statusA = listStatusMap.get(a.list_id);
|
|
616
|
-
const statusB = listStatusMap.get(b.list_id);
|
|
617
|
-
|
|
618
|
-
// For done tasks, sort by completed_at
|
|
619
|
-
if (statusA === 'done' && statusB === 'done') {
|
|
620
|
-
const completionA = a.completed_at
|
|
621
|
-
? new Date(a.completed_at).getTime()
|
|
622
|
-
: 0;
|
|
623
|
-
const completionB = b.completed_at
|
|
624
|
-
? new Date(b.completed_at).getTime()
|
|
625
|
-
: 0;
|
|
626
|
-
return completionB - completionA; // Most recent first
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// For closed tasks, sort by closed_at
|
|
630
|
-
if (statusA === 'closed' && statusB === 'closed') {
|
|
631
|
-
const closedA = a.closed_at ? new Date(a.closed_at).getTime() : 0;
|
|
632
|
-
const closedB = b.closed_at ? new Date(b.closed_at).getTime() : 0;
|
|
633
|
-
return closedB - closedA; // Most recent first
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Mixed statuses - maintain original order
|
|
637
|
-
return 0;
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
// Combine sorted tasks with completion tasks (unsorted)
|
|
641
|
-
return [...sorted, ...sortedCompletionTasks];
|
|
642
|
-
}
|
|
643
|
-
|
|
644
502
|
return tasks;
|
|
645
|
-
}, [filteredTasks, taskOverrides
|
|
503
|
+
}, [filteredTasks, taskOverrides]);
|
|
646
504
|
|
|
647
505
|
const handleTaskPartialUpdate = (taskId: string, partial: Partial<Task>) => {
|
|
648
506
|
setTaskOverrides((prev) => ({
|
|
@@ -728,6 +586,8 @@ export function BoardViews({
|
|
|
728
586
|
isMultiSelectMode={isMultiSelectMode}
|
|
729
587
|
setIsMultiSelectMode={setIsMultiSelectMode}
|
|
730
588
|
onExternalTasksCollapsedChange={handleExternalTasksCollapsedChange}
|
|
589
|
+
onTaskListCollapsedChange={handleTaskListCollapsedChange}
|
|
590
|
+
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
731
591
|
/>
|
|
732
592
|
);
|
|
733
593
|
case 'list':
|
|
@@ -768,11 +628,17 @@ export function BoardViews({
|
|
|
768
628
|
isMultiSelectMode={isMultiSelectMode}
|
|
769
629
|
setIsMultiSelectMode={setIsMultiSelectMode}
|
|
770
630
|
onExternalTasksCollapsedChange={handleExternalTasksCollapsedChange}
|
|
631
|
+
onTaskListCollapsedChange={handleTaskListCollapsedChange}
|
|
632
|
+
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
771
633
|
/>
|
|
772
634
|
);
|
|
773
635
|
}
|
|
774
636
|
};
|
|
775
637
|
|
|
638
|
+
const showIdleBottomIsland =
|
|
639
|
+
!!idleBottomIsland &&
|
|
640
|
+
(currentView !== 'kanban' || !kanbanBulkSelectionActive);
|
|
641
|
+
|
|
776
642
|
return (
|
|
777
643
|
<div className="-m-2 -mb-4 flex h-[calc(100vh-0.5rem)] flex-1 flex-col md:-mx-4">
|
|
778
644
|
<BoardHeader
|
|
@@ -787,7 +653,7 @@ export function BoardViews({
|
|
|
787
653
|
listStatusFilter={listStatusFilter}
|
|
788
654
|
onListStatusFilterChange={setListStatusFilter}
|
|
789
655
|
isPersonalWorkspace={workspace.personal}
|
|
790
|
-
isSearching={
|
|
656
|
+
isSearching={isFullTasksFetching || isFilteredListCountsFetching}
|
|
791
657
|
lists={boardLists}
|
|
792
658
|
onUpdate={handleUpdate}
|
|
793
659
|
onRecycleBinOpen={() => setRecycleBinOpen(true)}
|
|
@@ -796,6 +662,7 @@ export function BoardViews({
|
|
|
796
662
|
hideActions={!canManageBoard}
|
|
797
663
|
/>
|
|
798
664
|
<div className="h-full overflow-hidden">{renderView()}</div>
|
|
665
|
+
{showIdleBottomIsland ? idleBottomIsland : null}
|
|
799
666
|
|
|
800
667
|
<RecycleBinPanel
|
|
801
668
|
open={recycleBinOpen}
|