@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
@@ -53,10 +53,11 @@ import {
53
53
  SidebarProvider,
54
54
  } from '@tuturuuu/ui/sidebar';
55
55
  import { cn } from '@tuturuuu/utils/format';
56
- import { removeAccents } from '@tuturuuu/utils/text-helper';
57
56
  import { useTranslations } from 'next-intl';
58
57
  import type { ComponentType, KeyboardEvent, ReactNode } from 'react';
59
58
  import { useCallback, useMemo, useRef, useState } from 'react';
59
+ import type { createSettingsSearchEngine } from './settings-dialog-search';
60
+ import { loadSettingsSearchEngine } from './settings-dialog-search-loader';
60
61
 
61
62
  export interface SettingsNavItem {
62
63
  name: string;
@@ -65,6 +66,8 @@ export interface SettingsNavItem {
65
66
  description?: string;
66
67
  disabled?: boolean;
67
68
  keywords?: string[];
69
+ aliases?: string[];
70
+ searchLabels?: string[];
68
71
  }
69
72
 
70
73
  export interface SettingsNavGroup {
@@ -93,6 +96,9 @@ export interface SettingsDialogShellProps {
93
96
  children: ReactNode;
94
97
  }
95
98
 
99
+ type SettingsSearchEngine = ReturnType<typeof createSettingsSearchEngine>;
100
+ type SettingsSearchEngineFactory = typeof createSettingsSearchEngine;
101
+
96
102
  function isEditableShortcutTarget(target: EventTarget | null) {
97
103
  if (!(target instanceof HTMLElement)) return false;
98
104
 
@@ -127,12 +133,20 @@ export function SettingsDialogShell({
127
133
  const isMobile = useIsMobile();
128
134
  const desktopSearchInputRef = useRef<HTMLInputElement>(null);
129
135
  const mobileSearchInputRef = useRef<HTMLInputElement>(null);
136
+ const searchEngineLoadRef = useRef<Promise<void> | null>(null);
130
137
  const [searchQuery, setSearchQuery] = useState('');
138
+ const [searchEngineFactory, setSearchEngineFactory] =
139
+ useState<SettingsSearchEngineFactory | null>(null);
131
140
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
132
141
 
142
+ const searchEngine = useMemo<SettingsSearchEngine | null>(
143
+ () => searchEngineFactory?.(navItems) ?? null,
144
+ [navItems, searchEngineFactory]
145
+ );
146
+
133
147
  const allNavItems = useMemo(
134
- () => navItems.flatMap((group) => group.items),
135
- [navItems]
148
+ () => searchEngine?.allItems ?? navItems.flatMap((group) => group.items),
149
+ [navItems, searchEngine]
136
150
  );
137
151
 
138
152
  const activeGroup = navItems.find((group) =>
@@ -144,26 +158,39 @@ export function SettingsDialogShell({
144
158
  allNavItems.find((item) => !item.disabled) ||
145
159
  allNavItems[0];
146
160
 
147
- const filteredNavItems = navItems
148
- .map((group) => {
149
- const normalizedQuery = removeAccents(searchQuery.toLowerCase());
150
- const filteredItems = group.items.filter(
151
- (item) =>
152
- removeAccents(item.label.toLowerCase()).includes(normalizedQuery) ||
153
- (item.description &&
154
- removeAccents(item.description.toLowerCase()).includes(
155
- normalizedQuery
156
- )) ||
157
- item.keywords?.some((keyword) =>
158
- removeAccents(keyword.toLowerCase()).includes(normalizedQuery)
159
- )
160
- );
161
- return { ...group, items: filteredItems };
162
- })
163
- .filter((group) => group.items.length > 0);
161
+ const ensureSearchEngine = useCallback(() => {
162
+ if (searchEngineFactory || searchEngineLoadRef.current) return;
163
+
164
+ searchEngineLoadRef.current = loadSettingsSearchEngine()
165
+ .then((module) => {
166
+ setSearchEngineFactory(() => module.createSettingsSearchEngine);
167
+ })
168
+ .finally(() => {
169
+ searchEngineLoadRef.current = null;
170
+ });
171
+ }, [searchEngineFactory]);
164
172
 
165
- const filteredEnabledItems = filteredNavItems.flatMap((group) =>
166
- group.items.filter((item) => !item.disabled)
173
+ const filteredNavItems = useMemo(
174
+ () =>
175
+ searchQuery ? (searchEngine?.search(searchQuery) ?? navItems) : navItems,
176
+ [navItems, searchEngine, searchQuery]
177
+ );
178
+
179
+ const filteredEnabledItems = useMemo(
180
+ () =>
181
+ searchEngine?.getEnabledItems(searchQuery) ??
182
+ filteredNavItems.flatMap((group) =>
183
+ group.items.filter((item) => !item.disabled)
184
+ ),
185
+ [filteredNavItems, searchEngine, searchQuery]
186
+ );
187
+
188
+ const updateSearchQuery = useCallback(
189
+ (value: string) => {
190
+ if (value) ensureSearchEngine();
191
+ setSearchQuery(value);
192
+ },
193
+ [ensureSearchEngine]
167
194
  );
168
195
 
169
196
  const isGroupExpandedByDefault = (groupLabel: string, index: number) => {
@@ -175,14 +202,16 @@ export function SettingsDialogShell({
175
202
  const focusSearch = useCallback(() => {
176
203
  if (isMobile) {
177
204
  setMobileNavOpen(true);
205
+ ensureSearchEngine();
178
206
  requestAnimationFrame(() => {
179
207
  mobileSearchInputRef.current?.focus();
180
208
  });
181
209
  return;
182
210
  }
183
211
 
212
+ ensureSearchEngine();
184
213
  desktopSearchInputRef.current?.focus();
185
- }, [isMobile]);
214
+ }, [ensureSearchEngine, isMobile]);
186
215
 
187
216
  const changeActiveItem = useCallback(
188
217
  (targetIndex: number) => {
@@ -273,7 +302,8 @@ export function SettingsDialogShell({
273
302
 
274
303
  return (
275
304
  <DialogContent
276
- className="top-0 left-0 flex h-dvh max-h-dvh w-screen max-w-none translate-x-0 translate-y-0 flex-col gap-0 overflow-hidden rounded-none border-0 p-0 shadow-none sm:max-w-none"
305
+ presentation="fullscreen"
306
+ className="flex-col"
277
307
  onKeyDown={handleKeyboardNavigation}
278
308
  showCloseButton={false}
279
309
  >
@@ -304,7 +334,8 @@ export function SettingsDialogShell({
304
334
  placeholder={t('settings.search_settings_placeholder')}
305
335
  className="bg-background pl-8"
306
336
  value={searchQuery}
307
- onChange={(e) => setSearchQuery(e.target.value)}
337
+ onChange={(e) => updateSearchQuery(e.target.value)}
338
+ onFocus={ensureSearchEngine}
308
339
  />
309
340
  </div>
310
341
  </SidebarHeader>
@@ -402,22 +433,28 @@ export function SettingsDialogShell({
402
433
  {t('search.search')}
403
434
  </DrawerDescription>
404
435
  </DrawerHeader>
405
- <Command className="rounded-none border-0">
436
+ <Command
437
+ className="rounded-none border-0"
438
+ shouldFilter={false}
439
+ >
406
440
  <CommandInput
407
441
  ref={mobileSearchInputRef}
442
+ value={searchQuery}
443
+ onFocus={ensureSearchEngine}
444
+ onValueChange={updateSearchQuery}
408
445
  placeholder={t('settings.search_settings_placeholder')}
409
446
  />
410
447
  <CommandList className="max-h-[50vh]">
411
448
  <CommandEmpty>
412
449
  {t('common.no_results_found')}
413
450
  </CommandEmpty>
414
- {navItems.map((group) => (
451
+ {filteredNavItems.map((group) => (
415
452
  <CommandGroup key={group.label} heading={group.label}>
416
453
  {group.items.map((item) => (
417
454
  <CommandItem
418
455
  disabled={item.disabled}
419
456
  key={item.name}
420
- value={`${group.label} ${item.label} ${item.keywords?.join(' ') || ''}`}
457
+ value={`${group.label} ${item.label} ${item.description || ''} ${item.keywords?.join(' ') || ''} ${item.aliases?.join(' ') || ''} ${item.searchLabels?.join(' ') || ''}`}
421
458
  onSelect={() => {
422
459
  if (item.disabled) return;
423
460
  onActiveTabChange(item.name);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Moon, Sun } from '@tuturuuu/icons';
3
+ import { Moon, Sun } from '@tuturuuu/icons/lucide-static';
4
4
  import { Button } from '@tuturuuu/ui/button';
5
5
  import { cn } from '@tuturuuu/utils/format';
6
6
  import { useTheme } from 'next-themes';
@@ -18,3 +18,26 @@ export function mergeWorkspaceSelectWorkspaces(
18
18
 
19
19
  return [...workspaceList, currentWorkspaceFallback];
20
20
  }
21
+
22
+ export function normalizeWorkspaceSwitchPath(
23
+ pathname: string,
24
+ nextSlug: string
25
+ ) {
26
+ const taskBoardsPath = `/${nextSlug}/tasks/boards`;
27
+
28
+ if (
29
+ pathname === taskBoardsPath ||
30
+ pathname.startsWith(`${taskBoardsPath}/`)
31
+ ) {
32
+ return `/${nextSlug}/tasks`;
33
+ }
34
+
35
+ const uuidRegex =
36
+ /\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|[0-9a-fA-F]{32})$/;
37
+
38
+ if (uuidRegex.test(pathname) && pathname !== `/${nextSlug}`) {
39
+ return pathname.replace(uuidRegex, '');
40
+ }
41
+
42
+ return pathname;
43
+ }
@@ -23,9 +23,9 @@ import {
23
23
  toWorkspaceSlug,
24
24
  } from '@tuturuuu/utils/constants';
25
25
  import { cn } from '@tuturuuu/utils/format';
26
- import { getInitials } from '@tuturuuu/utils/name-helper';
27
26
  import { workspaceHandleSchema } from '@tuturuuu/utils/workspace-handle';
28
27
  import { WORKSPACE_LIMIT_ERROR_CODE } from '@tuturuuu/utils/workspace-limits';
28
+ import Image from 'next/image';
29
29
  import { usePathname, useRouter } from 'next/navigation';
30
30
  import { useTranslations } from 'next-intl';
31
31
  import type { ReactNode } from 'react';
@@ -66,7 +66,10 @@ import {
66
66
  import { Input } from '../input';
67
67
  import { Popover, PopoverContent, PopoverTrigger } from '../popover';
68
68
  import { TUTURUUU_LOGO_URL } from './tuturuuu-logo';
69
- import { mergeWorkspaceSelectWorkspaces } from './workspace-select-helpers';
69
+ import {
70
+ mergeWorkspaceSelectWorkspaces,
71
+ normalizeWorkspaceSwitchPath,
72
+ } from './workspace-select-helpers';
70
73
 
71
74
  const FormSchema = z.object({
72
75
  name: z.string().min(1).max(100),
@@ -98,6 +101,7 @@ function WorkspaceIcon({
98
101
  avatarUrl,
99
102
  fallbackLogoUrl
100
103
  );
104
+ const shouldSkipFallbackOptimization = /^https?:\/\//u.test(fallbackLogoUrl);
101
105
 
102
106
  return (
103
107
  <Avatar
@@ -124,11 +128,15 @@ function WorkspaceIcon({
124
128
  resolvedAvatarUrl ? 'rounded-xs' : 'rounded-sm'
125
129
  )}
126
130
  >
127
- <AvatarImage
131
+ <Image
132
+ alt=""
133
+ aria-hidden="true"
128
134
  className="h-full w-full object-cover"
135
+ height={20}
129
136
  src={fallbackLogoUrl}
137
+ unoptimized={shouldSkipFallbackOptimization}
138
+ width={20}
130
139
  />
131
- {name ? getInitials(name) : '?'}
132
140
  </AvatarFallback>
133
141
  </Avatar>
134
142
  );
@@ -433,22 +441,20 @@ export function WorkspaceSelect({
433
441
  const selectedTeam = groups
434
442
  .flatMap((group) => group.teams)
435
443
  .find((team) => team.value === nextSlug);
436
- let newPathname =
437
- selectedTeam?.accessType === 'guest' && selectedTeam.guestLandingPath
438
- ? `/${nextSlug}${selectedTeam.guestLandingPath}`
439
- : pathname
440
- ? (resolveNextPathname?.({
441
- currentPathname: pathname,
442
- nextSlug,
443
- }) ?? pathname.replace(/^\/[^/]+/, `/${nextSlug}`))
444
- : undefined;
444
+ const usesGuestLandingPath =
445
+ selectedTeam?.accessType === 'guest' && selectedTeam.guestLandingPath;
446
+ let newPathname = usesGuestLandingPath
447
+ ? `/${nextSlug}${selectedTeam.guestLandingPath}`
448
+ : pathname
449
+ ? (resolveNextPathname?.({
450
+ currentPathname: pathname,
451
+ nextSlug,
452
+ }) ?? pathname.replace(/^\/[^/]+/, `/${nextSlug}`))
453
+ : undefined;
454
+ if (newPathname && !usesGuestLandingPath) {
455
+ newPathname = normalizeWorkspaceSwitchPath(newPathname, nextSlug);
456
+ }
445
457
  if (newPathname) {
446
- // Regex to match a UUID at the end of the string, with or without dashes
447
- const uuidRegex =
448
- /\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}|[0-9a-fA-F]{32})$/;
449
- // Remove the UUID if present, and the current path is not /:wsId
450
- if (uuidRegex.test(newPathname) && newPathname !== `/${nextSlug}`)
451
- newPathname = newPathname.replace(uuidRegex, '');
452
458
  router.push(newPathname);
453
459
  }
454
460
  };
@@ -0,0 +1,52 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
3
+ import type { ReactNode } from 'react';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { Dialog, DialogContent, DialogTitle } from './dialog';
6
+
7
+ function renderOpenDialog(children: ReactNode) {
8
+ return render(<Dialog open>{children}</Dialog>);
9
+ }
10
+
11
+ describe('DialogContent presentation', () => {
12
+ it('keeps the default centered dialog animation classes', () => {
13
+ renderOpenDialog(
14
+ <DialogContent>
15
+ <DialogTitle>Default dialog</DialogTitle>
16
+ </DialogContent>
17
+ );
18
+
19
+ const dialog = screen.getByRole('dialog', { name: 'Default dialog' });
20
+ const className = dialog.getAttribute('class') ?? '';
21
+
22
+ expect(className).toContain('top-[50%]');
23
+ expect(className).toContain('left-[50%]');
24
+ expect(className).toContain('data-[state=open]:animate-in');
25
+ expect(className).toContain('data-[state=open]:fade-in-0');
26
+ expect(className).toContain('data-[state=open]:zoom-in-95');
27
+ expect(dialog).toHaveClass('rounded-lg');
28
+ });
29
+
30
+ it('renders fullscreen content as an opaque non-animated viewport surface', () => {
31
+ renderOpenDialog(
32
+ <DialogContent presentation="fullscreen">
33
+ <DialogTitle>Fullscreen dialog</DialogTitle>
34
+ </DialogContent>
35
+ );
36
+
37
+ const dialog = screen.getByRole('dialog', { name: 'Fullscreen dialog' });
38
+ const className = dialog.getAttribute('class') ?? '';
39
+
40
+ expect(dialog).toHaveClass('fixed');
41
+ expect(dialog).toHaveClass('inset-0');
42
+ expect(dialog).toHaveClass('h-dvh');
43
+ expect(dialog).toHaveClass('max-h-dvh');
44
+ expect(dialog).toHaveClass('w-screen');
45
+ expect(dialog).toHaveClass('max-w-none');
46
+ expect(dialog).toHaveClass('bg-background');
47
+ expect(dialog).toHaveClass('rounded-none');
48
+ expect(dialog).toHaveClass('border-0');
49
+ expect(dialog).toHaveClass('shadow-none');
50
+ expect(className).not.toMatch(/animate-in|fade-in|zoom-in|slide-in/);
51
+ });
52
+ });
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import * as DialogPrimitive from '@radix-ui/react-dialog';
4
- import { XIcon } from '@tuturuuu/icons';
4
+ import { XIcon } from '@tuturuuu/icons/lucide-static';
5
5
  import { cn } from '@tuturuuu/utils/format';
6
6
  import type * as React from 'react';
7
7
 
@@ -51,9 +51,11 @@ function DialogContent({
51
51
  children,
52
52
  role = 'dialog',
53
53
  showCloseButton = true,
54
+ presentation = 'default',
54
55
  ...props
55
56
  }: React.ComponentProps<typeof DialogPrimitive.Content> & {
56
57
  showCloseButton?: boolean;
58
+ presentation?: 'default' | 'fullscreen';
57
59
  }) {
58
60
  return (
59
61
  <DialogPortal data-slot="dialog-portal">
@@ -62,7 +64,9 @@ function DialogContent({
62
64
  data-slot="dialog-content"
63
65
  role={role}
64
66
  className={cn(
65
- 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg',
67
+ presentation === 'fullscreen'
68
+ ? 'fixed inset-0 z-50 flex h-dvh max-h-dvh w-screen max-w-none gap-0 overflow-hidden rounded-none border-0 bg-background p-0 shadow-none'
69
+ : 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-lg',
66
70
  className
67
71
  )}
68
72
  onClick={(e) => e.stopPropagation()}
@@ -1,7 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
4
- import { CheckIcon, ChevronRightIcon, CircleIcon } from '@tuturuuu/icons';
4
+ import {
5
+ CheckIcon,
6
+ ChevronRightIcon,
7
+ CircleIcon,
8
+ } from '@tuturuuu/icons/lucide-static';
5
9
  import { cn } from '@tuturuuu/utils/format';
6
10
  import type * as React from 'react';
7
11
 
@@ -9,6 +9,10 @@ import type {
9
9
  InterestCalculationType,
10
10
  } from '@tuturuuu/types/primitives/DebtLoan';
11
11
  import type { Wallet } from '@tuturuuu/types/primitives/Wallet';
12
+ import {
13
+ resolveSupportedCurrency,
14
+ SUPPORTED_CURRENCIES,
15
+ } from '@tuturuuu/utils/currencies';
12
16
  import { useTranslations } from 'next-intl';
13
17
  import { useCallback, useState } from 'react';
14
18
  import { useForm } from 'react-hook-form';
@@ -36,6 +40,7 @@ interface Props {
36
40
  data?: DebtLoan;
37
41
  wallets?: Wallet[];
38
42
  defaultType?: DebtLoanType;
43
+ defaultCurrency?: string;
39
44
  onFinish?: (data: DebtLoanFormData) => void;
40
45
  onCancel?: () => void;
41
46
  }
@@ -45,12 +50,14 @@ export function DebtLoanForm({
45
50
  data,
46
51
  wallets = [],
47
52
  defaultType = 'debt',
53
+ defaultCurrency,
48
54
  onFinish,
49
55
  onCancel,
50
56
  }: Props) {
51
57
  const t = useTranslations('ws-debt-loan');
52
58
  const [loading, setLoading] = useState(false);
53
59
  const formSchema = createDebtLoanFormSchema(t);
60
+ const workspaceCurrency = resolveSupportedCurrency(defaultCurrency);
54
61
 
55
62
  const form = useForm<DebtLoanFormValues>({
56
63
  resolver: zodResolver(formSchema),
@@ -60,7 +67,7 @@ export function DebtLoanForm({
60
67
  counterparty: data?.counterparty || '',
61
68
  type: (data?.type || defaultType) as DebtLoanType,
62
69
  principal_amount: data?.principal_amount || 0,
63
- currency: data?.currency || 'VND',
70
+ currency: data?.currency || workspaceCurrency,
64
71
  interest_rate: data?.interest_rate ?? undefined,
65
72
  interest_type: (data?.interest_type || undefined) as
66
73
  | InterestCalculationType
@@ -226,10 +233,10 @@ export function DebtLoanForm({
226
233
  <SelectField
227
234
  id="currency"
228
235
  placeholder={t('select_currency')}
229
- options={[
230
- { value: 'VND', label: 'VND' },
231
- { value: 'USD', label: 'USD' },
232
- ]}
236
+ options={SUPPORTED_CURRENCIES.map((currency) => ({
237
+ value: currency.code,
238
+ label: `${currency.code} - ${currency.name}`,
239
+ }))}
233
240
  classNames={{ selectTrigger: 'w-full' }}
234
241
  value={field.value}
235
242
  onValueChange={field.onChange}
@@ -8,6 +8,7 @@ import {
8
8
  TrendingUp,
9
9
  } from '@tuturuuu/icons';
10
10
  import type { DebtLoanSummary } from '@tuturuuu/types/primitives/DebtLoan';
11
+ import { getCurrencyLocale } from '@tuturuuu/utils/currencies';
11
12
  import { cn } from '@tuturuuu/utils/format';
12
13
  import { useTranslations } from 'next-intl';
13
14
  import { useFinanceHref } from '../finance-route-context';
@@ -22,8 +23,8 @@ interface Props {
22
23
 
23
24
  export function DebtLoanSummaryCards({
24
25
  summary,
25
- currency = 'VND',
26
- locale = 'vi-VN',
26
+ currency = 'USD',
27
+ locale = getCurrencyLocale(currency),
27
28
  wsId,
28
29
  }: Props) {
29
30
  const t = useTranslations('ws-debt-loan');
@@ -5,6 +5,8 @@ import { DebtsPage } from './debts-page';
5
5
 
6
6
  const mocks = vi.hoisted(() => ({
7
7
  debtLoanForm: vi.fn(),
8
+ debtLoanList: vi.fn(),
9
+ debtLoanSummaryCards: vi.fn(),
8
10
  getDebtLoanSummary: vi.fn(),
9
11
  listDebtLoans: vi.fn(),
10
12
  listWallets: vi.fn(),
@@ -31,15 +33,22 @@ vi.mock('./debt-loan-form', () => ({
31
33
  }));
32
34
 
33
35
  vi.mock('./debt-loan-list', () => ({
34
- DebtLoanList: () => null,
36
+ DebtLoanList: (props: unknown) => {
37
+ mocks.debtLoanList(props);
38
+ return null;
39
+ },
35
40
  }));
36
41
 
37
42
  vi.mock('./debt-loan-summary', () => ({
38
- DebtLoanSummaryCards: () => null,
43
+ DebtLoanSummaryCards: (props: unknown) => {
44
+ mocks.debtLoanSummaryCards(props);
45
+ return null;
46
+ },
39
47
  }));
40
48
 
41
49
  function renderDebtsPage(
42
- searchParams: { create?: string; type?: string } = {}
50
+ searchParams: { create?: string; type?: string } = {},
51
+ props: { currency?: string } = {}
43
52
  ) {
44
53
  const queryClient = new QueryClient({
45
54
  defaultOptions: {
@@ -51,7 +60,11 @@ function renderDebtsPage(
51
60
 
52
61
  return render(
53
62
  <QueryClientProvider client={queryClient}>
54
- <DebtsPage wsId="ws-1" searchParams={searchParams} />
63
+ <DebtsPage
64
+ wsId="ws-1"
65
+ searchParams={searchParams}
66
+ currency={props.currency}
67
+ />
55
68
  </QueryClientProvider>
56
69
  );
57
70
  }
@@ -83,15 +96,51 @@ describe('debts page', () => {
83
96
  });
84
97
 
85
98
  it('opens the loan create dialog from query state', async () => {
86
- renderDebtsPage({ create: 'loan' });
99
+ renderDebtsPage({ create: 'loan' }, { currency: 'SGD' });
87
100
 
88
101
  await waitFor(() => {
89
102
  expect(mocks.debtLoanForm).toHaveBeenCalledWith(
90
103
  expect.objectContaining({
104
+ defaultCurrency: 'SGD',
91
105
  defaultType: 'loan',
92
106
  wsId: 'ws-1',
93
107
  })
94
108
  );
95
109
  });
96
110
  });
111
+
112
+ it('passes workspace currency to summary without overriding list row currencies', async () => {
113
+ mocks.listDebtLoans.mockResolvedValueOnce([
114
+ {
115
+ currency: 'USD',
116
+ id: 'debt-1',
117
+ name: 'USD Debt',
118
+ status: 'active',
119
+ type: 'debt',
120
+ },
121
+ ]);
122
+
123
+ renderDebtsPage({}, { currency: 'SGD' });
124
+
125
+ await waitFor(() => {
126
+ expect(mocks.debtLoanSummaryCards).toHaveBeenCalledWith(
127
+ expect.objectContaining({
128
+ currency: 'SGD',
129
+ locale: 'en-SG',
130
+ })
131
+ );
132
+ expect(mocks.debtLoanList).toHaveBeenCalledWith(
133
+ expect.not.objectContaining({
134
+ currency: expect.anything(),
135
+ })
136
+ );
137
+ expect(mocks.debtLoanList).toHaveBeenCalledWith(
138
+ expect.objectContaining({
139
+ debtLoans: expect.arrayContaining([
140
+ expect.objectContaining({ currency: 'USD' }),
141
+ ]),
142
+ })
143
+ );
144
+ });
145
+ });
97
146
  });
@@ -13,6 +13,10 @@ import type {
13
13
  DebtLoanWithBalance,
14
14
  } from '@tuturuuu/types/primitives/DebtLoan';
15
15
  import type { Wallet } from '@tuturuuu/types/primitives/Wallet';
16
+ import {
17
+ getCurrencyLocale,
18
+ resolveSupportedCurrency,
19
+ } from '@tuturuuu/utils/currencies';
16
20
  import { useTranslations } from 'next-intl';
17
21
  import { useEffect, useState } from 'react';
18
22
  import { Button } from '../../button';
@@ -37,11 +41,14 @@ interface Props {
37
41
  create?: string;
38
42
  type?: string;
39
43
  };
44
+ currency?: string;
40
45
  }
41
46
 
42
- export function DebtsPage({ wsId, searchParams }: Props) {
47
+ export function DebtsPage({ wsId, searchParams, currency }: Props) {
43
48
  const t = useTranslations('ws-debt-loan');
44
49
  const queryClient = useQueryClient();
50
+ const workspaceCurrency = resolveSupportedCurrency(currency);
51
+ const workspaceCurrencyLocale = getCurrencyLocale(workspaceCurrency);
45
52
  const requestedCreateType =
46
53
  searchParams?.create === 'loan'
47
54
  ? 'loan'
@@ -135,7 +142,12 @@ export function DebtsPage({ wsId, searchParams }: Props) {
135
142
  ))}
136
143
  </div>
137
144
  ) : summary ? (
138
- <DebtLoanSummaryCards summary={summary} wsId={wsId} />
145
+ <DebtLoanSummaryCards
146
+ summary={summary}
147
+ wsId={wsId}
148
+ currency={workspaceCurrency}
149
+ locale={workspaceCurrencyLocale}
150
+ />
139
151
  ) : null}
140
152
 
141
153
  <Separator />
@@ -220,6 +232,7 @@ export function DebtsPage({ wsId, searchParams }: Props) {
220
232
  wsId={wsId}
221
233
  wallets={wallets}
222
234
  defaultType={defaultCreateType}
235
+ defaultCurrency={workspaceCurrency}
223
236
  onFinish={handleFormFinish}
224
237
  onCancel={() => setIsCreateDialogOpen(false)}
225
238
  />
@@ -6,7 +6,6 @@ import {
6
6
  ChevronDown,
7
7
  Loader2,
8
8
  } from '@tuturuuu/icons';
9
- import type { Database } from '@tuturuuu/types';
10
9
  import {
11
10
  Card,
12
11
  CardContent,
@@ -29,6 +28,7 @@ import {
29
28
  getSubscriptionCoverageInvoiceForGroup,
30
29
  isSubscriptionMonthPaidForGroup,
31
30
  parseLocalCalendarDate,
31
+ type WorkspaceUserGroup,
32
32
  } from '../utils';
33
33
 
34
34
  type LatestInvoice = {
@@ -38,9 +38,7 @@ type LatestInvoice = {
38
38
  };
39
39
 
40
40
  type UserGroupItem = {
41
- workspace_user_groups:
42
- | Database['public']['Tables']['workspace_user_groups']['Row']
43
- | null;
41
+ workspace_user_groups: WorkspaceUserGroup | null;
44
42
  };
45
43
 
46
44
  function hasSchedule(group: UserGroupItem['workspace_user_groups']): boolean {
@@ -84,7 +82,7 @@ function GroupRow({
84
82
  onToggle,
85
83
  t,
86
84
  }: {
87
- group: Database['public']['Tables']['workspace_user_groups']['Row'];
85
+ group: WorkspaceUserGroup;
88
86
  isSelected: boolean;
89
87
  isMonthPaid: boolean;
90
88
  latestInvoice: LatestInvoice | undefined;