@tuturuuu/ui 0.8.0 → 0.10.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 (245) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/biome.json +1 -1
  3. package/package.json +74 -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-search.test.ts +78 -0
  29. package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
  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 +46 -1
  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/nav-link.test.tsx +165 -0
  40. package/src/components/ui/custom/nav-link.tsx +69 -11
  41. package/src/components/ui/custom/navigation.tsx +1 -0
  42. package/src/components/ui/custom/settings/task-settings.tsx +104 -0
  43. package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
  44. package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
  45. package/src/components/ui/custom/settings-dialog-search.ts +75 -0
  46. package/src/components/ui/custom/settings-dialog-shell.tsx +65 -28
  47. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  48. package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
  49. package/src/components/ui/custom/workspace-select.tsx +25 -19
  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 +1 -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-edit-dialog.tsx +1 -4
  69. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  70. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  71. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  72. package/src/components/ui/finance/wallets/form.tsx +15 -4
  73. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  74. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  75. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  76. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  77. package/src/components/ui/input-otp.tsx +1 -1
  78. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  79. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  80. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  81. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  82. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  83. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  84. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  85. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  86. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  87. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  88. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  89. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  90. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  91. package/src/components/ui/navigation-menu.tsx +1 -1
  92. package/src/components/ui/pagination.tsx +1 -1
  93. package/src/components/ui/radio-group.tsx +1 -1
  94. package/src/components/ui/select.tsx +5 -1
  95. package/src/components/ui/sheet.tsx +1 -1
  96. package/src/components/ui/sidebar.tsx +1 -1
  97. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  98. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  99. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  100. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  101. package/src/components/ui/storefront/listing-card.tsx +1 -1
  102. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  103. package/src/components/ui/storefront/product-detail.tsx +1 -1
  104. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  105. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  106. package/src/components/ui/storefront/types.ts +4 -0
  107. package/src/components/ui/storefront/utils.ts +6 -0
  108. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  109. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  110. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  111. package/src/components/ui/text-editor/editor.tsx +69 -14
  112. package/src/components/ui/text-editor/extensions.ts +8 -2
  113. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  114. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  115. package/src/components/ui/toast.tsx +1 -1
  116. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +286 -0
  117. package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
  118. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  119. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +15 -226
  120. package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
  121. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +121 -39
  122. package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
  127. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  128. package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
  129. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  131. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
  132. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  133. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  134. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  135. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  136. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  137. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  138. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  139. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  140. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  141. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  142. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  143. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  144. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  145. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  146. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  147. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +642 -5
  148. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +224 -15
  149. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +535 -53
  150. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +101 -33
  151. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +235 -113
  152. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +50 -5
  153. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +12 -2
  154. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +10 -1
  155. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  156. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
  157. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
  158. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +271 -36
  159. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  160. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  161. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +22 -0
  162. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
  163. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
  164. package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
  165. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
  166. package/src/components/ui/tu-do/boards/form.tsx +1 -1
  167. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  168. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  169. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  170. package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
  171. package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
  172. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
  173. package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
  174. package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
  175. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  176. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  177. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  178. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  179. package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
  180. package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
  181. package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
  182. package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
  183. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
  184. package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
  185. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +141 -1
  186. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +377 -36
  187. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +374 -0
  188. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +419 -5
  189. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +38 -0
  190. package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
  191. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  192. package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
  193. package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
  194. package/src/components/ui/tu-do/shared/board-client.tsx +15 -10
  195. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  196. package/src/components/ui/tu-do/shared/board-header.tsx +471 -975
  197. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  198. package/src/components/ui/tu-do/shared/board-switcher.tsx +244 -220
  199. package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
  200. package/src/components/ui/tu-do/shared/board-views.tsx +577 -85
  201. package/src/components/ui/tu-do/shared/list-view.tsx +246 -2
  202. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  203. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  204. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  205. package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
  206. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
  207. package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
  208. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
  209. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
  210. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  211. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
  212. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
  213. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
  214. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
  215. package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
  216. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
  217. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
  218. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
  219. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
  220. package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
  221. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  222. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  223. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  224. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
  225. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
  226. package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
  227. package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
  228. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  229. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +11 -1
  230. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  231. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
  232. package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
  233. package/src/declarations.d.ts +1 -0
  234. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  235. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  236. package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
  237. package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
  238. package/src/hooks/use-calendar-sync.tsx +247 -243
  239. package/src/hooks/use-calendar.tsx +323 -138
  240. package/src/hooks/use-task-actions.ts +24 -0
  241. package/src/hooks/use-user-workspace-config.ts +75 -0
  242. package/src/hooks/use-workspace-currency.ts +8 -3
  243. package/src/hooks/useBoardPresence.ts +364 -0
  244. package/src/hooks/useBoardRealtimeEventHandler.ts +45 -90
  245. package/src/lib/workspace-actions.ts +2 -6
