@tuturuuu/ui 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/biome.json +1 -1
  3. package/package.json +75 -73
  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/currency-input.test.tsx +43 -0
  29. package/src/components/ui/currency-input.tsx +1 -1
  30. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  31. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
  32. package/src/components/ui/custom/combobox.test.tsx +195 -0
  33. package/src/components/ui/custom/combobox.tsx +273 -156
  34. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  35. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  36. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  37. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  38. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  39. package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
  40. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  41. package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
  42. package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
  43. package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
  44. package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
  45. package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
  46. package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
  47. package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
  48. package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
  49. package/src/components/ui/custom/workspace-select.tsx +8 -3
  50. package/src/components/ui/dialog.test.tsx +52 -0
  51. package/src/components/ui/dialog.tsx +6 -2
  52. package/src/components/ui/dropdown-menu.tsx +5 -1
  53. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  54. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  55. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  56. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  57. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  58. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  59. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  60. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  61. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  62. package/src/components/ui/finance/invoices/utils.ts +3 -1
  63. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  64. package/src/components/ui/finance/transactions/form-types.ts +3 -0
  65. package/src/components/ui/finance/transactions/form.tsx +2 -0
  66. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  67. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  68. package/src/components/ui/finance/transactions/transaction-card.tsx +21 -9
  69. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  70. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  71. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  72. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  73. package/src/components/ui/finance/wallets/form.tsx +15 -4
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  75. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  76. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  77. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  78. package/src/components/ui/input-otp.tsx +1 -1
  79. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  80. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  81. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  82. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  83. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  84. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  85. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  86. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  87. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  88. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  89. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  90. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  91. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  92. package/src/components/ui/money-input.test.tsx +64 -0
  93. package/src/components/ui/money-input.tsx +63 -0
  94. package/src/components/ui/navigation-menu.tsx +1 -1
  95. package/src/components/ui/pagination.tsx +1 -1
  96. package/src/components/ui/radio-group.tsx +1 -1
  97. package/src/components/ui/select.tsx +5 -1
  98. package/src/components/ui/sheet.tsx +1 -1
  99. package/src/components/ui/sidebar.tsx +1 -1
  100. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  101. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  102. package/src/components/ui/storefront/cart-summary.tsx +104 -80
  103. package/src/components/ui/storefront/checkout-overlay.tsx +26 -0
  104. package/src/components/ui/storefront/hero-panel.tsx +2 -8
  105. package/src/components/ui/storefront/image-panel.tsx +6 -0
  106. package/src/components/ui/storefront/index.ts +11 -0
  107. package/src/components/ui/storefront/listing-card.tsx +84 -22
  108. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  109. package/src/components/ui/storefront/product-detail.tsx +289 -0
  110. package/src/components/ui/storefront/product-dialog.tsx +72 -0
  111. package/src/components/ui/storefront/storefront-surface.test.tsx +221 -3
  112. package/src/components/ui/storefront/storefront-surface.tsx +288 -153
  113. package/src/components/ui/storefront/types.ts +27 -1
  114. package/src/components/ui/storefront/utils.ts +117 -27
  115. package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
  116. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  117. package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
  118. package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
  119. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  120. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  121. package/src/components/ui/text-editor/content-migration.ts +41 -18
  122. package/src/components/ui/text-editor/editor.tsx +69 -14
  123. package/src/components/ui/text-editor/extensions.ts +9 -3
  124. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  125. package/src/components/ui/text-editor/image-extension.ts +40 -18
  126. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  127. package/src/components/ui/text-editor/video-extension.ts +11 -2
  128. package/src/components/ui/toast.tsx +1 -1
  129. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  130. package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
  131. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  132. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  133. package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
  134. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +113 -46
  135. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  136. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  139. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  141. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +51 -9
  142. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  143. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +127 -38
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  148. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  149. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  150. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  151. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  152. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  153. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  154. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  155. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  156. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  157. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +410 -4
  158. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +106 -14
  159. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  160. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  161. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  162. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +186 -0
  163. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +59 -2
  164. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  165. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  166. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  167. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  168. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  169. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  170. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
  171. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
  172. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
  173. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
  174. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
  175. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  176. package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
  177. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  178. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  179. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  180. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  181. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  182. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  183. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  184. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  185. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  186. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +237 -3
  187. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  188. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  189. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  190. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  191. package/src/components/ui/tu-do/shared/board-header.tsx +465 -937
  192. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  193. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  194. package/src/components/ui/tu-do/shared/board-views.tsx +596 -82
  195. package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
  196. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  197. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  198. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  199. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  200. package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
  201. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
  202. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
  203. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
  204. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
  205. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  206. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  210. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +44 -15
  211. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  212. package/src/declarations.d.ts +1 -0
  213. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  214. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  215. package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
  216. package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
  217. package/src/hooks/use-calendar-sync.tsx +247 -243
  218. package/src/hooks/use-calendar.tsx +323 -138
  219. package/src/hooks/use-task-actions.ts +24 -0
  220. package/src/hooks/use-user-workspace-config.ts +75 -0
  221. package/src/hooks/use-workspace-currency.ts +8 -3
  222. package/src/hooks/useBoardRealtime.ts +6 -3
  223. package/src/hooks/useBoardRealtime.types.ts +11 -0
  224. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
  225. package/src/hooks/useCursorTracking.ts +91 -27
  226. package/src/hooks/useTaskUserRealtime.ts +5 -3
