@tuturuuu/ui 0.8.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 +69 -0
- package/biome.json +1 -1
- package/package.json +74 -71
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +1 -1
- package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
- package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
- package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
- package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/carousel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
- package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
- package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/color-picker.tsx +1 -1
- package/src/components/ui/command.tsx +1 -1
- package/src/components/ui/context-menu.tsx +5 -1
- 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__/settings-dialog-shell.test.tsx +3 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +46 -1
- package/src/components/ui/custom/combobox.test.tsx +195 -0
- package/src/components/ui/custom/combobox.tsx +273 -156
- package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
- package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
- package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -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 +65 -28
- package/src/components/ui/custom/theme-toggle.tsx +1 -1
- package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
- package/src/components/ui/custom/workspace-select.tsx +25 -19
- package/src/components/ui/dialog.test.tsx +52 -0
- package/src/components/ui/dialog.tsx +6 -2
- package/src/components/ui/dropdown-menu.tsx +5 -1
- package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
- package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
- package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
- package/src/components/ui/finance/debts/debts-page.tsx +15 -2
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
- package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/utils.ts +3 -1
- package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
- package/src/components/ui/finance/transactions/form-types.ts +1 -0
- package/src/components/ui/finance/transactions/form.tsx +2 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
- package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
- package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
- package/src/components/ui/finance/wallets/form.test.tsx +51 -3
- package/src/components/ui/finance/wallets/form.tsx +15 -4
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
- package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
- package/src/components/ui/input-otp.tsx +1 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
- package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
- package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
- package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
- package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
- package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
- package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
- package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
- package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
- package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
- package/src/components/ui/navigation-menu.tsx +1 -1
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/radio-group.tsx +1 -1
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/ui/storefront/cart-popover.tsx +61 -0
- package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
- package/src/components/ui/storefront/cart-summary.tsx +93 -154
- package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
- package/src/components/ui/storefront/listing-card.tsx +1 -1
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +1 -1
- package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
- package/src/components/ui/storefront/storefront-surface.tsx +101 -166
- package/src/components/ui/storefront/types.ts +4 -0
- package/src/components/ui/storefront/utils.ts +6 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/background-color-extension.ts +62 -0
- package/src/components/ui/text-editor/color-controls.tsx +284 -0
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +8 -2
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +286 -0
- package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
- package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
- package/src/components/ui/tu-do/boards/board-share-dialog.tsx +15 -226
- 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 +121 -39
- 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-mutations-clear-delete.ts +2 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -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/kanban-deadline-query.ts +50 -2
- 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/__tests__/column-reorder.test.ts +17 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +642 -5
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +224 -15
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +535 -53
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +101 -33
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +235 -113
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +50 -5
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +12 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +10 -1
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
- 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 +271 -36
- package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
- package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
- package/src/components/ui/tu-do/boards/boardId/task-list.tsx +22 -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/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
- package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
- 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/initiatives/task-initiatives-client.tsx +56 -88
- package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
- package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
- 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 +141 -1
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +377 -36
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +374 -0
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +419 -5
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +38 -0
- package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
- package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -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 +15 -10
- package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +471 -975
- package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
- package/src/components/ui/tu-do/shared/board-switcher.tsx +244 -220
- package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
- package/src/components/ui/tu-do/shared/board-views.tsx +577 -85
- package/src/components/ui/tu-do/shared/list-view.tsx +246 -2
- package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
- package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
- 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/field-diff-viewer.tsx +3 -2
- 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/selective-revert-panel.test.tsx +91 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
- 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/task-snapshot-dialog.tsx +8 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +11 -1
- package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
- 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/declarations.d.ts +1 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
- package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
- package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
- package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
- package/src/hooks/use-calendar-sync.tsx +247 -243
- package/src/hooks/use-calendar.tsx +323 -138
- package/src/hooks/use-task-actions.ts +24 -0
- package/src/hooks/use-user-workspace-config.ts +75 -0
- package/src/hooks/use-workspace-currency.ts +8 -3
- package/src/hooks/useBoardPresence.ts +364 -0
- package/src/hooks/useBoardRealtimeEventHandler.ts +45 -90
- package/src/lib/workspace-actions.ts +2 -6
|
@@ -10,6 +10,10 @@ import {
|
|
|
10
10
|
type ListWorkspaceTasksOptions,
|
|
11
11
|
listWorkspaceTasks,
|
|
12
12
|
} from '@tuturuuu/internal-api/tasks';
|
|
13
|
+
import {
|
|
14
|
+
TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID,
|
|
15
|
+
TASK_LAST_BOARD_VIEW_CONFIG_ID,
|
|
16
|
+
} from '@tuturuuu/internal-api/users';
|
|
13
17
|
import type {
|
|
14
18
|
Workspace,
|
|
15
19
|
WorkspaceProductTier,
|
|
@@ -19,6 +23,7 @@ import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
|
19
23
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
20
24
|
import {
|
|
21
25
|
getPersonalExternalStagingListId,
|
|
26
|
+
priorityCompare,
|
|
22
27
|
type WorkspaceLabel,
|
|
23
28
|
} from '@tuturuuu/utils/task-helper';
|
|
24
29
|
import { useTranslations } from 'next-intl';
|
|
@@ -30,25 +35,51 @@ import {
|
|
|
30
35
|
useMemo,
|
|
31
36
|
useState,
|
|
32
37
|
} from 'react';
|
|
38
|
+
import {
|
|
39
|
+
useUpdateUserWorkspaceConfig,
|
|
40
|
+
useUserWorkspaceConfig,
|
|
41
|
+
} from '../../../../hooks/use-user-workspace-config';
|
|
33
42
|
import { KanbanBoard } from '../boards/boardId/kanban';
|
|
43
|
+
import type {
|
|
44
|
+
KanbanDeadlineCollapsedState,
|
|
45
|
+
KanbanDeadlineSection,
|
|
46
|
+
} from '../boards/boardId/kanban/rendering/kanban-deadline-panels';
|
|
34
47
|
import type { TaskFilters } from '../boards/boardId/task-filter';
|
|
35
48
|
import { TimelineBoard } from '../boards/boardId/timeline-board';
|
|
49
|
+
import { DraftsPage } from '../drafts/drafts-page';
|
|
36
50
|
import { useTaskDialog } from '../hooks/useTaskDialog';
|
|
51
|
+
import MyTasksContent from '../my-tasks/my-tasks-content';
|
|
37
52
|
import { BoardHeader, type ListStatusFilter } from '../shared/board-header';
|
|
38
53
|
import { ListView } from '../shared/list-view';
|
|
39
|
-
import {
|
|
54
|
+
import { RecycleBinContent } from '../shared/recycle-bin-panel';
|
|
40
55
|
import { loadBoardConfig } from './board-config-storage';
|
|
56
|
+
import {
|
|
57
|
+
parseSpecialTaskListPins,
|
|
58
|
+
type SpecialTaskListPin,
|
|
59
|
+
serializeSpecialTaskListPins,
|
|
60
|
+
} from './special-task-list-pins';
|
|
41
61
|
|
|
42
|
-
export type ViewType =
|
|
62
|
+
export type ViewType =
|
|
63
|
+
| 'kanban'
|
|
64
|
+
| 'list'
|
|
65
|
+
| 'my_tasks'
|
|
66
|
+
| 'timeline'
|
|
67
|
+
| 'drafts'
|
|
68
|
+
| 'recycle_bin';
|
|
43
69
|
|
|
44
70
|
const HOTKEY_CREATE_TASK = 'C';
|
|
45
71
|
const HOTKEY_GO_TO_KANBAN: ['G', 'K'] = ['G', 'K'];
|
|
46
72
|
const HOTKEY_GO_TO_LIST: ['G', 'L'] = ['G', 'L'];
|
|
73
|
+
const HOTKEY_GO_TO_MY_TASKS: ['G', 'M'] = ['G', 'M'];
|
|
47
74
|
const HOTKEY_GO_TO_TIMELINE: ['G', 'T'] = ['G', 'T'];
|
|
75
|
+
const HOTKEY_GO_TO_DRAFTS: ['G', 'D'] = ['G', 'D'];
|
|
76
|
+
const HOTKEY_GO_TO_RECYCLE_BIN: ['G', 'R'] = ['G', 'R'];
|
|
48
77
|
const EXTERNAL_TASKS_COLLAPSED_STORAGE_PREFIX =
|
|
49
78
|
'personal-board-external-tasks-collapsed';
|
|
50
79
|
const CLOSED_TASK_LIST_COLLAPSED_STORAGE_PREFIX =
|
|
51
80
|
'task-board-closed-list-collapsed';
|
|
81
|
+
const DEADLINE_SECTION_COLLAPSED_STORAGE_PREFIX =
|
|
82
|
+
'task-board-deadline-section-collapsed';
|
|
52
83
|
const DEFAULT_TASK_FILTERS: TaskFilters = {
|
|
53
84
|
labels: [],
|
|
54
85
|
assignees: [],
|
|
@@ -63,31 +94,163 @@ const DEFAULT_TASK_FILTERS: TaskFilters = {
|
|
|
63
94
|
sourceBoardIds: [],
|
|
64
95
|
};
|
|
65
96
|
|
|
66
|
-
function
|
|
67
|
-
|
|
97
|
+
function getClosedTaskListCollapsedStorageKey(boardId: string, listId: string) {
|
|
98
|
+
return `${CLOSED_TASK_LIST_COLLAPSED_STORAGE_PREFIX}:${boardId}:${listId}`;
|
|
99
|
+
}
|
|
68
100
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
101
|
+
function getDeadlineSectionCollapsedStorageKey(
|
|
102
|
+
boardId: string,
|
|
103
|
+
section: KanbanDeadlineSection
|
|
104
|
+
) {
|
|
105
|
+
return `${DEADLINE_SECTION_COLLAPSED_STORAGE_PREFIX}:${boardId}:${section}`;
|
|
75
106
|
}
|
|
76
107
|
|
|
77
|
-
function
|
|
78
|
-
|
|
108
|
+
function taskMatchesLocalFilters(
|
|
109
|
+
task: Task,
|
|
110
|
+
filters: TaskFilters,
|
|
111
|
+
currentUserId?: string
|
|
112
|
+
) {
|
|
113
|
+
const query = filters.searchQuery?.trim().toLowerCase();
|
|
114
|
+
if (query) {
|
|
115
|
+
const searchableText = [
|
|
116
|
+
task.name,
|
|
117
|
+
task.display_number ? String(task.display_number) : null,
|
|
118
|
+
...(task.labels ?? []).map((label) => label.name),
|
|
119
|
+
...(task.projects ?? []).map((project) => project.name),
|
|
120
|
+
...(task.assignees ?? []).map(
|
|
121
|
+
(assignee) => assignee.display_name ?? assignee.email ?? assignee.handle
|
|
122
|
+
),
|
|
123
|
+
]
|
|
124
|
+
.filter(Boolean)
|
|
125
|
+
.join(' ')
|
|
126
|
+
.toLowerCase();
|
|
127
|
+
|
|
128
|
+
if (!searchableText.includes(query)) return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
filters.labels.length > 0 &&
|
|
133
|
+
!filters.labels.every((label) =>
|
|
134
|
+
task.labels?.some((taskLabel) => taskLabel.id === label.id)
|
|
135
|
+
)
|
|
136
|
+
) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
filters.projects.length > 0 &&
|
|
142
|
+
!filters.projects.every((project) =>
|
|
143
|
+
task.projects?.some((taskProject) => taskProject.id === project.id)
|
|
144
|
+
)
|
|
145
|
+
) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
filters.priorities.length > 0 &&
|
|
151
|
+
(!task.priority || !filters.priorities.includes(task.priority))
|
|
152
|
+
) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (filters.assignees.length > 0) {
|
|
157
|
+
const assigneeIds = new Set(
|
|
158
|
+
filters.assignees.map((assignee) => assignee.id)
|
|
159
|
+
);
|
|
160
|
+
if (!task.assignees?.some((assignee) => assigneeIds.has(assignee.id))) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
filters.includeMyTasks &&
|
|
167
|
+
currentUserId &&
|
|
168
|
+
!task.assignees?.some((assignee) => assignee.id === currentUserId)
|
|
169
|
+
) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (filters.includeUnassigned && (task.assignees?.length ?? 0) > 0) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (filters.dueDateRange?.from || filters.dueDateRange?.to) {
|
|
178
|
+
if (!task.end_date) return false;
|
|
179
|
+
const dueTime = new Date(task.end_date).getTime();
|
|
180
|
+
const fromTime = filters.dueDateRange.from?.getTime() ?? -Infinity;
|
|
181
|
+
const toTime = filters.dueDateRange.to?.getTime() ?? Infinity;
|
|
182
|
+
if (dueTime < fromTime || dueTime > toTime) return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
typeof filters.estimationRange?.min === 'number' ||
|
|
187
|
+
typeof filters.estimationRange?.max === 'number'
|
|
188
|
+
) {
|
|
189
|
+
const estimate = task.estimation_points ?? 0;
|
|
190
|
+
const min = filters.estimationRange.min ?? -Infinity;
|
|
191
|
+
const max = filters.estimationRange.max ?? Infinity;
|
|
192
|
+
if (estimate < min || estimate > max) return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getTaskTimestamp(value: string | null | undefined) {
|
|
199
|
+
if (!value) return Number.MAX_SAFE_INTEGER;
|
|
200
|
+
const time = new Date(value).getTime();
|
|
201
|
+
return Number.isFinite(time) ? time : Number.MAX_SAFE_INTEGER;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function sortLocalTasks(tasks: Task[], sortBy: TaskFilters['sortBy']) {
|
|
205
|
+
if (!sortBy) return tasks;
|
|
206
|
+
|
|
207
|
+
return [...tasks].sort((a, b) => {
|
|
208
|
+
switch (sortBy) {
|
|
209
|
+
case 'name-asc':
|
|
210
|
+
return a.name.localeCompare(b.name);
|
|
211
|
+
case 'name-desc':
|
|
212
|
+
return b.name.localeCompare(a.name);
|
|
213
|
+
case 'priority-high':
|
|
214
|
+
return priorityCompare(a.priority ?? null, b.priority ?? null);
|
|
215
|
+
case 'priority-low':
|
|
216
|
+
return priorityCompare(b.priority ?? null, a.priority ?? null);
|
|
217
|
+
case 'due-date-asc':
|
|
218
|
+
return getTaskTimestamp(a.end_date) - getTaskTimestamp(b.end_date);
|
|
219
|
+
case 'due-date-desc':
|
|
220
|
+
return getTaskTimestamp(b.end_date) - getTaskTimestamp(a.end_date);
|
|
221
|
+
case 'created-date-asc':
|
|
222
|
+
return getTaskTimestamp(a.created_at) - getTaskTimestamp(b.created_at);
|
|
223
|
+
case 'created-date-desc':
|
|
224
|
+
return getTaskTimestamp(b.created_at) - getTaskTimestamp(a.created_at);
|
|
225
|
+
case 'estimation-high':
|
|
226
|
+
return (b.estimation_points ?? 0) - (a.estimation_points ?? 0);
|
|
227
|
+
case 'estimation-low':
|
|
228
|
+
return (a.estimation_points ?? 0) - (b.estimation_points ?? 0);
|
|
229
|
+
default:
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
79
233
|
}
|
|
80
234
|
|
|
81
235
|
interface Props {
|
|
82
236
|
workspace: Workspace;
|
|
83
237
|
workspaceTier?: WorkspaceProductTier | null;
|
|
84
|
-
board: WorkspaceTaskBoard
|
|
238
|
+
board: WorkspaceTaskBoard & {
|
|
239
|
+
access_type?: 'member' | 'guest';
|
|
240
|
+
guest_permission?: 'view' | 'edit' | null;
|
|
241
|
+
has_guest_access?: boolean;
|
|
242
|
+
};
|
|
85
243
|
tasks: Task[];
|
|
86
244
|
lists: TaskList[];
|
|
87
245
|
workspaceLabels: WorkspaceLabel[];
|
|
246
|
+
availableViews?: ViewType[];
|
|
88
247
|
canManageBoard?: boolean;
|
|
89
248
|
currentUserId?: string;
|
|
249
|
+
defaultView?: ViewType;
|
|
90
250
|
idleBottomIsland?: ReactNode;
|
|
251
|
+
publicHeaderPrefix?: ReactNode;
|
|
252
|
+
publicView?: boolean;
|
|
253
|
+
readOnly?: boolean;
|
|
91
254
|
}
|
|
92
255
|
|
|
93
256
|
export function BoardViews({
|
|
@@ -96,9 +259,14 @@ export function BoardViews({
|
|
|
96
259
|
board,
|
|
97
260
|
tasks,
|
|
98
261
|
lists,
|
|
262
|
+
availableViews,
|
|
99
263
|
canManageBoard = true,
|
|
100
264
|
currentUserId,
|
|
265
|
+
defaultView,
|
|
101
266
|
idleBottomIsland,
|
|
267
|
+
publicHeaderPrefix,
|
|
268
|
+
publicView = false,
|
|
269
|
+
readOnly = false,
|
|
102
270
|
}: Props) {
|
|
103
271
|
const t = useTranslations('common');
|
|
104
272
|
const tTasks = useTranslations('ws-tasks');
|
|
@@ -110,6 +278,8 @@ export function BoardViews({
|
|
|
110
278
|
const [closedTaskListsCollapsed, setClosedTaskListsCollapsed] = useState<
|
|
111
279
|
Record<string, boolean>
|
|
112
280
|
>({});
|
|
281
|
+
const [deadlineSectionsCollapsed, setDeadlineSectionsCollapsed] =
|
|
282
|
+
useState<KanbanDeadlineCollapsedState>({});
|
|
113
283
|
const [filters, setFilters] = useState<TaskFilters>(DEFAULT_TASK_FILTERS);
|
|
114
284
|
const [listStatusFilter, setListStatusFilter] =
|
|
115
285
|
useState<ListStatusFilter>('all');
|
|
@@ -117,11 +287,70 @@ export function BoardViews({
|
|
|
117
287
|
const [taskOverrides, setTaskOverrides] = useState<
|
|
118
288
|
Record<string, Partial<Task>>
|
|
119
289
|
>({});
|
|
120
|
-
const [recycleBinOpen, setRecycleBinOpen] = useState(false);
|
|
121
290
|
const [isMultiSelectMode, setIsMultiSelectMode] = useState(false);
|
|
122
291
|
const [kanbanBulkSelectionActive, setKanbanBulkSelectionActive] =
|
|
123
292
|
useState(false);
|
|
124
293
|
const { createTask } = useTaskDialog();
|
|
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';
|
|
307
|
+
const { data: pinnedSpecialListsRaw } = useUserWorkspaceConfig(
|
|
308
|
+
effectiveWorkspaceId,
|
|
309
|
+
TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID,
|
|
310
|
+
null,
|
|
311
|
+
{ enabled: !localTaskState }
|
|
312
|
+
);
|
|
313
|
+
const updateUserWorkspaceConfig = useUpdateUserWorkspaceConfig();
|
|
314
|
+
const specialTaskListPins = useMemo(
|
|
315
|
+
() => parseSpecialTaskListPins(pinnedSpecialListsRaw),
|
|
316
|
+
[pinnedSpecialListsRaw]
|
|
317
|
+
);
|
|
318
|
+
const handleSpecialTaskListPinnedChange = useCallback(
|
|
319
|
+
(pin: SpecialTaskListPin, pinned: boolean) => {
|
|
320
|
+
const nextPins = {
|
|
321
|
+
...specialTaskListPins,
|
|
322
|
+
[pin]: pinned,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (!pinned) delete nextPins[pin];
|
|
326
|
+
|
|
327
|
+
updateUserWorkspaceConfig.mutate({
|
|
328
|
+
configId: TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID,
|
|
329
|
+
value: serializeSpecialTaskListPins(nextPins),
|
|
330
|
+
workspaceId: effectiveWorkspaceId,
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
[effectiveWorkspaceId, specialTaskListPins, updateUserWorkspaceConfig]
|
|
334
|
+
);
|
|
335
|
+
const enabledViews = useMemo(
|
|
336
|
+
() =>
|
|
337
|
+
availableViews ??
|
|
338
|
+
(publicView || readOnly
|
|
339
|
+
? (['kanban', 'list', 'timeline'] as ViewType[])
|
|
340
|
+
: ([
|
|
341
|
+
'kanban',
|
|
342
|
+
'list',
|
|
343
|
+
'my_tasks',
|
|
344
|
+
'timeline',
|
|
345
|
+
'drafts',
|
|
346
|
+
'recycle_bin',
|
|
347
|
+
] as ViewType[])),
|
|
348
|
+
[availableViews, publicView, readOnly]
|
|
349
|
+
);
|
|
350
|
+
const viewIsEnabled = useCallback(
|
|
351
|
+
(view: ViewType) => !enabledViews || enabledViews.includes(view),
|
|
352
|
+
[enabledViews]
|
|
353
|
+
);
|
|
125
354
|
const sourceScope = filters.sourceScope ?? 'all_visible';
|
|
126
355
|
const sourceWorkspaceIds = filters.sourceWorkspaceIds ?? [];
|
|
127
356
|
const sourceBoardIds = filters.sourceBoardIds ?? [];
|
|
@@ -206,11 +435,21 @@ export function BoardViews({
|
|
|
206
435
|
taskQueryOptions,
|
|
207
436
|
]
|
|
208
437
|
);
|
|
438
|
+
const deadlineTaskQueryOptions = useMemo<ListWorkspaceTasksOptions>(() => {
|
|
439
|
+
const { sortBy: _sortBy, ...filterOptions } = taskQueryOptions;
|
|
440
|
+
return {
|
|
441
|
+
...filterOptions,
|
|
442
|
+
listStatuses: listStatusesForQuery,
|
|
443
|
+
};
|
|
444
|
+
}, [listStatusesForQuery, taskQueryOptions]);
|
|
209
445
|
const viewHotkeyLabels = useMemo(
|
|
210
446
|
() => ({
|
|
211
447
|
kanban: formatHotkeySequence(HOTKEY_GO_TO_KANBAN),
|
|
212
448
|
list: formatHotkeySequence(HOTKEY_GO_TO_LIST),
|
|
449
|
+
my_tasks: formatHotkeySequence(HOTKEY_GO_TO_MY_TASKS),
|
|
213
450
|
timeline: formatHotkeySequence(HOTKEY_GO_TO_TIMELINE),
|
|
451
|
+
drafts: formatHotkeySequence(HOTKEY_GO_TO_DRAFTS),
|
|
452
|
+
recycle_bin: formatHotkeySequence(HOTKEY_GO_TO_RECYCLE_BIN),
|
|
214
453
|
}),
|
|
215
454
|
[]
|
|
216
455
|
);
|
|
@@ -220,6 +459,7 @@ export function BoardViews({
|
|
|
220
459
|
const result = await listWorkspaceTasks(effectiveWorkspaceId, {
|
|
221
460
|
...taskQueryOptions,
|
|
222
461
|
boardId: board.id,
|
|
462
|
+
includeRelationshipSummary: false,
|
|
223
463
|
listStatuses: listStatusesForQuery,
|
|
224
464
|
limit: 200,
|
|
225
465
|
});
|
|
@@ -228,6 +468,7 @@ export function BoardViews({
|
|
|
228
468
|
|
|
229
469
|
const primeFullTaskCache = useCallback(
|
|
230
470
|
(nextView: ViewType) => {
|
|
471
|
+
if (localTaskState) return;
|
|
231
472
|
if (nextView !== 'list' && nextView !== 'timeline') return;
|
|
232
473
|
|
|
233
474
|
void queryClient.prefetchQuery({
|
|
@@ -236,20 +477,52 @@ export function BoardViews({
|
|
|
236
477
|
staleTime: 0,
|
|
237
478
|
});
|
|
238
479
|
},
|
|
239
|
-
[board.id, fetchBoardTasks, queryClient, taskFilterKey]
|
|
480
|
+
[board.id, fetchBoardTasks, localTaskState, queryClient, taskFilterKey]
|
|
240
481
|
);
|
|
241
482
|
|
|
242
483
|
const handleViewChange = useCallback(
|
|
243
484
|
(nextView: ViewType) => {
|
|
485
|
+
if (!viewIsEnabled(nextView)) return;
|
|
244
486
|
setCurrentView(nextView);
|
|
245
487
|
primeFullTaskCache(nextView);
|
|
488
|
+
if (!localTaskState) {
|
|
489
|
+
updateUserWorkspaceConfig.mutate({
|
|
490
|
+
configId: TASK_LAST_BOARD_VIEW_CONFIG_ID,
|
|
491
|
+
value: nextView,
|
|
492
|
+
workspaceId: effectiveWorkspaceId,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (typeof window !== 'undefined') {
|
|
497
|
+
const currentUrl = new URL(window.location.href);
|
|
498
|
+
if (!currentUrl.pathname.includes('/tasks/boards/')) return;
|
|
499
|
+
|
|
500
|
+
const params = currentUrl.searchParams;
|
|
501
|
+
if (nextView === 'kanban') {
|
|
502
|
+
params.delete('view');
|
|
503
|
+
} else {
|
|
504
|
+
params.set('view', nextView);
|
|
505
|
+
}
|
|
506
|
+
const nextQuery = params.toString();
|
|
507
|
+
window.history.replaceState(
|
|
508
|
+
window.history.state,
|
|
509
|
+
'',
|
|
510
|
+
`${currentUrl.pathname}${nextQuery ? `?${nextQuery}` : ''}${currentUrl.hash}`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
246
513
|
},
|
|
247
|
-
[
|
|
514
|
+
[
|
|
515
|
+
effectiveWorkspaceId,
|
|
516
|
+
localTaskState,
|
|
517
|
+
primeFullTaskCache,
|
|
518
|
+
updateUserWorkspaceConfig,
|
|
519
|
+
viewIsEnabled,
|
|
520
|
+
]
|
|
248
521
|
);
|
|
249
522
|
|
|
250
523
|
const { data: fullTasks = [], isFetching: isFullTasksFetching } = useQuery({
|
|
251
524
|
queryKey: ['tasks-full', board.id, taskFilterKey],
|
|
252
|
-
enabled: shouldEagerLoadTasks,
|
|
525
|
+
enabled: !localTaskState && shouldEagerLoadTasks,
|
|
253
526
|
queryFn: fetchBoardTasks,
|
|
254
527
|
refetchOnMount: 'always',
|
|
255
528
|
staleTime: 0,
|
|
@@ -273,21 +546,40 @@ export function BoardViews({
|
|
|
273
546
|
|
|
274
547
|
useLayoutEffect(() => {
|
|
275
548
|
const savedConfig = loadBoardConfig(board.id);
|
|
549
|
+
const requestedView =
|
|
550
|
+
typeof window === 'undefined' ||
|
|
551
|
+
!window.location.pathname.includes('/tasks/boards/')
|
|
552
|
+
? null
|
|
553
|
+
: (new URLSearchParams(window.location.search).get(
|
|
554
|
+
'view'
|
|
555
|
+
) as ViewType | null);
|
|
556
|
+
const fallbackView = enabledViews?.[0] ?? 'kanban';
|
|
557
|
+
const routeDefaultView =
|
|
558
|
+
defaultView && viewIsEnabled(defaultView) ? defaultView : null;
|
|
559
|
+
const effectiveDefaultView = routeDefaultView ?? fallbackView;
|
|
560
|
+
const initialView =
|
|
561
|
+
requestedView && viewIsEnabled(requestedView) ? requestedView : null;
|
|
276
562
|
|
|
277
563
|
if (!savedConfig) {
|
|
278
|
-
setCurrentView(
|
|
564
|
+
setCurrentView(initialView ?? effectiveDefaultView);
|
|
279
565
|
setFilters(DEFAULT_TASK_FILTERS);
|
|
280
566
|
setListStatusFilter('all');
|
|
281
567
|
return;
|
|
282
568
|
}
|
|
283
569
|
|
|
284
|
-
setCurrentView(
|
|
570
|
+
setCurrentView(
|
|
571
|
+
initialView ??
|
|
572
|
+
routeDefaultView ??
|
|
573
|
+
(viewIsEnabled(savedConfig.currentView)
|
|
574
|
+
? savedConfig.currentView
|
|
575
|
+
: effectiveDefaultView)
|
|
576
|
+
);
|
|
285
577
|
setFilters({
|
|
286
578
|
...DEFAULT_TASK_FILTERS,
|
|
287
579
|
...savedConfig.filters,
|
|
288
580
|
});
|
|
289
581
|
setListStatusFilter(savedConfig.listStatusFilter);
|
|
290
|
-
}, [board.id]);
|
|
582
|
+
}, [board.id, defaultView, enabledViews, viewIsEnabled]);
|
|
291
583
|
|
|
292
584
|
useEffect(() => {
|
|
293
585
|
if (!workspace.personal || typeof window === 'undefined') {
|
|
@@ -301,10 +593,8 @@ export function BoardViews({
|
|
|
301
593
|
const storedPreference =
|
|
302
594
|
storedValue === null ? null : storedValue === 'true';
|
|
303
595
|
|
|
304
|
-
setExternalTasksCollapsed(
|
|
305
|
-
|
|
306
|
-
);
|
|
307
|
-
}, [board.id, tasks, workspace.personal]);
|
|
596
|
+
setExternalTasksCollapsed(storedPreference ?? true);
|
|
597
|
+
}, [board.id, workspace.personal]);
|
|
308
598
|
|
|
309
599
|
const handleExternalTasksCollapsedChange = useCallback(
|
|
310
600
|
(collapsed: boolean) => {
|
|
@@ -368,6 +658,45 @@ export function BoardViews({
|
|
|
368
658
|
[board.id]
|
|
369
659
|
);
|
|
370
660
|
|
|
661
|
+
useEffect(() => {
|
|
662
|
+
setDeadlineSectionsCollapsed((previous) => {
|
|
663
|
+
const next: KanbanDeadlineCollapsedState = {};
|
|
664
|
+
|
|
665
|
+
for (const section of ['overdue', 'upcoming'] as const) {
|
|
666
|
+
const storedValue =
|
|
667
|
+
typeof window === 'undefined'
|
|
668
|
+
? null
|
|
669
|
+
: window.localStorage.getItem(
|
|
670
|
+
getDeadlineSectionCollapsedStorageKey(board.id, section)
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
next[section] =
|
|
674
|
+
storedValue === null
|
|
675
|
+
? (previous[section] ?? true)
|
|
676
|
+
: storedValue === 'true';
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return next;
|
|
680
|
+
});
|
|
681
|
+
}, [board.id]);
|
|
682
|
+
|
|
683
|
+
const handleDeadlineSectionCollapsedChange = useCallback(
|
|
684
|
+
(section: KanbanDeadlineSection, collapsed: boolean) => {
|
|
685
|
+
setDeadlineSectionsCollapsed((previous) => ({
|
|
686
|
+
...previous,
|
|
687
|
+
[section]: collapsed,
|
|
688
|
+
}));
|
|
689
|
+
|
|
690
|
+
if (typeof window === 'undefined') return;
|
|
691
|
+
|
|
692
|
+
window.localStorage.setItem(
|
|
693
|
+
getDeadlineSectionCollapsedStorageKey(board.id, section),
|
|
694
|
+
String(collapsed)
|
|
695
|
+
);
|
|
696
|
+
},
|
|
697
|
+
[board.id]
|
|
698
|
+
);
|
|
699
|
+
|
|
371
700
|
const externalStagingList = useMemo<TaskList | null>(() => {
|
|
372
701
|
if (!workspace.personal) return null;
|
|
373
702
|
|
|
@@ -409,10 +738,19 @@ export function BoardViews({
|
|
|
409
738
|
: realLists;
|
|
410
739
|
}, [boardLists, closedTaskListsCollapsed, externalStagingList]);
|
|
411
740
|
|
|
741
|
+
const effectiveDeadlineSectionsCollapsed =
|
|
742
|
+
useMemo<KanbanDeadlineCollapsedState>(
|
|
743
|
+
() => ({
|
|
744
|
+
overdue: deadlineSectionsCollapsed.overdue,
|
|
745
|
+
upcoming: deadlineSectionsCollapsed.upcoming,
|
|
746
|
+
}),
|
|
747
|
+
[deadlineSectionsCollapsed.overdue, deadlineSectionsCollapsed.upcoming]
|
|
748
|
+
);
|
|
749
|
+
|
|
412
750
|
const { data: filteredListCounts, isFetching: isFilteredListCountsFetching } =
|
|
413
751
|
useQuery({
|
|
414
752
|
queryKey: ['task-list-counts', board.id, taskFilterKey],
|
|
415
|
-
enabled: hasTaskFilters,
|
|
753
|
+
enabled: !localTaskState && hasTaskFilters,
|
|
416
754
|
queryFn: async () => {
|
|
417
755
|
const result = await listWorkspaceTasks(effectiveWorkspaceId, {
|
|
418
756
|
...taskQueryOptions,
|
|
@@ -427,6 +765,31 @@ export function BoardViews({
|
|
|
427
765
|
staleTime: 30_000,
|
|
428
766
|
});
|
|
429
767
|
|
|
768
|
+
const locallyFilteredTasks = useMemo(
|
|
769
|
+
() =>
|
|
770
|
+
sortLocalTasks(
|
|
771
|
+
tasks.filter((task) =>
|
|
772
|
+
taskMatchesLocalFilters(task, filters, currentUserId)
|
|
773
|
+
),
|
|
774
|
+
filters.sortBy
|
|
775
|
+
),
|
|
776
|
+
[currentUserId, filters, tasks]
|
|
777
|
+
);
|
|
778
|
+
|
|
779
|
+
const localListCounts = useMemo(() => {
|
|
780
|
+
if (!localTaskState || !hasTaskFilters) return null;
|
|
781
|
+
|
|
782
|
+
const counts = new Map<string, number>();
|
|
783
|
+
for (const task of locallyFilteredTasks) {
|
|
784
|
+
counts.set(task.list_id, (counts.get(task.list_id) ?? 0) + 1);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
return [...counts.entries()].map(([list_id, count]) => ({
|
|
788
|
+
list_id,
|
|
789
|
+
count,
|
|
790
|
+
}));
|
|
791
|
+
}, [hasTaskFilters, localTaskState, locallyFilteredTasks]);
|
|
792
|
+
|
|
430
793
|
// Filter lists based on selected status filter
|
|
431
794
|
const statusFilteredLists = useMemo(() => {
|
|
432
795
|
if (listStatusFilter === 'all') return activeLists;
|
|
@@ -439,18 +802,27 @@ export function BoardViews({
|
|
|
439
802
|
}, [activeLists, listStatusFilter]);
|
|
440
803
|
|
|
441
804
|
const filteredLists = useMemo(() => {
|
|
442
|
-
|
|
805
|
+
const listCounts = localTaskState ? localListCounts : filteredListCounts;
|
|
806
|
+
if (!hasTaskFilters || !listCounts) return statusFilteredLists;
|
|
443
807
|
|
|
444
808
|
const countByListId = new Map(
|
|
445
|
-
|
|
809
|
+
listCounts.map((entry) => [entry.list_id, entry.count] as const)
|
|
446
810
|
);
|
|
447
811
|
|
|
448
812
|
return statusFilteredLists.filter(
|
|
449
813
|
(list) => (countByListId.get(list.id) ?? 0) > 0
|
|
450
814
|
);
|
|
451
|
-
}, [
|
|
815
|
+
}, [
|
|
816
|
+
filteredListCounts,
|
|
817
|
+
hasTaskFilters,
|
|
818
|
+
localListCounts,
|
|
819
|
+
localTaskState,
|
|
820
|
+
statusFilteredLists,
|
|
821
|
+
]);
|
|
452
822
|
|
|
453
823
|
const sourceTasks = useMemo(() => {
|
|
824
|
+
if (localTaskState) return locallyFilteredTasks;
|
|
825
|
+
|
|
454
826
|
if (!shouldEagerLoadTasks) return tasks;
|
|
455
827
|
|
|
456
828
|
if (fullTasks.length === 0) {
|
|
@@ -475,7 +847,15 @@ export function BoardViews({
|
|
|
475
847
|
}
|
|
476
848
|
|
|
477
849
|
return merged;
|
|
478
|
-
}, [
|
|
850
|
+
}, [
|
|
851
|
+
fullTasks,
|
|
852
|
+
hasServerTaskQuery,
|
|
853
|
+
localTaskState,
|
|
854
|
+
locallyFilteredTasks,
|
|
855
|
+
shouldEagerLoadTasks,
|
|
856
|
+
sourceScope,
|
|
857
|
+
tasks,
|
|
858
|
+
]);
|
|
479
859
|
|
|
480
860
|
// Keep only tasks that belong to the server-visible lists/status scope.
|
|
481
861
|
const filteredTasks = useMemo(() => {
|
|
@@ -531,7 +911,11 @@ export function BoardViews({
|
|
|
531
911
|
createTask(board.id, targetList.id, selectableLists, filters);
|
|
532
912
|
},
|
|
533
913
|
{
|
|
534
|
-
enabled:
|
|
914
|
+
enabled:
|
|
915
|
+
!readOnly &&
|
|
916
|
+
currentView !== 'drafts' &&
|
|
917
|
+
currentView !== 'recycle_bin' &&
|
|
918
|
+
filteredLists.some((list) => !list.is_external_staging),
|
|
535
919
|
ignoreInputs: true,
|
|
536
920
|
preventDefault: true,
|
|
537
921
|
}
|
|
@@ -543,6 +927,7 @@ export function BoardViews({
|
|
|
543
927
|
handleViewChange('kanban');
|
|
544
928
|
},
|
|
545
929
|
{
|
|
930
|
+
enabled: viewIsEnabled('kanban'),
|
|
546
931
|
ignoreInputs: true,
|
|
547
932
|
preventDefault: true,
|
|
548
933
|
}
|
|
@@ -554,6 +939,19 @@ export function BoardViews({
|
|
|
554
939
|
handleViewChange('list');
|
|
555
940
|
},
|
|
556
941
|
{
|
|
942
|
+
enabled: viewIsEnabled('list'),
|
|
943
|
+
ignoreInputs: true,
|
|
944
|
+
preventDefault: true,
|
|
945
|
+
}
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
useHotkeySequence(
|
|
949
|
+
HOTKEY_GO_TO_MY_TASKS,
|
|
950
|
+
() => {
|
|
951
|
+
handleViewChange('my_tasks');
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
enabled: viewIsEnabled('my_tasks'),
|
|
557
955
|
ignoreInputs: true,
|
|
558
956
|
preventDefault: true,
|
|
559
957
|
}
|
|
@@ -565,6 +963,31 @@ export function BoardViews({
|
|
|
565
963
|
handleViewChange('timeline');
|
|
566
964
|
},
|
|
567
965
|
{
|
|
966
|
+
enabled: viewIsEnabled('timeline'),
|
|
967
|
+
ignoreInputs: true,
|
|
968
|
+
preventDefault: true,
|
|
969
|
+
}
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
useHotkeySequence(
|
|
973
|
+
HOTKEY_GO_TO_DRAFTS,
|
|
974
|
+
() => {
|
|
975
|
+
handleViewChange('drafts');
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
enabled: viewIsEnabled('drafts'),
|
|
979
|
+
ignoreInputs: true,
|
|
980
|
+
preventDefault: true,
|
|
981
|
+
}
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
useHotkeySequence(
|
|
985
|
+
HOTKEY_GO_TO_RECYCLE_BIN,
|
|
986
|
+
() => {
|
|
987
|
+
handleViewChange('recycle_bin');
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
enabled: viewIsEnabled('recycle_bin'),
|
|
568
991
|
ignoreInputs: true,
|
|
569
992
|
preventDefault: true,
|
|
570
993
|
}
|
|
@@ -583,15 +1006,47 @@ export function BoardViews({
|
|
|
583
1006
|
lists={filteredLists}
|
|
584
1007
|
isLoading={false}
|
|
585
1008
|
disableSort={!!filters.sortBy}
|
|
1009
|
+
deadlineTaskQueryOptions={deadlineTaskQueryOptions}
|
|
586
1010
|
listStatusFilter={listStatusFilter}
|
|
587
1011
|
filters={filters}
|
|
588
|
-
isMultiSelectMode={isMultiSelectMode}
|
|
589
|
-
setIsMultiSelectMode={setIsMultiSelectMode}
|
|
1012
|
+
isMultiSelectMode={readOnly ? false : isMultiSelectMode}
|
|
1013
|
+
setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
|
|
590
1014
|
onExternalTasksCollapsedChange={handleExternalTasksCollapsedChange}
|
|
591
1015
|
onTaskListCollapsedChange={handleTaskListCollapsedChange}
|
|
1016
|
+
deadlineSectionsCollapsed={effectiveDeadlineSectionsCollapsed}
|
|
1017
|
+
onDeadlineSectionCollapsedChange={
|
|
1018
|
+
handleDeadlineSectionCollapsedChange
|
|
1019
|
+
}
|
|
1020
|
+
specialTaskListPins={specialTaskListPins}
|
|
1021
|
+
onSpecialTaskListPinnedChange={handleSpecialTaskListPinnedChange}
|
|
592
1022
|
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
1023
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1024
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
1025
|
+
readOnly={readOnly}
|
|
593
1026
|
/>
|
|
594
1027
|
);
|
|
1028
|
+
case 'my_tasks':
|
|
1029
|
+
return (
|
|
1030
|
+
<div className="h-full overflow-y-auto p-3 sm:p-4">
|
|
1031
|
+
<div className="mx-auto max-w-5xl pb-20">
|
|
1032
|
+
{currentUserId ? (
|
|
1033
|
+
<MyTasksContent
|
|
1034
|
+
disableAutoCreateBoard
|
|
1035
|
+
embedded
|
|
1036
|
+
initialBoard={{
|
|
1037
|
+
id: board.id,
|
|
1038
|
+
name: board.name ?? null,
|
|
1039
|
+
}}
|
|
1040
|
+
initialLists={boardLists}
|
|
1041
|
+
initialListId={board.default_list_id ?? undefined}
|
|
1042
|
+
isPersonal={workspace.personal}
|
|
1043
|
+
userId={currentUserId}
|
|
1044
|
+
wsId={effectiveWorkspaceId}
|
|
1045
|
+
/>
|
|
1046
|
+
) : null}
|
|
1047
|
+
</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
);
|
|
595
1050
|
case 'list':
|
|
596
1051
|
return (
|
|
597
1052
|
<ListView
|
|
@@ -600,8 +1055,11 @@ export function BoardViews({
|
|
|
600
1055
|
tasks={effectiveTasks}
|
|
601
1056
|
lists={filteredLists}
|
|
602
1057
|
isPersonalWorkspace={workspace.personal}
|
|
1058
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1059
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
603
1060
|
preserveTaskOrder={!!filters.sortBy}
|
|
604
1061
|
searchQuery={filters.searchQuery}
|
|
1062
|
+
readOnly={readOnly}
|
|
605
1063
|
/>
|
|
606
1064
|
);
|
|
607
1065
|
case 'timeline':
|
|
@@ -611,9 +1069,72 @@ export function BoardViews({
|
|
|
611
1069
|
boardId={board.id}
|
|
612
1070
|
tasks={effectiveTasks}
|
|
613
1071
|
lists={filteredLists}
|
|
1072
|
+
isPersonalWorkspace={workspace.personal}
|
|
1073
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1074
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
614
1075
|
onTaskPartialUpdate={handleTaskPartialUpdate}
|
|
615
1076
|
/>
|
|
616
1077
|
);
|
|
1078
|
+
case 'drafts':
|
|
1079
|
+
return (
|
|
1080
|
+
<div className="h-full overflow-y-auto p-3 sm:p-4">
|
|
1081
|
+
<DraftsPage
|
|
1082
|
+
boardId={board.id}
|
|
1083
|
+
includeUnassignedForBoard
|
|
1084
|
+
wsId={effectiveWorkspaceId}
|
|
1085
|
+
/>
|
|
1086
|
+
</div>
|
|
1087
|
+
);
|
|
1088
|
+
case 'recycle_bin':
|
|
1089
|
+
return (
|
|
1090
|
+
<RecycleBinContent
|
|
1091
|
+
active
|
|
1092
|
+
boardId={board.id}
|
|
1093
|
+
className="h-full"
|
|
1094
|
+
lists={boardLists}
|
|
1095
|
+
translations={{
|
|
1096
|
+
recycleBin: t('recycle_bin'),
|
|
1097
|
+
recycleBinDescription: t('recycle_bin_description'),
|
|
1098
|
+
noDeletedTasks: t('no_deleted_tasks'),
|
|
1099
|
+
deletedTasksWillAppearHere: t('deleted_tasks_will_appear_here'),
|
|
1100
|
+
selectedOfTotal: t('selected_of_total', {
|
|
1101
|
+
selected: '{selected}',
|
|
1102
|
+
total: '{total}',
|
|
1103
|
+
}),
|
|
1104
|
+
deletedTasksCount: t('deleted_tasks_count', { count: '{count}' }),
|
|
1105
|
+
restore: t('restore'),
|
|
1106
|
+
delete: t('delete'),
|
|
1107
|
+
restoreTasksTitle: t('restore_tasks_title', { count: '{count}' }),
|
|
1108
|
+
restoreTasksDescription: t('restore_tasks_description'),
|
|
1109
|
+
cancel: t('cancel'),
|
|
1110
|
+
restoring: t('restoring'),
|
|
1111
|
+
permanentlyDeleteTitle: t('permanently_delete_title', {
|
|
1112
|
+
count: '{count}',
|
|
1113
|
+
}),
|
|
1114
|
+
permanentlyDeleteDescription: t('permanently_delete_description'),
|
|
1115
|
+
deleting: t('deleting'),
|
|
1116
|
+
deletePermanently: t('delete_permanently'),
|
|
1117
|
+
noListsAvailable: t('no_lists_available'),
|
|
1118
|
+
restoredTasks: t('restored_tasks', { count: '{count}' }),
|
|
1119
|
+
failedToRestore: t('failed_to_restore'),
|
|
1120
|
+
permanentlyDeleted: t('permanently_deleted', {
|
|
1121
|
+
count: '{count}',
|
|
1122
|
+
}),
|
|
1123
|
+
failedToDelete: t('failed_to_delete'),
|
|
1124
|
+
deletedAgo: t('deleted_ago', { time: '{time}' }),
|
|
1125
|
+
fromList: t('from_list', { list: '{list}' }),
|
|
1126
|
+
nProjects: t('n_projects', { count: '{count}' }),
|
|
1127
|
+
selectAllTasks: t('select_all_tasks'),
|
|
1128
|
+
selectTask: t('select_task', { name: '{name}' }),
|
|
1129
|
+
critical: tBoards('dialog.priority.critical'),
|
|
1130
|
+
high: tBoards('dialog.priority.high'),
|
|
1131
|
+
normal: tBoards('dialog.priority.normal'),
|
|
1132
|
+
low: tBoards('dialog.priority.low'),
|
|
1133
|
+
unknownList: t('unknown_list'),
|
|
1134
|
+
}}
|
|
1135
|
+
wsId={effectiveWorkspaceId}
|
|
1136
|
+
/>
|
|
1137
|
+
);
|
|
617
1138
|
default:
|
|
618
1139
|
return (
|
|
619
1140
|
<KanbanBoard
|
|
@@ -625,19 +1146,30 @@ export function BoardViews({
|
|
|
625
1146
|
lists={filteredLists}
|
|
626
1147
|
isLoading={false}
|
|
627
1148
|
disableSort={!!filters.sortBy}
|
|
1149
|
+
deadlineTaskQueryOptions={deadlineTaskQueryOptions}
|
|
628
1150
|
listStatusFilter={listStatusFilter}
|
|
629
1151
|
filters={filters}
|
|
630
|
-
isMultiSelectMode={isMultiSelectMode}
|
|
631
|
-
setIsMultiSelectMode={setIsMultiSelectMode}
|
|
1152
|
+
isMultiSelectMode={readOnly ? false : isMultiSelectMode}
|
|
1153
|
+
setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
|
|
632
1154
|
onExternalTasksCollapsedChange={handleExternalTasksCollapsedChange}
|
|
633
1155
|
onTaskListCollapsedChange={handleTaskListCollapsedChange}
|
|
1156
|
+
deadlineSectionsCollapsed={effectiveDeadlineSectionsCollapsed}
|
|
1157
|
+
onDeadlineSectionCollapsedChange={
|
|
1158
|
+
handleDeadlineSectionCollapsedChange
|
|
1159
|
+
}
|
|
1160
|
+
specialTaskListPins={specialTaskListPins}
|
|
1161
|
+
onSpecialTaskListPinnedChange={handleSpecialTaskListPinnedChange}
|
|
634
1162
|
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
1163
|
+
canUseBoardAssignees={boardAssigneesEnabled}
|
|
1164
|
+
assigneeMemberSource={assigneeMemberSource}
|
|
1165
|
+
readOnly={readOnly}
|
|
635
1166
|
/>
|
|
636
1167
|
);
|
|
637
1168
|
}
|
|
638
1169
|
};
|
|
639
1170
|
|
|
640
1171
|
const showIdleBottomIsland =
|
|
1172
|
+
!readOnly &&
|
|
641
1173
|
!!idleBottomIsland &&
|
|
642
1174
|
(currentView !== 'kanban' || !kanbanBulkSelectionActive);
|
|
643
1175
|
|
|
@@ -655,62 +1187,22 @@ export function BoardViews({
|
|
|
655
1187
|
listStatusFilter={listStatusFilter}
|
|
656
1188
|
onListStatusFilterChange={setListStatusFilter}
|
|
657
1189
|
isPersonalWorkspace={workspace.personal}
|
|
658
|
-
isSearching={
|
|
1190
|
+
isSearching={
|
|
1191
|
+
!localTaskState &&
|
|
1192
|
+
(isFullTasksFetching || isFilteredListCountsFetching)
|
|
1193
|
+
}
|
|
659
1194
|
lists={boardLists}
|
|
660
1195
|
onUpdate={handleUpdate}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
hideActions={!canManageBoard}
|
|
1196
|
+
isMultiSelectMode={readOnly ? false : isMultiSelectMode}
|
|
1197
|
+
setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
|
|
1198
|
+
availableViews={enabledViews ?? undefined}
|
|
1199
|
+
hideActions={!canManageBoard || readOnly}
|
|
1200
|
+
publicView={publicView}
|
|
1201
|
+
readOnly={readOnly}
|
|
1202
|
+
titlePrefix={publicHeaderPrefix}
|
|
665
1203
|
/>
|
|
666
1204
|
<div className="h-full overflow-hidden">{renderView()}</div>
|
|
667
1205
|
{showIdleBottomIsland ? idleBottomIsland : null}
|
|
668
|
-
|
|
669
|
-
<RecycleBinPanel
|
|
670
|
-
open={recycleBinOpen}
|
|
671
|
-
onOpenChange={setRecycleBinOpen}
|
|
672
|
-
wsId={effectiveWorkspaceId}
|
|
673
|
-
boardId={board.id}
|
|
674
|
-
lists={boardLists}
|
|
675
|
-
translations={{
|
|
676
|
-
recycleBin: t('recycle_bin'),
|
|
677
|
-
recycleBinDescription: t('recycle_bin_description'),
|
|
678
|
-
noDeletedTasks: t('no_deleted_tasks'),
|
|
679
|
-
deletedTasksWillAppearHere: t('deleted_tasks_will_appear_here'),
|
|
680
|
-
selectedOfTotal: t('selected_of_total', {
|
|
681
|
-
selected: '{selected}',
|
|
682
|
-
total: '{total}',
|
|
683
|
-
}),
|
|
684
|
-
deletedTasksCount: t('deleted_tasks_count', { count: '{count}' }),
|
|
685
|
-
restore: t('restore'),
|
|
686
|
-
delete: t('delete'),
|
|
687
|
-
restoreTasksTitle: t('restore_tasks_title', { count: '{count}' }),
|
|
688
|
-
restoreTasksDescription: t('restore_tasks_description'),
|
|
689
|
-
cancel: t('cancel'),
|
|
690
|
-
restoring: t('restoring'),
|
|
691
|
-
permanentlyDeleteTitle: t('permanently_delete_title', {
|
|
692
|
-
count: '{count}',
|
|
693
|
-
}),
|
|
694
|
-
permanentlyDeleteDescription: t('permanently_delete_description'),
|
|
695
|
-
deleting: t('deleting'),
|
|
696
|
-
deletePermanently: t('delete_permanently'),
|
|
697
|
-
noListsAvailable: t('no_lists_available'),
|
|
698
|
-
restoredTasks: t('restored_tasks', { count: '{count}' }),
|
|
699
|
-
failedToRestore: t('failed_to_restore'),
|
|
700
|
-
permanentlyDeleted: t('permanently_deleted', { count: '{count}' }),
|
|
701
|
-
failedToDelete: t('failed_to_delete'),
|
|
702
|
-
deletedAgo: t('deleted_ago', { time: '{time}' }),
|
|
703
|
-
fromList: t('from_list', { list: '{list}' }),
|
|
704
|
-
nProjects: t('n_projects', { count: '{count}' }),
|
|
705
|
-
selectAllTasks: t('select_all_tasks'),
|
|
706
|
-
selectTask: t('select_task', { name: '{name}' }),
|
|
707
|
-
critical: tBoards('dialog.priority.critical'),
|
|
708
|
-
high: tBoards('dialog.priority.high'),
|
|
709
|
-
normal: tBoards('dialog.priority.normal'),
|
|
710
|
-
low: tBoards('dialog.priority.low'),
|
|
711
|
-
unknownList: t('unknown_list'),
|
|
712
|
-
}}
|
|
713
|
-
/>
|
|
714
1206
|
</div>
|
|
715
1207
|
);
|
|
716
1208
|
}
|