@tuturuuu/ui 0.4.1 → 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.
Files changed (107) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +41 -34
  3. package/src/components/ui/currency-input.tsx +65 -23
  4. package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
  5. package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
  6. package/src/components/ui/custom/combobox.test.tsx +141 -0
  7. package/src/components/ui/custom/combobox.tsx +105 -36
  8. package/src/components/ui/custom/settings/task-settings.tsx +126 -0
  9. package/src/components/ui/custom/settings/task-sound-settings.test.tsx +146 -0
  10. package/src/components/ui/custom/sidebar-context.tsx +68 -6
  11. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
  12. package/src/components/ui/finance/finance-layout.tsx +2 -4
  13. package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
  14. package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
  15. package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
  16. package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
  17. package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
  18. package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
  19. package/src/components/ui/finance/transactions/form-types.ts +23 -0
  20. package/src/components/ui/finance/transactions/form.tsx +81 -22
  21. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +29 -18
  22. package/src/components/ui/finance/transactions/transaction-card.tsx +75 -43
  23. package/src/components/ui/finance/transactions/transfer-merge.test.ts +90 -0
  24. package/src/components/ui/finance/transactions/transfer-merge.ts +52 -0
  25. package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
  26. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +219 -0
  27. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-amount.tsx +32 -0
  28. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-delete-dialog.tsx +50 -0
  29. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-dialog.tsx +138 -0
  30. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
  31. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +197 -0
  32. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +201 -0
  33. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +541 -0
  34. package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +362 -0
  35. package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
  36. package/src/components/ui/finance/wallets/columns.test.ts +56 -0
  37. package/src/components/ui/finance/wallets/columns.tsx +196 -43
  38. package/src/components/ui/finance/wallets/form.test.tsx +79 -14
  39. package/src/components/ui/finance/wallets/form.tsx +41 -197
  40. package/src/components/ui/finance/wallets/query-invalidation.ts +3 -0
  41. package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
  42. package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
  43. package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
  44. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
  45. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
  46. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
  47. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
  48. package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +71 -5
  49. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +52 -35
  50. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
  51. package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
  52. package/src/components/ui/finance/wallets/wallets-page.test.tsx +117 -36
  53. package/src/components/ui/finance/wallets/wallets-page.tsx +40 -64
  54. package/src/components/ui/storefront/accent-button.tsx +33 -0
  55. package/src/components/ui/storefront/cart-summary.tsx +140 -0
  56. package/src/components/ui/storefront/empty-listings.tsx +32 -0
  57. package/src/components/ui/storefront/hero-panel.tsx +70 -0
  58. package/src/components/ui/storefront/image-panel.tsx +40 -0
  59. package/src/components/ui/storefront/index.ts +12 -0
  60. package/src/components/ui/storefront/listing-card.tsx +129 -0
  61. package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
  62. package/src/components/ui/storefront/storefront-surface.tsx +235 -0
  63. package/src/components/ui/storefront/types.ts +99 -0
  64. package/src/components/ui/storefront/utils.ts +90 -0
  65. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-move.test.tsx +14 -0
  66. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +29 -0
  67. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
  68. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
  69. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
  70. package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
  71. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
  72. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
  73. package/src/components/ui/tu-do/my-tasks/__tests__/use-task-context-actions.test.ts +11 -0
  74. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
  75. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +124 -7
  76. package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
  77. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
  78. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
  79. package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
  80. package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
  81. package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
  82. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
  83. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +268 -0
  85. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +243 -0
  86. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +26 -0
  87. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
  88. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
  89. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
  90. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
  91. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +36 -20
  92. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +41 -1
  93. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +157 -102
  94. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
  95. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
  96. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.test.ts +84 -1
  97. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +5 -1
  98. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
  99. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +300 -172
  100. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +959 -340
  101. package/src/components/ui/tu-do/shared/task-sound-effects.test.ts +189 -0
  102. package/src/components/ui/tu-do/shared/task-sound-effects.tsx +468 -0
  103. package/src/hooks/__tests__/use-task-actions.test.tsx +61 -0
  104. package/src/hooks/use-task-actions.ts +45 -0
  105. package/src/hooks/useBoardRealtime.ts +54 -1
  106. package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
  107. 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,9 +45,21 @@ 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';
48
+ import {
49
+ normalizeTaskDialogPresentation,
50
+ type TaskDialogPresentation,
51
+ } from './task-dialog-presentation';
52
+ import { CompactTaskDialogPanel } from './task-edit-dialog/components/compact-task-create-popover';
43
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';
44
58
  import { TaskDescriptionEditor } from './task-edit-dialog/components/task-description-editor';
45
- import { TaskDialogHeader } from './task-edit-dialog/components/task-dialog-header';
59
+ import {
60
+ getTaskDialogHeaderInfo,
61
+ TaskDialogHeader,
62
+ } from './task-edit-dialog/components/task-dialog-header';
46
63
  import { TaskNameInput } from './task-edit-dialog/components/task-name-input';
47
64
  import { TaskSuggestionMenus } from './task-edit-dialog/components/task-suggestion-menus';
48
65
  import { NAME_UPDATE_DEBOUNCE_MS } from './task-edit-dialog/constants';