@@ -39,12 +39,28 @@ dayjs.extend(utc);
39
39
  dayjs.extend(timezone);
40
40
 
41
41
  interface UseMyTasksStateProps {
42
+ disableAutoCreateBoard?: boolean;
43
+ initialBoard?: {
44
+ id: string;
45
+ name: string | null;
46
+ };
47
+ initialListId?: string;
48
+ initialLists?: Array<{
49
+ deleted?: boolean | null;
50
+ id: string;
51
+ name: string | null;
52
+ position?: number | null;
53
+ }>;
42
54
  wsId: string;
43
55
  userId: string;
44
56
  isPersonal: boolean;
45
57
  }
46
58
 
47
59
  export function useMyTasksState({
60
+ disableAutoCreateBoard = false,
61
+ initialBoard,
62
+ initialListId,
63
+ initialLists,
48
64
  wsId,
49
65
  userId,
50
66
  isPersonal,
@@ -96,8 +112,16 @@ export function useMyTasksState({
96
112
  // Board selector state
97
113
  const [boardSelectorOpen, setBoardSelectorOpen] = useState(false);
98
114
  const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(wsId);
99
- const [selectedBoardId, setSelectedBoardId] = useState<string>('');
100
- const [selectedListId, setSelectedListId] = useState<string>('');
115
+ const [selectedBoardId, setSelectedBoardId] = useState<string>(
116
+ initialBoard?.id ?? ''
117
+ );
118
+ const [selectedListId, setSelectedListId] = useState<string>(
119
+ initialListId ??
120
+ initialLists
121
+ ?.filter((list) => !list.deleted)
122
+ .sort((a, b) => (a.position ?? 0) - (b.position ?? 0))[0]?.id ??
123
+ ''
124
+ );
101
125
  const [newBoardDialogOpen, setNewBoardDialogOpen] = useState(false);
102
126
  const [newBoardName, setNewBoardName] = useState<string>('');
103
127
  const [newListDialogOpen, setNewListDialogOpen] = useState(false);
@@ -276,6 +300,7 @@ export function useMyTasksState({
276
300
  });
277
301
  return payload.count ?? 0;
278
302
  },
303
+ enabled: !disableAutoCreateBoard,
279
304
  });
280
305
 
281
306
  // Auto-create a board if the workspace has none
@@ -294,7 +319,13 @@ export function useMyTasksState({
294
319
  );
295
320
 
296
321
  useEffect(() => {
297
- if (wsBoardCountLoading || wsBoardCount === undefined) return;
322
+ if (
323
+ disableAutoCreateBoard ||
324
+ wsBoardCountLoading ||
325
+ wsBoardCount === undefined
326
+ ) {
327
+ return;
328
+ }
298
329
  if (wsBoardCount > 0) return;
299
330
  if (autoCreateAttemptedRef.current) return;
300
331
  autoCreateAttemptedRef.current = true;
@@ -344,6 +375,7 @@ export function useMyTasksState({
344
375
  queryClient,
345
376
  defaultBoardName,
346
377
  defaultListNames,
378
+ disableAutoCreateBoard,
347
379
  ]);
348
380
 
349
381
  // Fetch boards with lists for selected workspace
@@ -422,11 +454,16 @@ export function useMyTasksState({
422
454
  const availableLists = useMemo(() => {
423
455
  if (!selectedBoardId) return [];
424
456
  const board = boardsData.find((b: any) => b.id === selectedBoardId);
425
- if (!board?.task_lists) return [];
457
+ if (!board?.task_lists) {
458
+ if (selectedBoardId !== initialBoard?.id) return [];
459
+ return (initialLists ?? [])
460
+ .filter((list) => !list.deleted)
461
+ .sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
462
+ }
426
463
  return (board.task_lists as any[])
427
464
  .filter((l: any) => !l.deleted)
428
465
  .sort((a: any, b: any) => (a.position || 0) - (b.position || 0));
429
- }, [selectedBoardId, boardsData]);
466
+ }, [selectedBoardId, boardsData, initialBoard?.id, initialLists]);
430
467
 
431
468
  const hasValidSelectedList = useMemo(
432
469
  () => availableLists.some((list: any) => list.id === selectedListId),
@@ -794,13 +831,23 @@ export function useMyTasksState({
794
831
  const selectedDestination = useMemo(() => {
795
832
  if (!selectedBoardId || !selectedListId) return null;
796
833
  const board = boardsData.find((b: any) => b.id === selectedBoardId);
797
- const lists = (board?.task_lists as any[]) || [];
834
+ const isInitialBoard = selectedBoardId === initialBoard?.id;
835
+ const lists =
836
+ (board?.task_lists as any[]) ||
837
+ (isInitialBoard ? (initialLists ?? []) : []);
798
838
  const list = lists.find((l: any) => l.id === selectedListId);
799
839
  return {
800
- boardName: board?.name || 'Unknown Board',
840
+ boardName: board?.name || initialBoard?.name || 'Unknown Board',
801
841
  listName: list?.name || 'Unknown List',
802
842
  };
803
- }, [selectedBoardId, selectedListId, boardsData]);
843
+ }, [
844
+ selectedBoardId,
845
+ selectedListId,
846
+ boardsData,
847
+ initialBoard?.id,
848
+ initialBoard?.name,
849
+ initialLists,
850
+ ]);
804
851
 
805
852
  const handleClearDestination = () => {
806
853
  setSelectedBoardId('');
@@ -41,10 +41,7 @@ export function NoteEditDialog({
41
41
 
42
42
  return (
43
43
  <Dialog open={isOpen} onOpenChange={onOpenChange} modal={true}>
44
- <DialogContent
45
- showCloseButton={false}
46
- className="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"
47
- >
44
+ <DialogContent showCloseButton={false} presentation="fullscreen">
48
45
  {/* Main content area - Note title and description */}
49
46
  <div className="flex min-w-0 flex-1 flex-col bg-background transition-all duration-300">
50
47
  {/* Enhanced Header with gradient */}
@@ -0,0 +1,60 @@
1
+ import type { UseMutationResult } from '@tanstack/react-query';
2
+ import { BarChart3, Upload } from '@tuturuuu/icons';
3
+ import { Button } from '@tuturuuu/ui/button';
4
+ import { Card, CardContent, CardHeader, CardTitle } from '@tuturuuu/ui/card';
5
+ import { Textarea } from '@tuturuuu/ui/textarea';
6
+ import type { useTranslations } from 'next-intl';
7
+
8
+ type Translate = ReturnType<typeof useTranslations>;
9
+
10
+ export function ImportPanel({
11
+ importMutation,
12
+ importPreviewCount,
13
+ importText,
14
+ setImportText,
15
+ t,
16
+ }: {
17
+ importMutation: UseMutationResult<any, unknown, boolean>;
18
+ importPreviewCount: number;
19
+ importText: string;
20
+ setImportText: (value: string) => void;
21
+ t: Translate;
22
+ }) {
23
+ return (
24
+ <Card>
25
+ <CardHeader>
26
+ <CardTitle>{t('import.title')}</CardTitle>
27
+ </CardHeader>
28
+ <CardContent className="space-y-4">
29
+ <Textarea
30
+ className="min-h-56 font-mono text-sm"
31
+ onChange={(event) => setImportText(event.target.value)}
32
+ placeholder={t('import.placeholder')}
33
+ value={importText}
34
+ />
35
+ <div className="flex flex-wrap items-center justify-between gap-3">
36
+ <div className="text-muted-foreground text-sm">
37
+ {t('import.preview_count', { count: importPreviewCount })}
38
+ </div>
39
+ <div className="flex gap-2">
40
+ <Button
41
+ disabled={!importText.trim() || importMutation.isPending}
42
+ onClick={() => importMutation.mutate(false)}
43
+ variant="outline"
44
+ >
45
+ <BarChart3 className="mr-2 h-4 w-4" />
46
+ {t('actions.preview')}
47
+ </Button>
48
+ <Button
49
+ disabled={!importText.trim() || importMutation.isPending}
50
+ onClick={() => importMutation.mutate(true)}
51
+ >
52
+ <Upload className="mr-2 h-4 w-4" />
53
+ {t('actions.commit_import')}
54
+ </Button>
55
+ </div>
56
+ </div>
57
+ </CardContent>
58
+ </Card>
59
+ );
60
+ }
@@ -0,0 +1,156 @@
1
+ import type { UseMutationResult } from '@tanstack/react-query';
2
+ import { Trophy } from '@tuturuuu/icons';
3
+ import type { TaskProgressMetric } from '@tuturuuu/internal-api';
4
+ import { Badge } from '@tuturuuu/ui/badge';
5
+ import { Button } from '@tuturuuu/ui/button';
6
+ import { Card, CardContent, CardHeader, CardTitle } from '@tuturuuu/ui/card';
7
+ import { Input } from '@tuturuuu/ui/input';
8
+ import type { useTranslations } from 'next-intl';
9
+
10
+ type Translate = ReturnType<typeof useTranslations>;
11
+ const today = () => new Date().toISOString().slice(0, 10);
12
+
13
+ function MetricSelect({
14
+ metrics,
15
+ selectedMetric,
16
+ }: {
17
+ metrics: TaskProgressMetric[];
18
+ selectedMetric: TaskProgressMetric | null;
19
+ }) {
20
+ return (
21
+ <select
22
+ className="h-10 rounded-md border bg-background px-3 text-sm"
23
+ defaultValue={selectedMetric?.id}
24
+ name="metric_id"
25
+ required
26
+ >
27
+ {metrics.map((metric) => (
28
+ <option key={metric.id} value={metric.id}>
29
+ {metric.name}
30
+ </option>
31
+ ))}
32
+ </select>
33
+ );
34
+ }
35
+
36
+ export function LeaderboardsPanel(props: {
37
+ createLeaderboardMutation: UseMutationResult<any, unknown, FormData>;
38
+ createTeamMutation: UseMutationResult<
39
+ any,
40
+ unknown,
41
+ { formData: FormData; leaderboardId: string }
42
+ >;
43
+ leaderboards: any[];
44
+ metrics: TaskProgressMetric[];
45
+ selectedMetric: TaskProgressMetric | null;
46
+ t: Translate;
47
+ }) {
48
+ const {
49
+ createLeaderboardMutation,
50
+ createTeamMutation,
51
+ leaderboards,
52
+ metrics,
53
+ selectedMetric,
54
+ t,
55
+ } = props;
56
+
57
+ return (
58
+ <div className="grid gap-4 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)]">
59
+ <Card>
60
+ <CardHeader>
61
+ <CardTitle>{t('leaderboards.create_leaderboard')}</CardTitle>
62
+ </CardHeader>
63
+ <CardContent>
64
+ <form
65
+ className="grid gap-3"
66
+ onSubmit={(event) => {
67
+ event.preventDefault();
68
+ createLeaderboardMutation.mutate(
69
+ new FormData(event.currentTarget)
70
+ );
71
+ event.currentTarget.reset();
72
+ }}
73
+ >
74
+ <Input
75
+ name="name"
76
+ placeholder={t('fields.leaderboard_name')}
77
+ required
78
+ />
79
+ <MetricSelect metrics={metrics} selectedMetric={selectedMetric} />
80
+ <Input defaultValue={today()} name="period_start" type="date" />
81
+ <Input name="period_end" type="date" />
82
+ <Button
83
+ disabled={!selectedMetric || createLeaderboardMutation.isPending}
84
+ >
85
+ <Trophy className="mr-2 h-4 w-4" />
86
+ {t('actions.add_leaderboard')}
87
+ </Button>
88
+ </form>
89
+ </CardContent>
90
+ </Card>
91
+ <div className="grid gap-3">
92
+ {leaderboards.length === 0 ? (
93
+ <Card>
94
+ <CardContent className="py-8 text-sm">
95
+ {t('empty.leaderboards')}
96
+ </CardContent>
97
+ </Card>
98
+ ) : (
99
+ leaderboards.map((leaderboard) => (
100
+ <Card key={leaderboard.id}>
101
+ <CardContent className="space-y-4 py-4">
102
+ <div className="flex items-start justify-between gap-3">
103
+ <div>
104
+ <div className="font-semibold">{leaderboard.name}</div>
105
+ <div className="text-muted-foreground text-sm">
106
+ {leaderboard.metric?.name} · {leaderboard.join_code}
107
+ </div>
108
+ </div>
109
+ <Badge>{leaderboard.rankings?.length ?? 0}</Badge>
110
+ </div>
111
+ <div className="space-y-2">
112
+ {(leaderboard.rankings ?? [])
113
+ .slice(0, 5)
114
+ .map((member: any) => (
115
+ <div
116
+ className="flex items-center justify-between rounded-md border p-2"
117
+ key={member.id}
118
+ >
119
+ <span>
120
+ #{member.rank} {member.display_name || member.user_id}
121
+ </span>
122
+ <strong>
123
+ {Number(member.value ?? 0).toLocaleString()}
124
+ </strong>
125
+ </div>
126
+ ))}
127
+ </div>
128
+ <form
129
+ className="grid gap-2 border-t pt-3 sm:grid-cols-[1fr_7rem_auto]"
130
+ onSubmit={(event) => {
131
+ event.preventDefault();
132
+ createTeamMutation.mutate({
133
+ formData: new FormData(event.currentTarget),
134
+ leaderboardId: leaderboard.id,
135
+ });
136
+ event.currentTarget.reset();
137
+ }}
138
+ >
139
+ <Input
140
+ name="name"
141
+ placeholder={t('fields.team_name')}
142
+ required
143
+ />
144
+ <Input name="color" placeholder={t('fields.color')} />
145
+ <Button size="sm" variant="outline">
146
+ {t('actions.add_team')}
147
+ </Button>
148
+ </form>
149
+ </CardContent>
150
+ </Card>
151
+ ))
152
+ )}
153
+ </div>
154
+ </div>
155
+ );
156
+ }
@@ -0,0 +1,348 @@
1
+ 'use client';
2
+
3
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
+ import { BarChart3, Flag, Target, TrendingUp, Trophy } from '@tuturuuu/icons';
5
+ import {
6
+ createTaskLeaderboard,
7
+ createTaskLeaderboardTeam,
8
+ createTaskProgressEntry,
9
+ createTaskProgressGoal,
10
+ createTaskProgressMetric,
11
+ getTaskProgressStats,
12
+ importTaskProgressEntries,
13
+ isTaskProgressSchemaUnavailable,
14
+ listTaskLeaderboards,
15
+ listTaskProgressEntries,
16
+ listTaskProgressGoals,
17
+ listTaskProgressMetrics,
18
+ type TaskProgressMetric,
19
+ } from '@tuturuuu/internal-api';
20
+ import { Button } from '@tuturuuu/ui/button';
21
+ import { Card, CardContent } from '@tuturuuu/ui/card';
22
+ import { toast } from '@tuturuuu/ui/sonner';
23
+ import Link from 'next/link';
24
+ import { useTranslations } from 'next-intl';
25
+ import { useMemo, useState } from 'react';
26
+ import { ImportPanel } from './task-progress-import-panel';
27
+ import { LeaderboardsPanel } from './task-progress-leaderboards-panel';
28
+ import {
29
+ GoalsPanel,
30
+ ProgressPanel,
31
+ StatsPanel,
32
+ SummaryCard,
33
+ } from './task-progress-panels';
34
+
35
+ export type TaskProgressView =
36
+ | 'progress'
37
+ | 'goals'
38
+ | 'stats'
39
+ | 'leaderboards'
40
+ | 'import';
41
+
42
+ interface TaskProgressPageProps {
43
+ routeWsId: string;
44
+ view: TaskProgressView;
45
+ wsId: string;
46
+ }
47
+
48
+ const today = () => new Date().toISOString().slice(0, 10);
49
+
50
+ function metricOption(metrics: TaskProgressMetric[]) {
51
+ return metrics.find((metric) => metric.is_default) ?? metrics[0] ?? null;
52
+ }
53
+
54
+ function parseImportRows(text: string, metrics: TaskProgressMetric[]) {
55
+ const metricByName = new Map(
56
+ metrics.map((metric) => [metric.name.trim().toLowerCase(), metric])
57
+ );
58
+ const fallbackMetric = metricOption(metrics);
59
+
60
+ return text
61
+ .split(/\r?\n/u)
62
+ .map((line) => line.trim())
63
+ .filter(Boolean)
64
+ .map((line) => {
65
+ const [entry_date, rawValue, metricName, rawTags, note] = line
66
+ .split(',')
67
+ .map((part) => part.trim());
68
+ const metric =
69
+ (metricName ? metricByName.get(metricName.toLowerCase()) : null) ??
70
+ fallbackMetric;
71
+
72
+ if (!metric) throw new Error('missing_metric');
73
+
74
+ return {
75
+ entry_date: entry_date || today(),
76
+ metric_id: metric.id,
77
+ value: Number(rawValue || 0),
78
+ tags: rawTags
79
+ ? rawTags
80
+ .split('|')
81
+ .map((tag) => tag.trim())
82
+ .filter(Boolean)
83
+ : [],
84
+ note: note || null,
85
+ };
86
+ });
87
+ }
88
+
89
+ export function TaskProgressPage({
90
+ routeWsId,
91
+ view,
92
+ wsId,
93
+ }: TaskProgressPageProps) {
94
+ const t = useTranslations('task-progress');
95
+ const queryClient = useQueryClient();
96
+ const [importText, setImportText] = useState('');
97
+ const [importPreviewCount, setImportPreviewCount] = useState(0);
98
+ const queryRoot = ['task-progress', wsId];
99
+
100
+ const metricsQuery = useQuery({
101
+ queryKey: [...queryRoot, 'metrics'],
102
+ queryFn: () => listTaskProgressMetrics(wsId),
103
+ });
104
+ const metrics =
105
+ metricsQuery.data?.ok === true ? metricsQuery.data.metrics : [];
106
+ const selectedMetric = useMemo(() => metricOption(metrics), [metrics]);
107
+
108
+ const entriesQuery = useQuery({
109
+ queryKey: [...queryRoot, 'entries', selectedMetric?.id],
110
+ queryFn: () =>
111
+ listTaskProgressEntries(wsId, {
112
+ metric_id: selectedMetric?.id,
113
+ pageSize: 25,
114
+ }),
115
+ enabled: metrics.length > 0,
116
+ });
117
+ const goalsQuery = useQuery({
118
+ queryKey: [...queryRoot, 'goals'],
119
+ queryFn: () => listTaskProgressGoals(wsId, { status: 'active' }),
120
+ });
121
+ const statsQuery = useQuery({
122
+ queryKey: [...queryRoot, 'stats', selectedMetric?.id],
123
+ queryFn: () =>
124
+ getTaskProgressStats(wsId, { metric_id: selectedMetric?.id }),
125
+ enabled: metrics.length > 0,
126
+ });
127
+ const leaderboardsQuery = useQuery({
128
+ queryKey: [...queryRoot, 'leaderboards'],
129
+ queryFn: () => listTaskLeaderboards(wsId, { status: 'active' }),
130
+ });
131
+
132
+ const invalidateProgress = () =>
133
+ queryClient.invalidateQueries({ queryKey: queryRoot });
134
+
135
+ const createMetricMutation = useMutation({
136
+ mutationFn: (formData: FormData) =>
137
+ createTaskProgressMetric(wsId, {
138
+ name: String(formData.get('name') ?? ''),
139
+ unit_label: String(formData.get('unit_label') ?? ''),
140
+ unit_kind: 'custom',
141
+ }),
142
+ onSuccess: () => {
143
+ toast.success(t('toast.metric_created'));
144
+ invalidateProgress();
145
+ },
146
+ });
147
+ const createEntryMutation = useMutation({
148
+ mutationFn: (formData: FormData) =>
149
+ createTaskProgressEntry(wsId, {
150
+ metric_id: String(formData.get('metric_id') ?? ''),
151
+ entry_date: String(formData.get('entry_date') ?? today()),
152
+ value: Number(formData.get('value') ?? 0),
153
+ tags: String(formData.get('tags') ?? '')
154
+ .split(',')
155
+ .map((tag) => tag.trim())
156
+ .filter(Boolean),
157
+ note: String(formData.get('note') ?? '') || null,
158
+ }),
159
+ onSuccess: () => {
160
+ toast.success(t('toast.entry_created'));
161
+ invalidateProgress();
162
+ },
163
+ });
164
+ const createGoalMutation = useMutation({
165
+ mutationFn: (formData: FormData) =>
166
+ createTaskProgressGoal(wsId, {
167
+ metric_id: String(formData.get('metric_id') ?? ''),
168
+ name: String(formData.get('name') ?? ''),
169
+ target_value: Number(formData.get('target_value') ?? 0),
170
+ period_start: String(formData.get('period_start') ?? today()),
171
+ period_end: String(formData.get('period_end') || '') || null,
172
+ goal_type:
173
+ String(formData.get('goal_type')) === 'habit' ? 'habit' : 'target',
174
+ }),
175
+ onSuccess: () => {
176
+ toast.success(t('toast.goal_created'));
177
+ invalidateProgress();
178
+ },
179
+ });
180
+ const createLeaderboardMutation = useMutation({
181
+ mutationFn: (formData: FormData) =>
182
+ createTaskLeaderboard(wsId, {
183
+ metric_id: String(formData.get('metric_id') ?? ''),
184
+ name: String(formData.get('name') ?? ''),
185
+ period_start: String(formData.get('period_start') ?? today()),
186
+ period_end: String(formData.get('period_end') || '') || null,
187
+ }),
188
+ onSuccess: () => {
189
+ toast.success(t('toast.leaderboard_created'));
190
+ invalidateProgress();
191
+ },
192
+ });
193
+ const createTeamMutation = useMutation({
194
+ mutationFn: ({
195
+ formData,
196
+ leaderboardId,
197
+ }: {
198
+ formData: FormData;
199
+ leaderboardId: string;
200
+ }) =>
201
+ createTaskLeaderboardTeam(wsId, leaderboardId, {
202
+ name: String(formData.get('name') ?? ''),
203
+ color: String(formData.get('color') || '') || null,
204
+ }),
205
+ onSuccess: () => {
206
+ toast.success(t('toast.team_created'));
207
+ invalidateProgress();
208
+ },
209
+ });
210
+ const importMutation = useMutation({
211
+ mutationFn: (commit: boolean) =>
212
+ importTaskProgressEntries(wsId, {
213
+ commit,
214
+ entries: parseImportRows(importText, metrics),
215
+ }),
216
+ onSuccess: (response) => {
217
+ if (response.ok) {
218
+ setImportPreviewCount(response.summary.entriesCount);
219
+ toast.success(
220
+ response.committed
221
+ ? t('toast.import_committed')
222
+ : t('toast.import_previewed')
223
+ );
224
+ }
225
+ invalidateProgress();
226
+ },
227
+ onError: () => toast.error(t('toast.import_failed')),
228
+ });
229
+
230
+ const hasPendingSchema =
231
+ isTaskProgressSchemaUnavailable(metricsQuery.data) ||
232
+ isTaskProgressSchemaUnavailable(statsQuery.data);
233
+ const entries = entriesQuery.data?.ok ? entriesQuery.data.entries : [];
234
+ const goals = goalsQuery.data?.ok ? goalsQuery.data.goals : [];
235
+ const stats = statsQuery.data?.ok ? statsQuery.data : null;
236
+ const leaderboards = leaderboardsQuery.data?.ok
237
+ ? leaderboardsQuery.data.leaderboards
238
+ : [];
239
+
240
+ return (
241
+ <div className="flex flex-col gap-6 p-4 md:p-6">
242
+ <div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
243
+ <div className="space-y-2">
244
+ <div className="flex items-center gap-3">
245
+ <div className="flex h-10 w-10 items-center justify-center rounded-lg bg-dynamic-blue/10 ring-1 ring-dynamic-blue/20">
246
+ <TrendingUp className="h-5 w-5 text-dynamic-blue" />
247
+ </div>
248
+ <div>
249
+ <h1 className="font-bold text-2xl tracking-tight">
250
+ {t(`views.${view}.title`)}
251
+ </h1>
252
+ <p className="text-muted-foreground text-sm">
253
+ {t(`views.${view}.description`)}
254
+ </p>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ <div className="flex flex-wrap gap-2">
259
+ {(
260
+ ['progress', 'goals', 'stats', 'leaderboards', 'import'] as const
261
+ ).map((tab) => (
262
+ <Button
263
+ key={tab}
264
+ asChild
265
+ size="sm"
266
+ variant={tab === view ? 'default' : 'outline'}
267
+ >
268
+ <Link href={`/${routeWsId}/tasks/${tab}`}>
269
+ {t(`tabs.${tab}`)}
270
+ </Link>
271
+ </Button>
272
+ ))}
273
+ </div>
274
+ </div>
275
+
276
+ {hasPendingSchema ? (
277
+ <Card>
278
+ <CardContent className="py-8 text-sm">
279
+ {t('schema_unavailable')}
280
+ </CardContent>
281
+ </Card>
282
+ ) : null}
283
+
284
+ <div className="grid gap-4 md:grid-cols-4">
285
+ <SummaryCard
286
+ icon={<BarChart3 className="h-4 w-4" />}
287
+ label={t('summary.total')}
288
+ value={stats?.summary.total ?? 0}
289
+ />
290
+ <SummaryCard
291
+ icon={<Flag className="h-4 w-4" />}
292
+ label={t('summary.entries')}
293
+ value={stats?.summary.entriesCount ?? entries.length}
294
+ />
295
+ <SummaryCard
296
+ icon={<Target className="h-4 w-4" />}
297
+ label={t('summary.goals')}
298
+ value={goals.length}
299
+ />
300
+ <SummaryCard
301
+ icon={<Trophy className="h-4 w-4" />}
302
+ label={t('summary.streak')}
303
+ value={stats?.summary.currentStreak ?? 0}
304
+ />
305
+ </div>
306
+
307
+ {view === 'progress' ? (
308
+ <ProgressPanel
309
+ createEntryMutation={createEntryMutation}
310
+ createMetricMutation={createMetricMutation}
311
+ entries={entries}
312
+ metrics={metrics}
313
+ selectedMetric={selectedMetric}
314
+ t={t}
315
+ />
316
+ ) : null}
317
+ {view === 'goals' ? (
318
+ <GoalsPanel
319
+ createGoalMutation={createGoalMutation}
320
+ goals={goals}
321
+ metrics={metrics}
322
+ selectedMetric={selectedMetric}
323
+ t={t}
324
+ />
325
+ ) : null}
326
+ {view === 'stats' ? <StatsPanel stats={stats} t={t} /> : null}
327
+ {view === 'leaderboards' ? (
328
+ <LeaderboardsPanel
329
+ createLeaderboardMutation={createLeaderboardMutation}
330
+ createTeamMutation={createTeamMutation}
331
+ leaderboards={leaderboards}
332
+ metrics={metrics}
333
+ selectedMetric={selectedMetric}
334
+ t={t}
335
+ />
336
+ ) : null}
337
+ {view === 'import' ? (
338
+ <ImportPanel
339
+ importMutation={importMutation}
340
+ importPreviewCount={importPreviewCount}
341
+ importText={importText}
342
+ setImportText={setImportText}
343
+ t={t}
344
+ />
345
+ ) : null}
346
+ </div>
347
+ );
348
+ }