@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
|
@@ -3,17 +3,27 @@ import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
|
3
3
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
4
4
|
import { MAX_SAFE_INTEGER_SORT } from '../kanban-constants';
|
|
5
5
|
import type { DragCacheSnapshot, TaskSortKeyRepair } from './task-drag-types';
|
|
6
|
+
import { getEffectiveTaskSortKey } from './task-sort-key';
|
|
6
7
|
|
|
7
8
|
const SORT_KEY_BASE_UNIT = 1_000_000;
|
|
8
9
|
const SORT_KEY_DEFAULT = SORT_KEY_BASE_UNIT * 1000;
|
|
9
10
|
const SORT_KEY_MIN_GAP = 1000;
|
|
10
11
|
|
|
12
|
+
type SortKeyPlanTask = Pick<
|
|
13
|
+
Task,
|
|
14
|
+
| 'id'
|
|
15
|
+
| 'is_personal_external'
|
|
16
|
+
| 'is_personal_external_default'
|
|
17
|
+
| 'personal_sort_key'
|
|
18
|
+
| 'sort_key'
|
|
19
|
+
>;
|
|
20
|
+
|
|
11
21
|
function getTaskSortKeyInsertionContext({
|
|
12
22
|
activeTaskId,
|
|
13
23
|
orderedTasks,
|
|
14
24
|
}: {
|
|
15
25
|
activeTaskId: string;
|
|
16
|
-
orderedTasks:
|
|
26
|
+
orderedTasks: SortKeyPlanTask[];
|
|
17
27
|
}) {
|
|
18
28
|
const activeIndex = orderedTasks.findIndex(
|
|
19
29
|
(task) => task.id === activeTaskId
|
|
@@ -26,10 +36,15 @@ function getTaskSortKeyInsertionContext({
|
|
|
26
36
|
};
|
|
27
37
|
}
|
|
28
38
|
|
|
39
|
+
const nextTask = orderedTasks[activeIndex + 1];
|
|
40
|
+
const previousTask = orderedTasks[activeIndex - 1];
|
|
41
|
+
|
|
29
42
|
return {
|
|
30
43
|
activeIndex,
|
|
31
|
-
nextSortKey:
|
|
32
|
-
previousSortKey:
|
|
44
|
+
nextSortKey: nextTask ? getEffectiveTaskSortKey(nextTask) : null,
|
|
45
|
+
previousSortKey: previousTask
|
|
46
|
+
? getEffectiveTaskSortKey(previousTask)
|
|
47
|
+
: null,
|
|
33
48
|
};
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -94,7 +109,7 @@ function getPreviewSortKeyPlan({
|
|
|
94
109
|
targetListId,
|
|
95
110
|
}: {
|
|
96
111
|
activeTaskId: string;
|
|
97
|
-
orderedTasks:
|
|
112
|
+
orderedTasks: SortKeyPlanTask[];
|
|
98
113
|
targetListId: string;
|
|
99
114
|
}): {
|
|
100
115
|
previewSortKey: number;
|
|
@@ -119,10 +134,14 @@ function getPreviewSortKeyPlan({
|
|
|
119
134
|
});
|
|
120
135
|
const effectiveOrderedTasks = orderedTasks.map((task) => ({
|
|
121
136
|
...task,
|
|
122
|
-
|
|
137
|
+
effective_sort_key:
|
|
138
|
+
task.id === activeTaskId ? previewSortKey : getEffectiveTaskSortKey(task),
|
|
123
139
|
}));
|
|
124
140
|
const orderNeedsRepair = effectiveOrderedTasks.some((task, index) => {
|
|
125
|
-
if (
|
|
141
|
+
if (
|
|
142
|
+
typeof task.effective_sort_key !== 'number' ||
|
|
143
|
+
!Number.isFinite(task.effective_sort_key)
|
|
144
|
+
) {
|
|
126
145
|
return true;
|
|
127
146
|
}
|
|
128
147
|
|
|
@@ -130,13 +149,16 @@ function getPreviewSortKeyPlan({
|
|
|
130
149
|
if (!previousTask) return false;
|
|
131
150
|
|
|
132
151
|
if (
|
|
133
|
-
typeof previousTask.
|
|
134
|
-
!Number.isFinite(previousTask.
|
|
152
|
+
typeof previousTask.effective_sort_key !== 'number' ||
|
|
153
|
+
!Number.isFinite(previousTask.effective_sort_key)
|
|
135
154
|
) {
|
|
136
155
|
return true;
|
|
137
156
|
}
|
|
138
157
|
|
|
139
|
-
return
|
|
158
|
+
return (
|
|
159
|
+
task.effective_sort_key - previousTask.effective_sort_key <
|
|
160
|
+
SORT_KEY_MIN_GAP
|
|
161
|
+
);
|
|
140
162
|
});
|
|
141
163
|
|
|
142
164
|
if (
|
|
@@ -197,6 +219,9 @@ export function getTaskDropPreviewCacheTasks({
|
|
|
197
219
|
...task,
|
|
198
220
|
list_id: targetListId,
|
|
199
221
|
sort_key: previewSortKey,
|
|
222
|
+
personal_sort_key: task.is_personal_external
|
|
223
|
+
? previewSortKey
|
|
224
|
+
: task.personal_sort_key,
|
|
200
225
|
completed: targetIsCompleted,
|
|
201
226
|
completed_at: targetIsCompleted
|
|
202
227
|
? (task.completed_at ?? mutationTimestamp)
|
|
@@ -210,6 +235,10 @@ export function getTaskDropPreviewCacheTasks({
|
|
|
210
235
|
? ({
|
|
211
236
|
...task,
|
|
212
237
|
sort_key: repairedSortKeysByTaskId.get(task.id) ?? task.sort_key,
|
|
238
|
+
personal_sort_key: task.is_personal_external
|
|
239
|
+
? (repairedSortKeysByTaskId.get(task.id) ??
|
|
240
|
+
task.personal_sort_key)
|
|
241
|
+
: task.personal_sort_key,
|
|
213
242
|
_localMutationAt: localMutationAt,
|
|
214
243
|
} as Task & { _localMutationAt: number })
|
|
215
244
|
: task
|
|
@@ -263,6 +292,7 @@ export function applyTaskDropPreviewToCache({
|
|
|
263
292
|
}
|
|
264
293
|
|
|
265
294
|
return {
|
|
295
|
+
localMutationAt,
|
|
266
296
|
previousFullTasks: snapshot.fullTasks,
|
|
267
297
|
previousTasks: snapshot.tasks,
|
|
268
298
|
previewSortKey: previewTasks.previewSortKey,
|
|
@@ -270,6 +300,18 @@ export function applyTaskDropPreviewToCache({
|
|
|
270
300
|
};
|
|
271
301
|
}
|
|
272
302
|
|
|
303
|
+
export function hasTaskLocalMutationAt(
|
|
304
|
+
tasks: Task[] | undefined,
|
|
305
|
+
taskId: string,
|
|
306
|
+
localMutationAt: number
|
|
307
|
+
) {
|
|
308
|
+
const task = tasks?.find((item) => item.id === taskId) as
|
|
309
|
+
| (Task & { _localMutationAt?: unknown })
|
|
310
|
+
| undefined;
|
|
311
|
+
|
|
312
|
+
return task?._localMutationAt === localMutationAt;
|
|
313
|
+
}
|
|
314
|
+
|
|
273
315
|
export function mergeTaskIntoBoardTaskCache(
|
|
274
316
|
currentTasks: Task[] | undefined,
|
|
275
317
|
nextTask: Task
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
2
2
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
3
|
-
import { MAX_SAFE_INTEGER_SORT } from '../kanban-constants';
|
|
4
3
|
import type { DragPreviewPosition, TaskDropPosition } from './task-drag-types';
|
|
4
|
+
import { compareTasksByEffectiveSortKey } from './task-sort-key';
|
|
5
5
|
|
|
6
6
|
export function getNeighborTaskIds(tasks: Task[], taskId: string) {
|
|
7
7
|
const taskIndex = tasks.findIndex((task) => task.id === taskId);
|
|
@@ -142,13 +142,7 @@ export function sortTasksForList({
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
if (!disableSort) {
|
|
145
|
-
|
|
146
|
-
const sortB = b.sort_key ?? MAX_SAFE_INTEGER_SORT;
|
|
147
|
-
if (sortA !== sortB) return sortA - sortB;
|
|
148
|
-
if (!a.created_at || !b.created_at) return 0;
|
|
149
|
-
return (
|
|
150
|
-
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
|
|
151
|
-
);
|
|
145
|
+
return compareTasksByEffectiveSortKey(a, b);
|
|
152
146
|
}
|
|
153
147
|
|
|
154
148
|
return 0;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
2
|
+
import { MAX_SAFE_INTEGER_SORT } from '../kanban-constants';
|
|
3
|
+
|
|
4
|
+
type SortKeyTask = Pick<
|
|
5
|
+
Task,
|
|
6
|
+
| 'is_personal_external'
|
|
7
|
+
| 'is_personal_external_default'
|
|
8
|
+
| 'personal_sort_key'
|
|
9
|
+
| 'sort_key'
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
type SortableTask = SortKeyTask & Pick<Task, 'created_at'>;
|
|
13
|
+
|
|
14
|
+
export function getEffectiveTaskSortKey(task: SortKeyTask) {
|
|
15
|
+
if (typeof task.sort_key === 'number' && Number.isFinite(task.sort_key)) {
|
|
16
|
+
return task.sort_key;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
task.is_personal_external === true &&
|
|
21
|
+
task.is_personal_external_default !== true &&
|
|
22
|
+
typeof task.personal_sort_key === 'number' &&
|
|
23
|
+
Number.isFinite(task.personal_sort_key)
|
|
24
|
+
) {
|
|
25
|
+
return task.personal_sort_key;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function compareTasksByEffectiveSortKey(
|
|
32
|
+
left: SortableTask,
|
|
33
|
+
right: SortableTask
|
|
34
|
+
) {
|
|
35
|
+
const leftSortKey = getEffectiveTaskSortKey(left) ?? MAX_SAFE_INTEGER_SORT;
|
|
36
|
+
const rightSortKey = getEffectiveTaskSortKey(right) ?? MAX_SAFE_INTEGER_SORT;
|
|
37
|
+
|
|
38
|
+
if (leftSortKey !== rightSortKey) {
|
|
39
|
+
return leftSortKey - rightSortKey;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!left.created_at || !right.created_at) return 0;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
new Date(left.created_at).getTime() - new Date(right.created_at).getTime()
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
1
2
|
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
2
3
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
3
4
|
import { describe, expect, it } from 'vitest';
|
|
4
5
|
import {
|
|
6
|
+
applyTaskDropPreviewToCache,
|
|
5
7
|
getProjectedTaskDropOrderFromPreview,
|
|
6
8
|
getTaskDropEndPreviewFromRects,
|
|
7
9
|
getTaskDropPositionFromRects,
|
|
8
10
|
getTaskDropPreviewCacheTasks,
|
|
9
11
|
getTaskDropPreviewFromRects,
|
|
10
12
|
getTaskInsertionIndex,
|
|
13
|
+
hasTaskLocalMutationAt,
|
|
11
14
|
insertTaskAtDropPosition,
|
|
12
15
|
mergePersonalPlacementMutationTask,
|
|
13
16
|
mergeTaskIntoBoardTaskCache,
|
|
@@ -683,4 +686,64 @@ describe('task drag insertion helpers', () => {
|
|
|
683
686
|
})
|
|
684
687
|
);
|
|
685
688
|
});
|
|
689
|
+
|
|
690
|
+
it('marks optimistic previews so stale drag rollbacks can be skipped', () => {
|
|
691
|
+
const queryClient = new QueryClient();
|
|
692
|
+
const boardId = 'board-1';
|
|
693
|
+
const activeTask = createTask({
|
|
694
|
+
id: 'task-1',
|
|
695
|
+
list_id: 'source-list',
|
|
696
|
+
sort_key: 1_000_000,
|
|
697
|
+
});
|
|
698
|
+
const targetTask = createTask({
|
|
699
|
+
id: 'task-2',
|
|
700
|
+
list_id: 'target-list',
|
|
701
|
+
sort_key: 2_000_000,
|
|
702
|
+
});
|
|
703
|
+
const snapshot = {
|
|
704
|
+
fullTasks: [activeTask, targetTask],
|
|
705
|
+
tasks: [activeTask, targetTask],
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
queryClient.setQueryData(['tasks', boardId], snapshot.tasks);
|
|
709
|
+
queryClient.setQueryData(['tasks-full', boardId], snapshot.fullTasks);
|
|
710
|
+
|
|
711
|
+
const preview = applyTaskDropPreviewToCache({
|
|
712
|
+
activeTask,
|
|
713
|
+
boardId,
|
|
714
|
+
orderedTasks: [targetTask, activeTask],
|
|
715
|
+
queryClient,
|
|
716
|
+
snapshot,
|
|
717
|
+
targetList: createList({ id: 'target-list' }),
|
|
718
|
+
targetListId: 'target-list',
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
expect(preview?.localMutationAt).toEqual(expect.any(Number));
|
|
722
|
+
expect(
|
|
723
|
+
hasTaskLocalMutationAt(
|
|
724
|
+
queryClient.getQueryData<Task[]>(['tasks', boardId]),
|
|
725
|
+
activeTask.id,
|
|
726
|
+
preview?.localMutationAt ?? -1
|
|
727
|
+
)
|
|
728
|
+
).toBe(true);
|
|
729
|
+
|
|
730
|
+
queryClient.setQueryData<Task[]>(['tasks', boardId], (currentTasks) =>
|
|
731
|
+
currentTasks?.map((task) =>
|
|
732
|
+
task.id === activeTask.id
|
|
733
|
+
? ({
|
|
734
|
+
...task,
|
|
735
|
+
_localMutationAt: (preview?.localMutationAt ?? 0) + 1,
|
|
736
|
+
} as Task & { _localMutationAt: number })
|
|
737
|
+
: task
|
|
738
|
+
)
|
|
739
|
+
);
|
|
740
|
+
|
|
741
|
+
expect(
|
|
742
|
+
hasTaskLocalMutationAt(
|
|
743
|
+
queryClient.getQueryData<Task[]>(['tasks', boardId]),
|
|
744
|
+
activeTask.id,
|
|
745
|
+
preview?.localMutationAt ?? -1
|
|
746
|
+
)
|
|
747
|
+
).toBe(false);
|
|
748
|
+
});
|
|
686
749
|
});
|
|
@@ -22,12 +22,14 @@ import {
|
|
|
22
22
|
import { hasDraggableData } from '@tuturuuu/utils/task-helpers';
|
|
23
23
|
import { useCallback, useRef, useState } from 'react';
|
|
24
24
|
import { useBoardBroadcast } from '../../../../shared/board-broadcast-context';
|
|
25
|
+
import { invalidateKanbanDeadlineTasks } from '../data/kanban-deadline-query';
|
|
25
26
|
import { MAX_SAFE_INTEGER_SORT } from '../kanban-constants';
|
|
26
27
|
import { useAutoScroll } from './auto-scroll';
|
|
27
28
|
import { getColumnReorderUpdates } from './column-reorder';
|
|
28
29
|
import { calculateSortKeyWithRetry as createCalculateSortKeyWithRetry } from './kanban-sort-helpers';
|
|
29
30
|
import {
|
|
30
31
|
applyTaskDropPreviewToCache,
|
|
32
|
+
hasTaskLocalMutationAt,
|
|
31
33
|
mergePersonalPlacementMutationTask,
|
|
32
34
|
setBoardTaskCache,
|
|
33
35
|
} from './task-drag-cache';
|
|
@@ -61,10 +63,15 @@ import type {
|
|
|
61
63
|
TaskRect,
|
|
62
64
|
VerticalRect,
|
|
63
65
|
} from './task-drag-types';
|
|
66
|
+
import {
|
|
67
|
+
compareTasksByEffectiveSortKey,
|
|
68
|
+
getEffectiveTaskSortKey,
|
|
69
|
+
} from './task-sort-key';
|
|
64
70
|
|
|
65
71
|
export {
|
|
66
72
|
applyTaskDropPreviewToCache,
|
|
67
73
|
getTaskDropPreviewCacheTasks,
|
|
74
|
+
hasTaskLocalMutationAt,
|
|
68
75
|
mergePersonalPlacementMutationTask,
|
|
69
76
|
mergeTaskIntoBoardTaskCache,
|
|
70
77
|
} from './task-drag-cache';
|
|
@@ -504,6 +511,16 @@ export function useKanbanDnd({
|
|
|
504
511
|
queryClient.setQueryData<Task[]>(queryKey, (currentTasks) => {
|
|
505
512
|
if (!currentTasks) return previousCache;
|
|
506
513
|
|
|
514
|
+
if (
|
|
515
|
+
!hasTaskLocalMutationAt(
|
|
516
|
+
currentTasks,
|
|
517
|
+
task.id,
|
|
518
|
+
nextTask._localMutationAt
|
|
519
|
+
)
|
|
520
|
+
) {
|
|
521
|
+
return currentTasks;
|
|
522
|
+
}
|
|
523
|
+
|
|
507
524
|
if (!previousTaskValue) {
|
|
508
525
|
return currentTasks.filter((item) => item.id !== task.id);
|
|
509
526
|
}
|
|
@@ -1079,17 +1096,43 @@ export function useKanbanDnd({
|
|
|
1079
1096
|
if (!boardId || !optimisticDropPreview) return;
|
|
1080
1097
|
|
|
1081
1098
|
if (optimisticDropPreview.previousTasks) {
|
|
1082
|
-
queryClient.
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
);
|
|
1099
|
+
const currentTasks = queryClient.getQueryData<Task[]>([
|
|
1100
|
+
'tasks',
|
|
1101
|
+
boardId,
|
|
1102
|
+
]);
|
|
1103
|
+
|
|
1104
|
+
if (
|
|
1105
|
+
hasTaskLocalMutationAt(
|
|
1106
|
+
currentTasks,
|
|
1107
|
+
activeTaskForDrop.id,
|
|
1108
|
+
optimisticDropPreview.localMutationAt
|
|
1109
|
+
)
|
|
1110
|
+
) {
|
|
1111
|
+
queryClient.setQueryData(
|
|
1112
|
+
['tasks', boardId],
|
|
1113
|
+
optimisticDropPreview.previousTasks
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1086
1116
|
}
|
|
1087
1117
|
|
|
1088
1118
|
if (optimisticDropPreview.previousFullTasks) {
|
|
1089
|
-
queryClient.
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
);
|
|
1119
|
+
const currentFullTasks = queryClient.getQueryData<Task[]>([
|
|
1120
|
+
'tasks-full',
|
|
1121
|
+
boardId,
|
|
1122
|
+
]);
|
|
1123
|
+
|
|
1124
|
+
if (
|
|
1125
|
+
hasTaskLocalMutationAt(
|
|
1126
|
+
currentFullTasks,
|
|
1127
|
+
activeTaskForDrop.id,
|
|
1128
|
+
optimisticDropPreview.localMutationAt
|
|
1129
|
+
)
|
|
1130
|
+
) {
|
|
1131
|
+
queryClient.setQueryData(
|
|
1132
|
+
['tasks-full', boardId],
|
|
1133
|
+
optimisticDropPreview.previousFullTasks
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1093
1136
|
}
|
|
1094
1137
|
};
|
|
1095
1138
|
|
|
@@ -1124,14 +1167,14 @@ export function useKanbanDnd({
|
|
|
1124
1167
|
const nextTask = projectedDropOrder[1];
|
|
1125
1168
|
newSortKey = await calculateSortKeyWithRetry(
|
|
1126
1169
|
null,
|
|
1127
|
-
nextTask
|
|
1170
|
+
nextTask ? getEffectiveTaskSortKey(nextTask) : null,
|
|
1128
1171
|
targetListId,
|
|
1129
1172
|
projectedDropOrder
|
|
1130
1173
|
);
|
|
1131
1174
|
} else if (newIndex === projectedDropOrder.length - 1) {
|
|
1132
1175
|
const prevTask = projectedDropOrder[projectedDropOrder.length - 2];
|
|
1133
1176
|
newSortKey = await calculateSortKeyWithRetry(
|
|
1134
|
-
prevTask
|
|
1177
|
+
prevTask ? getEffectiveTaskSortKey(prevTask) : null,
|
|
1135
1178
|
null,
|
|
1136
1179
|
targetListId,
|
|
1137
1180
|
projectedDropOrder
|
|
@@ -1140,8 +1183,8 @@ export function useKanbanDnd({
|
|
|
1140
1183
|
const prevTask = projectedDropOrder[newIndex - 1];
|
|
1141
1184
|
const nextTask = projectedDropOrder[newIndex + 1];
|
|
1142
1185
|
newSortKey = await calculateSortKeyWithRetry(
|
|
1143
|
-
prevTask
|
|
1144
|
-
nextTask
|
|
1186
|
+
prevTask ? getEffectiveTaskSortKey(prevTask) : null,
|
|
1187
|
+
nextTask ? getEffectiveTaskSortKey(nextTask) : null,
|
|
1145
1188
|
targetListId,
|
|
1146
1189
|
projectedDropOrder
|
|
1147
1190
|
);
|
|
@@ -1157,7 +1200,8 @@ export function useKanbanDnd({
|
|
|
1157
1200
|
const needsUpdate =
|
|
1158
1201
|
dropChangesVisualOrder ||
|
|
1159
1202
|
(newSortKey !== null &&
|
|
1160
|
-
(activeTaskForDrop
|
|
1203
|
+
(getEffectiveTaskSortKey(activeTaskForDrop) ??
|
|
1204
|
+
MAX_SAFE_INTEGER_SORT) !== newSortKey);
|
|
1161
1205
|
|
|
1162
1206
|
let shouldPreservePendingAfterDragReset = false;
|
|
1163
1207
|
const persistPersonalPlacementMove = (
|
|
@@ -1173,6 +1217,19 @@ export function useKanbanDnd({
|
|
|
1173
1217
|
shouldPreservePendingAfterDragReset = true;
|
|
1174
1218
|
|
|
1175
1219
|
void movePersonalPlacementTask(task, targetListId, sortKey, order)
|
|
1220
|
+
.then((updatedTask) => {
|
|
1221
|
+
broadcast?.('task:upsert', {
|
|
1222
|
+
task: {
|
|
1223
|
+
id: updatedTask.id,
|
|
1224
|
+
list_id: updatedTask.list_id,
|
|
1225
|
+
sort_key: updatedTask.sort_key,
|
|
1226
|
+
personal_sort_key: updatedTask.personal_sort_key,
|
|
1227
|
+
completed_at: updatedTask.completed_at,
|
|
1228
|
+
closed_at: updatedTask.closed_at,
|
|
1229
|
+
},
|
|
1230
|
+
});
|
|
1231
|
+
void invalidateKanbanDeadlineTasks(queryClient, boardId);
|
|
1232
|
+
})
|
|
1176
1233
|
.catch((error) => {
|
|
1177
1234
|
console.error('Failed to update personal task placement:', error);
|
|
1178
1235
|
rollbackOptimisticDropPreview();
|
|
@@ -1193,16 +1250,9 @@ export function useKanbanDnd({
|
|
|
1193
1250
|
.map((taskId) => baseTasks.find((t) => t.id === taskId))
|
|
1194
1251
|
.filter((t): t is Task => t !== undefined);
|
|
1195
1252
|
|
|
1196
|
-
const sortedTasksToMove = selectedTaskObjects.sort(
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
if (sortA !== sortB) return sortA - sortB;
|
|
1200
|
-
if (!a.created_at || !b.created_at) return 0;
|
|
1201
|
-
return (
|
|
1202
|
-
new Date(a.created_at).getTime() -
|
|
1203
|
-
new Date(b.created_at).getTime()
|
|
1204
|
-
);
|
|
1205
|
-
});
|
|
1253
|
+
const sortedTasksToMove = selectedTaskObjects.sort(
|
|
1254
|
+
compareTasksByEffectiveSortKey
|
|
1255
|
+
);
|
|
1206
1256
|
|
|
1207
1257
|
if (
|
|
1208
1258
|
targetIsExternalStaging &&
|
|
@@ -1268,7 +1318,7 @@ export function useKanbanDnd({
|
|
|
1268
1318
|
const nextTask = simulatedTargetList[1];
|
|
1269
1319
|
batchSortKey = await calculateSortKeyWithRetry(
|
|
1270
1320
|
null,
|
|
1271
|
-
nextTask
|
|
1321
|
+
nextTask ? getEffectiveTaskSortKey(nextTask) : null,
|
|
1272
1322
|
targetListId,
|
|
1273
1323
|
targetListTasks
|
|
1274
1324
|
);
|
|
@@ -1278,7 +1328,7 @@ export function useKanbanDnd({
|
|
|
1278
1328
|
) {
|
|
1279
1329
|
const prevTask = simulatedTargetList[positionInSimulated - 1];
|
|
1280
1330
|
batchSortKey = await calculateSortKeyWithRetry(
|
|
1281
|
-
prevTask
|
|
1331
|
+
prevTask ? getEffectiveTaskSortKey(prevTask) : null,
|
|
1282
1332
|
null,
|
|
1283
1333
|
targetListId,
|
|
1284
1334
|
targetListTasks
|
|
@@ -1296,8 +1346,8 @@ export function useKanbanDnd({
|
|
|
1296
1346
|
|
|
1297
1347
|
if (!prevIsMoving && !nextIsMoving) {
|
|
1298
1348
|
batchSortKey = await calculateSortKeyWithRetry(
|
|
1299
|
-
prevTask
|
|
1300
|
-
nextTask
|
|
1349
|
+
prevTask ? getEffectiveTaskSortKey(prevTask) : null,
|
|
1350
|
+
nextTask ? getEffectiveTaskSortKey(nextTask) : null,
|
|
1301
1351
|
targetListId,
|
|
1302
1352
|
targetListTasks
|
|
1303
1353
|
);
|
|
@@ -1311,8 +1361,10 @@ export function useKanbanDnd({
|
|
|
1311
1361
|
}
|
|
1312
1362
|
}
|
|
1313
1363
|
batchSortKey = await calculateSortKeyWithRetry(
|
|
1314
|
-
stationaryPrev
|
|
1315
|
-
|
|
1364
|
+
stationaryPrev
|
|
1365
|
+
? getEffectiveTaskSortKey(stationaryPrev)
|
|
1366
|
+
: null,
|
|
1367
|
+
nextTask ? getEffectiveTaskSortKey(nextTask) : null,
|
|
1316
1368
|
targetListId,
|
|
1317
1369
|
targetListTasks
|
|
1318
1370
|
);
|
|
@@ -1330,8 +1382,10 @@ export function useKanbanDnd({
|
|
|
1330
1382
|
}
|
|
1331
1383
|
}
|
|
1332
1384
|
batchSortKey = await calculateSortKeyWithRetry(
|
|
1333
|
-
prevTask
|
|
1334
|
-
stationaryNext
|
|
1385
|
+
prevTask ? getEffectiveTaskSortKey(prevTask) : null,
|
|
1386
|
+
stationaryNext
|
|
1387
|
+
? getEffectiveTaskSortKey(stationaryNext)
|
|
1388
|
+
: null,
|
|
1335
1389
|
targetListId,
|
|
1336
1390
|
targetListTasks
|
|
1337
1391
|
);
|
|
@@ -1360,8 +1414,8 @@ export function useKanbanDnd({
|
|
|
1360
1414
|
}
|
|
1361
1415
|
|
|
1362
1416
|
batchSortKey = await calculateSortKeyWithRetry(
|
|
1363
|
-
boundaryPrev
|
|
1364
|
-
boundaryNext
|
|
1417
|
+
boundaryPrev ? getEffectiveTaskSortKey(boundaryPrev) : null,
|
|
1418
|
+
boundaryNext ? getEffectiveTaskSortKey(boundaryNext) : null,
|
|
1365
1419
|
targetListId,
|
|
1366
1420
|
targetListTasks
|
|
1367
1421
|
);
|
|
@@ -1392,6 +1446,7 @@ export function useKanbanDnd({
|
|
|
1392
1446
|
closed_at: updatedTask.closed_at,
|
|
1393
1447
|
},
|
|
1394
1448
|
});
|
|
1449
|
+
void invalidateKanbanDeadlineTasks(queryClient, boardId);
|
|
1395
1450
|
},
|
|
1396
1451
|
onSettled: () => {
|
|
1397
1452
|
clearPendingTaskIds(pendingTaskIds);
|
|
@@ -1437,14 +1492,45 @@ export function useKanbanDnd({
|
|
|
1437
1492
|
closed_at: updatedTask.closed_at,
|
|
1438
1493
|
},
|
|
1439
1494
|
});
|
|
1495
|
+
void invalidateKanbanDeadlineTasks(queryClient, boardId);
|
|
1440
1496
|
};
|
|
1441
1497
|
|
|
1442
1498
|
if (repairedTaskSortKeys.length > 0) {
|
|
1443
1499
|
void (async () => {
|
|
1444
1500
|
try {
|
|
1501
|
+
const repairTaskById = new Map<string, Task>();
|
|
1502
|
+
for (const task of [
|
|
1503
|
+
...baseTasks,
|
|
1504
|
+
...(optimisticDropPreview?.previousTasks ?? []),
|
|
1505
|
+
...(optimisticDropPreview?.previousFullTasks ?? []),
|
|
1506
|
+
]) {
|
|
1507
|
+
repairTaskById.set(task.id, task);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1445
1510
|
const results = await Promise.allSettled(
|
|
1446
|
-
repairedTaskSortKeys.map((repair) =>
|
|
1447
|
-
|
|
1511
|
+
repairedTaskSortKeys.map(async (repair) => {
|
|
1512
|
+
const repairTask = repairTaskById.get(repair.taskId);
|
|
1513
|
+
|
|
1514
|
+
if (repairTask && usesPersonalPlacement(repairTask)) {
|
|
1515
|
+
const updatedTask = await movePersonalPlacementTask(
|
|
1516
|
+
repairTask,
|
|
1517
|
+
repair.listId,
|
|
1518
|
+
repair.sortKey
|
|
1519
|
+
);
|
|
1520
|
+
broadcast?.('task:upsert', {
|
|
1521
|
+
task: {
|
|
1522
|
+
id: updatedTask.id,
|
|
1523
|
+
list_id: updatedTask.list_id,
|
|
1524
|
+
sort_key: updatedTask.sort_key,
|
|
1525
|
+
personal_sort_key: updatedTask.personal_sort_key,
|
|
1526
|
+
completed_at: updatedTask.completed_at,
|
|
1527
|
+
closed_at: updatedTask.closed_at,
|
|
1528
|
+
},
|
|
1529
|
+
});
|
|
1530
|
+
return updatedTask;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
return reorderTaskMutation.mutateAsync(
|
|
1448
1534
|
{
|
|
1449
1535
|
taskId: repair.taskId,
|
|
1450
1536
|
newListId: repair.listId,
|
|
@@ -1457,14 +1543,17 @@ export function useKanbanDnd({
|
|
|
1457
1543
|
{
|
|
1458
1544
|
onSuccess: handleReorderSuccess,
|
|
1459
1545
|
}
|
|
1460
|
-
)
|
|
1461
|
-
)
|
|
1546
|
+
);
|
|
1547
|
+
})
|
|
1462
1548
|
);
|
|
1463
1549
|
const failedResults = results.filter(
|
|
1464
1550
|
(result) => result.status === 'rejected'
|
|
1465
1551
|
);
|
|
1466
1552
|
|
|
1467
|
-
if (failedResults.length === 0)
|
|
1553
|
+
if (failedResults.length === 0) {
|
|
1554
|
+
void invalidateKanbanDeadlineTasks(queryClient, boardId);
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1468
1557
|
|
|
1469
1558
|
console.error(
|
|
1470
1559
|
'Failed to persist repaired task sort keys:',
|