@tuturuuu/ui 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/biome.json +1 -1
  3. package/package.json +73 -71
  4. package/src/components/ui/accordion.tsx +1 -1
  5. package/src/components/ui/breadcrumb.tsx +1 -1
  6. package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
  8. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
  9. package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
  10. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
  12. package/src/components/ui/calendar.tsx +1 -1
  13. package/src/components/ui/carousel.tsx +1 -1
  14. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
  15. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
  16. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
  17. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
  18. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
  19. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
  20. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
  21. package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
  22. package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
  23. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
  24. package/src/components/ui/checkbox.tsx +1 -1
  25. package/src/components/ui/color-picker.tsx +1 -1
  26. package/src/components/ui/command.tsx +1 -1
  27. package/src/components/ui/context-menu.tsx +5 -1
  28. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  29. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
  30. package/src/components/ui/custom/combobox.test.tsx +195 -0
  31. package/src/components/ui/custom/combobox.tsx +273 -156
  32. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  33. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  34. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  35. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  36. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  37. package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
  38. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  39. package/src/components/ui/custom/workspace-select.tsx +8 -3
  40. package/src/components/ui/dialog.test.tsx +52 -0
  41. package/src/components/ui/dialog.tsx +6 -2
  42. package/src/components/ui/dropdown-menu.tsx +5 -1
  43. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  44. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  45. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  46. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  47. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  48. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  49. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  50. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  51. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  52. package/src/components/ui/finance/invoices/utils.ts +3 -1
  53. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  54. package/src/components/ui/finance/transactions/form-types.ts +1 -0
  55. package/src/components/ui/finance/transactions/form.tsx +2 -0
  56. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  57. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  58. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  59. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  60. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  61. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  62. package/src/components/ui/finance/wallets/form.tsx +15 -4
  63. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  64. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  65. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  66. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  67. package/src/components/ui/input-otp.tsx +1 -1
  68. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  69. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  70. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  71. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  72. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  73. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  74. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  75. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  76. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  77. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  78. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  79. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  80. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  81. package/src/components/ui/navigation-menu.tsx +1 -1
  82. package/src/components/ui/pagination.tsx +1 -1
  83. package/src/components/ui/radio-group.tsx +1 -1
  84. package/src/components/ui/select.tsx +5 -1
  85. package/src/components/ui/sheet.tsx +1 -1
  86. package/src/components/ui/sidebar.tsx +1 -1
  87. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  88. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  89. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  90. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  91. package/src/components/ui/storefront/listing-card.tsx +1 -1
  92. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  93. package/src/components/ui/storefront/product-detail.tsx +1 -1
  94. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  95. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  96. package/src/components/ui/storefront/types.ts +4 -0
  97. package/src/components/ui/storefront/utils.ts +6 -0
  98. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  99. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  100. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  101. package/src/components/ui/text-editor/editor.tsx +69 -14
  102. package/src/components/ui/text-editor/extensions.ts +8 -2
  103. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  104. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  105. package/src/components/ui/toast.tsx +1 -1
  106. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  107. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  108. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  109. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +112 -43
  110. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  111. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  112. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  113. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  114. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  115. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  116. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  117. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  118. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  119. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  120. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  121. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  122. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  127. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  128. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  129. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  131. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +397 -2
  132. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +103 -13
  133. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  134. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  135. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  136. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +26 -4
  137. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +5 -2
  138. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  139. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  140. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  141. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  142. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  143. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  144. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  145. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  146. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  147. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  148. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  149. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  150. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  151. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  152. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  153. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  154. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +203 -2
  155. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  156. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  157. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  158. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  159. package/src/components/ui/tu-do/shared/board-header.tsx +464 -975
  160. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  161. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  162. package/src/components/ui/tu-do/shared/board-views.tsx +587 -75
  163. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  164. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  165. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  166. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  167. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  168. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  169. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  170. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  171. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  172. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +2 -1
  173. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  174. package/src/declarations.d.ts +1 -0
  175. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  176. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  177. package/src/hooks/use-calendar-sync.tsx +247 -243
  178. package/src/hooks/use-calendar.tsx +323 -138
  179. package/src/hooks/use-task-actions.ts +24 -0
  180. package/src/hooks/use-user-workspace-config.ts +75 -0
  181. package/src/hooks/use-workspace-currency.ts +8 -3
  182. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