@@ -87,6 +104,7 @@ import {
87
104
  import {
88
105
  broadcastTaskDescriptionUpsert,
89
106
  clearDraft,
107
+ getDescriptionContent,
90
108
  getDraftStorageKey,
91
109
  getTaskDescriptionPercentLeft,
92
110
  getTaskDescriptionStorageLength,
@@ -125,6 +143,9 @@ export interface TaskEditDialogProps {
125
143
  collaborationMode?: boolean;
126
144
  /** Whether realtime features (Yjs sync, presence avatars) are enabled - true for all tiers */
127
145
  realtimeEnabled?: boolean;
146
+ isHydratingTask?: boolean;
147
+ taskLoadError?: boolean;
148
+ taskHydrationVersion?: number;
128
149
  isPersonalWorkspace?: boolean;
129
150
  parentTaskId?: string;
130
151
  parentTaskName?: string;
@@ -134,6 +155,8 @@ export interface TaskEditDialogProps {
134
155
  sharedContext?: SharedTaskContext;
135
156
  /** Whether draft mode is enabled from user settings */
136
157
  draftModeEnabled?: boolean;
158
+ /** Preferred opening presentation for normal task dialogs */
159
+ defaultPresentation?: TaskDialogPresentation;
137
160
  /** When editing an existing draft, this is the draft ID */
138
161
  draftId?: string;
139
162
  onClose: () => void;
@@ -144,6 +167,7 @@ export interface TaskEditDialogProps {
144
167
  onAddBlockingTask?: () => void;
145
168
  onAddBlockedByTask?: () => void;
146
169
  onAddRelatedTask?: () => void;
170
+ onRetryTaskLoad?: () => void;
147
171
  }
148
172
 
149
173
  export function TaskEditDialog({
@@ -159,6 +183,9 @@ export function TaskEditDialog({
159
183
  mode = 'edit',
160
184
  collaborationMode = false,
161
185
  realtimeEnabled = false,
186
+ isHydratingTask = false,
187
+ taskLoadError = false,
188
+ taskHydrationVersion = 0,
162
189
  isPersonalWorkspace = false,
163
190
  parentTaskId,
164
191
  parentTaskName,
@@ -166,6 +193,7 @@ export function TaskEditDialog({
166
193
  currentUser: propsCurrentUser,
167
194
  sharedContext,
168
195
  draftModeEnabled = false,
196
+ defaultPresentation = 'compact',
169
197
  draftId,
170
198
  onClose,
171
199
  onUpdate,
@@ -175,12 +203,14 @@ export function TaskEditDialog({
175
203
  onAddBlockingTask,
176
204
  onAddBlockedByTask,
177
205
  onAddRelatedTask,
206
+ onRetryTaskLoad,
178
207
  }: TaskEditDialogProps) {
179
208
  const isCreateMode = mode === 'create';
180
209
  const effectiveTaskWsId = !isCreateMode ? (taskWsId ?? wsId) : wsId;
181
210
  const pathname = usePathname();
182
211
  const queryClient = useQueryClient();
183
212
  const t = useTranslations('common');
213
+ const rootT = useTranslations();
184
214
  const dialogT = useTranslations('ws-task-boards.dialog');
185
215
  const { registerCloseRequestHandler } = useTaskDialogContext();
186
216
 
@@ -191,6 +221,12 @@ export function TaskEditDialog({
191
221
  // Disable editing if we are viewing via a shared link
192
222
  // User requested: always disable editing for shared tasks, regardless of permission
193
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;
194
230
 
195
231
  // Keep this permission query disabled when `disabled` shared-link mode is active
196
232
  // (`enabled` becomes false) so `canManageTaskMedia` stays undefined while
@@ -212,7 +248,7 @@ export function TaskEditDialog({
212
248
  );
213
249
  return result.hasPermission;
214
250
  },
215
- enabled: Boolean(effectiveTaskWsId) && !disabled,
251
+ enabled: Boolean(effectiveTaskWsId) && !taskControlsDisabled,
216
252
  staleTime: 5 * 60 * 1000,
217
253
  });
218
254
 
@@ -230,7 +266,7 @@ export function TaskEditDialog({
230
266
  });
231
267
 
232
268
  // Refs
233
- const titleInputRef = useRef<HTMLInputElement>(null);
269
+ const titleInputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
234
270
  const editorRef = useRef<HTMLDivElement>(null);
235
271
  const richTextEditorRef = useRef<HTMLDivElement>(null);
236
272
  const lastCursorPositionRef = useRef<number | null>(null);
@@ -240,6 +276,7 @@ export function TaskEditDialog({
240
276
  );
241
277
  const nameUpdateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
242
278
  const pendingNameRef = useRef<string | null>(null);
279
+ const nameEditedDuringHydrationRef = useRef(false);
243
280
  const quickDueRef = useRef<(days: number | null) => void>(() => {});
244
281
  const updateEstimationRef = useRef<(points: number | null) => void>(() => {});
245
282
  const handleConvertToTaskRef = useRef<(() => Promise<void>) | null>(null);
@@ -251,6 +288,17 @@ export function TaskEditDialog({
251
288
  descriptionRef.current = formState.description;
252
289
  }, [formState.description]);
253
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
+
254
302
  // User state
255
303
  const [user, setUser] = useState<TaskDialogUserIdentity | null>(
256
304
  propsCurrentUser ? normalizeTaskDialogCurrentUser(propsCurrentUser) : null
@@ -381,7 +429,7 @@ export function TaskEditDialog({
381
429
  columnName: 'description_yjs_state',
382
430
  id: task?.id || '',
383
431
  user: yjsUser,
384
- enabled: isOpen && !isCreateMode && realtimeEnabled && !!task?.id,
432
+ enabled: isOpen && !isCreateMode && effectiveRealtimeEnabled && !!task?.id,
385
433
  broadcastDebounceMs: workspaceTier && workspaceTier !== 'FREE' ? 0 : 200,
386
434
  saveDebounceMs: 5000,
387
435
  loadDocumentState: loadTaskDescriptionState,
@@ -391,7 +439,7 @@ export function TaskEditDialog({
391
439
  const [hasHydratedYjsState, setHasHydratedYjsState] = useState(false);
392
440
 
393
441
  useEffect(() => {
394
- if (!isOpen || isCreateMode || !realtimeEnabled || !task?.id) {
442
+ if (!isOpen || isCreateMode || !effectiveRealtimeEnabled || !task?.id) {
395
443
  setHasHydratedYjsState(false);
396
444
  return;
397
445
  }
@@ -399,13 +447,13 @@ export function TaskEditDialog({
399
447
  if (synced) {
400
448
  setHasHydratedYjsState(true);
401
449
  }
402
- }, [isOpen, isCreateMode, realtimeEnabled, task?.id, synced]);
450
+ }, [isOpen, isCreateMode, effectiveRealtimeEnabled, task?.id, synced]);
403
451
 
404
452
  const isYjsSyncing = useMemo(() => {
405
453
  return (
406
454
  isOpen &&
407
455
  !isCreateMode &&
408
- realtimeEnabled &&
456
+ effectiveRealtimeEnabled &&
409
457
  !!task?.id &&
410
458
  !hasHydratedYjsState &&
411
459
  !synced
@@ -414,7 +462,7 @@ export function TaskEditDialog({
414
462
  hasHydratedYjsState,
415
463
  isOpen,
416
464
  isCreateMode,
417
- realtimeEnabled,
465
+ effectiveRealtimeEnabled,
418
466
  task?.id,
419
467
  synced,
420
468
  ]);
@@ -504,6 +552,31 @@ export function TaskEditDialog({
504
552
  useState(false);
505
553
  const [showShareDialog, setShowShareDialog] = useState(false);
506
554
  const [saveAsDraft, setSaveAsDraft] = useState(draftModeEnabled);
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);
579
+ const previousOpenRef = useRef(false);
507
580
  const [isTitleVisible, setIsTitleVisible] = useState(true);
508
581
  const [scrollContainer, setScrollContainer] = useState<HTMLDivElement | null>(
509
582
  null
@@ -702,8 +775,8 @@ export function TaskEditDialog({
702
775
  selectedLabels: formState.selectedLabels,
703
776
  selectedAssignees: formState.selectedAssignees,
704
777
  isCreateMode,
705
- isLoading,
706
- collaborationMode,
778
+ isLoading: isLoading || taskControlsDisabled,
779
+ collaborationMode: effectiveCollaborationMode,
707
780
  draftId,
708
781
  });
709
782
 
@@ -726,7 +799,7 @@ export function TaskEditDialog({
726
799
  boardId,
727
800
  isCreateMode,
728
801
  isOpen,
729
- realtimeEnabled,
802
+ realtimeEnabled: effectiveRealtimeEnabled,
730
803
  isPersonalWorkspace,
731
804
  name: formState.name,
732
805
  priority: formState.priority,
@@ -776,6 +849,41 @@ export function TaskEditDialog({
776
849
  onUpdate,
777
850
  });
778
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
+
779
887
  // Task relationships
780
888
  const {
781
889
  toggleLabel,
@@ -854,6 +962,8 @@ export function TaskEditDialog({
854
962
  isCreateMode,
855
963
  task,
856
964
  filters,
965
+ taskHydrationVersion,
966
+ preserveNameOnHydration: nameEditedDuringHydrationRef.current,
857
967
  setName: formState.setName,
858
968
  setDescription: formState.setDescription,
859
969
  setPriority: formState.setPriority,
@@ -866,6 +976,12 @@ export function TaskEditDialog({
866
976
  setSelectedProjects: formState.setSelectedProjects,
867
977
  });
868
978
 
979
+ useEffect(() => {
980
+ if (!isHydratingTask) {
981
+ nameEditedDuringHydrationRef.current = false;
982
+ }
983
+ }, [isHydratingTask]);
984
+
869
985
  // Yjs sync
870
986
  useTaskYjsSync({
871
987
  taskId: task?.id,
@@ -873,7 +989,7 @@ export function TaskEditDialog({
873
989
  boardId,
874
990
  isOpen,
875
991
  isCreateMode,
876
- realtimeEnabled,
992
+ realtimeEnabled: effectiveRealtimeEnabled,
877
993
  editorInstance,
878
994
  doc,
879
995
  yjsProvider: provider,
@@ -1196,7 +1312,7 @@ export function TaskEditDialog({
1196
1312
  saveAsDraft: isCreateMode && (!!draftId || saveAsDraft),
1197
1313
  draftId,
1198
1314
  isCreateMode,
1199
- collaborationMode,
1315
+ collaborationMode: effectiveCollaborationMode,
1200
1316
  isPersonalWorkspace,
1201
1317
  shareCode,
1202
1318
  sharedPermission,
@@ -1245,6 +1361,309 @@ export function TaskEditDialog({
1245
1361
  setSelectedProjects: formState.setSelectedProjects,
1246
1362
  });
1247
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
+
1248
1667
  const persistTaskDescriptionOnClose = useCallback(async () => {
1249
1668
  if (isCreateMode || !task?.id || !flushEditorPendingRef.current) {
1250
1669
  return true;
@@ -1328,7 +1747,7 @@ export function TaskEditDialog({
1328
1747
  useTaskDialogClose({
1329
1748
  taskId: task?.id,
1330
1749
  isCreateMode,
1331
- collaborationMode,
1750
+ collaborationMode: effectiveCollaborationMode,
1332
1751
  synced,
1333
1752
  connected,
1334
1753
  draftStorageKey,
@@ -1476,8 +1895,8 @@ export function TaskEditDialog({
1476
1895
  isOpen,
1477
1896
  canSave,
1478
1897
  isCreateMode,
1479
- collaborationMode,
1480
- disabled,
1898
+ collaborationMode: effectiveCollaborationMode,
1899
+ disabled: taskControlsDisabled,
1481
1900
  editorInstance,
1482
1901
  boardConfig,
1483
1902
  slashState: suggestionMenus.slashState,
@@ -1522,6 +1941,26 @@ export function TaskEditDialog({
1522
1941
  }
1523
1942
  }, [isOpen, draftModeEnabled, draftId]);
1524
1943
 
1944
+ useEffect(() => {
1945
+ const justOpened = isOpen && !previousOpenRef.current;
1946
+ previousOpenRef.current = isOpen;
1947
+
1948
+ if (!isOpen) {
1949
+ setPresentation(normalizedDefaultPresentation);
1950
+ setSmartSuggestions([]);
1951
+ setSelectedSmartSuggestionIds([]);
1952
+ setSmartSuggestionError(null);
1953
+ setSmartCreateErrors({});
1954
+ setCreatingSmartSuggestionIds([]);
1955
+ setIsCreatingSmartSuggestions(false);
1956
+ return;
1957
+ }
1958
+
1959
+ if (justOpened) {
1960
+ setPresentation(draftId ? 'fullscreen' : normalizedDefaultPresentation);
1961
+ }
1962
+ }, [isOpen, draftId, normalizedDefaultPresentation]);
1963
+
1525
1964
  // Track whether the title input is scrolled out of view
1526
1965
  useEffect(() => {
1527
1966
  const el = titleInputRef.current;
@@ -1583,11 +2022,183 @@ export function TaskEditDialog({
1583
2022
  taskSearchQuery,
1584
2023
  ]);
1585
2024
 
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;
2044
+ const compactHeaderInfo = useMemo(
2045
+ () =>
2046
+ getTaskDialogHeaderInfo(
2047
+ {
2048
+ isCreateMode,
2049
+ parentTaskId,
2050
+ parentTaskName,
2051
+ pendingRelationship,
2052
+ draftId,
2053
+ },
2054
+ rootT
2055
+ ),
2056
+ [
2057
+ isCreateMode,
2058
+ parentTaskId,
2059
+ parentTaskName,
2060
+ pendingRelationship,
2061
+ draftId,
2062
+ rootT,
2063
+ ]
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;
2121
+
1586
2122
  // Update refs
1587
2123
  quickDueRef.current = handleQuickDueDate;
1588
2124
  updateEstimationRef.current = updateEstimation;
1589
2125
  handleConvertToTaskRef.current = handleConvertToTask;
1590
2126
 
2127
+ const renderTaskPropertiesSection = (
2128
+ variant: 'default' | 'compact' = 'default'
2129
+ ) => (
2130
+ <TaskPropertiesSection
2131
+ wsId={effectiveTaskWsId}
2132
+ boardId={boardId}
2133
+ taskId={task?.id}
2134
+ priority={formState.priority}
2135
+ startDate={formState.startDate}
2136
+ endDate={formState.endDate}
2137
+ estimationPoints={formState.estimationPoints}
2138
+ selectedLabels={formState.selectedLabels}
2139
+ selectedProjects={formState.selectedProjects}
2140
+ selectedListId={formState.selectedListId}
2141
+ selectedAssignees={formState.selectedAssignees}
2142
+ isLoading={isLoading}
2143
+ isPersonalWorkspace={isPersonalWorkspace}
2144
+ totalDuration={formState.totalDuration}
2145
+ isSplittable={formState.isSplittable}
2146
+ minSplitDurationMinutes={formState.minSplitDurationMinutes}
2147
+ maxSplitDurationMinutes={formState.maxSplitDurationMinutes}
2148
+ calendarHours={formState.calendarHours}
2149
+ autoSchedule={formState.autoSchedule}
2150
+ availableLists={availableLists}
2151
+ availableLabels={availableLabels}
2152
+ taskProjects={taskProjects}
2153
+ workspaceMembers={workspaceMembers}
2154
+ boardConfig={boardConfig}
2155
+ onPriorityChange={updatePriority}
2156
+ onStartDateChange={updateStartDate}
2157
+ onEndDateChange={updateEndDate}
2158
+ onEstimationChange={updateEstimation}
2159
+ onLabelToggle={toggleLabel}
2160
+ onProjectToggle={toggleProject}
2161
+ onListChange={updateList}
2162
+ onAssigneeToggle={toggleAssignee}
2163
+ onQuickDueDate={handleQuickDueDate}
2164
+ onShowNewLabelDialog={() => {
2165
+ setNewLabelColor((previousColor) =>
2166
+ getRandomNewLabelColor(previousColor)
2167
+ );
2168
+ setShowNewLabelDialog(true);
2169
+ }}
2170
+ onShowNewProjectDialog={() => setShowNewProjectDialog(true)}
2171
+ onShowEstimationConfigDialog={() => setShowEstimationConfigDialog(true)}
2172
+ onTotalDurationChange={formState.setTotalDuration}
2173
+ onIsSplittableChange={formState.setIsSplittable}
2174
+ onMinSplitDurationChange={formState.setMinSplitDurationMinutes}
2175
+ onMaxSplitDurationChange={formState.setMaxSplitDurationMinutes}
2176
+ onCalendarHoursChange={formState.setCalendarHours}
2177
+ onAutoScheduleChange={formState.setAutoSchedule}
2178
+ isCreateMode={isCreateMode}
2179
+ savedSchedulingSettings={
2180
+ personalScheduleData?.task
2181
+ ? {
2182
+ totalDuration: personalScheduleData.task.total_duration ?? null,
2183
+ isSplittable: !!personalScheduleData.task.is_splittable,
2184
+ minSplitDurationMinutes:
2185
+ personalScheduleData.task.min_split_duration_minutes ?? null,
2186
+ maxSplitDurationMinutes:
2187
+ personalScheduleData.task.max_split_duration_minutes ?? null,
2188
+ calendarHours: personalScheduleData.task.calendar_hours ?? null,
2189
+ autoSchedule: !!personalScheduleData.task.auto_schedule,
2190
+ }
2191
+ : undefined
2192
+ }
2193
+ onSaveSchedulingSettings={saveSchedulingSettings}
2194
+ schedulingSaving={schedulingSaving}
2195
+ scheduledEvents={localCalendarEvents}
2196
+ disabled={taskControlsDisabled}
2197
+ isDraftMode={!!draftId || (isCreateMode && saveAsDraft)}
2198
+ variant={variant}
2199
+ />
2200
+ );
2201
+
1591
2202
  return (
1592
2203
  <>
1593
2204
  <TaskSuggestionMenus
@@ -1622,7 +2233,11 @@ export function TaskEditDialog({
1622
2233
  <Dialog open={isOpen} onOpenChange={handleDialogOpenChange} modal={true}>
1623
2234
  <DialogContent
1624
2235
  showCloseButton={false}
1625
- className="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"
2236
+ className={
2237
+ showCompactDialog
2238
+ ? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-hidden rounded-lg border p-0 shadow-xl'
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'
2240
+ }
1626
2241
  onContextMenu={(e) => {
1627
2242
  if (shouldPreserveNativeContextMenu(e.target)) {
1628
2243
  return;
@@ -1648,74 +2263,15 @@ export function TaskEditDialog({
1648
2263
  e.preventDefault();
1649
2264
  }}
1650
2265
  >
1651
- <div className="flex min-w-0 flex-1 flex-col bg-background transition-all duration-300">
1652
- {disabled && (
1653
- <DialogTitle className="sr-only">Task Details</DialogTitle>
1654
- )}
1655
- {!disabled && (
1656
- <TaskDialogHeader
1657
- isCreateMode={isCreateMode}
1658
- collaborationMode={collaborationMode}
1659
- realtimeEnabled={realtimeEnabled}
1660
- isOpen={isOpen}
1661
- synced={synced}
1662
- connected={connected}
1663
- taskId={task?.id}
1664
- parentTaskId={parentTaskId}
1665
- parentTaskName={parentTaskName}
1666
- pendingRelationship={pendingRelationship}
1667
- saveAsDraft={saveAsDraft}
1668
- setSaveAsDraft={setSaveAsDraft}
1669
- draftId={draftId}
1670
- isTitleVisible={isTitleVisible}
1671
- taskName={formState.name}
1672
- ticketPrefix={boardConfig?.ticket_prefix}
1673
- displayNumber={task?.display_number}
1674
- user={
1675
- user
1676
- ? {
1677
- id: user.id || '',
1678
- display_name: user.display_name ?? null,
1679
- avatar_url: user.avatar_url ?? null,
1680
- email: user.email ?? null,
1681
- }
1682
- : null
1683
- }
1684
- createMultiple={createMultiple}
1685
- hasDraft={formState.hasDraft}
1686
- wsId={effectiveTaskWsId}
1687
- boardId={boardId}
1688
- pathname={pathname}
1689
- canSave={canSave && !isDescriptionOverLimit}
1690
- isLoading={isLoading}
1691
- setCreateMultiple={setCreateMultiple}
1692
- handleClose={handleAttemptClose}
1693
- setShowDeleteConfirm={setShowDeleteConfirm}
1694
- clearDraftState={formState.clearDraftState}
1695
- handleSave={handleSave}
1696
- onNavigateBack={
1697
- isCreateMode && (pendingRelationship || parentTaskId)
1698
- ? handleNavigateBack
1699
- : undefined
1700
- }
1701
- isPersonalWorkspace={isPersonalWorkspace}
1702
- onOpenShareDialog={
1703
- !isCreateMode && task?.id
1704
- ? () => setShowShareDialog(true)
1705
- : undefined
1706
- }
1707
- disabled={disabled}
1708
- onScrollToUserCursor={
1709
- collaborationMode ? scrollToUserCursor : undefined
1710
- }
1711
- />
1712
- )}
1713
-
1714
- <div
1715
- ref={setScrollContainer}
1716
- className="relative flex min-h-0 flex-1 flex-col overflow-y-auto"
1717
- >
1718
- <div className="flex flex-col">
2266
+ {showCompactDialog ? (
2267
+ <CompactTaskDialogPanel
2268
+ title={compactHeaderInfo.title}
2269
+ description={compactHeaderInfo.description}
2270
+ icon={compactHeaderInfo.icon}
2271
+ iconBgClass={compactHeaderInfo.iconBgClass}
2272
+ iconRingClass={compactHeaderInfo.iconRingClass}
2273
+ showHeaderTitle={isCreateMode}
2274
+ titleInput={
1719
2275
  <TaskNameInput
1720
2276
  name={formState.name}
1721
2277
  isCreateMode={isCreateMode}
@@ -1723,278 +2279,341 @@ export function TaskEditDialog({
1723
2279
  editorRef={editorRef}
1724
2280
  lastCursorPositionRef={lastCursorPositionRef}
1725
2281
  targetEditorCursorRef={targetEditorCursorRef}
1726
- setName={formState.setName}
2282
+ setName={setTaskName}
1727
2283
  updateName={updateName}
1728
2284
  flushNameUpdate={flushNameUpdate}
1729
- disabled={disabled}
2285
+ disabled={taskTitleDisabled}
2286
+ variant="compact"
2287
+ onSubmit={
2288
+ isCreateMode && !taskControlsDisabled
2289
+ ? handleSave
2290
+ : undefined
2291
+ }
1730
2292
  />
1731
-
2293
+ }
2294
+ propertyControls={renderTaskPropertiesSection('compact')}
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
+ }
2305
+ isLoading={isLoading}
2306
+ isPersonalWorkspace={isPersonalWorkspace}
2307
+ editActions={compactEditActions}
2308
+ onSaveAsDraftChange={isCreateMode ? setSaveAsDraft : undefined}
2309
+ onCreateMultipleChange={
2310
+ isCreateMode ? setCreateMultiple : undefined
2311
+ }
2312
+ onClose={handleAttemptClose}
2313
+ onFullscreen={() => setPresentation('fullscreen')}
2314
+ onSave={
2315
+ isCreateMode && !taskControlsDisabled ? handleSave : undefined
2316
+ }
2317
+ />
2318
+ ) : (
2319
+ <>
2320
+ <div className="flex min-w-0 flex-1 flex-col bg-background transition-all duration-300">
2321
+ {disabled && (
2322
+ <DialogTitle className="sr-only">Task Details</DialogTitle>
2323
+ )}
1732
2324
  {!disabled && (
1733
- <TaskPropertiesSection
1734
- wsId={effectiveTaskWsId}
1735
- boardId={boardId}
1736
- taskId={task?.id}
1737
- priority={formState.priority}
1738
- startDate={formState.startDate}
1739
- endDate={formState.endDate}
1740
- estimationPoints={formState.estimationPoints}
1741
- selectedLabels={formState.selectedLabels}
1742
- selectedProjects={formState.selectedProjects}
1743
- selectedListId={formState.selectedListId}
1744
- selectedAssignees={formState.selectedAssignees}
1745
- isLoading={isLoading}
1746
- isPersonalWorkspace={isPersonalWorkspace}
1747
- totalDuration={formState.totalDuration}
1748
- isSplittable={formState.isSplittable}
1749
- minSplitDurationMinutes={formState.minSplitDurationMinutes}
1750
- maxSplitDurationMinutes={formState.maxSplitDurationMinutes}
1751
- calendarHours={formState.calendarHours}
1752
- autoSchedule={formState.autoSchedule}
1753
- availableLists={availableLists}
1754
- availableLabels={availableLabels}
1755
- taskProjects={taskProjects}
1756
- workspaceMembers={workspaceMembers}
1757
- boardConfig={boardConfig}
1758
- onPriorityChange={updatePriority}
1759
- onStartDateChange={updateStartDate}
1760
- onEndDateChange={updateEndDate}
1761
- onEstimationChange={updateEstimation}
1762
- onLabelToggle={toggleLabel}
1763
- onProjectToggle={toggleProject}
1764
- onListChange={updateList}
1765
- onAssigneeToggle={toggleAssignee}
1766
- onQuickDueDate={handleQuickDueDate}
1767
- onShowNewLabelDialog={() => {
1768
- setNewLabelColor((previousColor) =>
1769
- getRandomNewLabelColor(previousColor)
1770
- );
1771
- setShowNewLabelDialog(true);
1772
- }}
1773
- onShowNewProjectDialog={() => setShowNewProjectDialog(true)}
1774
- onShowEstimationConfigDialog={() =>
1775
- setShowEstimationConfigDialog(true)
1776
- }
1777
- onTotalDurationChange={formState.setTotalDuration}
1778
- onIsSplittableChange={formState.setIsSplittable}
1779
- onMinSplitDurationChange={
1780
- formState.setMinSplitDurationMinutes
1781
- }
1782
- onMaxSplitDurationChange={
1783
- formState.setMaxSplitDurationMinutes
1784
- }
1785
- onCalendarHoursChange={formState.setCalendarHours}
1786
- onAutoScheduleChange={formState.setAutoSchedule}
2325
+ <TaskDialogHeader
1787
2326
  isCreateMode={isCreateMode}
1788
- savedSchedulingSettings={
1789
- personalScheduleData?.task
2327
+ collaborationMode={effectiveCollaborationMode}
2328
+ realtimeEnabled={effectiveRealtimeEnabled}
2329
+ isOpen={isOpen}
2330
+ synced={synced}
2331
+ connected={connected}
2332
+ taskId={task?.id}
2333
+ parentTaskId={parentTaskId}
2334
+ parentTaskName={parentTaskName}
2335
+ pendingRelationship={pendingRelationship}
2336
+ saveAsDraft={saveAsDraft}
2337
+ setSaveAsDraft={setSaveAsDraft}
2338
+ draftId={draftId}
2339
+ isTitleVisible={isTitleVisible}
2340
+ taskName={formState.name}
2341
+ ticketPrefix={boardConfig?.ticket_prefix}
2342
+ displayNumber={task?.display_number}
2343
+ user={
2344
+ user
1790
2345
  ? {
1791
- totalDuration:
1792
- personalScheduleData.task.total_duration ?? null,
1793
- isSplittable:
1794
- !!personalScheduleData.task.is_splittable,
1795
- minSplitDurationMinutes:
1796
- personalScheduleData.task
1797
- .min_split_duration_minutes ?? null,
1798
- maxSplitDurationMinutes:
1799
- personalScheduleData.task
1800
- .max_split_duration_minutes ?? null,
1801
- calendarHours:
1802
- personalScheduleData.task.calendar_hours ?? null,
1803
- autoSchedule:
1804
- !!personalScheduleData.task.auto_schedule,
2346
+ id: user.id || '',
2347
+ display_name: user.display_name ?? null,
2348
+ avatar_url: user.avatar_url ?? null,
2349
+ email: user.email ?? null,
1805
2350
  }
1806
- : undefined
2351
+ : null
1807
2352
  }
1808
- onSaveSchedulingSettings={saveSchedulingSettings}
1809
- schedulingSaving={schedulingSaving}
1810
- scheduledEvents={localCalendarEvents}
1811
- disabled={disabled}
1812
- isDraftMode={!!draftId || (isCreateMode && saveAsDraft)}
1813
- />
1814
- )}
1815
-
1816
- {!disabled && !isCreateMode && (
1817
- <PersonalOverridesSection
1818
- taskId={task?.id}
1819
- isCreateMode={isCreateMode}
1820
- boardConfig={boardConfig}
1821
- onUpdate={onUpdate}
1822
- />
1823
- )}
1824
-
1825
- {!disabled && !draftId && !(isCreateMode && saveAsDraft) && (
1826
- <TaskRelationshipsProperties
2353
+ createMultiple={createMultiple}
2354
+ hasDraft={formState.hasDraft}
1827
2355
  wsId={effectiveTaskWsId}
1828
- taskId={task?.id}
1829
2356
  boardId={boardId}
1830
- listId={task?.list_id}
1831
- isCreateMode={isCreateMode}
1832
- initialActiveTab={
1833
- seededPendingRelationships.initialActiveTab
1834
- }
1835
- initialDependencySubTab={
1836
- seededPendingRelationships.initialDependencySubTab
1837
- }
1838
- parentTask={parentTask}
1839
- childTasks={childTasks}
1840
- blockingTasks={blockingTasks}
1841
- blockedByTasks={blockedByTasks}
1842
- relatedTasks={relatedTasks}
1843
- isLoading={dependenciesLoading}
1844
- onSetParent={setParentTask}
1845
- onRemoveParent={() => setParentTask(null)}
1846
- onAddBlockingTask={addBlockingTask}
1847
- onRemoveBlockingTask={removeBlockingTask}
1848
- onAddBlockedByTask={addBlockedByTask}
1849
- onRemoveBlockedByTask={removeBlockedByTask}
1850
- onAddRelatedTask={addRelatedTask}
1851
- onRemoveRelatedTask={removeRelatedTask}
1852
- onNavigateToTask={async (taskId) => {
1853
- if (onNavigateToTask) await onNavigateToTask(taskId);
1854
- }}
1855
- onAddSubtask={isCreateMode ? undefined : onAddSubtask}
1856
- onAddParentTask={isCreateMode ? undefined : onAddParentTask}
1857
- onAddBlockingTaskDialog={
1858
- isCreateMode ? undefined : onAddBlockingTask
2357
+ pathname={pathname}
2358
+ canSave={
2359
+ canSave &&
2360
+ !isDescriptionOverLimit &&
2361
+ !taskControlsDisabled
1859
2362
  }
1860
- onAddBlockedByTaskDialog={
1861
- isCreateMode ? undefined : onAddBlockedByTask
2363
+ isLoading={isLoading}
2364
+ setCreateMultiple={setCreateMultiple}
2365
+ handleClose={handleAttemptClose}
2366
+ setShowDeleteConfirm={setShowDeleteConfirm}
2367
+ clearDraftState={formState.clearDraftState}
2368
+ handleSave={handleSave}
2369
+ onNavigateBack={
2370
+ isCreateMode && (pendingRelationship || parentTaskId)
2371
+ ? handleNavigateBack
2372
+ : undefined
1862
2373
  }
1863
- onAddRelatedTaskDialog={
1864
- isCreateMode ? undefined : onAddRelatedTask
2374
+ isPersonalWorkspace={isPersonalWorkspace}
2375
+ onOpenShareDialog={
2376
+ !isCreateMode && task?.id
2377
+ ? () => setShowShareDialog(true)
2378
+ : undefined
1865
2379
  }
1866
- onAddExistingAsSubtask={addChildTask}
1867
- isSaving={!!savingRelationship}
1868
- savingTaskId={savingRelationship}
1869
2380
  disabled={disabled}
2381
+ controlsDisabled={taskControlsDisabled}
2382
+ onScrollToUserCursor={
2383
+ effectiveCollaborationMode
2384
+ ? scrollToUserCursor
2385
+ : undefined
2386
+ }
1870
2387
  />
1871
2388
  )}
1872
2389
 
1873
- <TaskDescriptionEditor
1874
- description={formState.description}
1875
- setDescription={formState.setDescription}
1876
- isOpen={isOpen}
1877
- isCreateMode={isCreateMode}
1878
- collaborationMode={collaborationMode}
1879
- realtimeEnabled={realtimeEnabled}
1880
- isYjsSyncing={isYjsSyncing}
1881
- wsId={effectiveTaskWsId}
1882
- boardId={boardId}
1883
- taskId={task?.id}
1884
- availableLists={availableLists}
1885
- queryClient={queryClient}
1886
- editorRef={editorRef}
1887
- richTextEditorRef={richTextEditorRef}
1888
- titleInputRef={titleInputRef}
1889
- lastCursorPositionRef={lastCursorPositionRef}
1890
- targetEditorCursorRef={targetEditorCursorRef}
1891
- flushEditorPendingRef={flushEditorPendingRef}
1892
- yjsDoc={doc}
1893
- yjsProvider={provider ?? undefined}
1894
- collaborationUser={
1895
- user
1896
- ? {
1897
- id: user.id || '',
1898
- name: userDisplayName,
1899
- color: userColor || '',
1900
- }
1901
- : null
1902
- }
1903
- onImageUpload={imageUploadHandler}
1904
- onEditorReady={handleEditorReady}
1905
- onConvertToTask={handleConvertToTask}
1906
- onDescriptionStorageLengthChange={
1907
- handleDescriptionStorageLengthChange
1908
- }
1909
- descriptionStorageLength={descriptionStorageLength}
1910
- descriptionPercentLeft={descriptionPercentLeft}
1911
- descriptionLimit={MAX_TASK_DESCRIPTION_LENGTH}
1912
- isDescriptionOverLimit={isDescriptionOverLimit}
1913
- disabled={disabled}
1914
- mentionTranslations={{
1915
- delete_task: t('delete_task'),
1916
- delete_task_confirmation: (name: string) =>
1917
- t('delete_task_confirmation', { name }),
1918
- cancel: t('cancel'),
1919
- deleting: t('deleting'),
1920
- set_custom_due_date: t('set_custom_due_date'),
1921
- custom_due_date_description: t(
1922
- 'custom_due_date_description'
1923
- ),
1924
- remove_due_date: t('remove_due_date'),
1925
- create_new_label: t('create_new_label'),
1926
- create_new_label_description: t(
1927
- 'create_new_label_description'
1928
- ),
1929
- label_name: t('label_name'),
1930
- color: t('color'),
1931
- preview: t('preview'),
1932
- creating: t('creating'),
1933
- create_label: t('create_label'),
1934
- create_new_project: t('create_new_project'),
1935
- create_new_project_description: t(
1936
- 'create_new_project_description'
1937
- ),
1938
- project_name: t('project_name'),
1939
- create_project: t('create_project'),
1940
- }}
1941
- />
1942
-
1943
- {!isCreateMode && localCalendarEvents && (
1944
- <TaskInstancesSection
1945
- wsId={effectiveTaskWsId}
1946
- taskId={task?.id}
1947
- scheduledEvents={localCalendarEvents}
1948
- onLockToggle={handleLockToggle}
1949
- isLocking={lockingEventId}
1950
- />
1951
- )}
1952
-
1953
- {!disabled && !isCreateMode && task && (
1954
- <TaskActivitySection
1955
- wsId={effectiveTaskWsId}
1956
- taskId={task.id}
1957
- boardId={boardId}
1958
- currentTask={{
1959
- id: task.id,
1960
- name: formState.name || task.name || '',
1961
- description: formState.description,
1962
- priority: formState.priority,
1963
- start_date: formState.startDate?.toISOString() || null,
1964
- end_date: formState.endDate?.toISOString() || null,
1965
- estimation_points: formState.estimationPoints ?? null,
1966
- list_id: formState.selectedListId || task.list_id || '',
1967
- list_name:
1968
- availableLists?.find(
1969
- (l) => l.id === formState.selectedListId
1970
- )?.name || null,
1971
- completed: !!task.completed_at,
1972
- assignees: formState.selectedAssignees.map((a) => ({
1973
- id: a.id,
1974
- user_id: a.id,
1975
- })),
1976
- labels: formState.selectedLabels.map((l) => ({
1977
- id: l.id,
1978
- })),
1979
- projects: formState.selectedProjects.map((p) => ({
1980
- id: p.id,
1981
- })),
1982
- }}
1983
- revertDisabled={true}
1984
- />
1985
- )}
2390
+ <div
2391
+ ref={setScrollContainer}
2392
+ className="relative flex min-h-0 flex-1 flex-col overflow-y-auto"
2393
+ >
2394
+ <div className="flex flex-col">
2395
+ <TaskNameInput
2396
+ name={formState.name}
2397
+ isCreateMode={isCreateMode}
2398
+ titleInputRef={titleInputRef}
2399
+ editorRef={editorRef}
2400
+ lastCursorPositionRef={lastCursorPositionRef}
2401
+ targetEditorCursorRef={targetEditorCursorRef}
2402
+ setName={setTaskName}
2403
+ updateName={updateName}
2404
+ flushNameUpdate={flushNameUpdate}
2405
+ disabled={taskTitleDisabled}
2406
+ />
2407
+
2408
+ {smartSuggestionsButton && (
2409
+ <div className="flex justify-end px-4 pb-2 md:px-8">
2410
+ {smartSuggestionsButton}
2411
+ </div>
2412
+ )}
2413
+
2414
+ {!disabled && renderTaskPropertiesSection()}
2415
+
2416
+ {taskHydrationNotice}
2417
+
2418
+ {smartSuggestionsPanel && (
2419
+ <div className="px-4 pb-3 md:px-8">
2420
+ {smartSuggestionsPanel}
2421
+ </div>
2422
+ )}
2423
+
2424
+ {!taskControlsDisabled && !isCreateMode && (
2425
+ <PersonalOverridesSection
2426
+ taskId={task?.id}
2427
+ isCreateMode={isCreateMode}
2428
+ boardConfig={boardConfig}
2429
+ onUpdate={onUpdate}
2430
+ />
2431
+ )}
2432
+
2433
+ {!taskControlsDisabled &&
2434
+ !draftId &&
2435
+ !(isCreateMode && saveAsDraft) && (
2436
+ <TaskRelationshipsProperties
2437
+ wsId={effectiveTaskWsId}
2438
+ taskId={task?.id}
2439
+ boardId={boardId}
2440
+ listId={task?.list_id}
2441
+ isCreateMode={isCreateMode}
2442
+ initialActiveTab={
2443
+ seededPendingRelationships.initialActiveTab
2444
+ }
2445
+ initialDependencySubTab={
2446
+ seededPendingRelationships.initialDependencySubTab
2447
+ }
2448
+ parentTask={parentTask}
2449
+ childTasks={childTasks}
2450
+ blockingTasks={blockingTasks}
2451
+ blockedByTasks={blockedByTasks}
2452
+ relatedTasks={relatedTasks}
2453
+ isLoading={dependenciesLoading}
2454
+ onSetParent={setParentTask}
2455
+ onRemoveParent={() => setParentTask(null)}
2456
+ onAddBlockingTask={addBlockingTask}
2457
+ onRemoveBlockingTask={removeBlockingTask}
2458
+ onAddBlockedByTask={addBlockedByTask}
2459
+ onRemoveBlockedByTask={removeBlockedByTask}
2460
+ onAddRelatedTask={addRelatedTask}
2461
+ onRemoveRelatedTask={removeRelatedTask}
2462
+ onNavigateToTask={async (taskId) => {
2463
+ if (onNavigateToTask)
2464
+ await onNavigateToTask(taskId);
2465
+ }}
2466
+ onAddSubtask={isCreateMode ? undefined : onAddSubtask}
2467
+ onAddParentTask={
2468
+ isCreateMode ? undefined : onAddParentTask
2469
+ }
2470
+ onAddBlockingTaskDialog={
2471
+ isCreateMode ? undefined : onAddBlockingTask
2472
+ }
2473
+ onAddBlockedByTaskDialog={
2474
+ isCreateMode ? undefined : onAddBlockedByTask
2475
+ }
2476
+ onAddRelatedTaskDialog={
2477
+ isCreateMode ? undefined : onAddRelatedTask
2478
+ }
2479
+ onAddExistingAsSubtask={addChildTask}
2480
+ isSaving={!!savingRelationship}
2481
+ savingTaskId={savingRelationship}
2482
+ disabled={taskControlsDisabled}
2483
+ />
2484
+ )}
2485
+
2486
+ <TaskDescriptionEditor
2487
+ description={formState.description}
2488
+ setDescription={formState.setDescription}
2489
+ isOpen={isOpen}
2490
+ isCreateMode={isCreateMode}
2491
+ collaborationMode={effectiveCollaborationMode}
2492
+ realtimeEnabled={effectiveRealtimeEnabled}
2493
+ isYjsSyncing={isYjsSyncing}
2494
+ wsId={effectiveTaskWsId}
2495
+ boardId={boardId}
2496
+ taskId={task?.id}
2497
+ availableLists={availableLists}
2498
+ queryClient={queryClient}
2499
+ editorRef={editorRef}
2500
+ richTextEditorRef={richTextEditorRef}
2501
+ titleInputRef={titleInputRef}
2502
+ lastCursorPositionRef={lastCursorPositionRef}
2503
+ targetEditorCursorRef={targetEditorCursorRef}
2504
+ flushEditorPendingRef={flushEditorPendingRef}
2505
+ yjsDoc={doc}
2506
+ yjsProvider={provider ?? undefined}
2507
+ collaborationUser={
2508
+ user
2509
+ ? {
2510
+ id: user.id || '',
2511
+ name: userDisplayName,
2512
+ color: userColor || '',
2513
+ }
2514
+ : null
2515
+ }
2516
+ onImageUpload={imageUploadHandler}
2517
+ onEditorReady={handleEditorReady}
2518
+ onConvertToTask={handleConvertToTask}
2519
+ onDescriptionStorageLengthChange={
2520
+ handleDescriptionStorageLengthChange
2521
+ }
2522
+ descriptionStorageLength={descriptionStorageLength}
2523
+ descriptionPercentLeft={descriptionPercentLeft}
2524
+ descriptionLimit={MAX_TASK_DESCRIPTION_LENGTH}
2525
+ isDescriptionOverLimit={isDescriptionOverLimit}
2526
+ disabled={taskControlsDisabled}
2527
+ mentionTranslations={{
2528
+ delete_task: t('delete_task'),
2529
+ delete_task_confirmation: (name: string) =>
2530
+ t('delete_task_confirmation', { name }),
2531
+ cancel: t('cancel'),
2532
+ deleting: t('deleting'),
2533
+ set_custom_due_date: t('set_custom_due_date'),
2534
+ custom_due_date_description: t(
2535
+ 'custom_due_date_description'
2536
+ ),
2537
+ remove_due_date: t('remove_due_date'),
2538
+ create_new_label: t('create_new_label'),
2539
+ create_new_label_description: t(
2540
+ 'create_new_label_description'
2541
+ ),
2542
+ label_name: t('label_name'),
2543
+ color: t('color'),
2544
+ preview: t('preview'),
2545
+ creating: t('creating'),
2546
+ create_label: t('create_label'),
2547
+ create_new_project: t('create_new_project'),
2548
+ create_new_project_description: t(
2549
+ 'create_new_project_description'
2550
+ ),
2551
+ project_name: t('project_name'),
2552
+ create_project: t('create_project'),
2553
+ }}
2554
+ />
2555
+
2556
+ {!isCreateMode && localCalendarEvents && (
2557
+ <TaskInstancesSection
2558
+ wsId={effectiveTaskWsId}
2559
+ taskId={task?.id}
2560
+ scheduledEvents={localCalendarEvents}
2561
+ onLockToggle={handleLockToggle}
2562
+ isLocking={lockingEventId}
2563
+ />
2564
+ )}
2565
+
2566
+ {!disabled && !isCreateMode && task && (
2567
+ <TaskActivitySection
2568
+ wsId={effectiveTaskWsId}
2569
+ taskId={task.id}
2570
+ boardId={boardId}
2571
+ currentTask={{
2572
+ id: task.id,
2573
+ name: formState.name || task.name || '',
2574
+ description: formState.description,
2575
+ priority: formState.priority,
2576
+ start_date:
2577
+ formState.startDate?.toISOString() || null,
2578
+ end_date: formState.endDate?.toISOString() || null,
2579
+ estimation_points: formState.estimationPoints ?? null,
2580
+ list_id:
2581
+ formState.selectedListId || task.list_id || '',
2582
+ list_name:
2583
+ availableLists?.find(
2584
+ (l) => l.id === formState.selectedListId
2585
+ )?.name || null,
2586
+ completed: !!task.completed_at,
2587
+ assignees: formState.selectedAssignees.map((a) => ({
2588
+ id: a.id,
2589
+ user_id: a.id,
2590
+ })),
2591
+ labels: formState.selectedLabels.map((l) => ({
2592
+ id: l.id,
2593
+ })),
2594
+ projects: formState.selectedProjects.map((p) => ({
2595
+ id: p.id,
2596
+ })),
2597
+ }}
2598
+ revertDisabled={true}
2599
+ />
2600
+ )}
2601
+ </div>
2602
+ </div>
1986
2603
  </div>
1987
- </div>
1988
- </div>
1989
-
1990
- <MobileFloatingSaveButton
1991
- isCreateMode={isCreateMode}
1992
- collaborationMode={collaborationMode}
1993
- isLoading={isLoading}
1994
- canSave={canSave && !isDescriptionOverLimit}
1995
- handleSave={handleSave}
1996
- disabled={disabled}
1997
- />
2604
+
2605
+ <MobileFloatingSaveButton
2606
+ isCreateMode={isCreateMode}
2607
+ collaborationMode={effectiveCollaborationMode}
2608
+ isLoading={isLoading}
2609
+ canSave={
2610
+ canSave && !isDescriptionOverLimit && !taskControlsDisabled
2611
+ }
2612
+ handleSave={handleSave}
2613
+ disabled={taskControlsDisabled}
2614
+ />
2615
+ </>
2616
+ )}
1998
2617
  </DialogContent>
1999
2618
  </Dialog>
2000
2619