@tuturuuu/ui 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/package.json +41 -34
- package/src/components/ui/currency-input.tsx +65 -23
- package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
- package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
- package/src/components/ui/custom/combobox.test.tsx +141 -0
- package/src/components/ui/custom/combobox.tsx +105 -36
- package/src/components/ui/custom/settings/task-settings.tsx +50 -0
- package/src/components/ui/custom/settings/task-sound-settings.test.tsx +21 -1
- package/src/components/ui/custom/sidebar-context.tsx +68 -6
- package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
- package/src/components/ui/finance/finance-layout.tsx +2 -4
- package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
- package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
- package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
- package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
- package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
- package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
- package/src/components/ui/finance/transactions/form-types.ts +23 -0
- package/src/components/ui/finance/transactions/form.tsx +81 -22
- package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +73 -26
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +2 -1
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +4 -4
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +298 -34
- package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +219 -46
- package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
- package/src/components/ui/finance/wallets/columns.test.ts +56 -0
- package/src/components/ui/finance/wallets/columns.tsx +196 -43
- package/src/components/ui/finance/wallets/form.test.tsx +79 -14
- package/src/components/ui/finance/wallets/form.tsx +41 -197
- package/src/components/ui/finance/wallets/query-invalidation.ts +1 -0
- package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
- package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
- package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
- package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +64 -2
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +42 -35
- package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
- package/src/components/ui/finance/wallets/wallets-page.test.tsx +111 -37
- package/src/components/ui/finance/wallets/wallets-page.tsx +38 -78
- package/src/components/ui/storefront/accent-button.tsx +33 -0
- package/src/components/ui/storefront/cart-summary.tsx +140 -0
- package/src/components/ui/storefront/empty-listings.tsx +32 -0
- package/src/components/ui/storefront/hero-panel.tsx +70 -0
- package/src/components/ui/storefront/image-panel.tsx +40 -0
- package/src/components/ui/storefront/index.ts +12 -0
- package/src/components/ui/storefront/listing-card.tsx +129 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
- package/src/components/ui/storefront/storefront-surface.tsx +235 -0
- package/src/components/ui/storefront/types.ts +99 -0
- package/src/components/ui/storefront/utils.ts +90 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
- package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
- package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
- package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +114 -7
- package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
- package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
- package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
- package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
- package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
- package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
- package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +128 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +104 -69
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +17 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +151 -111
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +584 -53
- package/src/hooks/useBoardRealtime.ts +54 -1
- package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
- package/src/hooks/useTaskUserRealtime.ts +338 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import type { Editor, JSONContent } from '@tiptap/react';
|
|
5
|
+
import { Archive, CheckCircle2, Trash2 } from '@tuturuuu/icons';
|
|
5
6
|
import {
|
|
6
7
|
checkWorkspacePermission,
|
|
7
8
|
createWorkspaceTask,
|
|
9
|
+
createWorkspaceTaskSuggestions,
|
|
8
10
|
updateWorkspaceCalendarEvent,
|
|
9
11
|
uploadWorkspaceTaskFile,
|
|
10
12
|
} from '@tuturuuu/internal-api';
|
|
13
|
+
import type { WorkspaceTaskSuggestionTask } from '@tuturuuu/internal-api/tasks';
|
|
11
14
|
import type { Task } from '@tuturuuu/types/primitives/Task';
|
|
12
15
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
16
|
+
import { Button } from '@tuturuuu/ui/button';
|
|
13
17
|
import { Dialog, DialogContent, DialogTitle } from '@tuturuuu/ui/dialog';
|
|
14
18
|
import { useYjsCollaboration } from '@tuturuuu/ui/hooks/use-yjs-collaboration';
|
|
15
19
|
import { toast } from '@tuturuuu/ui/sonner';
|
|
20
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@tuturuuu/ui/tooltip';
|
|
16
21
|
import { MAX_TASK_DESCRIPTION_LENGTH } from '@tuturuuu/utils/constants';
|
|
17
22
|
import { convertListItemToTask } from '@tuturuuu/utils/editor';
|
|
18
23
|
import {
|
|
@@ -40,8 +45,16 @@ import {
|
|
|
40
45
|
import { DescriptionOverflowWarningDialog } from './description-overflow-warning-dialog';
|
|
41
46
|
import { createInitialSuggestionState } from './mention-system/types';
|
|
42
47
|
import { SyncWarningDialog } from './sync-warning-dialog';
|
|
43
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
normalizeTaskDialogPresentation,
|
|
50
|
+
type TaskDialogPresentation,
|
|
51
|
+
} from './task-dialog-presentation';
|
|
52
|
+
import { CompactTaskDialogPanel } from './task-edit-dialog/components/compact-task-create-popover';
|
|
44
53
|
import { MobileFloatingSaveButton } from './task-edit-dialog/components/mobile-floating-save-button';
|
|
54
|
+
import {
|
|
55
|
+
SmartTaskSuggestionsButton,
|
|
56
|
+
SmartTaskSuggestionsPanel,
|
|
57
|
+
} from './task-edit-dialog/components/smart-task-suggestions-panel';
|
|
45
58
|
import { TaskDescriptionEditor } from './task-edit-dialog/components/task-description-editor';
|
|
46
59
|
import {
|
|
47
60
|
getTaskDialogHeaderInfo,
|
|
@@ -91,6 +104,7 @@ import {
|
|
|
91
104
|
import {
|
|
92
105
|
broadcastTaskDescriptionUpsert,
|
|
93
106
|
clearDraft,
|
|
107
|
+
getDescriptionContent,
|
|
94
108
|
getDraftStorageKey,
|
|
95
109
|
getTaskDescriptionPercentLeft,
|
|
96
110
|
getTaskDescriptionStorageLength,
|
|
@@ -129,6 +143,9 @@ export interface TaskEditDialogProps {
|
|
|
129
143
|
collaborationMode?: boolean;
|
|
130
144
|
/** Whether realtime features (Yjs sync, presence avatars) are enabled - true for all tiers */
|
|
131
145
|
realtimeEnabled?: boolean;
|
|
146
|
+
isHydratingTask?: boolean;
|
|
147
|
+
taskLoadError?: boolean;
|
|
148
|
+
taskHydrationVersion?: number;
|
|
132
149
|
isPersonalWorkspace?: boolean;
|
|
133
150
|
parentTaskId?: string;
|
|
134
151
|
parentTaskName?: string;
|
|
@@ -138,6 +155,8 @@ export interface TaskEditDialogProps {
|
|
|
138
155
|
sharedContext?: SharedTaskContext;
|
|
139
156
|
/** Whether draft mode is enabled from user settings */
|
|
140
157
|
draftModeEnabled?: boolean;
|
|
158
|
+
/** Preferred opening presentation for normal task dialogs */
|
|
159
|
+
defaultPresentation?: TaskDialogPresentation;
|
|
141
160
|
/** When editing an existing draft, this is the draft ID */
|
|
142
161
|
draftId?: string;
|
|
143
162
|
onClose: () => void;
|
|
@@ -148,6 +167,7 @@ export interface TaskEditDialogProps {
|
|
|
148
167
|
onAddBlockingTask?: () => void;
|
|
149
168
|
onAddBlockedByTask?: () => void;
|
|
150
169
|
onAddRelatedTask?: () => void;
|
|
170
|
+
onRetryTaskLoad?: () => void;
|
|
151
171
|
}
|
|
152
172
|
|
|
153
173
|
export function TaskEditDialog({
|
|
@@ -163,6 +183,9 @@ export function TaskEditDialog({
|
|
|
163
183
|
mode = 'edit',
|
|
164
184
|
collaborationMode = false,
|
|
165
185
|
realtimeEnabled = false,
|
|
186
|
+
isHydratingTask = false,
|
|
187
|
+
taskLoadError = false,
|
|
188
|
+
taskHydrationVersion = 0,
|
|
166
189
|
isPersonalWorkspace = false,
|
|
167
190
|
parentTaskId,
|
|
168
191
|
parentTaskName,
|
|
@@ -170,6 +193,7 @@ export function TaskEditDialog({
|
|
|
170
193
|
currentUser: propsCurrentUser,
|
|
171
194
|
sharedContext,
|
|
172
195
|
draftModeEnabled = false,
|
|
196
|
+
defaultPresentation = 'compact',
|
|
173
197
|
draftId,
|
|
174
198
|
onClose,
|
|
175
199
|
onUpdate,
|
|
@@ -179,6 +203,7 @@ export function TaskEditDialog({
|
|
|
179
203
|
onAddBlockingTask,
|
|
180
204
|
onAddBlockedByTask,
|
|
181
205
|
onAddRelatedTask,
|
|
206
|
+
onRetryTaskLoad,
|
|
182
207
|
}: TaskEditDialogProps) {
|
|
183
208
|
const isCreateMode = mode === 'create';
|
|
184
209
|
const effectiveTaskWsId = !isCreateMode ? (taskWsId ?? wsId) : wsId;
|
|
@@ -196,6 +221,12 @@ export function TaskEditDialog({
|
|
|
196
221
|
// Disable editing if we are viewing via a shared link
|
|
197
222
|
// User requested: always disable editing for shared tasks, regardless of permission
|
|
198
223
|
const disabled = !!shareCode;
|
|
224
|
+
const taskControlsDisabled = disabled || isHydratingTask || taskLoadError;
|
|
225
|
+
const taskTitleDisabled = disabled || taskLoadError;
|
|
226
|
+
const effectiveRealtimeEnabled =
|
|
227
|
+
realtimeEnabled && !isHydratingTask && !taskLoadError;
|
|
228
|
+
const effectiveCollaborationMode =
|
|
229
|
+
collaborationMode && !isHydratingTask && !taskLoadError;
|
|
199
230
|
|
|
200
231
|
// Keep this permission query disabled when `disabled` shared-link mode is active
|
|
201
232
|
// (`enabled` becomes false) so `canManageTaskMedia` stays undefined while
|
|
@@ -217,7 +248,7 @@ export function TaskEditDialog({
|
|
|
217
248
|
);
|
|
218
249
|
return result.hasPermission;
|
|
219
250
|
},
|
|
220
|
-
enabled: Boolean(effectiveTaskWsId) && !
|
|
251
|
+
enabled: Boolean(effectiveTaskWsId) && !taskControlsDisabled,
|
|
221
252
|
staleTime: 5 * 60 * 1000,
|
|
222
253
|
});
|
|
223
254
|
|
|
@@ -235,7 +266,7 @@ export function TaskEditDialog({
|
|
|
235
266
|
});
|
|
236
267
|
|
|
237
268
|
// Refs
|
|
238
|
-
const titleInputRef = useRef<HTMLInputElement>(null);
|
|
269
|
+
const titleInputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
|
|
239
270
|
const editorRef = useRef<HTMLDivElement>(null);
|
|
240
271
|
const richTextEditorRef = useRef<HTMLDivElement>(null);
|
|
241
272
|
const lastCursorPositionRef = useRef<number | null>(null);
|
|
@@ -245,6 +276,7 @@ export function TaskEditDialog({
|
|
|
245
276
|
);
|
|
246
277
|
const nameUpdateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
247
278
|
const pendingNameRef = useRef<string | null>(null);
|
|
279
|
+
const nameEditedDuringHydrationRef = useRef(false);
|
|
248
280
|
const quickDueRef = useRef<(days: number | null) => void>(() => {});
|
|
249
281
|
const updateEstimationRef = useRef<(points: number | null) => void>(() => {});
|
|
250
282
|
const handleConvertToTaskRef = useRef<(() => Promise<void>) | null>(null);
|
|
@@ -256,6 +288,17 @@ export function TaskEditDialog({
|
|
|
256
288
|
descriptionRef.current = formState.description;
|
|
257
289
|
}, [formState.description]);
|
|
258
290
|
|
|
291
|
+
const setTaskName = useCallback(
|
|
292
|
+
(value: string) => {
|
|
293
|
+
if (isHydratingTask) {
|
|
294
|
+
nameEditedDuringHydrationRef.current = true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
formState.setName(value);
|
|
298
|
+
},
|
|
299
|
+
[formState.setName, isHydratingTask]
|
|
300
|
+
);
|
|
301
|
+
|
|
259
302
|
// User state
|
|
260
303
|
const [user, setUser] = useState<TaskDialogUserIdentity | null>(
|
|
261
304
|
propsCurrentUser ? normalizeTaskDialogCurrentUser(propsCurrentUser) : null
|
|
@@ -386,7 +429,7 @@ export function TaskEditDialog({
|
|
|
386
429
|
columnName: 'description_yjs_state',
|
|
387
430
|
id: task?.id || '',
|
|
388
431
|
user: yjsUser,
|
|
389
|
-
enabled: isOpen && !isCreateMode &&
|
|
432
|
+
enabled: isOpen && !isCreateMode && effectiveRealtimeEnabled && !!task?.id,
|
|
390
433
|
broadcastDebounceMs: workspaceTier && workspaceTier !== 'FREE' ? 0 : 200,
|
|
391
434
|
saveDebounceMs: 5000,
|
|
392
435
|
loadDocumentState: loadTaskDescriptionState,
|
|
@@ -396,7 +439,7 @@ export function TaskEditDialog({
|
|
|
396
439
|
const [hasHydratedYjsState, setHasHydratedYjsState] = useState(false);
|
|
397
440
|
|
|
398
441
|
useEffect(() => {
|
|
399
|
-
if (!isOpen || isCreateMode || !
|
|
442
|
+
if (!isOpen || isCreateMode || !effectiveRealtimeEnabled || !task?.id) {
|
|
400
443
|
setHasHydratedYjsState(false);
|
|
401
444
|
return;
|
|
402
445
|
}
|
|
@@ -404,13 +447,13 @@ export function TaskEditDialog({
|
|
|
404
447
|
if (synced) {
|
|
405
448
|
setHasHydratedYjsState(true);
|
|
406
449
|
}
|
|
407
|
-
}, [isOpen, isCreateMode,
|
|
450
|
+
}, [isOpen, isCreateMode, effectiveRealtimeEnabled, task?.id, synced]);
|
|
408
451
|
|
|
409
452
|
const isYjsSyncing = useMemo(() => {
|
|
410
453
|
return (
|
|
411
454
|
isOpen &&
|
|
412
455
|
!isCreateMode &&
|
|
413
|
-
|
|
456
|
+
effectiveRealtimeEnabled &&
|
|
414
457
|
!!task?.id &&
|
|
415
458
|
!hasHydratedYjsState &&
|
|
416
459
|
!synced
|
|
@@ -419,7 +462,7 @@ export function TaskEditDialog({
|
|
|
419
462
|
hasHydratedYjsState,
|
|
420
463
|
isOpen,
|
|
421
464
|
isCreateMode,
|
|
422
|
-
|
|
465
|
+
effectiveRealtimeEnabled,
|
|
423
466
|
task?.id,
|
|
424
467
|
synced,
|
|
425
468
|
]);
|
|
@@ -509,7 +552,30 @@ export function TaskEditDialog({
|
|
|
509
552
|
useState(false);
|
|
510
553
|
const [showShareDialog, setShowShareDialog] = useState(false);
|
|
511
554
|
const [saveAsDraft, setSaveAsDraft] = useState(draftModeEnabled);
|
|
512
|
-
const
|
|
555
|
+
const normalizedDefaultPresentation = useMemo(
|
|
556
|
+
() => normalizeTaskDialogPresentation(defaultPresentation),
|
|
557
|
+
[defaultPresentation]
|
|
558
|
+
);
|
|
559
|
+
const [presentation, setPresentation] = useState<TaskDialogPresentation>(
|
|
560
|
+
normalizedDefaultPresentation
|
|
561
|
+
);
|
|
562
|
+
const [smartSuggestions, setSmartSuggestions] = useState<
|
|
563
|
+
WorkspaceTaskSuggestionTask[]
|
|
564
|
+
>([]);
|
|
565
|
+
const [selectedSmartSuggestionIds, setSelectedSmartSuggestionIds] = useState<
|
|
566
|
+
string[]
|
|
567
|
+
>([]);
|
|
568
|
+
const [smartSuggestionError, setSmartSuggestionError] = useState<
|
|
569
|
+
string | null
|
|
570
|
+
>(null);
|
|
571
|
+
const [smartCreateErrors, setSmartCreateErrors] = useState<
|
|
572
|
+
Record<string, string>
|
|
573
|
+
>({});
|
|
574
|
+
const [creatingSmartSuggestionIds, setCreatingSmartSuggestionIds] = useState<
|
|
575
|
+
string[]
|
|
576
|
+
>([]);
|
|
577
|
+
const [isCreatingSmartSuggestions, setIsCreatingSmartSuggestions] =
|
|
578
|
+
useState(false);
|
|
513
579
|
const previousOpenRef = useRef(false);
|
|
514
580
|
const [isTitleVisible, setIsTitleVisible] = useState(true);
|
|
515
581
|
const [scrollContainer, setScrollContainer] = useState<HTMLDivElement | null>(
|
|
@@ -709,8 +775,8 @@ export function TaskEditDialog({
|
|
|
709
775
|
selectedLabels: formState.selectedLabels,
|
|
710
776
|
selectedAssignees: formState.selectedAssignees,
|
|
711
777
|
isCreateMode,
|
|
712
|
-
isLoading,
|
|
713
|
-
collaborationMode,
|
|
778
|
+
isLoading: isLoading || taskControlsDisabled,
|
|
779
|
+
collaborationMode: effectiveCollaborationMode,
|
|
714
780
|
draftId,
|
|
715
781
|
});
|
|
716
782
|
|
|
@@ -733,7 +799,7 @@ export function TaskEditDialog({
|
|
|
733
799
|
boardId,
|
|
734
800
|
isCreateMode,
|
|
735
801
|
isOpen,
|
|
736
|
-
realtimeEnabled,
|
|
802
|
+
realtimeEnabled: effectiveRealtimeEnabled,
|
|
737
803
|
isPersonalWorkspace,
|
|
738
804
|
name: formState.name,
|
|
739
805
|
priority: formState.priority,
|
|
@@ -783,6 +849,41 @@ export function TaskEditDialog({
|
|
|
783
849
|
onUpdate,
|
|
784
850
|
});
|
|
785
851
|
|
|
852
|
+
const currentList = availableLists?.find(
|
|
853
|
+
(list) => list.id === formState.selectedListId
|
|
854
|
+
);
|
|
855
|
+
const doneList = availableLists?.find(
|
|
856
|
+
(list) => list.status === 'done' && !list.deleted
|
|
857
|
+
);
|
|
858
|
+
const closedList = availableLists?.find(
|
|
859
|
+
(list) => list.status === 'closed' && !list.deleted
|
|
860
|
+
);
|
|
861
|
+
const isDeletedTask = Boolean(
|
|
862
|
+
task?.deleted_at ||
|
|
863
|
+
(task as (Task & { deleted?: boolean }) | undefined)?.deleted
|
|
864
|
+
);
|
|
865
|
+
const canShowCompactEditActions =
|
|
866
|
+
!isCreateMode &&
|
|
867
|
+
!!task?.id &&
|
|
868
|
+
task.id !== 'new' &&
|
|
869
|
+
!isDeletedTask &&
|
|
870
|
+
!disabled &&
|
|
871
|
+
!taskLoadError;
|
|
872
|
+
const compactEditActionsDisabled = isLoading || isHydratingTask;
|
|
873
|
+
const canShowCompactStatusActions =
|
|
874
|
+
canShowCompactEditActions && currentList?.status !== 'documents';
|
|
875
|
+
const showCompactDoneAction =
|
|
876
|
+
canShowCompactStatusActions &&
|
|
877
|
+
!!doneList &&
|
|
878
|
+
doneList.id !== formState.selectedListId &&
|
|
879
|
+
currentList?.status !== 'done';
|
|
880
|
+
const showCompactClosedAction =
|
|
881
|
+
canShowCompactStatusActions &&
|
|
882
|
+
!!closedList &&
|
|
883
|
+
closedList.id !== formState.selectedListId &&
|
|
884
|
+
closedList.id !== doneList?.id &&
|
|
885
|
+
currentList?.status !== 'closed';
|
|
886
|
+
|
|
786
887
|
// Task relationships
|
|
787
888
|
const {
|
|
788
889
|
toggleLabel,
|
|
@@ -861,6 +962,8 @@ export function TaskEditDialog({
|
|
|
861
962
|
isCreateMode,
|
|
862
963
|
task,
|
|
863
964
|
filters,
|
|
965
|
+
taskHydrationVersion,
|
|
966
|
+
preserveNameOnHydration: nameEditedDuringHydrationRef.current,
|
|
864
967
|
setName: formState.setName,
|
|
865
968
|
setDescription: formState.setDescription,
|
|
866
969
|
setPriority: formState.setPriority,
|
|
@@ -873,6 +976,12 @@ export function TaskEditDialog({
|
|
|
873
976
|
setSelectedProjects: formState.setSelectedProjects,
|
|
874
977
|
});
|
|
875
978
|
|
|
979
|
+
useEffect(() => {
|
|
980
|
+
if (!isHydratingTask) {
|
|
981
|
+
nameEditedDuringHydrationRef.current = false;
|
|
982
|
+
}
|
|
983
|
+
}, [isHydratingTask]);
|
|
984
|
+
|
|
876
985
|
// Yjs sync
|
|
877
986
|
useTaskYjsSync({
|
|
878
987
|
taskId: task?.id,
|
|
@@ -880,7 +989,7 @@ export function TaskEditDialog({
|
|
|
880
989
|
boardId,
|
|
881
990
|
isOpen,
|
|
882
991
|
isCreateMode,
|
|
883
|
-
realtimeEnabled,
|
|
992
|
+
realtimeEnabled: effectiveRealtimeEnabled,
|
|
884
993
|
editorInstance,
|
|
885
994
|
doc,
|
|
886
995
|
yjsProvider: provider,
|
|
@@ -1203,7 +1312,7 @@ export function TaskEditDialog({
|
|
|
1203
1312
|
saveAsDraft: isCreateMode && (!!draftId || saveAsDraft),
|
|
1204
1313
|
draftId,
|
|
1205
1314
|
isCreateMode,
|
|
1206
|
-
collaborationMode,
|
|
1315
|
+
collaborationMode: effectiveCollaborationMode,
|
|
1207
1316
|
isPersonalWorkspace,
|
|
1208
1317
|
shareCode,
|
|
1209
1318
|
sharedPermission,
|
|
@@ -1252,6 +1361,309 @@ export function TaskEditDialog({
|
|
|
1252
1361
|
setSelectedProjects: formState.setSelectedProjects,
|
|
1253
1362
|
});
|
|
1254
1363
|
|
|
1364
|
+
const smartSuggestionsMutation = useMutation({
|
|
1365
|
+
mutationFn: async () => {
|
|
1366
|
+
const prompt = formState.name.trim();
|
|
1367
|
+
if (!prompt) {
|
|
1368
|
+
throw new Error(dialogT('smart_prompt_required'));
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const currentDescription =
|
|
1372
|
+
flushEditorPendingRef.current?.() ?? formState.description;
|
|
1373
|
+
|
|
1374
|
+
return createWorkspaceTaskSuggestions(effectiveTaskWsId, {
|
|
1375
|
+
boardId,
|
|
1376
|
+
prompt,
|
|
1377
|
+
description: serializeTaskDescriptionContent(currentDescription),
|
|
1378
|
+
currentListId: formState.selectedListId || undefined,
|
|
1379
|
+
clientTimezone:
|
|
1380
|
+
Intl.DateTimeFormat().resolvedOptions().timeZone || undefined,
|
|
1381
|
+
clientTimestamp: new Date().toISOString(),
|
|
1382
|
+
});
|
|
1383
|
+
},
|
|
1384
|
+
onMutate: () => {
|
|
1385
|
+
setSmartSuggestionError(null);
|
|
1386
|
+
setSmartCreateErrors({});
|
|
1387
|
+
},
|
|
1388
|
+
onSuccess: (response) => {
|
|
1389
|
+
const suggestions = response.tasks ?? [];
|
|
1390
|
+
setSmartSuggestions(suggestions);
|
|
1391
|
+
setSelectedSmartSuggestionIds(
|
|
1392
|
+
suggestions.map((suggestion) => suggestion.id)
|
|
1393
|
+
);
|
|
1394
|
+
if (!suggestions.length) {
|
|
1395
|
+
setSmartSuggestionError(dialogT('smart_no_suggestions'));
|
|
1396
|
+
}
|
|
1397
|
+
},
|
|
1398
|
+
onError: (error) => {
|
|
1399
|
+
setSmartSuggestionError(
|
|
1400
|
+
error instanceof Error
|
|
1401
|
+
? error.message
|
|
1402
|
+
: dialogT('smart_suggestions_failed_description')
|
|
1403
|
+
);
|
|
1404
|
+
},
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
const canUseSmartSuggestions =
|
|
1408
|
+
isCreateMode && !draftId && !disabled && Boolean(boardId);
|
|
1409
|
+
|
|
1410
|
+
const handleGenerateSmartSuggestions = useCallback(() => {
|
|
1411
|
+
if (!canUseSmartSuggestions) return;
|
|
1412
|
+
|
|
1413
|
+
if (!formState.name.trim()) {
|
|
1414
|
+
toast.error(dialogT('smart_prompt_required'));
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
smartSuggestionsMutation.mutate();
|
|
1419
|
+
}, [
|
|
1420
|
+
canUseSmartSuggestions,
|
|
1421
|
+
dialogT,
|
|
1422
|
+
formState.name,
|
|
1423
|
+
smartSuggestionsMutation,
|
|
1424
|
+
]);
|
|
1425
|
+
|
|
1426
|
+
const applySmartSuggestion = useCallback(
|
|
1427
|
+
(suggestion: WorkspaceTaskSuggestionTask) => {
|
|
1428
|
+
formState.setName(suggestion.title);
|
|
1429
|
+
formState.setDescription(getDescriptionContent(suggestion.description));
|
|
1430
|
+
formState.setPriority(suggestion.priority);
|
|
1431
|
+
formState.setEndDate(
|
|
1432
|
+
suggestion.endDate ? new Date(suggestion.endDate) : undefined
|
|
1433
|
+
);
|
|
1434
|
+
formState.setSelectedListId(suggestion.listId);
|
|
1435
|
+
formState.setEstimationPoints(suggestion.estimationPoints);
|
|
1436
|
+
formState.setSelectedLabels(
|
|
1437
|
+
suggestion.labels.map((label) => ({
|
|
1438
|
+
id: label.id,
|
|
1439
|
+
name: label.name,
|
|
1440
|
+
color: label.color ?? '',
|
|
1441
|
+
created_at: label.created_at ?? '',
|
|
1442
|
+
}))
|
|
1443
|
+
);
|
|
1444
|
+
formState.setSelectedProjects(
|
|
1445
|
+
suggestion.projects.map((project) => ({
|
|
1446
|
+
id: project.id,
|
|
1447
|
+
name: project.name,
|
|
1448
|
+
status: project.status ?? 'active',
|
|
1449
|
+
}))
|
|
1450
|
+
);
|
|
1451
|
+
formState.setTotalDuration(
|
|
1452
|
+
suggestion.durationMinutes ? suggestion.durationMinutes / 60 : null
|
|
1453
|
+
);
|
|
1454
|
+
formState.setIsSplittable(suggestion.isSplittable);
|
|
1455
|
+
formState.setMinSplitDurationMinutes(suggestion.minSplitDurationMinutes);
|
|
1456
|
+
formState.setMaxSplitDurationMinutes(suggestion.maxSplitDurationMinutes);
|
|
1457
|
+
formState.setCalendarHours(suggestion.calendarHours ?? null);
|
|
1458
|
+
formState.setAutoSchedule(suggestion.autoSchedule);
|
|
1459
|
+
toast.success(dialogT('smart_suggestion_applied'));
|
|
1460
|
+
},
|
|
1461
|
+
[
|
|
1462
|
+
dialogT,
|
|
1463
|
+
formState.setAutoSchedule,
|
|
1464
|
+
formState.setCalendarHours,
|
|
1465
|
+
formState.setDescription,
|
|
1466
|
+
formState.setEndDate,
|
|
1467
|
+
formState.setEstimationPoints,
|
|
1468
|
+
formState.setIsSplittable,
|
|
1469
|
+
formState.setMaxSplitDurationMinutes,
|
|
1470
|
+
formState.setMinSplitDurationMinutes,
|
|
1471
|
+
formState.setName,
|
|
1472
|
+
formState.setPriority,
|
|
1473
|
+
formState.setSelectedLabels,
|
|
1474
|
+
formState.setSelectedListId,
|
|
1475
|
+
formState.setSelectedProjects,
|
|
1476
|
+
formState.setTotalDuration,
|
|
1477
|
+
]
|
|
1478
|
+
);
|
|
1479
|
+
|
|
1480
|
+
const handleToggleSmartSuggestion = useCallback((suggestionId: string) => {
|
|
1481
|
+
setSelectedSmartSuggestionIds((current) =>
|
|
1482
|
+
current.includes(suggestionId)
|
|
1483
|
+
? current.filter((id) => id !== suggestionId)
|
|
1484
|
+
: [...current, suggestionId]
|
|
1485
|
+
);
|
|
1486
|
+
}, []);
|
|
1487
|
+
|
|
1488
|
+
const handleCreateSelectedSmartSuggestions = useCallback(async () => {
|
|
1489
|
+
if (!selectedSmartSuggestionIds.length) return;
|
|
1490
|
+
|
|
1491
|
+
const selectedSuggestions = smartSuggestions.filter((suggestion) =>
|
|
1492
|
+
selectedSmartSuggestionIds.includes(suggestion.id)
|
|
1493
|
+
);
|
|
1494
|
+
if (!selectedSuggestions.length) return;
|
|
1495
|
+
|
|
1496
|
+
setIsCreatingSmartSuggestions(true);
|
|
1497
|
+
setSmartCreateErrors({});
|
|
1498
|
+
setCreatingSmartSuggestionIds(selectedSuggestions.map((s) => s.id));
|
|
1499
|
+
|
|
1500
|
+
let resolvedUserId = user?.id;
|
|
1501
|
+
if (!resolvedUserId && isPersonalWorkspace) {
|
|
1502
|
+
resolvedUserId = userForSave?.id;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const currentAssigneeIds = formState.selectedAssignees
|
|
1506
|
+
.map((assignee) => assignee.user_id || assignee.id)
|
|
1507
|
+
.filter((assigneeId): assigneeId is string => !!assigneeId);
|
|
1508
|
+
const desiredAssigneeIds =
|
|
1509
|
+
currentAssigneeIds.length > 0
|
|
1510
|
+
? currentAssigneeIds
|
|
1511
|
+
: userTaskSettings?.task_auto_assign_to_self &&
|
|
1512
|
+
resolvedUserId &&
|
|
1513
|
+
!isPersonalWorkspace
|
|
1514
|
+
? [resolvedUserId]
|
|
1515
|
+
: [];
|
|
1516
|
+
const nextErrors: Record<string, string> = {};
|
|
1517
|
+
const broadcast = getActiveBroadcast() ?? taskRealtimeBroadcastRef.current;
|
|
1518
|
+
|
|
1519
|
+
for (const suggestion of selectedSuggestions) {
|
|
1520
|
+
setCreatingSmartSuggestionIds((current) =>
|
|
1521
|
+
current.includes(suggestion.id) ? current : [...current, suggestion.id]
|
|
1522
|
+
);
|
|
1523
|
+
|
|
1524
|
+
try {
|
|
1525
|
+
const response = await createWorkspaceTask(effectiveTaskWsId, {
|
|
1526
|
+
name: suggestion.title,
|
|
1527
|
+
description: suggestion.description,
|
|
1528
|
+
listId: suggestion.listId,
|
|
1529
|
+
priority: suggestion.priority,
|
|
1530
|
+
end_date: suggestion.endDate,
|
|
1531
|
+
estimation_points: suggestion.estimationPoints,
|
|
1532
|
+
label_ids: suggestion.labelIds,
|
|
1533
|
+
project_ids: suggestion.projectIds,
|
|
1534
|
+
assignee_ids: desiredAssigneeIds,
|
|
1535
|
+
total_duration: suggestion.durationMinutes
|
|
1536
|
+
? suggestion.durationMinutes / 60
|
|
1537
|
+
: null,
|
|
1538
|
+
is_splittable: suggestion.isSplittable,
|
|
1539
|
+
min_split_duration_minutes: suggestion.minSplitDurationMinutes,
|
|
1540
|
+
max_split_duration_minutes: suggestion.maxSplitDurationMinutes,
|
|
1541
|
+
calendar_hours: suggestion.calendarHours,
|
|
1542
|
+
auto_schedule: suggestion.autoSchedule,
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1545
|
+
const createdTask: Task = {
|
|
1546
|
+
...(response.task as Task),
|
|
1547
|
+
labels: suggestion.labels.map((label) => ({
|
|
1548
|
+
id: label.id,
|
|
1549
|
+
name: label.name,
|
|
1550
|
+
color: label.color ?? '',
|
|
1551
|
+
created_at: label.created_at ?? '',
|
|
1552
|
+
})),
|
|
1553
|
+
assignees: desiredAssigneeIds.map((assigneeId) => ({
|
|
1554
|
+
id: assigneeId,
|
|
1555
|
+
})),
|
|
1556
|
+
projects: suggestion.projects.map((project) => ({
|
|
1557
|
+
id: project.id,
|
|
1558
|
+
name: project.name,
|
|
1559
|
+
status: project.status ?? 'active',
|
|
1560
|
+
})),
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
queryClient.setQueryData(
|
|
1564
|
+
['tasks', boardId],
|
|
1565
|
+
(old: Task[] | undefined) => {
|
|
1566
|
+
if (!old) return [createdTask];
|
|
1567
|
+
if (old.some((task) => task.id === createdTask.id)) return old;
|
|
1568
|
+
return [...old, createdTask];
|
|
1569
|
+
}
|
|
1570
|
+
);
|
|
1571
|
+
broadcast?.('task:upsert', { task: createdTask });
|
|
1572
|
+
if (
|
|
1573
|
+
suggestion.labelIds.length ||
|
|
1574
|
+
suggestion.projectIds.length ||
|
|
1575
|
+
desiredAssigneeIds.length
|
|
1576
|
+
) {
|
|
1577
|
+
broadcast?.('task:relations-changed', { taskId: createdTask.id });
|
|
1578
|
+
}
|
|
1579
|
+
} catch (error) {
|
|
1580
|
+
nextErrors[suggestion.id] =
|
|
1581
|
+
error instanceof Error
|
|
1582
|
+
? error.message
|
|
1583
|
+
: dialogT('smart_create_failed');
|
|
1584
|
+
} finally {
|
|
1585
|
+
setCreatingSmartSuggestionIds((current) =>
|
|
1586
|
+
current.filter((id) => id !== suggestion.id)
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
await invalidateTaskCaches(queryClient, boardId);
|
|
1592
|
+
onUpdate();
|
|
1593
|
+
|
|
1594
|
+
setSmartCreateErrors(nextErrors);
|
|
1595
|
+
const failedIds = Object.keys(nextErrors);
|
|
1596
|
+
setSelectedSmartSuggestionIds(failedIds);
|
|
1597
|
+
|
|
1598
|
+
if (failedIds.length) {
|
|
1599
|
+
toast.error(dialogT('smart_create_partial_failed'));
|
|
1600
|
+
} else {
|
|
1601
|
+
toast.success(
|
|
1602
|
+
dialogT('smart_create_selected_success', {
|
|
1603
|
+
count: selectedSuggestions.length,
|
|
1604
|
+
})
|
|
1605
|
+
);
|
|
1606
|
+
setSmartSuggestions([]);
|
|
1607
|
+
setSelectedSmartSuggestionIds([]);
|
|
1608
|
+
onClose();
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
setIsCreatingSmartSuggestions(false);
|
|
1612
|
+
setCreatingSmartSuggestionIds([]);
|
|
1613
|
+
}, [
|
|
1614
|
+
boardId,
|
|
1615
|
+
dialogT,
|
|
1616
|
+
effectiveTaskWsId,
|
|
1617
|
+
formState.selectedAssignees,
|
|
1618
|
+
isPersonalWorkspace,
|
|
1619
|
+
onClose,
|
|
1620
|
+
onUpdate,
|
|
1621
|
+
queryClient,
|
|
1622
|
+
selectedSmartSuggestionIds,
|
|
1623
|
+
smartSuggestions,
|
|
1624
|
+
user?.id,
|
|
1625
|
+
userForSave?.id,
|
|
1626
|
+
userTaskSettings?.task_auto_assign_to_self,
|
|
1627
|
+
]);
|
|
1628
|
+
|
|
1629
|
+
const smartSuggestionsPanel =
|
|
1630
|
+
canUseSmartSuggestions &&
|
|
1631
|
+
(smartSuggestionsMutation.isPending ||
|
|
1632
|
+
smartSuggestionError ||
|
|
1633
|
+
smartSuggestions.length > 0) ? (
|
|
1634
|
+
<SmartTaskSuggestionsPanel
|
|
1635
|
+
suggestions={smartSuggestions}
|
|
1636
|
+
selectedSuggestionIds={selectedSmartSuggestionIds}
|
|
1637
|
+
createErrors={smartCreateErrors}
|
|
1638
|
+
creatingSuggestionIds={creatingSmartSuggestionIds}
|
|
1639
|
+
errorMessage={smartSuggestionError}
|
|
1640
|
+
isCreatingSelected={isCreatingSmartSuggestions}
|
|
1641
|
+
isLoading={smartSuggestionsMutation.isPending}
|
|
1642
|
+
onApplyFirst={() => {
|
|
1643
|
+
const firstSuggestion = smartSuggestions[0];
|
|
1644
|
+
if (firstSuggestion) applySmartSuggestion(firstSuggestion);
|
|
1645
|
+
}}
|
|
1646
|
+
onApplySuggestion={applySmartSuggestion}
|
|
1647
|
+
onClose={() => {
|
|
1648
|
+
setSmartSuggestions([]);
|
|
1649
|
+
setSelectedSmartSuggestionIds([]);
|
|
1650
|
+
setSmartSuggestionError(null);
|
|
1651
|
+
setSmartCreateErrors({});
|
|
1652
|
+
}}
|
|
1653
|
+
onCreateSelected={handleCreateSelectedSmartSuggestions}
|
|
1654
|
+
onRetry={handleGenerateSmartSuggestions}
|
|
1655
|
+
onToggleSuggestion={handleToggleSmartSuggestion}
|
|
1656
|
+
/>
|
|
1657
|
+
) : null;
|
|
1658
|
+
|
|
1659
|
+
const smartSuggestionsButton = canUseSmartSuggestions ? (
|
|
1660
|
+
<SmartTaskSuggestionsButton
|
|
1661
|
+
disabled={smartSuggestionsMutation.isPending || isLoading}
|
|
1662
|
+
isLoading={smartSuggestionsMutation.isPending}
|
|
1663
|
+
onClick={handleGenerateSmartSuggestions}
|
|
1664
|
+
/>
|
|
1665
|
+
) : null;
|
|
1666
|
+
|
|
1255
1667
|
const persistTaskDescriptionOnClose = useCallback(async () => {
|
|
1256
1668
|
if (isCreateMode || !task?.id || !flushEditorPendingRef.current) {
|
|
1257
1669
|
return true;
|
|
@@ -1335,7 +1747,7 @@ export function TaskEditDialog({
|
|
|
1335
1747
|
useTaskDialogClose({
|
|
1336
1748
|
taskId: task?.id,
|
|
1337
1749
|
isCreateMode,
|
|
1338
|
-
collaborationMode,
|
|
1750
|
+
collaborationMode: effectiveCollaborationMode,
|
|
1339
1751
|
synced,
|
|
1340
1752
|
connected,
|
|
1341
1753
|
draftStorageKey,
|
|
@@ -1483,8 +1895,8 @@ export function TaskEditDialog({
|
|
|
1483
1895
|
isOpen,
|
|
1484
1896
|
canSave,
|
|
1485
1897
|
isCreateMode,
|
|
1486
|
-
collaborationMode,
|
|
1487
|
-
disabled,
|
|
1898
|
+
collaborationMode: effectiveCollaborationMode,
|
|
1899
|
+
disabled: taskControlsDisabled,
|
|
1488
1900
|
editorInstance,
|
|
1489
1901
|
boardConfig,
|
|
1490
1902
|
slashState: suggestionMenus.slashState,
|
|
@@ -1534,14 +1946,20 @@ export function TaskEditDialog({
|
|
|
1534
1946
|
previousOpenRef.current = isOpen;
|
|
1535
1947
|
|
|
1536
1948
|
if (!isOpen) {
|
|
1537
|
-
|
|
1949
|
+
setPresentation(normalizedDefaultPresentation);
|
|
1950
|
+
setSmartSuggestions([]);
|
|
1951
|
+
setSelectedSmartSuggestionIds([]);
|
|
1952
|
+
setSmartSuggestionError(null);
|
|
1953
|
+
setSmartCreateErrors({});
|
|
1954
|
+
setCreatingSmartSuggestionIds([]);
|
|
1955
|
+
setIsCreatingSmartSuggestions(false);
|
|
1538
1956
|
return;
|
|
1539
1957
|
}
|
|
1540
1958
|
|
|
1541
|
-
if (justOpened
|
|
1542
|
-
|
|
1959
|
+
if (justOpened) {
|
|
1960
|
+
setPresentation(draftId ? 'fullscreen' : normalizedDefaultPresentation);
|
|
1543
1961
|
}
|
|
1544
|
-
}, [isOpen,
|
|
1962
|
+
}, [isOpen, draftId, normalizedDefaultPresentation]);
|
|
1545
1963
|
|
|
1546
1964
|
// Track whether the title input is scrolled out of view
|
|
1547
1965
|
useEffect(() => {
|
|
@@ -1604,8 +2022,25 @@ export function TaskEditDialog({
|
|
|
1604
2022
|
taskSearchQuery,
|
|
1605
2023
|
]);
|
|
1606
2024
|
|
|
1607
|
-
const
|
|
1608
|
-
|
|
2025
|
+
const showCompactDialog = presentation === 'compact' && !draftId;
|
|
2026
|
+
const taskHydrationNotice = taskLoadError ? (
|
|
2027
|
+
<div
|
|
2028
|
+
className="mx-4 mb-2 flex items-center justify-between gap-3 rounded-md border border-dynamic-red/30 bg-dynamic-red/10 px-3 py-2 text-dynamic-red text-sm md:mx-8"
|
|
2029
|
+
role="alert"
|
|
2030
|
+
>
|
|
2031
|
+
<span>{t('please_try_again_later')}</span>
|
|
2032
|
+
{onRetryTaskLoad && (
|
|
2033
|
+
<Button
|
|
2034
|
+
type="button"
|
|
2035
|
+
variant="secondary"
|
|
2036
|
+
size="xs"
|
|
2037
|
+
onClick={onRetryTaskLoad}
|
|
2038
|
+
>
|
|
2039
|
+
{t('retry')}
|
|
2040
|
+
</Button>
|
|
2041
|
+
)}
|
|
2042
|
+
</div>
|
|
2043
|
+
) : null;
|
|
1609
2044
|
const compactHeaderInfo = useMemo(
|
|
1610
2045
|
() =>
|
|
1611
2046
|
getTaskDialogHeaderInfo(
|
|
@@ -1627,6 +2062,62 @@ export function TaskEditDialog({
|
|
|
1627
2062
|
rootT,
|
|
1628
2063
|
]
|
|
1629
2064
|
);
|
|
2065
|
+
const compactEditActions = canShowCompactEditActions ? (
|
|
2066
|
+
<>
|
|
2067
|
+
{showCompactDoneAction && doneList && (
|
|
2068
|
+
<Tooltip>
|
|
2069
|
+
<TooltipTrigger asChild>
|
|
2070
|
+
<Button
|
|
2071
|
+
type="button"
|
|
2072
|
+
variant="ghost"
|
|
2073
|
+
size="icon"
|
|
2074
|
+
aria-label={t('mark_as_done')}
|
|
2075
|
+
disabled={compactEditActionsDisabled}
|
|
2076
|
+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
2077
|
+
onClick={() => void updateList(doneList.id)}
|
|
2078
|
+
>
|
|
2079
|
+
<CheckCircle2 className="h-4 w-4" />
|
|
2080
|
+
</Button>
|
|
2081
|
+
</TooltipTrigger>
|
|
2082
|
+
<TooltipContent side="top">{t('mark_as_done')}</TooltipContent>
|
|
2083
|
+
</Tooltip>
|
|
2084
|
+
)}
|
|
2085
|
+
{showCompactClosedAction && closedList && (
|
|
2086
|
+
<Tooltip>
|
|
2087
|
+
<TooltipTrigger asChild>
|
|
2088
|
+
<Button
|
|
2089
|
+
type="button"
|
|
2090
|
+
variant="ghost"
|
|
2091
|
+
size="icon"
|
|
2092
|
+
aria-label={t('archive')}
|
|
2093
|
+
disabled={compactEditActionsDisabled}
|
|
2094
|
+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
2095
|
+
onClick={() => void updateList(closedList.id)}
|
|
2096
|
+
>
|
|
2097
|
+
<Archive className="h-4 w-4" />
|
|
2098
|
+
</Button>
|
|
2099
|
+
</TooltipTrigger>
|
|
2100
|
+
<TooltipContent side="top">{t('archive')}</TooltipContent>
|
|
2101
|
+
</Tooltip>
|
|
2102
|
+
)}
|
|
2103
|
+
<Tooltip>
|
|
2104
|
+
<TooltipTrigger asChild>
|
|
2105
|
+
<Button
|
|
2106
|
+
type="button"
|
|
2107
|
+
variant="ghost"
|
|
2108
|
+
size="icon"
|
|
2109
|
+
aria-label={t('delete_task')}
|
|
2110
|
+
disabled={compactEditActionsDisabled}
|
|
2111
|
+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
2112
|
+
onClick={() => setShowDeleteConfirm(true)}
|
|
2113
|
+
>
|
|
2114
|
+
<Trash2 className="h-4 w-4" />
|
|
2115
|
+
</Button>
|
|
2116
|
+
</TooltipTrigger>
|
|
2117
|
+
<TooltipContent side="top">{t('delete_task')}</TooltipContent>
|
|
2118
|
+
</Tooltip>
|
|
2119
|
+
</>
|
|
2120
|
+
) : undefined;
|
|
1630
2121
|
|
|
1631
2122
|
// Update refs
|
|
1632
2123
|
quickDueRef.current = handleQuickDueDate;
|
|
@@ -1702,7 +2193,7 @@ export function TaskEditDialog({
|
|
|
1702
2193
|
onSaveSchedulingSettings={saveSchedulingSettings}
|
|
1703
2194
|
schedulingSaving={schedulingSaving}
|
|
1704
2195
|
scheduledEvents={localCalendarEvents}
|
|
1705
|
-
disabled={
|
|
2196
|
+
disabled={taskControlsDisabled}
|
|
1706
2197
|
isDraftMode={!!draftId || (isCreateMode && saveAsDraft)}
|
|
1707
2198
|
variant={variant}
|
|
1708
2199
|
/>
|
|
@@ -1743,7 +2234,7 @@ export function TaskEditDialog({
|
|
|
1743
2234
|
<DialogContent
|
|
1744
2235
|
showCloseButton={false}
|
|
1745
2236
|
className={
|
|
1746
|
-
|
|
2237
|
+
showCompactDialog
|
|
1747
2238
|
? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-hidden rounded-lg border p-0 shadow-xl'
|
|
1748
2239
|
: 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-bottom-2 data-[state=open]:slide-in-from-bottom-2 inset-0! top-0! left-0! flex h-screen max-h-screen w-screen max-w-none! translate-x-0! translate-y-0! gap-0 rounded-none! border-0 p-0'
|
|
1749
2240
|
}
|
|
@@ -1772,13 +2263,14 @@ export function TaskEditDialog({
|
|
|
1772
2263
|
e.preventDefault();
|
|
1773
2264
|
}}
|
|
1774
2265
|
>
|
|
1775
|
-
{
|
|
1776
|
-
<
|
|
2266
|
+
{showCompactDialog ? (
|
|
2267
|
+
<CompactTaskDialogPanel
|
|
1777
2268
|
title={compactHeaderInfo.title}
|
|
1778
2269
|
description={compactHeaderInfo.description}
|
|
1779
2270
|
icon={compactHeaderInfo.icon}
|
|
1780
2271
|
iconBgClass={compactHeaderInfo.iconBgClass}
|
|
1781
2272
|
iconRingClass={compactHeaderInfo.iconRingClass}
|
|
2273
|
+
showHeaderTitle={isCreateMode}
|
|
1782
2274
|
titleInput={
|
|
1783
2275
|
<TaskNameInput
|
|
1784
2276
|
name={formState.name}
|
|
@@ -1787,25 +2279,41 @@ export function TaskEditDialog({
|
|
|
1787
2279
|
editorRef={editorRef}
|
|
1788
2280
|
lastCursorPositionRef={lastCursorPositionRef}
|
|
1789
2281
|
targetEditorCursorRef={targetEditorCursorRef}
|
|
1790
|
-
setName={
|
|
2282
|
+
setName={setTaskName}
|
|
1791
2283
|
updateName={updateName}
|
|
1792
2284
|
flushNameUpdate={flushNameUpdate}
|
|
1793
|
-
disabled={
|
|
2285
|
+
disabled={taskTitleDisabled}
|
|
1794
2286
|
variant="compact"
|
|
1795
|
-
onSubmit={
|
|
2287
|
+
onSubmit={
|
|
2288
|
+
isCreateMode && !taskControlsDisabled
|
|
2289
|
+
? handleSave
|
|
2290
|
+
: undefined
|
|
2291
|
+
}
|
|
1796
2292
|
/>
|
|
1797
2293
|
}
|
|
1798
2294
|
propertyControls={renderTaskPropertiesSection('compact')}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
2295
|
+
taskStatus={taskHydrationNotice}
|
|
2296
|
+
smartAction={smartSuggestionsButton}
|
|
2297
|
+
smartPanel={smartSuggestionsPanel}
|
|
2298
|
+
saveAsDraft={isCreateMode ? saveAsDraft : undefined}
|
|
2299
|
+
createMultiple={isCreateMode ? createMultiple : undefined}
|
|
2300
|
+
canSave={
|
|
2301
|
+
isCreateMode
|
|
2302
|
+
? canSave && !isDescriptionOverLimit && !taskControlsDisabled
|
|
2303
|
+
: undefined
|
|
2304
|
+
}
|
|
1802
2305
|
isLoading={isLoading}
|
|
1803
2306
|
isPersonalWorkspace={isPersonalWorkspace}
|
|
1804
|
-
|
|
1805
|
-
|
|
2307
|
+
editActions={compactEditActions}
|
|
2308
|
+
onSaveAsDraftChange={isCreateMode ? setSaveAsDraft : undefined}
|
|
2309
|
+
onCreateMultipleChange={
|
|
2310
|
+
isCreateMode ? setCreateMultiple : undefined
|
|
2311
|
+
}
|
|
1806
2312
|
onClose={handleAttemptClose}
|
|
1807
|
-
onFullscreen={() =>
|
|
1808
|
-
onSave={
|
|
2313
|
+
onFullscreen={() => setPresentation('fullscreen')}
|
|
2314
|
+
onSave={
|
|
2315
|
+
isCreateMode && !taskControlsDisabled ? handleSave : undefined
|
|
2316
|
+
}
|
|
1809
2317
|
/>
|
|
1810
2318
|
) : (
|
|
1811
2319
|
<>
|
|
@@ -1816,8 +2324,8 @@ export function TaskEditDialog({
|
|
|
1816
2324
|
{!disabled && (
|
|
1817
2325
|
<TaskDialogHeader
|
|
1818
2326
|
isCreateMode={isCreateMode}
|
|
1819
|
-
collaborationMode={
|
|
1820
|
-
realtimeEnabled={
|
|
2327
|
+
collaborationMode={effectiveCollaborationMode}
|
|
2328
|
+
realtimeEnabled={effectiveRealtimeEnabled}
|
|
1821
2329
|
isOpen={isOpen}
|
|
1822
2330
|
synced={synced}
|
|
1823
2331
|
connected={connected}
|
|
@@ -1847,7 +2355,11 @@ export function TaskEditDialog({
|
|
|
1847
2355
|
wsId={effectiveTaskWsId}
|
|
1848
2356
|
boardId={boardId}
|
|
1849
2357
|
pathname={pathname}
|
|
1850
|
-
canSave={
|
|
2358
|
+
canSave={
|
|
2359
|
+
canSave &&
|
|
2360
|
+
!isDescriptionOverLimit &&
|
|
2361
|
+
!taskControlsDisabled
|
|
2362
|
+
}
|
|
1851
2363
|
isLoading={isLoading}
|
|
1852
2364
|
setCreateMultiple={setCreateMultiple}
|
|
1853
2365
|
handleClose={handleAttemptClose}
|
|
@@ -1866,8 +2378,11 @@ export function TaskEditDialog({
|
|
|
1866
2378
|
: undefined
|
|
1867
2379
|
}
|
|
1868
2380
|
disabled={disabled}
|
|
2381
|
+
controlsDisabled={taskControlsDisabled}
|
|
1869
2382
|
onScrollToUserCursor={
|
|
1870
|
-
|
|
2383
|
+
effectiveCollaborationMode
|
|
2384
|
+
? scrollToUserCursor
|
|
2385
|
+
: undefined
|
|
1871
2386
|
}
|
|
1872
2387
|
/>
|
|
1873
2388
|
)}
|
|
@@ -1884,15 +2399,29 @@ export function TaskEditDialog({
|
|
|
1884
2399
|
editorRef={editorRef}
|
|
1885
2400
|
lastCursorPositionRef={lastCursorPositionRef}
|
|
1886
2401
|
targetEditorCursorRef={targetEditorCursorRef}
|
|
1887
|
-
setName={
|
|
2402
|
+
setName={setTaskName}
|
|
1888
2403
|
updateName={updateName}
|
|
1889
2404
|
flushNameUpdate={flushNameUpdate}
|
|
1890
|
-
disabled={
|
|
2405
|
+
disabled={taskTitleDisabled}
|
|
1891
2406
|
/>
|
|
1892
2407
|
|
|
2408
|
+
{smartSuggestionsButton && (
|
|
2409
|
+
<div className="flex justify-end px-4 pb-2 md:px-8">
|
|
2410
|
+
{smartSuggestionsButton}
|
|
2411
|
+
</div>
|
|
2412
|
+
)}
|
|
2413
|
+
|
|
1893
2414
|
{!disabled && renderTaskPropertiesSection()}
|
|
1894
2415
|
|
|
1895
|
-
{
|
|
2416
|
+
{taskHydrationNotice}
|
|
2417
|
+
|
|
2418
|
+
{smartSuggestionsPanel && (
|
|
2419
|
+
<div className="px-4 pb-3 md:px-8">
|
|
2420
|
+
{smartSuggestionsPanel}
|
|
2421
|
+
</div>
|
|
2422
|
+
)}
|
|
2423
|
+
|
|
2424
|
+
{!taskControlsDisabled && !isCreateMode && (
|
|
1896
2425
|
<PersonalOverridesSection
|
|
1897
2426
|
taskId={task?.id}
|
|
1898
2427
|
isCreateMode={isCreateMode}
|
|
@@ -1901,7 +2430,7 @@ export function TaskEditDialog({
|
|
|
1901
2430
|
/>
|
|
1902
2431
|
)}
|
|
1903
2432
|
|
|
1904
|
-
{!
|
|
2433
|
+
{!taskControlsDisabled &&
|
|
1905
2434
|
!draftId &&
|
|
1906
2435
|
!(isCreateMode && saveAsDraft) && (
|
|
1907
2436
|
<TaskRelationshipsProperties
|
|
@@ -1950,7 +2479,7 @@ export function TaskEditDialog({
|
|
|
1950
2479
|
onAddExistingAsSubtask={addChildTask}
|
|
1951
2480
|
isSaving={!!savingRelationship}
|
|
1952
2481
|
savingTaskId={savingRelationship}
|
|
1953
|
-
disabled={
|
|
2482
|
+
disabled={taskControlsDisabled}
|
|
1954
2483
|
/>
|
|
1955
2484
|
)}
|
|
1956
2485
|
|
|
@@ -1959,8 +2488,8 @@ export function TaskEditDialog({
|
|
|
1959
2488
|
setDescription={formState.setDescription}
|
|
1960
2489
|
isOpen={isOpen}
|
|
1961
2490
|
isCreateMode={isCreateMode}
|
|
1962
|
-
collaborationMode={
|
|
1963
|
-
realtimeEnabled={
|
|
2491
|
+
collaborationMode={effectiveCollaborationMode}
|
|
2492
|
+
realtimeEnabled={effectiveRealtimeEnabled}
|
|
1964
2493
|
isYjsSyncing={isYjsSyncing}
|
|
1965
2494
|
wsId={effectiveTaskWsId}
|
|
1966
2495
|
boardId={boardId}
|
|
@@ -1994,7 +2523,7 @@ export function TaskEditDialog({
|
|
|
1994
2523
|
descriptionPercentLeft={descriptionPercentLeft}
|
|
1995
2524
|
descriptionLimit={MAX_TASK_DESCRIPTION_LENGTH}
|
|
1996
2525
|
isDescriptionOverLimit={isDescriptionOverLimit}
|
|
1997
|
-
disabled={
|
|
2526
|
+
disabled={taskControlsDisabled}
|
|
1998
2527
|
mentionTranslations={{
|
|
1999
2528
|
delete_task: t('delete_task'),
|
|
2000
2529
|
delete_task_confirmation: (name: string) =>
|
|
@@ -2075,11 +2604,13 @@ export function TaskEditDialog({
|
|
|
2075
2604
|
|
|
2076
2605
|
<MobileFloatingSaveButton
|
|
2077
2606
|
isCreateMode={isCreateMode}
|
|
2078
|
-
collaborationMode={
|
|
2607
|
+
collaborationMode={effectiveCollaborationMode}
|
|
2079
2608
|
isLoading={isLoading}
|
|
2080
|
-
canSave={
|
|
2609
|
+
canSave={
|
|
2610
|
+
canSave && !isDescriptionOverLimit && !taskControlsDisabled
|
|
2611
|
+
}
|
|
2081
2612
|
handleSave={handleSave}
|
|
2082
|
-
disabled={
|
|
2613
|
+
disabled={taskControlsDisabled}
|
|
2083
2614
|
/>
|
|
2084
2615
|
</>
|
|
2085
2616
|
)}
|