@@ -0,0 +1,91 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+
3
+ import { fireEvent, render, screen } from '@testing-library/react';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { SelectiveRevertPanel } from './selective-revert-panel';
6
+
7
+ vi.mock('./description-diff-viewer', () => ({
8
+ DescriptionDiffViewer: () => (
9
+ <button type="button">view-description-diff</button>
10
+ ),
11
+ }));
12
+
13
+ const t = (key: string, options?: { defaultValue?: string }) => {
14
+ const messages: Record<string, string> = {
15
+ changed: 'Changed',
16
+ 'field.description': 'Description',
17
+ 'field.name': 'Name',
18
+ 'field.priority': 'Priority',
19
+ unchanged_fields: 'Unchanged fields',
20
+ };
21
+
22
+ return messages[key] ?? options?.defaultValue ?? key;
23
+ };
24
+
25
+ const snapshot = {
26
+ assignees: [],
27
+ completed: false,
28
+ description: {
29
+ content: [
30
+ {
31
+ content: [{ text: 'Previous description', type: 'text' }],
32
+ type: 'paragraph',
33
+ },
34
+ ],
35
+ type: 'doc',
36
+ },
37
+ end_date: null,
38
+ estimation_points: null,
39
+ id: 'task-1',
40
+ labels: [],
41
+ list_id: 'list-1',
42
+ list_name: 'Review',
43
+ name: 'Previous task name',
44
+ priority: 'normal' as const,
45
+ projects: [],
46
+ start_date: null,
47
+ };
48
+
49
+ const currentTask = {
50
+ ...snapshot,
51
+ description: {
52
+ content: [
53
+ {
54
+ content: [{ text: 'Current description', type: 'text' }],
55
+ type: 'paragraph',
56
+ },
57
+ ],
58
+ type: 'doc',
59
+ },
60
+ list_name: 'Review',
61
+ name: 'Current task name',
62
+ };
63
+
64
+ describe('SelectiveRevertPanel', () => {
65
+ it('renders changed fields first and keeps unchanged fields collapsed', () => {
66
+ render(
67
+ <SelectiveRevertPanel
68
+ currentTask={currentTask}
69
+ isReverting={false}
70
+ onRevert={vi.fn()}
71
+ snapshot={snapshot}
72
+ t={t}
73
+ />
74
+ );
75
+
76
+ expect(screen.getByText('Core Fields')).toBeInTheDocument();
77
+ expect(screen.getByText('Name')).toBeInTheDocument();
78
+ expect(screen.getByText('Description')).toBeInTheDocument();
79
+ expect(screen.getByText('view-description-diff')).toBeInTheDocument();
80
+
81
+ const unchangedButton = screen.getByRole('button', {
82
+ name: /Unchanged fields/i,
83
+ });
84
+ expect(unchangedButton).toBeInTheDocument();
85
+ expect(screen.queryByText('Priority')).not.toBeInTheDocument();
86
+
87
+ fireEvent.click(unchangedButton);
88
+
89
+ expect(screen.getByText('Priority')).toBeInTheDocument();
90
+ });
91
+ });
@@ -1,7 +1,14 @@
1
1
  'use client';
2
2
 
3
- import { AlertTriangle, Loader2, RotateCcw } from '@tuturuuu/icons';
3
+ import {
4
+ AlertTriangle,
5
+ ChevronDown,
6
+ ChevronRight,
7
+ Loader2,
8
+ RotateCcw,
9
+ } from '@tuturuuu/icons';
4
10
  import { Alert, AlertDescription } from '@tuturuuu/ui/alert';
11
+ import { Badge } from '@tuturuuu/ui/badge';
5
12
  import { Button } from '@tuturuuu/ui/button';
