@tuturuuu/ui 0.7.0 → 0.9.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 +88 -0
- package/biome.json +1 -1
- package/package.json +75 -73
- 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/currency-input.test.tsx +43 -0
- package/src/components/ui/currency-input.tsx +1 -1
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
- 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/settings-dialog-shell.tsx +2 -1
- package/src/components/ui/custom/theme-toggle.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
- package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
- package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
- package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
- package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
- package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
- package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
- package/src/components/ui/custom/workspace-select.tsx +8 -3
- 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 +3 -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-card.tsx +21 -9
- 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/money-input.test.tsx +64 -0
- package/src/components/ui/money-input.tsx +63 -0
- 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 +104 -80
- package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
- package/src/components/ui/storefront/hero-panel.tsx +2 -8
- package/src/components/ui/storefront/image-panel.tsx +6 -0
- package/src/components/ui/storefront/index.ts +11 -0
- package/src/components/ui/storefront/listing-card.tsx +84 -22
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +289 -0
- package/src/components/ui/storefront/product-dialog.tsx +72 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
- package/src/components/ui/storefront/storefront-surface.tsx +288 -153
- package/src/components/ui/storefront/types.ts +27 -1
- package/src/components/ui/storefront/utils.ts +117 -27
- package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
- package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -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/content-migration.ts +41 -18
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +9 -3
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/image-extension.ts +40 -18
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/text-editor/video-extension.ts +11 -2
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
- package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
- 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 +222 -109
- package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
- 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/data/kanban-deadline-query.ts +50 -2
- 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/task-drag-cache.ts +51 -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.test.ts +63 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
- 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 +410 -4
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +186 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
- 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.tsx +191 -28
- 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 +7 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
- package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
- 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/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/shared/__tests__/board-client.test.tsx +25 -0
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +237 -3
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
- package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
- package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
- package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +465 -937
- package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
- package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
- package/src/components/ui/tu-do/shared/board-views.tsx +596 -82
- package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
- package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
- 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-dialog-presentation.test.ts +53 -0
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -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/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-snapshot-dialog.tsx +8 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +44 -15
- package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
- 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__/useBoardRealtime.test.tsx +2 -2
- package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
- 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/useBoardRealtime.ts +6 -3
- package/src/hooks/useBoardRealtime.types.ts +11 -0
- package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
- package/src/hooks/useCursorTracking.ts +91 -27
- package/src/hooks/useTaskUserRealtime.ts +5 -3
|
@@ -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: [],
|
|
@@ -78,6 +109,140 @@ function getClosedTaskListCollapsedStorageKey(boardId: string, listId: string) {
|
|
|
78
109
|
return `${CLOSED_TASK_LIST_COLLAPSED_STORAGE_PREFIX}:${boardId}:${listId}`;
|
|
79
110
|
}
|
|
80
111
|
|
|
112
|
+
function getDeadlineSectionCollapsedStorageKey(
|
|
113
|
+
boardId: string,
|
|
114
|
+
section: KanbanDeadlineSection
|
|
115
|
+
) {
|
|
116
|
+
return `${DEADLINE_SECTION_COLLAPSED_STORAGE_PREFIX}:${boardId}:${section}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function taskMatchesLocalFilters(
|
|
120
|
+
task: Task,
|
|
121
|
+
filters: TaskFilters,
|
|
122
|
+
currentUserId?: string
|
|
123
|
+
) {
|
|
124
|
+
const query = filters.searchQuery?.trim().toLowerCase();
|
|
125
|
+
if (query) {
|
|
126
|
+
const searchableText = [
|
|
127
|
+
task.name,
|
|
128
|
+
task.display_number ? String(task.display_number) : null,
|
|
129
|
+
...(task.labels ?? []).map((label) => label.name),
|
|
130
|
+
...(task.projects ?? []).map((project) => project.name),
|
|
131
|
+
...(task.assignees ?? []).map(
|
|
132
|
+
(assignee) => assignee.display_name ?? assignee.email ?? assignee.handle
|
|
133
|
+
),
|
|
134
|
+
]
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.join(' ')
|
|
137
|
+
.toLowerCase();
|
|
138
|
+
|
|
139
|
+
if (!searchableText.includes(query)) return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
filters.labels.length > 0 &&
|
|
144
|
+
!filters.labels.every((label) =>
|
|
145
|
+
task.labels?.some((taskLabel) => taskLabel.id === label.id)
|
|
146
|
+
)
|
|
147
|
+
) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
filters.projects.length > 0 &&
|
|
153
|
+
!filters.projects.every((project) =>
|
|
154
|
+
task.projects?.some((taskProject) => taskProject.id === project.id)
|
|
155
|
+
)
|
|
156
|
+
) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
filters.priorities.length > 0 &&
|
|
162
|
+
(!task.priority || !filters.priorities.includes(task.priority))
|
|
163
|
+
) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (filters.assignees.length > 0) {
|
|
168
|
+
const assigneeIds = new Set(
|
|
169
|
+
filters.assignees.map((assignee) => assignee.id)
|
|
170
|
+
);
|
|
171
|
+
if (!task.assignees?.some((assignee) => assigneeIds.has(assignee.id))) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
filters.includeMyTasks &&
|
|
178
|
+
currentUserId &&
|
|
179
|
+
!task.assignees?.some((assignee) => assignee.id === currentUserId)
|
|
180
|
+
) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (filters.includeUnassigned && (task.assignees?.length ?? 0) > 0) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (filters.dueDateRange?.from || filters.dueDateRange?.to) {
|
|
189
|
+
if (!task.end_date) return false;
|
|
190
|
+
const dueTime = new Date(task.end_date).getTime();
|
|
191
|
+
const fromTime = filters.dueDateRange.from?.getTime() ?? -Infinity;
|
|
192
|
+
const toTime = filters.dueDateRange.to?.getTime() ?? Infinity;
|
|
193
|
+
if (dueTime < fromTime || dueTime > toTime) return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (
|
|
197
|
+
typeof filters.estimationRange?.min === 'number' ||
|
|
198
|
+
typeof filters.estimationRange?.max === 'number'
|
|
199
|
+
) {
|
|
200
|
+
const estimate = task.estimation_points ?? 0;
|
|
201
|
+
const min = filters.estimationRange.min ?? -Infinity;
|
|
202
|
+
const max = filters.estimationRange.max ?? Infinity;
|
|
203
|
+
if (estimate < min || estimate > max) return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function getTaskTimestamp(value: string | null | undefined) {
|
|
210
|
+
if (!value) return Number.MAX_SAFE_INTEGER;
|
|
211
|
+
const time = new Date(value).getTime();
|
|
212
|
+
return Number.isFinite(time) ? time : Number.MAX_SAFE_INTEGER;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function sortLocalTasks(tasks: Task[], sortBy: TaskFilters['sortBy']) {
|
|
216
|
+
if (!sortBy) return tasks;
|
|
217
|
+
|
|
218
|
+
return [...tasks].sort((a, b) => {
|
|
219
|
+
switch (sortBy) {
|
|
220
|
+
case 'name-asc':
|
|
221
|
+
return a.name.localeCompare(b.name);
|
|
222
|
+
case 'name-desc':
|
|
223
|
+
return b.name.localeCompare(a.name);
|
|
224
|
+
case 'priority-high':
|
|
225
|
+
return priorityCompare(a.priority ?? null, b.priority ?? null);
|
|
226
|
+
case 'priority-low':
|
|
227
|
+
return priorityCompare(b.priority ?? null, a.priority ?? null);
|
|
228
|
+
case 'due-date-asc':
|
|
229
|
+
return getTaskTimestamp(a.end_date) - getTaskTimestamp(b.end_date);
|
|
230
|
+
case 'due-date-desc':
|
|
231
|
+
return getTaskTimestamp(b.end_date) - getTaskTimestamp(a.end_date);
|
|
232
|
+
case 'created-date-asc':
|
|
233
|
+
return getTaskTimestamp(a.created_at) - getTaskTimestamp(b.created_at);
|
|
234
|
+
case 'created-date-desc':
|
|
235
|
+
return getTaskTimestamp(b.created_at) - getTaskTimestamp(a.created_at);
|
|
236
|
+
case 'estimation-high':
|
|
237
|
+
return (b.estimation_points ?? 0) - (a.estimation_points ?? 0);
|
|
238
|
+
case 'estimation-low':
|
|
239
|
+
return (a.estimation_points ?? 0) - (b.estimation_points ?? 0);
|
|
240
|
+
default:
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
81
246
|
interface Props {
|
|
82
247
|
workspace: Workspace;
|
|
83
248
|
workspaceTier?: WorkspaceProductTier | null;
|
|
@@ -85,9 +250,13 @@ interface Props {
|
|
|
85
250
|
tasks: Task[];
|
|
86
251
|
lists: TaskList[];
|
|
87
252
|
workspaceLabels: WorkspaceLabel[];
|
|
253
|
+
availableViews?: ViewType[];
|
|
88
254
|
canManageBoard?: boolean;
|
|
89
255
|
currentUserId?: string;
|
|
90
256
|
idleBottomIsland?: ReactNode;
|
|
257
|
+
publicHeaderPrefix?: ReactNode;
|
|
258
|
+
publicView?: boolean;
|
|
259
|
+
readOnly?: boolean;
|
|
91
260
|
}
|
|
92
261
|
|
|
93
262
|
export function BoardViews({
|
|
@@ -96,9 +265,13 @@ export function BoardViews({
|
|
|
96
265
|
board,
|
|
97
266
|
tasks,
|
|
98
267
|
lists,
|
|
268
|
+
availableViews,
|
|
99
269
|
canManageBoard = true,
|
|
100
270
|
currentUserId,
|
|
101
271
|
idleBottomIsland,
|
|
272
|
+
publicHeaderPrefix,
|
|
273
|
+
publicView = false,
|
|
274
|
+
readOnly = false,
|
|
102
275
|
}: Props) {
|
|
103
276
|
const t = useTranslations('common');
|
|
104
277
|
const tTasks = useTranslations('ws-tasks');
|
|
@@ -110,6 +283,8 @@ export function BoardViews({
|
|
|
110
283
|
const [closedTaskListsCollapsed, setClosedTaskListsCollapsed] = useState<
|
|
111
284
|
Record<string, boolean>
|
|
112
285
|
>({});
|
|
286
|
+
const [deadlineSectionsCollapsed, setDeadlineSectionsCollapsed] =
|
|
287
|
+
useState<KanbanDeadlineCollapsedState>({});
|
|
113
288
|
const [filters, setFilters] = useState<TaskFilters>(DEFAULT_TASK_FILTERS);
|
|
114
289
|
const [listStatusFilter, setListStatusFilter] =
|
|
115
290
|
useState<ListStatusFilter>('all');
|
|
@@ -117,11 +292,58 @@ export function BoardViews({
|
|
|
117
292
|
const [taskOverrides, setTaskOverrides] = useState<
|
|
118
293
|
Record<string, Partial<Task>>
|
|
119
294
|
>({});
|
|
120
|
-
const [recycleBinOpen, setRecycleBinOpen] = useState(false);
|
|
121
295
|
const [isMultiSelectMode, setIsMultiSelectMode] = useState(false);
|
|
122
296
|
const [kanbanBulkSelectionActive, setKanbanBulkSelectionActive] =
|
|
123
297
|
useState(false);
|
|
124
298
|
const { createTask } = useTaskDialog();
|
|
299
|
+
const localTaskState = readOnly || publicView;
|
|
300
|
+
const { data: pinnedSpecialListsRaw } = useUserWorkspaceConfig(
|
|
301
|
+
effectiveWorkspaceId,
|
|
302
|
+
TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID,
|
|
303
|
+
null,
|
|
304
|
+
{ enabled: !localTaskState }
|
|
305
|
+
);
|
|
306
|
+
const updateUserWorkspaceConfig = useUpdateUserWorkspaceConfig();
|
|
307
|
+
const specialTaskListPins = useMemo(
|
|
308
|
+
() => parseSpecialTaskListPins(pinnedSpecialListsRaw),
|
|
309
|
+
[pinnedSpecialListsRaw]
|
|
310
|
+
);
|
|
311
|
+
const handleSpecialTaskListPinnedChange = useCallback(
|
|
312
|
+
(pin: SpecialTaskListPin, pinned: boolean) => {
|
|
313
|
+
const nextPins = {
|
|
314
|
+
...specialTaskListPins,
|
|
315
|
+
[pin]: pinned,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
if (!pinned) delete nextPins[pin];
|
|
319
|
+
|
|
320
|
+
updateUserWorkspaceConfig.mutate({
|
|
321
|
+
configId: TASK_BOARD_PINNED_SPECIAL_LISTS_CONFIG_ID,
|
|
322
|
+
value: serializeSpecialTaskListPins(nextPins),
|
|
323
|
+
workspaceId: effectiveWorkspaceId,
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
[effectiveWorkspaceId, specialTaskListPins, updateUserWorkspaceConfig]
|
|
327
|
+
);
|
|
328
|
+
const enabledViews = useMemo(
|
|
329
|
+
() =>
|
|
330
|
+
availableViews ??
|
|
331
|
+
(publicView || readOnly
|
|
332
|
+
? (['kanban', 'list', 'timeline'] as ViewType[])
|
|
333
|
+
: ([
|
|
334
|
+
'kanban',
|
|
335
|
+
'list',
|
|
336
|
+
'my_tasks',
|
|
337
|
+
'timeline',
|
|
338
|
+
'drafts',
|
|
339
|
+
'recycle_bin',
|
|
340
|
+
] as ViewType[])),
|
|
341
|
+
[availableViews, publicView, readOnly]
|
|
342
|
+
);
|
|
343
|
+
const viewIsEnabled = useCallback(
|
|
344
|
+
(view: ViewType) => !enabledViews || enabledViews.includes(view),
|
|
345
|
+
[enabledViews]
|
|
346
|
+
);
|
|
125
347
|
const sourceScope = filters.sourceScope ?? 'all_visible';
|
|
126
348
|
const sourceWorkspaceIds = filters.sourceWorkspaceIds ?? [];
|
|
127
349
|
const sourceBoardIds = filters.sourceBoardIds ?? [];
|
|
@@ -206,11 +428,21 @@ export function BoardViews({
|
|
|
206
428
|
taskQueryOptions,
|
|
207
429
|
]
|
|
208
430
|
);
|
|
431
|
+
const deadlineTaskQueryOptions = useMemo<ListWorkspaceTasksOptions>(() => {
|
|
432
|
+
const { sortBy: _sortBy, ...filterOptions } = taskQueryOptions;
|
|
433
|
+
return {
|
|
434
|
+
...filterOptions,
|
|
435
|
+
listStatuses: listStatusesForQuery,
|
|
436
|
+
};
|
|
437
|
+
}, [listStatusesForQuery, taskQueryOptions]);
|
|
209
438
|
const viewHotkeyLabels = useMemo(
|
|
210
439
|
() => ({
|
|
211
440
|
kanban: formatHotkeySequence(HOTKEY_GO_TO_KANBAN),
|
|
212
441
|
list: formatHotkeySequence(HOTKEY_GO_TO_LIST),
|
|
442
|
+
my_tasks: formatHotkeySequence(HOTKEY_GO_TO_MY_TASKS),
|
|
213
443
|
timeline: formatHotkeySequence(HOTKEY_GO_TO_TIMELINE),
|
|
444
|
+
drafts: formatHotkeySequence(HOTKEY_GO_TO_DRAFTS),
|
|
445
|
+
recycle_bin: formatHotkeySequence(HOTKEY_GO_TO_RECYCLE_BIN),
|
|
214
446
|
}),
|
|
215
447
|
[]
|
|
216
448
|
);
|
|
@@ -228,6 +460,7 @@ export function BoardViews({
|
|
|
228
460
|
|
|
229
461
|
const primeFullTaskCache = useCallback(
|
|
230
462
|
(nextView: ViewType) => {
|
|
463
|
+
if (localTaskState) return;
|
|
231
464
|
if (nextView !== 'list' && nextView !== 'timeline') return;
|
|
232
465
|
|
|
233
466
|
void queryClient.prefetchQuery({
|
|
@@ -236,20 +469,52 @@ export function BoardViews({
|
|
|
236
469
|
staleTime: 0,
|
|
237
470
|
});
|
|
238
471
|
},
|
|
239
|
-
[board.id, fetchBoardTasks, queryClient, taskFilterKey]
|
|
472
|
+
[board.id, fetchBoardTasks, localTaskState, queryClient, taskFilterKey]
|
|
240
473
|
);
|
|
241
474
|
|
|
242
475
|
const handleViewChange = useCallback(
|
|
243
476
|
(nextView: ViewType) => {
|
|
477
|
+
if (!viewIsEnabled(nextView)) return;
|
|
244
478
|
setCurrentView(nextView);
|
|
245
479
|
primeFullTaskCache(nextView);
|
|
480
|
+
if (!localTaskState) {
|
|
481
|
+
updateUserWorkspaceConfig.mutate({
|
|
482
|
+
configId: TASK_LAST_BOARD_VIEW_CONFIG_ID,
|
|
483
|
+
value: nextView,
|
|
484
|
+
workspaceId: effectiveWorkspaceId,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (typeof window !== 'undefined') {
|
|
489
|
+
const currentUrl = new URL(window.location.href);
|
|
490
|
+
if (!currentUrl.pathname.includes('/tasks/boards/')) return;
|
|
491
|
+
|
|
492
|
+
const params = currentUrl.searchParams;
|
|
493
|
+
if (nextView === 'kanban') {
|
|
494
|
+
params.delete('view');
|
|
495
|
+
} else {
|
|
496
|
+
params.set('view', nextView);
|
|
497
|
+
}
|
|
498
|
+
const nextQuery = params.toString();
|
|
499
|
+
window.history.replaceState(
|
|
500
|
+
window.history.state,
|
|
501
|
+
'',
|
|
502
|
+
`${currentUrl.pathname}${nextQuery ? `?${nextQuery}` : ''}${currentUrl.hash}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
246
505
|
},
|
|
247
|
-
[
|
|
506
|
+
[
|
|
507
|
+
effectiveWorkspaceId,
|
|
508
|
+
localTaskState,
|
|
509
|
+
primeFullTaskCache,
|
|
510
|
+
updateUserWorkspaceConfig,
|
|
511
|
+
viewIsEnabled,
|
|
512
|
+
]
|
|
248
513
|
);
|
|
249
514
|
|
|
250
515
|
const { data: fullTasks = [], isFetching: isFullTasksFetching } = useQuery({
|
|
251
516
|
queryKey: ['tasks-full', board.id, taskFilterKey],
|
|
252
|
-
enabled: shouldEagerLoadTasks,
|
|
517
|
+
enabled: !localTaskState && shouldEagerLoadTasks,
|
|
253
518
|
queryFn: fetchBoardTasks,
|
|
254
519
|
refetchOnMount: 'always',
|
|
255
520
|
staleTime: 0,
|
|
@@ -273,21 +538,36 @@ export function BoardViews({
|
|
|
273
538
|
|
|
274
539
|
useLayoutEffect(() => {
|
|
275
540
|
const savedConfig = loadBoardConfig(board.id);
|
|
541
|
+
const requestedView =
|
|
542
|
+
typeof window === 'undefined' ||
|
|
543
|
+
!window.location.pathname.includes('/tasks/boards/')
|
|
544
|
+
? null
|
|
545
|
+
: (new URLSearchParams(window.location.search).get(
|
|
546
|
+
'view'
|
|
547
|
+
) as ViewType | null);
|
|
548
|
+
const defaultView = enabledViews?.[0] ?? 'kanban';
|
|
549
|
+
const initialView =
|
|
550
|
+
requestedView && viewIsEnabled(requestedView) ? requestedView : null;
|
|
276
551
|
|
|
277
552
|
if (!savedConfig) {
|
|
278
|
-
setCurrentView(
|
|
553
|
+
setCurrentView(initialView ?? defaultView);
|
|
279
554
|
setFilters(DEFAULT_TASK_FILTERS);
|
|
280
555
|
setListStatusFilter('all');
|
|
281
556
|
return;
|
|
282
557
|
}
|
|
283
558
|
|
|
284
|
-
setCurrentView(
|
|
559
|
+
setCurrentView(
|
|
560
|
+
initialView ??
|
|
561
|
+
(viewIsEnabled(savedConfig.currentView)
|
|
562
|
+
? savedConfig.currentView
|
|
563
|
+
: defaultView)
|
|
564
|
+
);
|
|
285
565
|
setFilters({
|
|
286
566
|
...DEFAULT_TASK_FILTERS,
|
|
287
567
|
...savedConfig.filters,
|
|
288
568
|
});
|
|
289
569
|
setListStatusFilter(savedConfig.listStatusFilter);
|
|
290
|
-
}, [board.id]);
|
|
570
|
+
}, [board.id, enabledViews, viewIsEnabled]);
|
|
291
571
|
|
|
292
572
|
useEffect(() => {
|
|
293
573
|
if (!workspace.personal || typeof window === 'undefined') {
|
|
@@ -308,6 +588,8 @@ export function BoardViews({
|
|
|
308
588
|
|
|
309
589
|
const handleExternalTasksCollapsedChange = useCallback(
|
|
310
590
|
(collapsed: boolean) => {
|
|
591
|
+
if (collapsed && specialTaskListPins.external_tasks) return;
|
|
592
|
+
|
|
311
593
|
setExternalTasksCollapsed(collapsed);
|
|
312
594
|
|
|
313
595
|
if (!workspace.personal || typeof window === 'undefined') return;
|
|
@@ -317,7 +599,7 @@ export function BoardViews({
|
|
|
317
599
|
String(collapsed)
|
|
318
600
|
);
|
|
319
601
|
},
|
|
320
|
-
[board.id, workspace.personal]
|
|
602
|
+
[board.id, specialTaskListPins.external_tasks, workspace.personal]
|
|
321
603
|
);
|
|
322
604
|
|
|
323
605
|
useEffect(() => {
|
|
@@ -353,6 +635,16 @@ export function BoardViews({
|
|
|
353
635
|
|
|
354
636
|
const handleTaskListCollapsedChange = useCallback(
|
|
355
637
|
(listId: string, collapsed: boolean) => {
|
|
638
|
+
if (
|
|
639
|
+
collapsed &&
|
|
640
|
+
specialTaskListPins.closed_tasks &&
|
|
641
|
+
boardLists.some(
|
|
642
|
+
(list) => list.id === listId && list.status === 'closed'
|
|
643
|
+
)
|
|
644
|
+
) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
|
|
356
648
|
setClosedTaskListsCollapsed((previous) => ({
|
|
357
649
|
...previous,
|
|
358
650
|
[listId]: collapsed,
|
|
@@ -365,7 +657,54 @@ export function BoardViews({
|
|
|
365
657
|
String(collapsed)
|
|
366
658
|
);
|
|
367
659
|
},
|
|
368
|
-
[board.id]
|
|
660
|
+
[board.id, boardLists, specialTaskListPins.closed_tasks]
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
useEffect(() => {
|
|
664
|
+
setDeadlineSectionsCollapsed((previous) => {
|
|
665
|
+
const next: KanbanDeadlineCollapsedState = {};
|
|
666
|
+
|
|
667
|
+
for (const section of ['overdue', 'upcoming'] as const) {
|
|
668
|
+
const storedValue =
|
|
669
|
+
typeof window === 'undefined'
|
|
670
|
+
? null
|
|
671
|
+
: window.localStorage.getItem(
|
|
672
|
+
getDeadlineSectionCollapsedStorageKey(board.id, section)
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
next[section] =
|
|
676
|
+
storedValue === null
|
|
677
|
+
? (previous[section] ?? false)
|
|
678
|
+
: storedValue === 'true';
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return next;
|
|
682
|
+
});
|
|
683
|
+
}, [board.id]);
|
|
684
|
+
|
|
685
|
+
const handleDeadlineSectionCollapsedChange = useCallback(
|
|
686
|
+
(section: KanbanDeadlineSection, collapsed: boolean) => {
|
|
687
|
+
if (
|
|
688
|
+
collapsed &&
|
|
689
|
+
((section === 'overdue' && specialTaskListPins.overdue) ||
|
|
690
|
+
(section === 'upcoming' && specialTaskListPins.upcoming))
|
|
691
|
+
) {
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
setDeadlineSectionsCollapsed((previous) => ({
|
|
696
|
+
...previous,
|
|
697
|
+
[section]: collapsed,
|
|
698
|
+
}));
|
|
699
|
+
|
|
700
|
+
if (typeof window === 'undefined') return;
|
|
701
|
+
|
|
702
|
+
window.localStorage.setItem(
|
|
703
|
+
getDeadlineSectionCollapsedStorageKey(board.id, section),
|
|
704
|
+
String(collapsed)
|
|
705
|
+
);
|
|
706
|
+
},
|
|
707
|
+
[board.id, specialTaskListPins.overdue, specialTaskListPins.upcoming]
|
|
369
708
|
);
|
|
370
709
|
|
|
371
710
|
const externalStagingList = useMemo<TaskList | null>(() => {
|
|
@@ -383,12 +722,15 @@ export function BoardViews({
|
|
|
383
722
|
color: 'CYAN',
|
|
384
723
|
position: Number.MIN_SAFE_INTEGER,
|
|
385
724
|
is_external_staging: true,
|
|
386
|
-
is_external_collapsed:
|
|
725
|
+
is_external_collapsed: specialTaskListPins.external_tasks
|
|
726
|
+
? false
|
|
727
|
+
: externalTasksCollapsed,
|
|
387
728
|
};
|
|
388
729
|
}, [
|
|
389
730
|
board.created_at,
|
|
390
731
|
board.id,
|
|
391
732
|
externalTasksCollapsed,
|
|
733
|
+
specialTaskListPins.external_tasks,
|
|
392
734
|
tTasks,
|
|
393
735
|
workspace.personal,
|
|
394
736
|
]);
|
|
@@ -400,19 +742,44 @@ export function BoardViews({
|
|
|
400
742
|
list.status === 'closed'
|
|
401
743
|
? {
|
|
402
744
|
...list,
|
|
403
|
-
is_collapsed:
|
|
745
|
+
is_collapsed: specialTaskListPins.closed_tasks
|
|
746
|
+
? false
|
|
747
|
+
: (closedTaskListsCollapsed[list.id] ?? true),
|
|
404
748
|
}
|
|
405
749
|
: list
|
|
406
750
|
);
|
|
407
751
|
return externalStagingList
|
|
408
752
|
? [externalStagingList, ...realLists]
|
|
409
753
|
: realLists;
|
|
410
|
-
}, [
|
|
754
|
+
}, [
|
|
755
|
+
boardLists,
|
|
756
|
+
closedTaskListsCollapsed,
|
|
757
|
+
externalStagingList,
|
|
758
|
+
specialTaskListPins.closed_tasks,
|
|
759
|
+
]);
|
|
760
|
+
|
|
761
|
+
const effectiveDeadlineSectionsCollapsed =
|
|
762
|
+
useMemo<KanbanDeadlineCollapsedState>(
|
|
763
|
+
() => ({
|
|
764
|
+
overdue: specialTaskListPins.overdue
|
|
765
|
+
? false
|
|
766
|
+
: deadlineSectionsCollapsed.overdue,
|
|
767
|
+
upcoming: specialTaskListPins.upcoming
|
|
768
|
+
? false
|
|
769
|
+
: deadlineSectionsCollapsed.upcoming,
|
|
770
|
+
}),
|
|
771
|
+
[
|
|
772
|
+
deadlineSectionsCollapsed.overdue,
|
|
773
|
+
deadlineSectionsCollapsed.upcoming,
|
|
774
|
+
specialTaskListPins.overdue,
|
|
775
|
+
specialTaskListPins.upcoming,
|
|
776
|
+
]
|
|
777
|
+
);
|
|
411
778
|
|
|
412
779
|
const { data: filteredListCounts, isFetching: isFilteredListCountsFetching } =
|
|
413
780
|
useQuery({
|
|
414
781
|
queryKey: ['task-list-counts', board.id, taskFilterKey],
|
|
415
|
-
enabled: hasTaskFilters,
|
|
782
|
+
enabled: !localTaskState && hasTaskFilters,
|
|
416
783
|
queryFn: async () => {
|
|
417
784
|
const result = await listWorkspaceTasks(effectiveWorkspaceId, {
|
|
418
785
|
...taskQueryOptions,
|
|
@@ -427,6 +794,31 @@ export function BoardViews({
|
|
|
427
794
|
staleTime: 30_000,
|
|
428
795
|
});
|
|
429
796
|
|
|
797
|
+
const locallyFilteredTasks = useMemo(
|
|
798
|
+
() =>
|
|
799
|
+
sortLocalTasks(
|
|
800
|
+
tasks.filter((task) =>
|
|
801
|
+
taskMatchesLocalFilters(task, filters, currentUserId)
|
|
802
|
+
),
|
|
803
|
+
filters.sortBy
|
|
804
|
+
),
|
|
805
|
+
[currentUserId, filters, tasks]
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
const localListCounts = useMemo(() => {
|
|
809
|
+
if (!localTaskState || !hasTaskFilters) return null;
|
|
810
|
+
|
|
811
|
+
const counts = new Map<string, number>();
|
|
812
|
+
for (const task of locallyFilteredTasks) {
|
|
813
|
+
counts.set(task.list_id, (counts.get(task.list_id) ?? 0) + 1);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return [...counts.entries()].map(([list_id, count]) => ({
|
|
817
|
+
list_id,
|
|
818
|
+
count,
|
|
819
|
+
}));
|
|
820
|
+
}, [hasTaskFilters, localTaskState, locallyFilteredTasks]);
|
|
821
|
+
|
|
430
822
|
// Filter lists based on selected status filter
|
|
431
823
|
const statusFilteredLists = useMemo(() => {
|
|
432
824
|
if (listStatusFilter === 'all') return activeLists;
|
|
@@ -439,18 +831,27 @@ export function BoardViews({
|
|
|
439
831
|
}, [activeLists, listStatusFilter]);
|
|
440
832
|
|
|
441
833
|
const filteredLists = useMemo(() => {
|
|
442
|
-
|
|
834
|
+
const listCounts = localTaskState ? localListCounts : filteredListCounts;
|
|
835
|
+
if (!hasTaskFilters || !listCounts) return statusFilteredLists;
|
|
443
836
|
|
|
444
837
|
const countByListId = new Map(
|
|
445
|
-
|
|
838
|
+
listCounts.map((entry) => [entry.list_id, entry.count] as const)
|
|
446
839
|
);
|
|
447
840
|
|
|
448
841
|
return statusFilteredLists.filter(
|
|
449
842
|
(list) => (countByListId.get(list.id) ?? 0) > 0
|
|
450
843
|
);
|
|
451
|
-
}, [
|
|
844
|
+
}, [
|
|
845
|
+
filteredListCounts,
|
|
846
|
+
hasTaskFilters,
|
|
847
|
+
localListCounts,
|
|
848
|
+
localTaskState,
|
|
849
|
+
statusFilteredLists,
|
|
850
|
+
]);
|
|
452
851
|
|
|
453
852
|
const sourceTasks = useMemo(() => {
|
|
853
|
+
if (localTaskState) return locallyFilteredTasks;
|
|
854
|
+
|
|
454
855
|
if (!shouldEagerLoadTasks) return tasks;
|
|
455
856
|
|
|
456
857
|
if (fullTasks.length === 0) {
|
|
@@ -475,7 +876,15 @@ export function BoardViews({
|
|
|
475
876
|
}
|
|
476
877
|
|
|
477
878
|
return merged;
|
|
478
|
-
}, [
|
|
879
|
+
}, [
|
|
880
|
+
fullTasks,
|
|
881
|
+
hasServerTaskQuery,
|
|
882
|
+
localTaskState,
|
|
883
|
+
locallyFilteredTasks,
|
|
884
|
+
shouldEagerLoadTasks,
|
|
885
|
+
sourceScope,
|
|
886
|
+
tasks,
|
|
887
|
+
]);
|
|
479
888
|
|
|
480
889
|
// Keep only tasks that belong to the server-visible lists/status scope.
|
|
481
890
|
const filteredTasks = useMemo(() => {
|
|
@@ -519,17 +928,23 @@ export function BoardViews({
|
|
|
519
928
|
useHotkey(
|
|
520
929
|
HOTKEY_CREATE_TASK,
|
|
521
930
|
() => {
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
createTask(
|
|
525
|
-
board.id,
|
|
526
|
-
firstList.id,
|
|
527
|
-
filteredLists.filter((list) => !list.is_external_staging),
|
|
528
|
-
filters
|
|
931
|
+
const selectableLists = filteredLists.filter(
|
|
932
|
+
(list) => !list.is_external_staging
|
|
529
933
|
);
|
|
934
|
+
// Prefer the board's configured default list for new tasks, falling back
|
|
935
|
+
// to the first selectable list when unset or the list is unavailable.
|
|
936
|
+
const targetList =
|
|
937
|
+
selectableLists.find((list) => list.id === board.default_list_id) ??
|
|
938
|
+
selectableLists[0];
|
|
939
|
+
if (!targetList) return;
|
|
940
|
+
createTask(board.id, targetList.id, selectableLists, filters);
|
|
530
941
|
},
|
|
531
942
|
{
|
|
532
|
-
enabled:
|
|
943
|
+
enabled:
|
|
944
|
+
!readOnly &&
|
|
945
|
+
currentView !== 'drafts' &&
|
|
946
|
+
currentView !== 'recycle_bin' &&
|
|
947
|
+
filteredLists.some((list) => !list.is_external_staging),
|
|
533
948
|
ignoreInputs: true,
|
|
534
949
|
preventDefault: true,
|
|
535
950
|
}
|
|
@@ -541,6 +956,7 @@ export function BoardViews({
|
|
|
541
956
|
handleViewChange('kanban');
|
|
542
957
|
},
|
|
543
958
|
{
|
|
959
|
+
enabled: viewIsEnabled('kanban'),
|
|
544
960
|
ignoreInputs: true,
|
|
545
961
|
preventDefault: true,
|
|
546
962
|
}
|
|
@@ -552,6 +968,19 @@ export function BoardViews({
|
|
|
552
968
|
handleViewChange('list');
|
|
553
969
|
},
|
|
554
970
|
{
|
|
971
|
+
enabled: viewIsEnabled('list'),
|
|
972
|
+
ignoreInputs: true,
|
|
973
|
+
preventDefault: true,
|
|
974
|
+
}
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
useHotkeySequence(
|
|
978
|
+
HOTKEY_GO_TO_MY_TASKS,
|
|
979
|
+
() => {
|
|
980
|
+
handleViewChange('my_tasks');
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
enabled: viewIsEnabled('my_tasks'),
|
|
555
984
|
ignoreInputs: true,
|
|
556
985
|
preventDefault: true,
|
|
557
986
|
}
|
|
@@ -563,6 +992,31 @@ export function BoardViews({
|
|
|
563
992
|
handleViewChange('timeline');
|
|
564
993
|
},
|
|
565
994
|
{
|
|
995
|
+
enabled: viewIsEnabled('timeline'),
|
|
996
|
+
ignoreInputs: true,
|
|
997
|
+
preventDefault: true,
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
useHotkeySequence(
|
|
1002
|
+
HOTKEY_GO_TO_DRAFTS,
|
|
1003
|
+
() => {
|
|
1004
|
+
handleViewChange('drafts');
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
enabled: viewIsEnabled('drafts'),
|
|
1008
|
+
ignoreInputs: true,
|
|
1009
|
+
preventDefault: true,
|
|
1010
|
+
}
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
useHotkeySequence(
|
|
1014
|
+
HOTKEY_GO_TO_RECYCLE_BIN,
|
|
1015
|
+
() => {
|
|
1016
|
+
handleViewChange('recycle_bin');
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
enabled: viewIsEnabled('recycle_bin'),
|
|
566
1020
|
ignoreInputs: true,
|
|
567
1021
|
preventDefault: true,
|
|
568
1022
|
}
|
|
@@ -581,15 +1035,45 @@ export function BoardViews({
|
|
|
581
1035
|
lists={filteredLists}
|
|
582
1036
|
isLoading={false}
|
|
583
1037
|
disableSort={!!filters.sortBy}
|
|
1038
|
+
deadlineTaskQueryOptions={deadlineTaskQueryOptions}
|
|
584
1039
|
listStatusFilter={listStatusFilter}
|
|
585
1040
|
filters={filters}
|
|
586
|
-
isMultiSelectMode={isMultiSelectMode}
|
|
587
|
-
setIsMultiSelectMode={setIsMultiSelectMode}
|
|
1041
|
+
isMultiSelectMode={readOnly ? false : isMultiSelectMode}
|
|
1042
|
+
setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
|
|
588
1043
|
onExternalTasksCollapsedChange={handleExternalTasksCollapsedChange}
|
|
589
1044
|
onTaskListCollapsedChange={handleTaskListCollapsedChange}
|
|
1045
|
+
deadlineSectionsCollapsed={effectiveDeadlineSectionsCollapsed}
|
|
1046
|
+
onDeadlineSectionCollapsedChange={
|
|
1047
|
+
handleDeadlineSectionCollapsedChange
|
|
1048
|
+
}
|
|
1049
|
+
specialTaskListPins={specialTaskListPins}
|
|
1050
|
+
onSpecialTaskListPinnedChange={handleSpecialTaskListPinnedChange}
|
|
590
1051
|
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
1052
|
+
readOnly={readOnly}
|
|
591
1053
|
/>
|
|
592
1054
|
);
|
|
1055
|
+
case 'my_tasks':
|
|
1056
|
+
return (
|
|
1057
|
+
<div className="h-full overflow-y-auto p-3 sm:p-4">
|
|
1058
|
+
<div className="mx-auto max-w-5xl pb-20">
|
|
1059
|
+
{currentUserId ? (
|
|
1060
|
+
<MyTasksContent
|
|
1061
|
+
disableAutoCreateBoard
|
|
1062
|
+
embedded
|
|
1063
|
+
initialBoard={{
|
|
1064
|
+
id: board.id,
|
|
1065
|
+
name: board.name ?? null,
|
|
1066
|
+
}}
|
|
1067
|
+
initialLists={boardLists}
|
|
1068
|
+
initialListId={board.default_list_id ?? undefined}
|
|
1069
|
+
isPersonal={workspace.personal}
|
|
1070
|
+
userId={currentUserId}
|
|
1071
|
+
wsId={effectiveWorkspaceId}
|
|
1072
|
+
/>
|
|
1073
|
+
) : null}
|
|
1074
|
+
</div>
|
|
1075
|
+
</div>
|
|
1076
|
+
);
|
|
593
1077
|
case 'list':
|
|
594
1078
|
return (
|
|
595
1079
|
<ListView
|
|
@@ -600,6 +1084,7 @@ export function BoardViews({
|
|
|
600
1084
|
isPersonalWorkspace={workspace.personal}
|
|
601
1085
|
preserveTaskOrder={!!filters.sortBy}
|
|
602
1086
|
searchQuery={filters.searchQuery}
|
|
1087
|
+
readOnly={readOnly}
|
|
603
1088
|
/>
|
|
604
1089
|
);
|
|
605
1090
|
case 'timeline':
|
|
@@ -612,6 +1097,66 @@ export function BoardViews({
|
|
|
612
1097
|
onTaskPartialUpdate={handleTaskPartialUpdate}
|
|
613
1098
|
/>
|
|
614
1099
|
);
|
|
1100
|
+
case 'drafts':
|
|
1101
|
+
return (
|
|
1102
|
+
<div className="h-full overflow-y-auto p-3 sm:p-4">
|
|
1103
|
+
<DraftsPage
|
|
1104
|
+
boardId={board.id}
|
|
1105
|
+
includeUnassignedForBoard
|
|
1106
|
+
wsId={effectiveWorkspaceId}
|
|
1107
|
+
/>
|
|
1108
|
+
</div>
|
|
1109
|
+
);
|
|
1110
|
+
case 'recycle_bin':
|
|
1111
|
+
return (
|
|
1112
|
+
<RecycleBinContent
|
|
1113
|
+
active
|
|
1114
|
+
boardId={board.id}
|
|
1115
|
+
className="h-full"
|
|
1116
|
+
lists={boardLists}
|
|
1117
|
+
translations={{
|
|
1118
|
+
recycleBin: t('recycle_bin'),
|
|
1119
|
+
recycleBinDescription: t('recycle_bin_description'),
|
|
1120
|
+
noDeletedTasks: t('no_deleted_tasks'),
|
|
1121
|
+
deletedTasksWillAppearHere: t('deleted_tasks_will_appear_here'),
|
|
1122
|
+
selectedOfTotal: t('selected_of_total', {
|
|
1123
|
+
selected: '{selected}',
|
|
1124
|
+
total: '{total}',
|
|
1125
|
+
}),
|
|
1126
|
+
deletedTasksCount: t('deleted_tasks_count', { count: '{count}' }),
|
|
1127
|
+
restore: t('restore'),
|
|
1128
|
+
delete: t('delete'),
|
|
1129
|
+
restoreTasksTitle: t('restore_tasks_title', { count: '{count}' }),
|
|
1130
|
+
restoreTasksDescription: t('restore_tasks_description'),
|
|
1131
|
+
cancel: t('cancel'),
|
|
1132
|
+
restoring: t('restoring'),
|
|
1133
|
+
permanentlyDeleteTitle: t('permanently_delete_title', {
|
|
1134
|
+
count: '{count}',
|
|
1135
|
+
}),
|
|
1136
|
+
permanentlyDeleteDescription: t('permanently_delete_description'),
|
|
1137
|
+
deleting: t('deleting'),
|
|
1138
|
+
deletePermanently: t('delete_permanently'),
|
|
1139
|
+
noListsAvailable: t('no_lists_available'),
|
|
1140
|
+
restoredTasks: t('restored_tasks', { count: '{count}' }),
|
|
1141
|
+
failedToRestore: t('failed_to_restore'),
|
|
1142
|
+
permanentlyDeleted: t('permanently_deleted', {
|
|
1143
|
+
count: '{count}',
|
|
1144
|
+
}),
|
|
1145
|
+
failedToDelete: t('failed_to_delete'),
|
|
1146
|
+
deletedAgo: t('deleted_ago', { time: '{time}' }),
|
|
1147
|
+
fromList: t('from_list', { list: '{list}' }),
|
|
1148
|
+
nProjects: t('n_projects', { count: '{count}' }),
|
|
1149
|
+
selectAllTasks: t('select_all_tasks'),
|
|
1150
|
+
selectTask: t('select_task', { name: '{name}' }),
|
|
1151
|
+
critical: tBoards('dialog.priority.critical'),
|
|
1152
|
+
high: tBoards('dialog.priority.high'),
|
|
1153
|
+
normal: tBoards('dialog.priority.normal'),
|
|
1154
|
+
low: tBoards('dialog.priority.low'),
|
|
1155
|
+
unknownList: t('unknown_list'),
|
|
1156
|
+
}}
|
|
1157
|
+
wsId={effectiveWorkspaceId}
|
|
1158
|
+
/>
|
|
1159
|
+
);
|
|
615
1160
|
default:
|
|
616
1161
|
return (
|
|
617
1162
|
<KanbanBoard
|
|
@@ -623,19 +1168,28 @@ export function BoardViews({
|
|
|
623
1168
|
lists={filteredLists}
|
|
624
1169
|
isLoading={false}
|
|
625
1170
|
disableSort={!!filters.sortBy}
|
|
1171
|
+
deadlineTaskQueryOptions={deadlineTaskQueryOptions}
|
|
626
1172
|
listStatusFilter={listStatusFilter}
|
|
627
1173
|
filters={filters}
|
|
628
|
-
isMultiSelectMode={isMultiSelectMode}
|
|
629
|
-
setIsMultiSelectMode={setIsMultiSelectMode}
|
|
1174
|
+
isMultiSelectMode={readOnly ? false : isMultiSelectMode}
|
|
1175
|
+
setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
|
|
630
1176
|
onExternalTasksCollapsedChange={handleExternalTasksCollapsedChange}
|
|
631
1177
|
onTaskListCollapsedChange={handleTaskListCollapsedChange}
|
|
1178
|
+
deadlineSectionsCollapsed={effectiveDeadlineSectionsCollapsed}
|
|
1179
|
+
onDeadlineSectionCollapsedChange={
|
|
1180
|
+
handleDeadlineSectionCollapsedChange
|
|
1181
|
+
}
|
|
1182
|
+
specialTaskListPins={specialTaskListPins}
|
|
1183
|
+
onSpecialTaskListPinnedChange={handleSpecialTaskListPinnedChange}
|
|
632
1184
|
onBulkSelectionActiveChange={setKanbanBulkSelectionActive}
|
|
1185
|
+
readOnly={readOnly}
|
|
633
1186
|
/>
|
|
634
1187
|
);
|
|
635
1188
|
}
|
|
636
1189
|
};
|
|
637
1190
|
|
|
638
1191
|
const showIdleBottomIsland =
|
|
1192
|
+
!readOnly &&
|
|
639
1193
|
!!idleBottomIsland &&
|
|
640
1194
|
(currentView !== 'kanban' || !kanbanBulkSelectionActive);
|
|
641
1195
|
|
|
@@ -653,62 +1207,22 @@ export function BoardViews({
|
|
|
653
1207
|
listStatusFilter={listStatusFilter}
|
|
654
1208
|
onListStatusFilterChange={setListStatusFilter}
|
|
655
1209
|
isPersonalWorkspace={workspace.personal}
|
|
656
|
-
isSearching={
|
|
1210
|
+
isSearching={
|
|
1211
|
+
!localTaskState &&
|
|
1212
|
+
(isFullTasksFetching || isFilteredListCountsFetching)
|
|
1213
|
+
}
|
|
657
1214
|
lists={boardLists}
|
|
658
1215
|
onUpdate={handleUpdate}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
hideActions={!canManageBoard}
|
|
1216
|
+
isMultiSelectMode={readOnly ? false : isMultiSelectMode}
|
|
1217
|
+
setIsMultiSelectMode={readOnly ? () => {} : setIsMultiSelectMode}
|
|
1218
|
+
availableViews={enabledViews ?? undefined}
|
|
1219
|
+
hideActions={!canManageBoard || readOnly}
|
|
1220
|
+
publicView={publicView}
|
|
1221
|
+
readOnly={readOnly}
|
|
1222
|
+
titlePrefix={publicHeaderPrefix}
|
|
663
1223
|
/>
|
|
664
1224
|
<div className="h-full overflow-hidden">{renderView()}</div>
|
|
665
1225
|
{showIdleBottomIsland ? idleBottomIsland : null}
|
|
666
|
-
|
|
667
|
-
<RecycleBinPanel
|
|
668
|
-
open={recycleBinOpen}
|
|
669
|
-
onOpenChange={setRecycleBinOpen}
|
|
670
|
-
wsId={effectiveWorkspaceId}
|
|
671
|
-
boardId={board.id}
|
|
672
|
-
lists={boardLists}
|
|
673
|
-
translations={{
|
|
674
|
-
recycleBin: t('recycle_bin'),
|
|
675
|
-
recycleBinDescription: t('recycle_bin_description'),
|
|
676
|
-
noDeletedTasks: t('no_deleted_tasks'),
|
|
677
|
-
deletedTasksWillAppearHere: t('deleted_tasks_will_appear_here'),
|
|
678
|
-
selectedOfTotal: t('selected_of_total', {
|
|
679
|
-
selected: '{selected}',
|
|
680
|
-
total: '{total}',
|
|
681
|
-
}),
|
|
682
|
-
deletedTasksCount: t('deleted_tasks_count', { count: '{count}' }),
|
|
683
|
-
restore: t('restore'),
|
|
684
|
-
delete: t('delete'),
|
|
685
|
-
restoreTasksTitle: t('restore_tasks_title', { count: '{count}' }),
|
|
686
|
-
restoreTasksDescription: t('restore_tasks_description'),
|
|
687
|
-
cancel: t('cancel'),
|
|
688
|
-
restoring: t('restoring'),
|
|
689
|
-
permanentlyDeleteTitle: t('permanently_delete_title', {
|
|
690
|
-
count: '{count}',
|
|
691
|
-
}),
|
|
692
|
-
permanentlyDeleteDescription: t('permanently_delete_description'),
|
|
693
|
-
deleting: t('deleting'),
|
|
694
|
-
deletePermanently: t('delete_permanently'),
|
|
695
|
-
noListsAvailable: t('no_lists_available'),
|
|
696
|
-
restoredTasks: t('restored_tasks', { count: '{count}' }),
|
|
697
|
-
failedToRestore: t('failed_to_restore'),
|
|
698
|
-
permanentlyDeleted: t('permanently_deleted', { count: '{count}' }),
|
|
699
|
-
failedToDelete: t('failed_to_delete'),
|
|
700
|
-
deletedAgo: t('deleted_ago', { time: '{time}' }),
|
|
701
|
-
fromList: t('from_list', { list: '{list}' }),
|
|
702
|
-
nProjects: t('n_projects', { count: '{count}' }),
|
|
703
|
-
selectAllTasks: t('select_all_tasks'),
|
|
704
|
-
selectTask: t('select_task', { name: '{name}' }),
|
|
705
|
-
critical: tBoards('dialog.priority.critical'),
|
|
706
|
-
high: tBoards('dialog.priority.high'),
|
|
707
|
-
normal: tBoards('dialog.priority.normal'),
|
|
708
|
-
low: tBoards('dialog.priority.low'),
|
|
709
|
-
unknownList: t('unknown_list'),
|
|
710
|
-
}}
|
|
711
|
-
/>
|
|
712
1226
|
</div>
|
|
713
1227
|
);
|
|
714
1228
|
}
|