@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,16 +1,20 @@
|
|
|
1
1
|
import { Input } from '@tuturuuu/ui/input';
|
|
2
|
+
import { Textarea } from '@tuturuuu/ui/textarea';
|
|
2
3
|
import { MAX_TASK_NAME_LENGTH } from '@tuturuuu/utils/constants';
|
|
3
4
|
import { useTranslations } from 'next-intl';
|
|
5
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
4
6
|
import {
|
|
5
7
|
getNormalizedCursorPosition,
|
|
6
8
|
normalizeLiveTextReplacements,
|
|
7
9
|
normalizeTextReplacements,
|
|
8
10
|
} from '../../../../text-editor/text-replacements';
|
|
9
11
|
|
|
12
|
+
type TaskTitleControlElement = HTMLInputElement | HTMLTextAreaElement;
|
|
13
|
+
|
|
10
14
|
interface TaskNameInputProps {
|
|
11
15
|
name: string;
|
|
12
16
|
isCreateMode: boolean;
|
|
13
|
-
titleInputRef: React.RefObject<
|
|
17
|
+
titleInputRef: React.RefObject<TaskTitleControlElement | null>;
|
|
14
18
|
editorRef: React.RefObject<HTMLDivElement | null>;
|
|
15
19
|
lastCursorPositionRef: React.RefObject<number | null>;
|
|
16
20
|
targetEditorCursorRef: React.MutableRefObject<number | null>;
|
|
@@ -38,6 +42,20 @@ export function TaskNameInput({
|
|
|
38
42
|
}: TaskNameInputProps) {
|
|
39
43
|
const t = useTranslations('ws-task-boards.dialog');
|
|
40
44
|
const isCompact = variant === 'compact';
|
|
45
|
+
const hasPlacedInitialCaretRef = useRef(false);
|
|
46
|
+
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
if (hasPlacedInitialCaretRef.current || disabled) return;
|
|
49
|
+
|
|
50
|
+
const titleInput = titleInputRef.current;
|
|
51
|
+
if (!titleInput) return;
|
|
52
|
+
|
|
53
|
+
hasPlacedInitialCaretRef.current = true;
|
|
54
|
+
const endPosition = titleInput.value.length;
|
|
55
|
+
|
|
56
|
+
titleInput.focus();
|
|
57
|
+
titleInput.setSelectionRange(endPosition, endPosition);
|
|
58
|
+
}, [disabled, titleInputRef]);
|
|
41
59
|
|
|
42
60
|
const focusDescriptionEditor = () => {
|
|
43
61
|
if (isCompact) return;
|
|
@@ -53,124 +71,146 @@ export function TaskNameInput({
|
|
|
53
71
|
}, 0);
|
|
54
72
|
};
|
|
55
73
|
|
|
74
|
+
const handleChange = (e: React.ChangeEvent<TaskTitleControlElement>) => {
|
|
75
|
+
const rawValue = e.target.value;
|
|
76
|
+
const normalizedValue = normalizeLiveTextReplacements(rawValue);
|
|
77
|
+
|
|
78
|
+
if (rawValue !== normalizedValue) {
|
|
79
|
+
const rawCursorPosition = e.target.selectionStart ?? rawValue.length;
|
|
80
|
+
const nextCursorPosition = getNormalizedCursorPosition(
|
|
81
|
+
rawValue,
|
|
82
|
+
rawCursorPosition,
|
|
83
|
+
normalizeLiveTextReplacements
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
requestAnimationFrame(() => {
|
|
87
|
+
titleInputRef.current?.setSelectionRange(
|
|
88
|
+
nextCursorPosition,
|
|
89
|
+
nextCursorPosition
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setName(normalizedValue);
|
|
95
|
+
// Trigger debounced save while typing (in edit mode)
|
|
96
|
+
if (!isCreateMode && normalizedValue.trim()) {
|
|
97
|
+
updateName(normalizedValue);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleBlur = (e: React.FocusEvent<TaskTitleControlElement>) => {
|
|
102
|
+
const normalizedValue = normalizeTextReplacements(e.target.value);
|
|
103
|
+
|
|
104
|
+
if (normalizedValue !== e.target.value) {
|
|
105
|
+
setName(normalizedValue);
|
|
106
|
+
if (!isCreateMode && normalizedValue.trim()) {
|
|
107
|
+
updateName(normalizedValue);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Flush pending save immediately when user clicks away (in edit mode)
|
|
112
|
+
if (!isCreateMode && normalizedValue.trim()) {
|
|
113
|
+
flushNameUpdate();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleKeyDown = (e: React.KeyboardEvent<TaskTitleControlElement>) => {
|
|
118
|
+
// Enter key moves to description
|
|
119
|
+
if (
|
|
120
|
+
e.key === 'Enter' &&
|
|
121
|
+
!e.altKey &&
|
|
122
|
+
!e.ctrlKey &&
|
|
123
|
+
!e.metaKey &&
|
|
124
|
+
!e.shiftKey
|
|
125
|
+
) {
|
|
126
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
e.stopPropagation();
|
|
132
|
+
// Flush pending save immediately when pressing Enter (in edit mode)
|
|
133
|
+
if (!isCreateMode && e.currentTarget.value.trim()) {
|
|
134
|
+
flushNameUpdate();
|
|
135
|
+
}
|
|
136
|
+
if (isCompact) {
|
|
137
|
+
onSubmit?.();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
focusDescriptionEditor();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!isCompact && e.key === 'ArrowDown') {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
const input = e.currentTarget;
|
|
146
|
+
const cursorPosition = input.selectionStart ?? 0;
|
|
147
|
+
|
|
148
|
+
// Store cursor position for smart navigation
|
|
149
|
+
lastCursorPositionRef.current = cursorPosition;
|
|
150
|
+
targetEditorCursorRef.current = cursorPosition;
|
|
151
|
+
|
|
152
|
+
// Focus the editor - cursor positioning will be handled by the editor via prop
|
|
153
|
+
const editorElement = editorRef.current?.querySelector(
|
|
154
|
+
'.ProseMirror'
|
|
155
|
+
) as HTMLElement;
|
|
156
|
+
if (editorElement) {
|
|
157
|
+
editorElement.focus();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Right arrow at end of title moves to description
|
|
162
|
+
if (!isCompact && e.key === 'ArrowRight') {
|
|
163
|
+
const input = e.currentTarget;
|
|
164
|
+
const cursorPosition = input.selectionStart ?? 0;
|
|
165
|
+
const textLength = input.value.length;
|
|
166
|
+
|
|
167
|
+
// Only move if cursor is at the end
|
|
168
|
+
if (cursorPosition === textLength) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
const editorElement = editorRef.current?.querySelector(
|
|
171
|
+
'.ProseMirror'
|
|
172
|
+
) as HTMLElement;
|
|
173
|
+
if (editorElement) {
|
|
174
|
+
editorElement.focus();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (isCompact) {
|
|
181
|
+
return (
|
|
182
|
+
<div className="group">
|
|
183
|
+
<Textarea
|
|
184
|
+
ref={titleInputRef as React.RefObject<HTMLTextAreaElement | null>}
|
|
185
|
+
data-task-name-input
|
|
186
|
+
disabled={disabled}
|
|
187
|
+
value={name}
|
|
188
|
+
maxLength={MAX_TASK_NAME_LENGTH}
|
|
189
|
+
rows={1}
|
|
190
|
+
onChange={handleChange}
|
|
191
|
+
onBlur={handleBlur}
|
|
192
|
+
onKeyDown={handleKeyDown}
|
|
193
|
+
placeholder={t('task_name_placeholder')}
|
|
194
|
+
className="max-h-32 min-h-11 resize-none overflow-y-auto border-0 bg-transparent px-0 py-0 font-semibold text-base text-foreground leading-tight shadow-none transition-colors placeholder:text-muted-foreground/40 focus-visible:outline-0 focus-visible:ring-0 disabled:opacity-100 md:text-lg"
|
|
195
|
+
autoFocus
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
56
201
|
return (
|
|
57
202
|
<div className="group">
|
|
58
203
|
<Input
|
|
59
|
-
ref={titleInputRef}
|
|
204
|
+
ref={titleInputRef as React.RefObject<HTMLInputElement | null>}
|
|
60
205
|
data-task-name-input
|
|
61
206
|
disabled={disabled}
|
|
62
207
|
value={name}
|
|
63
208
|
maxLength={MAX_TASK_NAME_LENGTH}
|
|
64
|
-
onChange={
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (rawValue !== normalizedValue) {
|
|
69
|
-
const rawCursorPosition =
|
|
70
|
-
e.target.selectionStart ?? rawValue.length;
|
|
71
|
-
const nextCursorPosition = getNormalizedCursorPosition(
|
|
72
|
-
rawValue,
|
|
73
|
-
rawCursorPosition,
|
|
74
|
-
normalizeLiveTextReplacements
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
requestAnimationFrame(() => {
|
|
78
|
-
titleInputRef.current?.setSelectionRange(
|
|
79
|
-
nextCursorPosition,
|
|
80
|
-
nextCursorPosition
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
setName(normalizedValue);
|
|
86
|
-
// Trigger debounced save while typing (in edit mode)
|
|
87
|
-
if (!isCreateMode && normalizedValue.trim()) {
|
|
88
|
-
updateName(normalizedValue);
|
|
89
|
-
}
|
|
90
|
-
}}
|
|
91
|
-
onBlur={(e) => {
|
|
92
|
-
const normalizedValue = normalizeTextReplacements(e.target.value);
|
|
93
|
-
|
|
94
|
-
if (normalizedValue !== e.target.value) {
|
|
95
|
-
setName(normalizedValue);
|
|
96
|
-
if (!isCreateMode && normalizedValue.trim()) {
|
|
97
|
-
updateName(normalizedValue);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Flush pending save immediately when user clicks away (in edit mode)
|
|
102
|
-
if (!isCreateMode && normalizedValue.trim()) {
|
|
103
|
-
flushNameUpdate();
|
|
104
|
-
}
|
|
105
|
-
}}
|
|
106
|
-
onKeyDown={(e) => {
|
|
107
|
-
// Enter key moves to description
|
|
108
|
-
if (
|
|
109
|
-
e.key === 'Enter' &&
|
|
110
|
-
!e.altKey &&
|
|
111
|
-
!e.ctrlKey &&
|
|
112
|
-
!e.metaKey &&
|
|
113
|
-
!e.shiftKey
|
|
114
|
-
) {
|
|
115
|
-
if (e.nativeEvent.isComposing || e.keyCode === 229) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
e.stopPropagation();
|
|
121
|
-
// Flush pending save immediately when pressing Enter (in edit mode)
|
|
122
|
-
if (!isCreateMode && e.currentTarget.value.trim()) {
|
|
123
|
-
flushNameUpdate();
|
|
124
|
-
}
|
|
125
|
-
if (isCompact) {
|
|
126
|
-
onSubmit?.();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
focusDescriptionEditor();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!isCompact && e.key === 'ArrowDown') {
|
|
133
|
-
e.preventDefault();
|
|
134
|
-
const input = e.currentTarget;
|
|
135
|
-
const cursorPosition = input.selectionStart ?? 0;
|
|
136
|
-
|
|
137
|
-
// Store cursor position for smart navigation
|
|
138
|
-
lastCursorPositionRef.current = cursorPosition;
|
|
139
|
-
targetEditorCursorRef.current = cursorPosition;
|
|
140
|
-
|
|
141
|
-
// Focus the editor - cursor positioning will be handled by the editor via prop
|
|
142
|
-
const editorElement = editorRef.current?.querySelector(
|
|
143
|
-
'.ProseMirror'
|
|
144
|
-
) as HTMLElement;
|
|
145
|
-
if (editorElement) {
|
|
146
|
-
editorElement.focus();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Right arrow at end of title moves to description
|
|
151
|
-
if (!isCompact && e.key === 'ArrowRight') {
|
|
152
|
-
const input = e.currentTarget;
|
|
153
|
-
const cursorPosition = input.selectionStart ?? 0;
|
|
154
|
-
const textLength = input.value.length;
|
|
155
|
-
|
|
156
|
-
// Only move if cursor is at the end
|
|
157
|
-
if (cursorPosition === textLength) {
|
|
158
|
-
e.preventDefault();
|
|
159
|
-
const editorElement = editorRef.current?.querySelector(
|
|
160
|
-
'.ProseMirror'
|
|
161
|
-
) as HTMLElement;
|
|
162
|
-
if (editorElement) {
|
|
163
|
-
editorElement.focus();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}}
|
|
209
|
+
onChange={handleChange}
|
|
210
|
+
onBlur={handleBlur}
|
|
211
|
+
onKeyDown={handleKeyDown}
|
|
168
212
|
placeholder={t('task_name_placeholder')}
|
|
169
|
-
className=
|
|
170
|
-
isCompact
|
|
171
|
-
? 'h-11 border-0 bg-transparent px-0 font-semibold text-base text-foreground leading-tight shadow-none transition-colors placeholder:text-muted-foreground/40 focus-visible:outline-0 focus-visible:ring-0 disabled:opacity-100 md:text-lg'
|
|
172
|
-
: 'h-auto border-0 bg-transparent px-4 pt-4 pb-2 font-bold text-2xl text-foreground leading-tight tracking-tight shadow-none transition-colors placeholder:text-muted-foreground/30 focus-visible:outline-0 focus-visible:ring-0 disabled:opacity-100 md:px-8 md:pt-4 md:pb-2 md:text-2xl'
|
|
173
|
-
}
|
|
213
|
+
className="h-auto border-0 bg-transparent px-4 pt-4 pb-2 font-bold text-2xl text-foreground leading-tight tracking-tight shadow-none transition-colors placeholder:text-muted-foreground/30 focus-visible:outline-0 focus-visible:ring-0 disabled:opacity-100 md:px-8 md:pt-4 md:pb-2 md:text-2xl"
|
|
174
214
|
autoFocus
|
|
175
215
|
/>
|
|
176
216
|
</div>
|
|
@@ -30,6 +30,8 @@ export interface UseTaskFormResetProps {
|
|
|
30
30
|
isCreateMode: boolean;
|
|
31
31
|
task?: Task;
|
|
32
32
|
filters?: TaskFilters;
|
|
33
|
+
taskHydrationVersion?: number;
|
|
34
|
+
preserveNameOnHydration?: boolean;
|
|
33
35
|
|
|
34
36
|
// State setters - using React dispatch types for compatibility
|
|
35
37
|
setName: React.Dispatch<React.SetStateAction<string>>;
|
|
@@ -55,6 +57,8 @@ export function useTaskFormReset({
|
|
|
55
57
|
isCreateMode,
|
|
56
58
|
task,
|
|
57
59
|
filters,
|
|
60
|
+
taskHydrationVersion = 0,
|
|
61
|
+
preserveNameOnHydration = false,
|
|
58
62
|
setName,
|
|
59
63
|
setDescription,
|
|
60
64
|
setPriority,
|
|
@@ -67,12 +71,15 @@ export function useTaskFormReset({
|
|
|
67
71
|
setSelectedProjects,
|
|
68
72
|
}: UseTaskFormResetProps): void {
|
|
69
73
|
const previousTaskIdRef = useRef<string | null>(null);
|
|
74
|
+
const previousTaskHydrationVersionRef = useRef<number>(taskHydrationVersion);
|
|
70
75
|
const previousIsOpenRef = useRef<boolean>(false);
|
|
71
76
|
const isMountedRef = useRef(true);
|
|
72
77
|
|
|
73
78
|
// Reset form when task changes or dialog opens
|
|
74
79
|
useEffect(() => {
|
|
75
80
|
const taskIdChanged = previousTaskIdRef.current !== task?.id;
|
|
81
|
+
const taskHydrationVersionChanged =
|
|
82
|
+
previousTaskHydrationVersionRef.current !== taskHydrationVersion;
|
|
76
83
|
const justOpened = isOpen && !previousIsOpenRef.current;
|
|
77
84
|
previousIsOpenRef.current = isOpen;
|
|
78
85
|
|
|
@@ -88,8 +95,14 @@ export function useTaskFormReset({
|
|
|
88
95
|
// In edit mode, reset whenever the dialog opens or the task changes.
|
|
89
96
|
// We don't reset on close (see below) so we must reset on every open
|
|
90
97
|
// to ensure the form reflects the latest DB state, even for the same task.
|
|
91
|
-
if (
|
|
92
|
-
|
|
98
|
+
if (
|
|
99
|
+
isOpen &&
|
|
100
|
+
!isCreateMode &&
|
|
101
|
+
(taskIdChanged || taskHydrationVersionChanged || justOpened)
|
|
102
|
+
) {
|
|
103
|
+
if (!(taskHydrationVersionChanged && preserveNameOnHydration)) {
|
|
104
|
+
setName(task?.name || '');
|
|
105
|
+
}
|
|
93
106
|
setDescription(getDescriptionContent(task?.description));
|
|
94
107
|
setPriority(task?.priority || null);
|
|
95
108
|
setStartDate(task?.start_date ? new Date(task?.start_date) : undefined);
|
|
@@ -100,6 +113,7 @@ export function useTaskFormReset({
|
|
|
100
113
|
setSelectedAssignees(task?.assignees || []);
|
|
101
114
|
setSelectedProjects(task?.projects || []);
|
|
102
115
|
if (task?.id) previousTaskIdRef.current = task.id;
|
|
116
|
+
previousTaskHydrationVersionRef.current = taskHydrationVersion;
|
|
103
117
|
} else if (
|
|
104
118
|
isOpen &&
|
|
105
119
|
(isCreateMode || task?.id === 'new') &&
|
|
@@ -122,6 +136,8 @@ export function useTaskFormReset({
|
|
|
122
136
|
isCreateMode,
|
|
123
137
|
isOpen,
|
|
124
138
|
task,
|
|
139
|
+
taskHydrationVersion,
|
|
140
|
+
preserveNameOnHydration,
|
|
125
141
|
filters,
|
|
126
142
|
setName,
|
|
127
143
|
setDescription,
|
|
@@ -66,7 +66,6 @@ export function useTaskRealtimeSync({
|
|
|
66
66
|
isCreateMode,
|
|
67
67
|
isOpen,
|
|
68
68
|
realtimeEnabled = true,
|
|
69
|
-
isPersonalWorkspace = false,
|
|
70
69
|
name,
|
|
71
70
|
priority,
|
|
72
71
|
startDate,
|
|
@@ -262,7 +261,7 @@ export function useTaskRealtimeSync({
|
|
|
262
261
|
);
|
|
263
262
|
|
|
264
263
|
const { broadcast } = useBoardRealtime(boardId, {
|
|
265
|
-
enabled: realtimeActive
|
|
264
|
+
enabled: realtimeActive,
|
|
266
265
|
onTaskChange: handleTaskChange,
|
|
267
266
|
onTaskRelationsChange: handleTaskRelationsChange,
|
|
268
267
|
});
|
|
@@ -47,6 +47,7 @@ interface TaskDialogActionsProps {
|
|
|
47
47
|
onNavigateBack?: () => void;
|
|
48
48
|
onOpenShareDialog?: () => void;
|
|
49
49
|
disabled?: boolean;
|
|
50
|
+
controlsDisabled?: boolean;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export function TaskDialogActions({
|
|
@@ -64,6 +65,7 @@ export function TaskDialogActions({
|
|
|
64
65
|
onNavigateBack,
|
|
65
66
|
onOpenShareDialog,
|
|
66
67
|
disabled = false,
|
|
68
|
+
controlsDisabled = false,
|
|
67
69
|
}: TaskDialogActionsProps) {
|
|
68
70
|
const t = useTranslations();
|
|
69
71
|
const tasksHref = useTasksHref();
|
|
@@ -75,7 +77,7 @@ export function TaskDialogActions({
|
|
|
75
77
|
return (
|
|
76
78
|
<>
|
|
77
79
|
{/* Share button - only in edit mode */}
|
|
78
|
-
{!isCreateMode && taskId && onOpenShareDialog && (
|
|
80
|
+
{!isCreateMode && taskId && onOpenShareDialog && !controlsDisabled && (
|
|
79
81
|
<Tooltip>
|
|
80
82
|
<TooltipTrigger asChild>
|
|
81
83
|
<Button
|
|
@@ -95,7 +97,7 @@ export function TaskDialogActions({
|
|
|
95
97
|
)}
|
|
96
98
|
|
|
97
99
|
{/* More options menu - only in edit mode */}
|
|
98
|
-
{!isCreateMode && taskId && !disabled && (
|
|
100
|
+
{!isCreateMode && taskId && !disabled && !controlsDisabled && (
|
|
99
101
|
<DropdownMenu open={isMoreMenuOpen} onOpenChange={setIsMoreMenuOpen}>
|
|
100
102
|
<DropdownMenuTrigger asChild>
|
|
101
103
|
<Button
|
|
@@ -164,7 +166,7 @@ export function TaskDialogActions({
|
|
|
164
166
|
)}
|
|
165
167
|
|
|
166
168
|
{/* Back to related task button - only in create mode with pending relationship */}
|
|
167
|
-
{showBackButton && (
|
|
169
|
+
{showBackButton && !controlsDisabled && (
|
|
168
170
|
<Tooltip>
|
|
169
171
|
<TooltipTrigger asChild>
|
|
170
172
|
<Button
|