6
13
  import {
7
14
  ALL_COMPARABLE_FIELDS,
@@ -21,15 +28,49 @@ interface SelectiveRevertPanelProps {
21
28
  onRevert: (fields: RevertibleField[]) => Promise<void>;
22
29
  isReverting: boolean;
23
30
  locale?: string;
24
- t?: (key: string, options?: { defaultValue?: string }) => string;
31
+ t?: (
32
+ key: string,
33
+ options?: { count?: number; defaultValue?: string }
34
+ ) => string;
25
35
  /** Estimation type for displaying points */
26
36
  estimationType?: EstimationType;
27
37
  /** When true, disables the revert functionality (feature not stable) */
28
38
  revertDisabled?: boolean;
29
39
  }
30
40
 
31
- const defaultT = (key: string, opts?: { defaultValue?: string }) =>
32
- opts?.defaultValue || key;
41
+ const defaultT = (
42
+ key: string,
43
+ opts?: { count?: number; defaultValue?: string }
44
+ ) => opts?.defaultValue || key;
45
+
46
+ const FIELD_SECTIONS: {
47
+ fields: ComparableField[];
48
+ titleKey: string;
49
+ titleFallback: string;
50
+ }[] = [
51
+ {
52
+ fields: [
53
+ 'name',
54
+ 'description',
55
+ 'priority',
56
+ 'estimation_points',
57
+ 'list_id',
58
+ 'completed',
59
+ ],
60
+ titleFallback: 'Core Fields',
61
+ titleKey: 'core_fields',
62
+ },
63
+ {
64
+ fields: ['start_date', 'end_date'],
65
+ titleFallback: 'Dates',
66
+ titleKey: 'dates',
67
+ },
68
+ {
69
+ fields: ['assignees', 'labels', 'projects'],
70
+ titleFallback: 'Relationships',
71
+ titleKey: 'relationships',
72
+ },
73
+ ];
33
74
 
34
75
  export function SelectiveRevertPanel({
35
76
  snapshot,
@@ -95,6 +136,31 @@ export function SelectiveRevertPanel({
95
136
 
96
137
  const hasChanges = changedFields.length > 0;
97
138
  const hasSelection = selectedFields.size > 0;
139
+ const changedFieldSet = useMemo(
140
+ () => new Set(changedFields),
141
+ [changedFields]
142
+ );
143
+ const unchangedFields = useMemo(
144
+ () => ALL_COMPARABLE_FIELDS.filter((field) => !changedFieldSet.has(field)),
145
+ [changedFieldSet]
146
+ );
147
+
148
+ const renderField = (field: ComparableField) => (
149
+ <FieldDiffViewer
150
+ key={field}
151
+ fieldName={field}
152
+ snapshotValue={fieldValues[field].snapshot}
153
+ currentValue={fieldValues[field].current}
154
+ selected={selectedFields.has(field)}
155
+ onSelectionChange={(selected) => handleFieldSelect(field, selected)}
156
+ hasChanged={changedFieldSet.has(field)}
157
+ locale={locale}
158
+ t={t}
159
+ snapshotListName={snapshot.list_name}
160
+ currentListName={currentTask.list_name}
161
+ estimationType={estimationType}
162
+ />
163
+ );
98
164
 
99
165
  return (
100
166
  <div className="flex flex-col gap-4">
@@ -171,78 +237,37 @@ export function SelectiveRevertPanel({
171
237
 
172
238
  {/* Field list */}
173
239
  <div className="space-y-2">
174
- {/* Core fields */}
175
- <FieldGroup title={t('core_fields', { defaultValue: 'Core Fields' })}>
176
- {(
177
- [
178
- 'name',
179
- 'description',
180
- 'priority',
181
- 'estimation_points',
182
- 'list_id',
183
- 'completed',
184
- ] as ComparableField[]
185
- ).map((field) => (
186
- <FieldDiffViewer
187
- key={field}
188
- fieldName={field}
189
- snapshotValue={fieldValues[field].snapshot}
190
- currentValue={fieldValues[field].current}
191
- selected={selectedFields.has(field)}
192
- onSelectionChange={(selected) =>
193
- handleFieldSelect(field, selected)
194
- }
195
- hasChanged={changedFields.includes(field)}
196
- locale={locale}
197
- t={t}
198
- snapshotListName={snapshot.list_name}
199
- currentListName={currentTask.list_name}
200
- estimationType={estimationType}
201
- />
202
- ))}
203
- </FieldGroup>
240
+ {FIELD_SECTIONS.map((section) => {
241
+ const sectionChangedFields = section.fields.filter((field) =>
242
+ changedFieldSet.has(field)
243
+ );
204
244
 
205
- {/* Date fields */}
206
- <FieldGroup title={t('dates', { defaultValue: 'Dates' })}>
207
- {(['start_date', 'end_date'] as ComparableField[]).map((field) => (
208
- <FieldDiffViewer
209
- key={field}
210
- fieldName={field}
211
- snapshotValue={fieldValues[field].snapshot}
212
- currentValue={fieldValues[field].current}
213
- selected={selectedFields.has(field)}
214
- onSelectionChange={(selected) =>
215
- handleFieldSelect(field, selected)
216
- }
217
- hasChanged={changedFields.includes(field)}
218
- locale={locale}
219
- t={t}
220
- />
221
- ))}
222
- </FieldGroup>
245
+ if (sectionChangedFields.length === 0) return null;
223
246
 
224
- {/* Relationship fields */}
225
- <FieldGroup
226
- title={t('relationships', { defaultValue: 'Relationships' })}
227
- >
228
- {(['assignees', 'labels', 'projects'] as ComparableField[]).map(
229
- (field) => (
230
- <FieldDiffViewer
231
- key={field}
232
- fieldName={field}
233
- snapshotValue={fieldValues[field].snapshot}
234
- currentValue={fieldValues[field].current}
235
- selected={selectedFields.has(field)}
236
- onSelectionChange={(selected) =>
237
- handleFieldSelect(field, selected)
238
- }
239
- hasChanged={changedFields.includes(field)}
240
- locale={locale}
241
- t={t}
242
- />
243
- )
244
- )}
245
- </FieldGroup>
247
+ return (
248
+ <FieldGroup
249
+ key={section.titleKey}
250
+ count={sectionChangedFields.length}
251
+ title={t(section.titleKey, {
252
+ defaultValue: section.titleFallback,
253
+ })}
254
+ >
255
+ {sectionChangedFields.map(renderField)}
256
+ </FieldGroup>
257
+ );
258
+ })}
259
+
260
+ {unchangedFields.length > 0 && (
261
+ <FieldGroup
262
+ count={unchangedFields.length}
263
+ defaultCollapsed
264
+ title={t('unchanged_fields', {
265
+ defaultValue: 'Unchanged fields',
266
+ })}
267
+ >
268
+ {unchangedFields.map(renderField)}
269
+ </FieldGroup>
270
+ )}
246
271
  </div>
247
272
 
248
273
  {/* Revert button */}
@@ -271,18 +296,38 @@ export function SelectiveRevertPanel({
271
296
  }
272
297
 
273
298
  function FieldGroup({
299
+ count,
300
+ defaultCollapsed = false,
274
301
  title,
275
302
  children,
276
303
  }: {
304
+ count: number;
305
+ defaultCollapsed?: boolean;
277
306
  title: string;
278
307
  children: React.ReactNode;
279
308
  }) {
309
+ const [isOpen, setIsOpen] = useState(!defaultCollapsed);
310
+
280
311
  return (
281
312
  <div className="space-y-2">
282
- <h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
283
- {title}
284
- </h4>
285
- <div className="space-y-2">{children}</div>
313
+ <button
314
+ className="flex w-full items-center gap-2 rounded-md px-1 py-1 text-left transition-colors hover:bg-muted/40"
315
+ onClick={() => setIsOpen((open) => !open)}
316
+ type="button"
317
+ >
318
+ {isOpen ? (
319
+ <ChevronDown className="h-3.5 w-3.5 text-muted-foreground" />
320
+ ) : (
321
+ <ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
322
+ )}
323
+ <span className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
324
+ {title}
325
+ </span>
326
+ <Badge variant="secondary" className="h-5 px-1.5 text-[10px]">
327
+ {count}
328
+ </Badge>
329
+ </button>
330
+ {isOpen && <div className="space-y-2">{children}</div>}
286
331
  </div>
287
332
  );
288
333
  }
@@ -93,6 +93,7 @@ export function TaskActivitySection({
93
93
  revertDisabled = false,
94
94
  }: TaskActivitySectionProps) {
95
95
  const t = useTranslations('tasks-history');
96
+ const snapshotT = useTranslations('tasks.history');
96
97
  const locale = useLocale();
97
98
  const [isExpanded, setIsExpanded] = useState(false);
98
99
  const [showAll, setShowAll] = useState(false);
@@ -219,7 +220,12 @@ export function TaskActivitySection({
219
220
  onTaskUpdate?.();
220
221
  }}
221
222
  locale={locale}
222
- t={t as (key: string, options?: { defaultValue?: string }) => string}
223
+ t={
224
+ snapshotT as (
225
+ key: string,
226
+ options?: { count?: number; defaultValue?: string }
227
+ ) => string
228
+ }
223
229
  estimationType={estimationType}
224
230
  revertDisabled={revertDisabled}
225
231
  />
@@ -29,15 +29,20 @@ interface TaskSnapshotDialogProps {
29
29
  onClose: () => void;
30
30
  onRevertSuccess?: () => void;
31
31
  locale?: string;
32
- t?: (key: string, options?: { defaultValue?: string }) => string;
32
+ t?: (
33
+ key: string,
34
+ options?: { count?: number; defaultValue?: string }
35
+ ) => string;
33
36
  /** Estimation type for displaying points */
34
37
  estimationType?: EstimationType;
35
38
  /** When true, disables the revert functionality (feature not stable) */
36
39
  revertDisabled?: boolean;
37
40
  }
38
41
 
39
- const defaultT = (key: string, opts?: { defaultValue?: string }) =>
40
- opts?.defaultValue || key;
42
+ const defaultT = (
43
+ key: string,
44
+ opts?: { count?: number; defaultValue?: string }
45
+ ) => opts?.defaultValue || key;
41
46
 
42
47
  export function TaskSnapshotDialog({
43
48
  wsId,
@@ -2258,10 +2258,11 @@ export function TaskEditDialog({
2258
2258
  <Dialog open={isOpen} onOpenChange={handleDialogOpenChange} modal={true}>
2259
2259
  <DialogContent
2260
2260
  showCloseButton={false}
2261
+ presentation={showCompactDialog ? 'default' : 'fullscreen'}
2261
2262
  className={
2262
2263
  showCompactDialog
2263
2264
  ? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-visible rounded-lg border p-0 shadow-xl'
2264
- : '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'
2265
+ : undefined
2265
2266
  }
2266
2267
  onContextMenu={(e) => {
2267
2268
  if (shouldPreserveNativeContextMenu(e.target)) {
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query';
4
- import { Loader2 } from '@tuturuuu/icons';
5
4
  import { getWorkspaceTask } from '@tuturuuu/internal-api/tasks';
6
5
  import { useRouter } from 'next/navigation';
7
6
  import { useTranslations } from 'next-intl';
8
7
  import { useEffect } from 'react';
8
+ import { TaskBoardLoadingState } from './task-board-loading-state';
9
9
  import { buildWorkspaceTaskUrl } from './task-url';
10
10
 
11
11
  interface TaskLegacyRouteRecoveryProps {
@@ -61,12 +61,5 @@ export function TaskLegacyRouteRecovery({
61
61
  );
62
62
  }
63
63
 
64
- return (
65
- <div className="flex min-h-[40vh] items-center justify-center p-6">
66
- <div className="flex items-center gap-3 text-muted-foreground">
67
- <Loader2 className="h-5 w-5 animate-spin" />
68
- <span>{t('loading')}</span>
69
- </div>
70
- </div>
71
- );
64
+ return <TaskBoardLoadingState />;
72
65
  }
@@ -1,2 +1,3 @@
1
1
  declare module '*.css';
2
2
  declare module '*.layer.css';
3
+ declare module 'moment/locale/vi';