@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
@@ -30,6 +30,7 @@ import {
30
30
  } from '@tuturuuu/icons';
31
31
  import {
32
32
  getWorkspaceTask,
33
+ listWorkspaceTaskBoardViewableMembers,
33
34
  listWorkspaceTaskLists,
34
35
  listWorkspaceTaskProjects,
35
36
  removeCurrentUserTaskPersonalPlacement,
@@ -50,7 +51,10 @@ import {
50
51
  import { useCalendarPreferences } from '@tuturuuu/ui/hooks/use-calendar-preferences';
51
52
  import { useTaskActions } from '@tuturuuu/ui/hooks/use-task-actions';
52
53
  import { useUserBooleanConfig } from '@tuturuuu/ui/hooks/use-user-config';
53
- import { useWorkspaceMembers } from '@tuturuuu/ui/hooks/use-workspace-members';
54
+ import {
55
+ useWorkspaceMembers,
56
+ type WorkspaceMember,
57
+ } from '@tuturuuu/ui/hooks/use-workspace-members';
54
58
  import {
55
59
  HoverCard,
56
60
  HoverCardContent,
@@ -69,7 +73,7 @@ import {
69
73
  import { isTaskBoardResolvedStatus } from '@tuturuuu/utils/task-list-status';
70
74
  import { getDescriptionMetadata } from '@tuturuuu/utils/text-helper';
71
75
  import { getTimeFormatPattern } from '@tuturuuu/utils/time-helper';
72
- import { format, formatDistanceToNow } from 'date-fns';
76
+ import { format, formatDistance } from 'date-fns';
73
77
  import { enUS, vi } from 'date-fns/locale';
74
78
  import Link from 'next/link';
75
79
  import { useParams } from 'next/navigation';
@@ -116,6 +120,7 @@ import {
116
120
  import { formatSmartDate } from '../../../utils/taskDateUtils';
117
121
  import { getPriorityIndicator } from '../../../utils/taskPriorityUtils';
118
122
  import { sortByDisplayName } from '../board-text-utils';
123
+ import { invalidateKanbanDeadlineTasks } from '../kanban/data/kanban-deadline-query';
119
124
  import {
120
125
  TaskAssigneesMenu,
121
126
  TaskBlockingMenu,
@@ -151,6 +156,11 @@ import {
151
156
  import { getTaskCardVisibilityState } from './task-card-visibility';
152
157
  import { TaskSchedulingBadge } from './task-scheduling-badge';
153
158
 
159
+ export type TaskCardAssigneeMemberSource =
160
+ | 'workspace'
161
+ | 'board'
162
+ | 'workspace-and-board';
163
+
154
164
  export interface TaskCardProps {
155
165
  task: Task;
156
166
  boardId: string;
@@ -162,6 +172,8 @@ export interface TaskCardProps {
162
172
  isSelected?: boolean;
163
173
  isMultiSelectMode?: boolean;
164
174
  isPersonalWorkspace?: boolean;
175
+ canUseBoardAssignees?: boolean;
176
+ assigneeMemberSource?: TaskCardAssigneeMemberSource;
165
177
  onSelect?: (taskId: string, event: React.MouseEvent) => void;
166
178
  onClearSelection?: () => void;
167
179
  dragDisabled?: boolean;
@@ -170,6 +182,128 @@ export interface TaskCardProps {
170
182
  optimisticUpdateInProgress?: Set<string>;
171
183
  selectedTasks?: Set<string>; // For bulk operations
172
184
  bulkUpdateCustomDueDate?: (date: Date | null) => Promise<void>; // From useBulkOperations
185
+ deadlineContext?: 'overdue' | 'upcoming';
186
+ deadlineNow?: number;
187
+ readOnly?: boolean;
188
+ }
189
+
190
+ function ReadOnlyTaskCard({ task, taskList }: TaskCardProps) {
191
+ const t = useTranslations('common');
192
+ const publicBoardT = useTranslations('ws-task-boards.public');
193
+ const priorityT = useTranslations('ws-task-boards.dialog.priority');
194
+ const locale = useLocale();
195
+ const dateLocale = locale === 'vi' ? vi : enUS;
196
+ const ticketPrefix = (task as Task & { ticket_prefix?: string | null })
197
+ .ticket_prefix;
198
+ const ticketIdentifier =
199
+ typeof task.display_number === 'number' && task.display_number > 0
200
+ ? getTicketIdentifier(ticketPrefix, task.display_number)
201
+ : null;
202
+ const dueDate = task.end_date
203
+ ? format(new Date(task.end_date), 'MMM d, yyyy', { locale: dateLocale })
204
+ : null;
205
+
206
+ return (
207
+ <Card
208
+ className={cn(
209
+ 'relative overflow-hidden rounded-lg border border-l-4 bg-background p-3 shadow-xs',
210
+ getCardColorClassesUtil(taskList, task.priority),
211
+ task.closed_at && 'opacity-60 saturate-50'
212
+ )}
213
+ data-task-card-id={task.id}
214
+ data-task-read-only="true"
215
+ >
216
+ <TaskCardIdentifierRow
217
+ externalSourceLabel=""
218
+ isMultiSelectMode={false}
219
+ isPersonalExternalTask={false}
220
+ isSelected={false}
221
+ selectTaskLabel={t('select_task', { name: task.name })}
222
+ taskListStatus={taskList?.status}
223
+ ticketBadgeClassName={getTicketBadgeColorClasses(
224
+ taskList,
225
+ task.priority
226
+ )}
227
+ ticketIdentifier={ticketIdentifier}
228
+ ticketTitle={ticketIdentifier ?? ''}
229
+ />
230
+
231
+ <div className="space-y-2">
232
+ <h3
233
+ className={cn(
234
+ 'line-clamp-2 break-words font-medium text-sm leading-5',
235
+ (task.completed_at || task.closed_at) &&
236
+ 'text-muted-foreground line-through'
237
+ )}
238
+ >
239
+ {task.name}
240
+ </h3>
241
+
242
+ <div className="flex flex-wrap items-center gap-1.5">
243
+ {task.priority && (
244
+ <Badge variant="outline" className="h-5 px-1.5 font-normal text-xs">
245
+ {priorityT(task.priority)}
246
+ </Badge>
247
+ )}
248
+ {dueDate && (
249
+ <Badge
250
+ variant="secondary"
251
+ className="h-5 gap-1 px-1.5 font-normal text-xs"
252
+ >
253
+ <Calendar className="h-3 w-3" />
254
+ {dueDate}
255
+ </Badge>
256
+ )}
257
+ {typeof task.estimation_points === 'number' && (
258
+ <Badge variant="outline" className="h-5 px-1.5 font-normal text-xs">
259
+ {publicBoardT('points', { count: task.estimation_points })}
260
+ </Badge>
261
+ )}
262
+ </div>
263
+
264
+ {(task.labels?.length ||
265
+ task.projects?.length ||
266
+ task.assignees?.length) && (
267
+ <div className="flex flex-wrap items-center gap-1.5">
268
+ {task.labels?.map((label) => (
269
+ <Badge
270
+ key={label.id}
271
+ variant="outline"
272
+ className="h-5 gap-1 px-1.5 font-normal text-xs"
273
+ >
274
+ <span
275
+ aria-hidden="true"
276
+ className="h-2 w-2 rounded-full"
277
+ style={{ backgroundColor: label.color }}
278
+ />
279
+ {label.name}
280
+ </Badge>
281
+ ))}
282
+ {task.projects?.map((project) => (
283
+ <Badge
284
+ key={project.id}
285
+ variant="outline"
286
+ className="h-5 gap-1 px-1.5 font-normal text-xs"
287
+ >
288
+ <Box className="h-3 w-3" />
289
+ {project.name}
290
+ </Badge>
291
+ ))}
292
+ {task.assignees?.map((assignee) => (
293
+ <Badge
294
+ key={assignee.id}
295
+ variant="secondary"
296
+ className="h-5 px-1.5 font-normal text-xs"
297
+ >
298
+ {assignee.display_name ||
299
+ (assignee.handle ? `@${assignee.handle}` : t('assignee'))}
300
+ </Badge>
301
+ ))}
302
+ </div>
303
+ )}
304
+ </div>
305
+ </Card>
306
+ );
173
307
  }
174
308
 
175
309
  // Memoized full TaskCard
@@ -184,6 +318,8 @@ function TaskCardInner({
184
318
  isSelected = false,
185
319
  isMultiSelectMode = false,
186
320
  isPersonalWorkspace = false,
321
+ canUseBoardAssignees,
322
+ assigneeMemberSource,
187
323
  onSelect,
188
324
  onClearSelection,
189
325
  dragDisabled: dragDisabledProp = false,
@@ -192,6 +328,8 @@ function TaskCardInner({
192
328
  optimisticUpdateInProgress,
193
329
  selectedTasks,
194
330
  bulkUpdateCustomDueDate,
331
+ deadlineContext,
332
+ deadlineNow,
195
333
  }: TaskCardProps) {
196
334
  const { wsId: rawWsId } = useParams();
197
335
  const wsId = Array.isArray(rawWsId) ? rawWsId[0] : rawWsId;
@@ -319,12 +457,59 @@ function TaskCardInner({
319
457
  isMultiSelectMode,
320
458
  onClearSelection,
321
459
  });
460
+ const shouldUseBoardAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
461
+ const effectiveAssigneeMemberSource =
462
+ assigneeMemberSource ?? (isPersonalWorkspace ? 'board' : 'workspace');
463
+ const shouldLoadWorkspaceMembers =
464
+ shouldUseBoardAssignees && effectiveAssigneeMemberSource !== 'board';
465
+ const shouldLoadBoardViewableMembers =
466
+ shouldUseBoardAssignees && effectiveAssigneeMemberSource !== 'workspace';
322
467
 
323
468
  // Fetch workspace members
324
- const { data: workspaceMembers = [], isLoading: membersLoading } =
325
- useWorkspaceMembers(effectiveWorkspaceId, {
326
- enabled: !!effectiveWorkspaceId && !isPersonalWorkspace,
327
- });
469
+ const normalMembersQuery = useWorkspaceMembers(effectiveWorkspaceId, {
470
+ enabled: !!effectiveWorkspaceId && shouldLoadWorkspaceMembers,
471
+ });
472
+ const boardViewableMembersQuery = useQuery({
473
+ queryKey: ['task-board-viewable-members', effectiveWorkspaceId, boardId],
474
+ queryFn: async (): Promise<WorkspaceMember[]> => {
475
+ if (!effectiveWorkspaceId || !boardId) return [];
476
+
477
+ const payload = await listWorkspaceTaskBoardViewableMembers(
478
+ effectiveWorkspaceId,
479
+ boardId
480
+ );
481
+ const members = Array.isArray(payload?.members) ? payload.members : [];
482
+
483
+ return members.map((member) => ({
484
+ id: member.user_id,
485
+ user_id: member.user_id,
486
+ workspace_id: effectiveWorkspaceId,
487
+ display_name: member.display_name ?? member.email ?? member.user_id,
488
+ email: member.email ?? undefined,
489
+ avatar_url: member.avatar_url ?? undefined,
490
+ }));
491
+ },
492
+ enabled:
493
+ !!effectiveWorkspaceId && !!boardId && shouldLoadBoardViewableMembers,
494
+ staleTime: 5 * 60 * 1000,
495
+ });
496
+ const normalWorkspaceMembers = normalMembersQuery.data ?? [];
497
+ const boardViewableMembers = boardViewableMembersQuery.data ?? [];
498
+ const workspaceMembers = useMemo(() => {
499
+ const seen = new Set<string>();
500
+ const merged: WorkspaceMember[] = [];
501
+
502
+ for (const member of [...normalWorkspaceMembers, ...boardViewableMembers]) {
503
+ const memberId = member.user_id ?? member.id;
504
+ if (!memberId || seen.has(memberId)) continue;
505
+ seen.add(memberId);
506
+ merged.push(member);
507
+ }
508
+
509
+ return merged;
510
+ }, [boardViewableMembers, normalWorkspaceMembers]);
511
+ const membersLoading =
512
+ normalMembersQuery.isLoading || boardViewableMembersQuery.isLoading;
328
513
 
329
514
  const relationshipSummary =
330
515
  task.relationship_summary ??
@@ -769,7 +954,7 @@ function TaskCardInner({
769
954
  opacity: isOverlay ? 1 : isOptimistic ? 0.6 : undefined,
770
955
  };
771
956
 
772
- const now = new Date();
957
+ const now = useMemo(() => new Date(deadlineNow ?? Date.now()), [deadlineNow]);
773
958
  const shouldRenderDueDate = shouldShowTaskDueDate({
774
959
  completedAt: task.completed_at,
775
960
  closedAt: task.closed_at,
@@ -789,6 +974,18 @@ function TaskCardInner({
789
974
  const isResolvedListStatus = isTaskBoardResolvedStatus(taskList?.status);
790
975
  const startDate = task.start_date ? new Date(task.start_date) : null;
791
976
  const endDate = task.end_date ? new Date(task.end_date) : null;
977
+ const upcomingDeadlineCountdown =
978
+ deadlineContext === 'upcoming' && endDate
979
+ ? formatDistance(endDate, now, {
980
+ addSuffix: true,
981
+ locale: dateLocale,
982
+ })
983
+ : null;
984
+ const upcomingDeadlineExactDate = endDate
985
+ ? format(endDate, `MMM dd '${t('at')}' ${timePattern}`, {
986
+ locale: dateLocale,
987
+ })
988
+ : null;
792
989
  const selectionCheckboxClassName = cn(
793
990
  getTaskCardSelectionCheckboxToneClasses(taskList?.color as SupportedColor),
794
991
  isOverdue &&
@@ -897,6 +1094,12 @@ function TaskCardInner({
897
1094
  task,
898
1095
  boardId,
899
1096
  availableLists,
1097
+ canUseBoardAssignees: task.source_workspace_id
1098
+ ? true
1099
+ : shouldUseBoardAssignees,
1100
+ assigneeMemberSource: task.source_workspace_id
1101
+ ? 'workspace'
1102
+ : effectiveAssigneeMemberSource,
900
1103
  effectiveWorkspaceId,
901
1104
  isPersonalWorkspace,
902
1105
  })
@@ -905,6 +1108,8 @@ function TaskCardInner({
905
1108
  task,
906
1109
  boardId,
907
1110
  availableLists,
1111
+ shouldUseBoardAssignees,
1112
+ effectiveAssigneeMemberSource,
908
1113
  effectiveWorkspaceId,
909
1114
  isPersonalWorkspace,
910
1115
  openTaskById,
@@ -938,6 +1143,8 @@ function TaskCardInner({
938
1143
  openTask(task, boardId, availableLists, false, {
939
1144
  taskWsId: effectiveWorkspaceId,
940
1145
  taskWorkspacePersonal: isPersonalWorkspace,
1146
+ canUseBoardAssignees: shouldUseBoardAssignees,
1147
+ assigneeMemberSource: effectiveAssigneeMemberSource,
941
1148
  });
942
1149
  }
943
1150
  },
@@ -945,7 +1152,9 @@ function TaskCardInner({
945
1152
  task,
946
1153
  boardId,
947
1154
  effectiveWorkspaceId,
1155
+ effectiveAssigneeMemberSource,
948
1156
  isPersonalWorkspace,
1157
+ shouldUseBoardAssignees,
949
1158
  isPersonalExternalTask,
950
1159
  isMultiSelectMode,
951
1160
  availableLists,
@@ -1025,6 +1234,7 @@ function TaskCardInner({
1025
1234
  )
1026
1235
  );
1027
1236
 
1237
+ void invalidateKanbanDeadlineTasks(queryClient, boardId);
1028
1238
  toast.success(tTasks('moved_to_external_tasks'));
1029
1239
  } catch (error) {
1030
1240
  console.error('Failed to move task to external staging:', error);
@@ -1045,6 +1255,7 @@ function TaskCardInner({
1045
1255
  old?.filter((candidate) => candidate.id !== task.id)
1046
1256
  );
1047
1257
 
1258
+ void invalidateKanbanDeadlineTasks(queryClient, boardId);
1048
1259
  toast.success(tTasks('removed_from_personal_board'));
1049
1260
  } catch (error) {
1050
1261
  console.error('Failed to remove task from personal board:', error);
@@ -2135,8 +2346,8 @@ function TaskCardInner({
2135
2346
  </>
2136
2347
  )}
2137
2348
 
2138
- {/* Assignee Actions - Show if not personal workspace */}
2139
- {!isPersonalWorkspace && (
2349
+ {/* Assignee Actions */}
2350
+ {shouldUseBoardAssignees && (
2140
2351
  <TaskAssigneesMenu
2141
2352
  taskAssignees={displayAssignees}
2142
2353
  availableMembers={workspaceMembers}
@@ -2206,7 +2417,7 @@ function TaskCardInner({
2206
2417
  )}
2207
2418
  </div>
2208
2419
  {/* Assignee: left, not cut off */}
2209
- {!isPersonalWorkspace && (
2420
+ {shouldUseBoardAssignees && (
2210
2421
  <div className="flex flex-none items-start justify-start">
2211
2422
  <AssigneeSelect
2212
2423
  taskId={task.id}
@@ -2247,30 +2458,46 @@ function TaskCardInner({
2247
2458
  : 'text-muted-foreground'
2248
2459
  )}
2249
2460
  >
2250
- <Calendar className="h-2.5 w-2.5 shrink-0" />
2251
- <span className="truncate">
2252
- {t('due_at', {
2253
- date: formatSmartDate(
2254
- endDate,
2255
- {
2256
- today: t('today'),
2257
- tomorrow: t('tomorrow'),
2258
- yesterday: t('yesterday'),
2259
- },
2260
- dateLocale
2261
- ),
2262
- })}
2263
- </span>
2264
- {isOverdue && !task.closed_at ? (
2265
- <Badge className="ml-1 h-4 bg-dynamic-red px-1 font-semibold text-[9px] text-white tracking-wide">
2266
- {t('overdue')}
2267
- </Badge>
2461
+ {upcomingDeadlineCountdown && upcomingDeadlineExactDate ? (
2462
+ <Tooltip>
2463
+ <TooltipTrigger asChild>
2464
+ <span className="inline-flex min-w-0 items-center gap-1 truncate font-medium">
2465
+ <Timer className="h-2.5 w-2.5" />
2466
+ <span className="truncate">
2467
+ {upcomingDeadlineCountdown}
2468
+ </span>
2469
+ </span>
2470
+ </TooltipTrigger>
2471
+ <TooltipContent side="top" className="text-xs">
2472
+ {upcomingDeadlineExactDate}
2473
+ </TooltipContent>
2474
+ </Tooltip>
2268
2475
  ) : (
2269
- <span className="ml-1 hidden text-[10px] text-muted-foreground md:inline">
2270
- {format(endDate, `MMM dd '${t('at')}' ${timePattern}`, {
2271
- locale: dateLocale,
2272
- })}
2273
- </span>
2476
+ <>
2477
+ <Calendar className="h-2.5 w-2.5 shrink-0" />
2478
+ <span className="truncate">
2479
+ {t('due_at', {
2480
+ date: formatSmartDate(
2481
+ endDate,
2482
+ {
2483
+ today: t('today'),
2484
+ tomorrow: t('tomorrow'),
2485
+ yesterday: t('yesterday'),
2486
+ },
2487
+ dateLocale
2488
+ ),
2489
+ })}
2490
+ </span>
2491
+ {isOverdue && !task.closed_at ? (
2492
+ <Badge className="ml-1 h-4 bg-dynamic-red px-1 font-semibold text-[9px] text-white tracking-wide">
2493
+ {t('overdue')}
2494
+ </Badge>
2495
+ ) : (
2496
+ <span className="ml-1 hidden text-[10px] text-muted-foreground md:inline">
2497
+ {upcomingDeadlineExactDate}
2498
+ </span>
2499
+ )}
2500
+ </>
2274
2501
  )}
2275
2502
  </div>
2276
2503
  )}
@@ -2293,7 +2520,7 @@ function TaskCardInner({
2293
2520
  <CheckCircle2 className="h-2.5 w-2.5 shrink-0" />
2294
2521
  <span className="truncate">
2295
2522
  {t('completed')}{' '}
2296
- {formatDistanceToNow(new Date(task.completed_at), {
2523
+ {formatDistance(new Date(task.completed_at), now, {
2297
2524
  addSuffix: true,
2298
2525
  locale: dateLocale,
2299
2526
  })}
@@ -2317,7 +2544,7 @@ function TaskCardInner({
2317
2544
  <CircleSlash className="h-2.5 w-2.5 shrink-0" />
2318
2545
  <span className="truncate">
2319
2546
  {t('closed')}{' '}
2320
- {formatDistanceToNow(new Date(task.closed_at), {
2547
+ {formatDistance(new Date(task.closed_at), now, {
2321
2548
  addSuffix: true,
2322
2549
  locale: dateLocale,
2323
2550
  })}
@@ -2590,4 +2817,12 @@ function TaskCardInner({
2590
2817
  );
2591
2818
  }
2592
2819
 
2593
- export const TaskCard = React.memo(TaskCardInner, areTaskCardPropsEqual);
2820
+ function TaskCardComponent(props: TaskCardProps) {
2821
+ if (props.readOnly) {
2822
+ return <ReadOnlyTaskCard {...props} />;
2823
+ }
2824
+
2825
+ return <TaskCardInner {...props} />;
2826
+ }
2827
+
2828
+ export const TaskCard = React.memo(TaskCardComponent, areTaskCardPropsEqual);
@@ -0,0 +1,152 @@
1
+ import '@testing-library/jest-dom';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { fireEvent, render, screen } from '@testing-library/react';
4
+ import type React from 'react';
5
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import type { TaskFilters } from '../../shared/task-filter.types';
7
+ import { TaskFilter } from './task-filter';
8
+
9
+ vi.mock('next-intl', () => ({
10
+ useTranslations: () => (key: string) => key,
11
+ }));
12
+
13
+ vi.mock('@tuturuuu/internal-api/tasks', () => ({
14
+ listWorkspaceLabels: vi.fn(() => Promise.resolve([])),
15
+ listWorkspaceTaskBoards: vi.fn(() =>
16
+ Promise.resolve({ boards: [], count: 0 })
17
+ ),
18
+ listWorkspaceTaskProjects: vi.fn(() => Promise.resolve([])),
19
+ }));
20
+
21
+ vi.mock('@tuturuuu/internal-api/workspaces', () => ({
22
+ listWorkspaces: vi.fn(() => Promise.resolve([])),
23
+ }));
24
+
25
+ vi.mock('@tuturuuu/ui/hooks/use-workspace-members', () => ({
26
+ useWorkspaceMembers: () => ({ data: [] }),
27
+ }));
28
+
29
+ vi.mock('@tuturuuu/ui/custom/combobox', () => ({
30
+ Combobox: ({
31
+ placeholder,
32
+ }: {
33
+ children?: React.ReactNode;
34
+ placeholder?: string;
35
+ }) => (
36
+ <button type="button" aria-label={placeholder}>
37
+ {placeholder}
38
+ </button>
39
+ ),
40
+ }));
41
+
42
+ beforeAll(() => {
43
+ class ResizeObserverMock {
44
+ observe() {}
45
+ unobserve() {}
46
+ disconnect() {}
47
+ }
48
+
49
+ vi.stubGlobal('ResizeObserver', ResizeObserverMock);
50
+ });
51
+
52
+ const baseFilters: TaskFilters = {
53
+ assignees: [],
54
+ dueDateRange: null,
55
+ estimationRange: null,
56
+ includeMyTasks: false,
57
+ includeUnassigned: false,
58
+ labels: [],
59
+ priorities: [],
60
+ projects: [],
61
+ sourceBoardIds: [],
62
+ sourceScope: 'all_visible',
63
+ sourceWorkspaceIds: [],
64
+ };
65
+
66
+ function renderTaskFilter(
67
+ overrides?: Partial<React.ComponentProps<typeof TaskFilter>>
68
+ ) {
69
+ const queryClient = new QueryClient({
70
+ defaultOptions: {
71
+ queries: {
72
+ retry: false,
73
+ },
74
+ },
75
+ });
76
+ const onFiltersChange = vi.fn();
77
+
78
+ render(
79
+ <QueryClientProvider client={queryClient}>
80
+ <TaskFilter
81
+ currentUserId="user-1"
82
+ filters={baseFilters}
83
+ onFiltersChange={onFiltersChange}
84
+ wsId="ws-1"
85
+ {...overrides}
86
+ />
87
+ </QueryClientProvider>
88
+ );
89
+
90
+ return { onFiltersChange };
91
+ }
92
+
93
+ describe('TaskFilter', () => {
94
+ beforeEach(() => {
95
+ vi.clearAllMocks();
96
+ });
97
+
98
+ it('renders compact filter sections with responsive due-date controls', () => {
99
+ const { onFiltersChange } = renderTaskFilter();
100
+
101
+ fireEvent.click(screen.getByRole('button', { name: 'common.filters' }));
102
+
103
+ expect(screen.getByText('common.quick_filters')).toBeInTheDocument();
104
+ expect(
105
+ screen.getAllByText('ws-tasks.filter_source_scope').length
106
+ ).toBeGreaterThan(0);
107
+ expect(screen.getByText('common.people')).toBeInTheDocument();
108
+ expect(screen.getByText('common.details')).toBeInTheDocument();
109
+ expect(screen.getAllByText('common.due_date').length).toBeGreaterThan(0);
110
+ expect(screen.getByLabelText('common.from')).toHaveAttribute(
111
+ 'type',
112
+ 'date'
113
+ );
114
+ expect(screen.getByLabelText('common.to')).toHaveAttribute('type', 'date');
115
+ expect(screen.getByLabelText('common.clear')).toBeInTheDocument();
116
+ expect(
117
+ screen.queryByRole('grid', { name: /calendar/i })
118
+ ).not.toBeInTheDocument();
119
+
120
+ fireEvent.change(screen.getByLabelText('common.from'), {
121
+ target: { value: '2026-06-22' },
122
+ });
123
+
124
+ expect(onFiltersChange).toHaveBeenCalledWith(
125
+ expect.objectContaining({
126
+ dueDateRange: expect.objectContaining({
127
+ from: expect.any(Date),
128
+ }),
129
+ })
130
+ );
131
+ });
132
+
133
+ it('shows active count badges for compact sections', () => {
134
+ renderTaskFilter({
135
+ filters: {
136
+ ...baseFilters,
137
+ dueDateRange: { from: new Date(2026, 5, 22), to: undefined },
138
+ includeMyTasks: true,
139
+ sourceScope: 'external_current_workspace',
140
+ },
141
+ });
142
+
143
+ expect(
144
+ screen.getByRole('button', { name: 'common.filters' })
145
+ ).toHaveTextContent('3');
146
+
147
+ fireEvent.click(screen.getByRole('button', { name: 'common.filters' }));
148
+
149
+ expect(screen.getByText('common.quick_filters')).toBeInTheDocument();
150
+ expect(screen.getAllByText('1').length).toBeGreaterThanOrEqual(3);
151
+ });
152
+ });