@@ -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,
@@ -47,6 +47,7 @@ import { createInitialSuggestionState } from './mention-system/types';
47
47
  import { SyncWarningDialog } from './sync-warning-dialog';
48
48
  import {
49
49
  normalizeTaskDialogPresentation,
50
+ resolveTaskDialogOpeningPresentation,
50
51
  type TaskDialogPresentation,
51
52
  } from './task-dialog-presentation';
52
53
  import { CompactTaskDialogPanel } from './task-edit-dialog/components/compact-task-create-popover';
@@ -107,6 +108,7 @@ import {
107
108
  getDescriptionContent,
108
109
  getDraftStorageKey,
109
110
  getTaskDescriptionPercentLeft,
111
+ getTaskDescriptionPreviewText,
110
112
  getTaskDescriptionStorageLength,
111
113
  saveAndVerifyYjsDescriptionToDatabase,
112
114
  saveYjsDescriptionToDatabase,
@@ -494,6 +496,23 @@ export function TaskEditDialog({
494
496
  taskSearchQuery,
495
497
  sharedContext,
496
498
  });
499
+ const currentList = availableLists?.find(
500
+ (list) => list.id === formState.selectedListId
501
+ );
502
+ const normalizedDefaultPresentation = useMemo(
503
+ () => normalizeTaskDialogPresentation(defaultPresentation),
504
+ [defaultPresentation]
505
+ );
506
+ const openingPresentation = useMemo(
507
+ () =>
508
+ resolveTaskDialogOpeningPresentation({
509
+ defaultPresentation: normalizedDefaultPresentation,
510
+ draftId,
511
+ mode,
512
+ selectedListStatus: currentList?.status,
513
+ }),
514
+ [currentList?.status, draftId, mode, normalizedDefaultPresentation]
515
+ );
497
516
 
498
517
  // Update browser tab title
499
518
  useEffect(() => {
@@ -552,13 +571,8 @@ export function TaskEditDialog({
552
571
  useState(false);
553
572
  const [showShareDialog, setShowShareDialog] = useState(false);
554
573
  const [saveAsDraft, setSaveAsDraft] = useState(draftModeEnabled);
555
- const normalizedDefaultPresentation = useMemo(
556
- () => normalizeTaskDialogPresentation(defaultPresentation),
557
- [defaultPresentation]
558
- );
559
- const [presentation, setPresentation] = useState<TaskDialogPresentation>(
560
- normalizedDefaultPresentation
561
- );
574
+ const [presentation, setPresentation] =
575
+ useState<TaskDialogPresentation>(openingPresentation);
562
576
  const [smartSuggestions, setSmartSuggestions] = useState<
563
577
  WorkspaceTaskSuggestionTask[]
564
578
  >([]);
@@ -849,9 +863,6 @@ export function TaskEditDialog({
849
863
  onUpdate,
850
864
  });
851
865
 
852
- const currentList = availableLists?.find(
853
- (list) => list.id === formState.selectedListId
854
- );
855
866
  const doneList = availableLists?.find(
856
867
  (list) => list.status === 'done' && !list.deleted
857
868
  );
@@ -1946,7 +1957,7 @@ export function TaskEditDialog({
1946
1957
  previousOpenRef.current = isOpen;
1947
1958
 
1948
1959
  if (!isOpen) {
1949
- setPresentation(normalizedDefaultPresentation);
1960
+ setPresentation(openingPresentation);
1950
1961
  setSmartSuggestions([]);
1951
1962
  setSelectedSmartSuggestionIds([]);
1952
1963
  setSmartSuggestionError(null);
@@ -1957,9 +1968,14 @@ export function TaskEditDialog({
1957
1968
  }
1958
1969
 
1959
1970
  if (justOpened) {
1960
- setPresentation(draftId ? 'fullscreen' : normalizedDefaultPresentation);
1971
+ setPresentation(openingPresentation);
1972
+ return;
1973
+ }
1974
+
1975
+ if (!isCreateMode && currentList?.status === 'documents') {
1976
+ setPresentation('fullscreen');
1961
1977
  }
1962
- }, [isOpen, draftId, normalizedDefaultPresentation]);
1978
+ }, [currentList?.status, isCreateMode, isOpen, openingPresentation]);
1963
1979
 
1964
1980
  // Track whether the title input is scrolled out of view
1965
1981
  useEffect(() => {
@@ -2023,6 +2039,15 @@ export function TaskEditDialog({
2023
2039
  ]);
2024
2040
 
2025
2041
  const showCompactDialog = presentation === 'compact' && !draftId;
2042
+ const compactDescriptionPreview = useMemo(() => {
2043
+ if (isCreateMode) return null;
2044
+
2045
+ const previewText = getTaskDescriptionPreviewText(
2046
+ formState.description
2047
+ ).trim();
2048
+
2049
+ return previewText || null;
2050
+ }, [formState.description, isCreateMode]);
2026
2051
  const taskHydrationNotice = taskLoadError ? (
2027
2052
  <div
2028
2053
  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"
@@ -2233,10 +2258,11 @@ export function TaskEditDialog({
2233
2258
  <Dialog open={isOpen} onOpenChange={handleDialogOpenChange} modal={true}>
2234
2259
  <DialogContent
2235
2260
  showCloseButton={false}
2261
+ presentation={showCompactDialog ? 'default' : 'fullscreen'}
2236
2262
  className={
2237
2263
  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'
2264
+ ? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-visible rounded-lg border p-0 shadow-xl'
2265
+ : undefined
2240
2266
  }
2241
2267
  onContextMenu={(e) => {
2242
2268
  if (shouldPreserveNativeContextMenu(e.target)) {
@@ -2271,6 +2297,8 @@ export function TaskEditDialog({
2271
2297
  iconBgClass={compactHeaderInfo.iconBgClass}
2272
2298
  iconRingClass={compactHeaderInfo.iconRingClass}
2273
2299
  showHeaderTitle={isCreateMode}
2300
+ descriptionPreview={compactDescriptionPreview}
2301
+ descriptionPreviewLabel={dialogT('open_fullscreen')}
2274
2302
  titleInput={
2275
2303
  <TaskNameInput
2276
2304
  name={formState.name}
@@ -2311,6 +2339,7 @@ export function TaskEditDialog({
2311
2339
  }
2312
2340
  onClose={handleAttemptClose}
2313
2341
  onFullscreen={() => setPresentation('fullscreen')}
2342
+ onDescriptionPreviewClick={() => setPresentation('fullscreen')}
2314
2343
  onSave={
2315
2344
  isCreateMode && !taskControlsDisabled ? handleSave : undefined
2316
2345
  }
@@ -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';