@tuturuuu/ui 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/biome.json +1 -1
  3. package/package.json +73 -71
  4. package/src/components/ui/accordion.tsx +1 -1
  5. package/src/components/ui/breadcrumb.tsx +1 -1
  6. package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
  8. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
  9. package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
  10. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
  12. package/src/components/ui/calendar.tsx +1 -1
  13. package/src/components/ui/carousel.tsx +1 -1
  14. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
  15. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
  16. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
  17. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
  18. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
  19. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
  20. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
  21. package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
  22. package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
  23. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
  24. package/src/components/ui/checkbox.tsx +1 -1
  25. package/src/components/ui/color-picker.tsx +1 -1
  26. package/src/components/ui/command.tsx +1 -1
  27. package/src/components/ui/context-menu.tsx +5 -1
  28. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  29. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
  30. package/src/components/ui/custom/combobox.test.tsx +195 -0
  31. package/src/components/ui/custom/combobox.tsx +273 -156
  32. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  33. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  34. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  35. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  36. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  37. package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
  38. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  39. package/src/components/ui/custom/workspace-select.tsx +8 -3
  40. package/src/components/ui/dialog.test.tsx +52 -0
  41. package/src/components/ui/dialog.tsx +6 -2
  42. package/src/components/ui/dropdown-menu.tsx +5 -1
  43. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  44. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  45. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  46. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  47. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  48. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  49. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  50. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  51. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  52. package/src/components/ui/finance/invoices/utils.ts +3 -1
  53. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  54. package/src/components/ui/finance/transactions/form-types.ts +1 -0
  55. package/src/components/ui/finance/transactions/form.tsx +2 -0
  56. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  57. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  58. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  59. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  60. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  61. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  62. package/src/components/ui/finance/wallets/form.tsx +15 -4
  63. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  64. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  65. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  66. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  67. package/src/components/ui/input-otp.tsx +1 -1
  68. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  69. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  70. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  71. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  72. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  73. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  74. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  75. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  76. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  77. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  78. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  79. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  80. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  81. package/src/components/ui/navigation-menu.tsx +1 -1
  82. package/src/components/ui/pagination.tsx +1 -1
  83. package/src/components/ui/radio-group.tsx +1 -1
  84. package/src/components/ui/select.tsx +5 -1
  85. package/src/components/ui/sheet.tsx +1 -1
  86. package/src/components/ui/sidebar.tsx +1 -1
  87. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  88. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  89. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  90. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  91. package/src/components/ui/storefront/listing-card.tsx +1 -1
  92. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  93. package/src/components/ui/storefront/product-detail.tsx +1 -1
  94. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  95. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  96. package/src/components/ui/storefront/types.ts +4 -0
  97. package/src/components/ui/storefront/utils.ts +6 -0
  98. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  99. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  100. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  101. package/src/components/ui/text-editor/editor.tsx +69 -14
  102. package/src/components/ui/text-editor/extensions.ts +8 -2
  103. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  104. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  105. package/src/components/ui/toast.tsx +1 -1
  106. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  107. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  108. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  109. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +112 -43
  110. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  111. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  112. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  113. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  114. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  115. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  116. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  117. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  118. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  119. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  120. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  121. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  122. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  127. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  128. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  129. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  131. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +397 -2
  132. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +103 -13
  133. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  134. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  135. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  136. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +26 -4
  137. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +5 -2
  138. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  139. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  140. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  141. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  142. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  143. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  144. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  145. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  146. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  147. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  148. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  149. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  150. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  151. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  152. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  153. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  154. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +203 -2
  155. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  156. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  157. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  158. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  159. package/src/components/ui/tu-do/shared/board-header.tsx +464 -975
  160. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  161. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  162. package/src/components/ui/tu-do/shared/board-views.tsx +587 -75
  163. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  164. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  165. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  166. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  167. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  168. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  169. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  170. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  171. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  172. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +2 -1
  173. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  174. package/src/declarations.d.ts +1 -0
  175. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  176. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  177. package/src/hooks/use-calendar-sync.tsx +247 -243
  178. package/src/hooks/use-calendar.tsx +323 -138
  179. package/src/hooks/use-task-actions.ts +24 -0
  180. package/src/hooks/use-user-workspace-config.ts +75 -0
  181. package/src/hooks/use-workspace-currency.ts +8 -3
  182. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
@@ -0,0 +1,143 @@
1
+ import { createAdminClient } from '@tuturuuu/supabase/next/server';
2
+ import type { ExtendedWorkspaceTask } from '../../time-tracker/types';
3
+
4
+ interface LoadSmartSchedulingTasksOptions {
5
+ resolvedWsId: string;
6
+ userId: string;
7
+ }
8
+
9
+ interface AccessibleTaskRow {
10
+ task_id: string;
11
+ task_name: string | null;
12
+ task_description: string | null;
13
+ task_creator_id: string | null;
14
+ task_list_id: string | null;
15
+ task_start_date: string | null;
16
+ task_end_date: string | null;
17
+ task_priority: ExtendedWorkspaceTask['priority'];
18
+ task_completed_at: string | null;
19
+ task_closed_at: string | null;
20
+ task_deleted_at: string | null;
21
+ task_estimation_points: number | null;
22
+ task_created_at: string | null;
23
+ }
24
+
25
+ interface TaskListWorkspaceRow {
26
+ id?: string | null;
27
+ workspace_boards?: {
28
+ ws_id?: string | null;
29
+ } | null;
30
+ }
31
+
32
+ interface TaskSchedulingRow {
33
+ task_id?: string | null;
34
+ total_duration: number | null;
35
+ is_splittable: boolean | null;
36
+ min_split_duration_minutes: number | null;
37
+ max_split_duration_minutes: number | null;
38
+ calendar_hours: ExtendedWorkspaceTask['calendar_hours'];
39
+ auto_schedule: boolean | null;
40
+ }
41
+
42
+ export async function loadSmartSchedulingTasks({
43
+ resolvedWsId,
44
+ userId,
45
+ }: LoadSmartSchedulingTasksOptions): Promise<ExtendedWorkspaceTask[]> {
46
+ const supabase = await createAdminClient({ noCookie: true });
47
+ const { data: rpcTasks } = await supabase.rpc('get_user_accessible_tasks', {
48
+ p_user_id: userId,
49
+ p_ws_id: resolvedWsId,
50
+ p_include_deleted: false,
51
+ p_list_statuses: ['not_started', 'active'],
52
+ });
53
+
54
+ const tasksBase = ((rpcTasks ?? []) as AccessibleTaskRow[]).map((task) => ({
55
+ id: task.task_id,
56
+ name: task.task_name,
57
+ description: task.task_description,
58
+ creator_id: task.task_creator_id,
59
+ list_id: task.task_list_id,
60
+ start_date: task.task_start_date,
61
+ end_date: task.task_end_date,
62
+ due_date: task.task_end_date,
63
+ priority: task.task_priority,
64
+ completed_at: task.task_completed_at,
65
+ closed_at: task.task_closed_at,
66
+ deleted_at: task.task_deleted_at,
67
+ estimation_points: task.task_estimation_points,
68
+ created_at: task.task_created_at,
69
+ })) as ExtendedWorkspaceTask[];
70
+
71
+ const listIds = Array.from(
72
+ new Set(tasksBase.map((task) => task.list_id).filter(Boolean))
73
+ ) as string[];
74
+
75
+ const wsIdByListId = new Map<string, string>();
76
+ if (listIds.length > 0) {
77
+ const { data: lists } = await supabase
78
+ .from('task_lists')
79
+ .select(
80
+ `
81
+ id,
82
+ workspace_boards!inner (
83
+ ws_id
84
+ )
85
+ `
86
+ )
87
+ .in('id', listIds);
88
+
89
+ (lists as TaskListWorkspaceRow[] | null)?.forEach((list) => {
90
+ const taskWsId = list.workspace_boards?.ws_id;
91
+ if (list.id && taskWsId) wsIdByListId.set(list.id, taskWsId);
92
+ });
93
+ }
94
+
95
+ const tasks = tasksBase.map((task) => ({
96
+ ...task,
97
+ ws_id:
98
+ (task.list_id ? wsIdByListId.get(task.list_id) : undefined) ??
99
+ resolvedWsId,
100
+ })) as ExtendedWorkspaceTask[];
101
+
102
+ const taskIds = tasks.map((task) => task.id).filter(Boolean);
103
+ const settingsByTaskId = new Map<string, TaskSchedulingRow>();
104
+
105
+ if (taskIds.length > 0) {
106
+ const { data: schedulingRows } = await supabase
107
+ .from('task_user_scheduling_settings')
108
+ .select(
109
+ `
110
+ task_id,
111
+ total_duration,
112
+ is_splittable,
113
+ min_split_duration_minutes,
114
+ max_split_duration_minutes,
115
+ calendar_hours,
116
+ auto_schedule
117
+ `
118
+ )
119
+ .eq('user_id', userId)
120
+ .in('task_id', taskIds);
121
+
122
+ (schedulingRows as TaskSchedulingRow[] | null)?.forEach((row) => {
123
+ if (row.task_id) settingsByTaskId.set(row.task_id, row);
124
+ });
125
+ }
126
+
127
+ return tasks.map((task) => {
128
+ const settings = settingsByTaskId.get(task.id);
129
+ return settings
130
+ ? ({
131
+ ...task,
132
+ total_duration: settings.total_duration,
133
+ is_splittable: settings.is_splittable ?? false,
134
+ min_split_duration_minutes:
135
+ settings.min_split_duration_minutes ?? null,
136
+ max_split_duration_minutes:
137
+ settings.max_split_duration_minutes ?? null,
138
+ calendar_hours: settings.calendar_hours ?? null,
139
+ auto_schedule: settings.auto_schedule ?? false,
140
+ } as ExtendedWorkspaceTask)
141
+ : task;
142
+ });
143
+ }
@@ -23,6 +23,7 @@ import {
23
23
  } from '@tuturuuu/icons';
24
24
  import {
25
25
  listWorkspaceTaskLists,
26
+ listWorkspaceTasks,
26
27
  updateWorkspaceTask,
27
28
  } from '@tuturuuu/internal-api/tasks';
28
29
  import type { TaskPriority } from '@tuturuuu/types/primitives/Priority';
@@ -40,7 +41,6 @@ import ActionsDropdown from './actions-dropdown';
40
41
  import PriorityDropdown from './priority-dropdown';
41
42
  import { QuickTaskDialog } from './quick-task-dialog';
42
43
  import { SchedulingDialog } from './scheduling-dialog';
43
- import { getAssignedTasks } from './task-fetcher';
44
44
 
45
45
  // Priority labels (matching task-properties-section.tsx)
46
46
  const PRIORITY_LABELS: Record<TaskPriority, string> = {
@@ -356,8 +356,15 @@ export default function PriorityView({
356
356
  setSearchError(null);
357
357
 
358
358
  try {
359
- const results = await getAssignedTasks(assigneeId, searchQuery.trim());
360
- setSearchResults(results);
359
+ const results = await listWorkspaceTasks(wsId, {
360
+ assigneeIds: [assigneeId],
361
+ closed: 'exclude',
362
+ completed: 'exclude',
363
+ includeArchivedBoards: true,
364
+ limit: 50,
365
+ q: searchQuery.trim(),
366
+ });
367
+ setSearchResults(results.tasks as ExtendedWorkspaceTask[]);
361
368
  } catch (error) {
362
369
  console.error('Error searching tasks:', error);
363
370
  setSearchError('Failed to search tasks');
@@ -1,4 +1,3 @@
1
- import { createAdminClient } from '@tuturuuu/supabase/next/server';
2
1
  import type { ExtendedWorkspaceTask } from '../../time-tracker/types';
3
2
  import { CalendarSidebar } from './sidebar';
4
3
 
@@ -6,126 +5,15 @@ interface TasksSidebarProps {
6
5
  resolvedWsId: string;
7
6
  locale: string;
8
7
  userId: string;
8
+ tasks?: ExtendedWorkspaceTask[];
9
9
  }
10
10
 
11
- export default async function TasksSidebar({
11
+ export default function TasksSidebar({
12
12
  resolvedWsId,
13
13
  locale,
14
14
  userId,
15
+ tasks = [],
15
16
  }: TasksSidebarProps) {
16
- // Use the same RPC as the tasks page to get accessible tasks
17
- const supabase = await createAdminClient({ noCookie: true });
18
- const { data: rpcTasks } = await supabase.rpc('get_user_accessible_tasks', {
19
- p_user_id: userId,
20
- p_ws_id: resolvedWsId,
21
- p_include_deleted: false,
22
- p_list_statuses: ['not_started', 'active'],
23
- });
24
-
25
- // Map RPC results to match expected structure (same as my-tasks-data-loader.tsx)
26
- const tasksBase = (rpcTasks?.map((task) => ({
27
- id: task.task_id,
28
- name: task.task_name,
29
- description: task.task_description,
30
- creator_id: task.task_creator_id,
31
- list_id: task.task_list_id,
32
- start_date: task.task_start_date,
33
- end_date: task.task_end_date,
34
- due_date: task.task_end_date, // Map end_date to due_date for display
35
- priority: task.task_priority,
36
- completed_at: task.task_completed_at,
37
- closed_at: task.task_closed_at,
38
- deleted_at: task.task_deleted_at,
39
- estimation_points: task.task_estimation_points,
40
- created_at: task.task_created_at,
41
- // Scheduling fields are per-user now. These legacy RPC fields may not exist after migration.
42
- })) || []) as ExtendedWorkspaceTask[];
43
-
44
- // Enrich tasks with the *actual* workspace UUID (ws_id) via list -> board relation.
45
- // This is critical for personal workspace views where tasks may belong to other workspaces.
46
- const listIds = Array.from(
47
- new Set(tasksBase.map((t) => t.list_id).filter(Boolean))
48
- ) as string[];
49
-
50
- const wsIdByListId = new Map<string, string>();
51
- if (listIds.length > 0) {
52
- const { data: lists } = await supabase
53
- .from('task_lists')
54
- .select(
55
- `
56
- id,
57
- workspace_boards!inner (
58
- ws_id
59
- )
60
- `
61
- )
62
- .in('id', listIds);
63
-
64
- (lists as any[] | null)?.forEach((l) => {
65
- const resolvedTaskWsId = l?.workspace_boards?.ws_id;
66
- if (l?.id && resolvedTaskWsId) wsIdByListId.set(l.id, resolvedTaskWsId);
67
- });
68
- }
69
-
70
- const tasks = tasksBase.map((t) => ({
71
- ...t,
72
- ws_id:
73
- (t.list_id ? wsIdByListId.get(t.list_id) : undefined) ??
74
- // Fallback: if a task is list-less, treat it as current workspace-scoped.
75
- resolvedWsId,
76
- })) as ExtendedWorkspaceTask[];
77
-
78
- // Merge per-user scheduling settings so the calendar UI can still show duration/hour type.
79
- const taskIds = tasks.map((t) => t.id).filter(Boolean);
80
- const settingsByTaskId = new Map<
81
- string,
82
- {
83
- total_duration: number | null;
84
- is_splittable: boolean | null;
85
- min_split_duration_minutes: number | null;
86
- max_split_duration_minutes: number | null;
87
- calendar_hours: any;
88
- auto_schedule: boolean | null;
89
- }
90
- >();
91
-
92
- if (taskIds.length > 0) {
93
- const { data: schedulingRows } = await (supabase as any)
94
- .from('task_user_scheduling_settings')
95
- .select(
96
- `
97
- task_id,
98
- total_duration,
99
- is_splittable,
100
- min_split_duration_minutes,
101
- max_split_duration_minutes,
102
- calendar_hours,
103
- auto_schedule
104
- `
105
- )
106
- .eq('user_id', userId)
107
- .in('task_id', taskIds);
108
-
109
- (schedulingRows as any[] | null)?.forEach((r) => {
110
- if (r?.task_id) settingsByTaskId.set(r.task_id, r);
111
- });
112
- }
113
-
114
- const tasksWithScheduling = tasks.map((t) => {
115
- const s = settingsByTaskId.get(t.id);
116
- return s
117
- ? ({
118
- ...t,
119
- total_duration: s.total_duration,
120
- is_splittable: s.is_splittable ?? false,
121
- min_split_duration_minutes: s.min_split_duration_minutes ?? null,
122
- max_split_duration_minutes: s.max_split_duration_minutes ?? null,
123
- calendar_hours: s.calendar_hours ?? null,
124
- auto_schedule: s.auto_schedule ?? false,
125
- } as ExtendedWorkspaceTask)
126
- : t;
127
- });
128
-
129
17
  // Personal workspace = workspace ID matches user ID (no need for auto-assignment)
130
18
  const isPersonalWorkspace = resolvedWsId === userId;
131
19
 
@@ -135,7 +23,7 @@ export default async function TasksSidebar({
135
23
  // Always pass the resolved workspace UUID, not the route slug (e.g. "personal").
136
24
  wsId={resolvedWsId}
137
25
  assigneeId={userId}
138
- tasks={tasksWithScheduling}
26
+ tasks={tasks}
139
27
  locale={locale}
140
28
  isPersonalWorkspace={isPersonalWorkspace}
141
29
  />
@@ -3,8 +3,11 @@ import {
3
3
  type CalendarSourceOption,
4
4
  getGoogleCalendarAuthUrl,
5
5
  getWorkspaceCalendarDefaultSource,
6
+ getWorkspaceCalendarSyncPreferences,
7
+ updateCalendarConnection as updateCalendarConnectionRequest,
6
8
  updateWorkspaceCalendarDefaultSource,
7
- } from '@tuturuuu/internal-api';
9
+ updateWorkspaceCalendarSyncPreferences,
10
+ } from '@tuturuuu/internal-api/calendar';
8
11
  import { createClient } from '@tuturuuu/supabase/next/client';
9
12
  import { useRouter } from 'next/navigation';
10
13
  import { useTranslations } from 'next-intl';
@@ -184,6 +187,12 @@ export function useCalendarConnectionsManager(wsId: string) {
184
187
  staleTime: 30_000,
185
188
  });
186
189
 
190
+ const { data: syncPreferencesData } = useQuery({
191
+ queryKey: ['calendar-sync-preferences', wsId],
192
+ queryFn: () => getWorkspaceCalendarSyncPreferences(wsId),
193
+ staleTime: 30_000,
194
+ });
195
+
187
196
  const defaultSourceMutation = useMutation({
188
197
  mutationFn: async (sourceId: string) => {
189
198
  const option = defaultSourceData?.options.find(
@@ -209,6 +218,46 @@ export function useCalendarConnectionsManager(wsId: string) {
209
218
  },
210
219
  });
211
220
 
221
+ const syncPreferencesMutation = useMutation({
222
+ mutationFn: (
223
+ payload: Parameters<typeof updateWorkspaceCalendarSyncPreferences>[1]
224
+ ) => updateWorkspaceCalendarSyncPreferences(wsId, payload),
225
+ onSuccess: () => {
226
+ queryClient.invalidateQueries({
227
+ queryKey: ['calendar-sync-preferences', wsId],
228
+ });
229
+ toast.success(
230
+ t('calendar_sync_settings_updated') || 'Calendar sync settings updated'
231
+ );
232
+ },
233
+ onError: (error: Error) => {
234
+ toast.error(error.message || 'Failed to update calendar sync settings');
235
+ },
236
+ });
237
+
238
+ const updateConnectionSyncSettingsMutation = useMutation({
239
+ mutationFn: (
240
+ payload: Parameters<typeof updateCalendarConnectionRequest>[0]
241
+ ) => updateCalendarConnectionRequest(payload),
242
+ onSuccess: () => {
243
+ queryClient.invalidateQueries({
244
+ queryKey: ['calendar-connections', wsId],
245
+ });
246
+ queryClient.invalidateQueries({
247
+ queryKey: ['provider-calendar-list', wsId],
248
+ });
249
+ queryClient.invalidateQueries({
250
+ queryKey: ['calendar-sync-preferences', wsId],
251
+ });
252
+ toast.success(
253
+ t('calendar_sync_settings_updated') || 'Calendar sync settings updated'
254
+ );
255
+ },
256
+ onError: (error: Error) => {
257
+ toast.error(error.message || 'Failed to update calendar sync settings');
258
+ },
259
+ });
260
+
212
261
  // Fetch current user's email
213
262
  const { data: userEmail } = useQuery({
214
263
  queryKey: ['current-user-email'],
@@ -621,7 +670,13 @@ export function useCalendarConnectionsManager(wsId: string) {
621
670
  const existingConnection = calendarConnections.find(
622
671
  (conn) =>
623
672
  conn.calendar_id === apiCal.id && conn.auth_token_id === account.id
624
- );
673
+ ) as
674
+ | ((typeof calendarConnections)[number] & {
675
+ sync_delete_enabled?: boolean | null;
676
+ sync_inbound_enabled?: boolean | null;
677
+ sync_outbound_enabled?: boolean | null;
678
+ })
679
+ | undefined;
625
680
 
626
681
  return {
627
682
  id: existingConnection?.id || apiCal.id,
@@ -634,6 +689,10 @@ export function useCalendarConnectionsManager(wsId: string) {
634
689
  connectionExists: !!existingConnection,
635
690
  accountId: account.id,
636
691
  accessRole: apiCal.accessRole,
692
+ syncDeleteEnabled: existingConnection?.sync_delete_enabled ?? true,
693
+ syncInboundEnabled: existingConnection?.sync_inbound_enabled ?? true,
694
+ syncOutboundEnabled:
695
+ existingConnection?.sync_outbound_enabled ?? false,
637
696
  };
638
697
  });
639
698
 
@@ -652,6 +711,9 @@ export function useCalendarConnectionsManager(wsId: string) {
652
711
  connectionExists: boolean;
653
712
  accountId: string;
654
713
  accessRole: string;
714
+ syncDeleteEnabled: boolean;
715
+ syncInboundEnabled: boolean;
716
+ syncOutboundEnabled: boolean;
655
717
  }>
656
718
  >
657
719
  );
@@ -688,6 +750,8 @@ export function useCalendarConnectionsManager(wsId: string) {
688
750
  showCreateCalendarDialog,
689
751
  syncHealth,
690
752
  syncMutation,
753
+ syncPreferencesData,
754
+ syncPreferencesMutation,
691
755
  syncStatusStyles,
692
756
  syncToTuturuuu,
693
757
  systemCalendars,
@@ -696,6 +760,7 @@ export function useCalendarConnectionsManager(wsId: string) {
696
760
  togglingTuturuuuIds,
697
761
  toggleAccountExpanded,
698
762
  toggleWorkspaceCalendarMutation,
763
+ updateConnectionSyncSettingsMutation,
699
764
  tuturuuuEnabledCount,
700
765
  userEmail,
701
766
  workspaceCalendars,
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ChevronLeft, ChevronRight } from '@tuturuuu/icons';
3
+ import { ChevronLeft, ChevronRight } from '@tuturuuu/icons/lucide-static';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import { format } from 'date-fns';
6
6
  import dayjs from 'dayjs';
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { ArrowLeft, ArrowRight } from '@tuturuuu/icons';
3
+ import { ArrowLeft, ArrowRight } from '@tuturuuu/icons/lucide-static';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import useEmblaCarousel, {
6
6
  type UseEmblaCarouselType,
@@ -18,7 +18,7 @@ vi.mock('next-intl', () => ({
18
18
  values?.count === undefined ? key : `${key}:${values.count}`,
19
19
  }));
20
20
 
21
- vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
21
+ vi.mock('@tuturuuu/internal-api/infrastructure/ai', () => ({
22
22
  draftAiAgentExternalResponse: (...args: unknown[]) =>
23
23
  mocks.draftAiAgentExternalResponse(...args),
24
24
  listAiAgentExternalThreads: (...args: unknown[]) =>
@@ -13,7 +13,7 @@ import {
13
13
  listAiAgentExternalThreads,
14
14
  sendAiAgentExternalResponse,
15
15
  syncAiAgentExternalThread,
16
- } from '@tuturuuu/internal-api/infrastructure';
16
+ } from '@tuturuuu/internal-api/infrastructure/ai';
17
17
  import { useTranslations } from 'next-intl';
18
18
  import { useState } from 'react';
19
19
  import { Button } from '../button';
@@ -16,7 +16,7 @@ vi.mock('next-intl', () => ({
16
16
  useTranslations: () => (key: string) => key,
17
17
  }));
18
18
 
19
- vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
19
+ vi.mock('@tuturuuu/internal-api/infrastructure/ai', () => ({
20
20
  abortAiAgentZaloPersonalQrLogin: (
21
21
  ...args: Parameters<typeof mocks.abortAiAgentZaloPersonalQrLogin>
22
22
  ) => mocks.abortAiAgentZaloPersonalQrLogin(...args),
@@ -14,7 +14,7 @@ import {
14
14
  import type {
15
15
  AiAgentChannelConfig,
16
16
  AiAgentTestResponse,
17
- } from '@tuturuuu/internal-api/infrastructure';
17
+ } from '@tuturuuu/internal-api/infrastructure/ai';
18
18
  import { useTranslations } from 'next-intl';
19
19
  import { useState } from 'react';
20
20
  import { Button } from '../button';
@@ -14,7 +14,7 @@ import type {
14
14
  AiAgentChannelConfig,
15
15
  AiAgentDefinition,
16
16
  SaveAiAgentPayload,
17
- } from '@tuturuuu/internal-api/infrastructure';
17
+ } from '@tuturuuu/internal-api/infrastructure/ai';
18
18
  import { useTranslations } from 'next-intl';
19
19
  import { useState } from 'react';
20
20
  import { Badge } from '../badge';
@@ -20,7 +20,7 @@ vi.mock('next-intl', () => ({
20
20
  useTranslations: () => (key: string) => key,
21
21
  }));
22
22
 
23
- vi.mock('@tuturuuu/internal-api/infrastructure', () => ({
23
+ vi.mock('@tuturuuu/internal-api/infrastructure/ai', () => ({
24
24
  abortAiAgentZaloPersonalQrLogin: vi.fn(),
25
25
  deployAiAgentChannel: vi.fn(),
26
26
  getAiAgentZaloPersonalQrLoginStatus: vi.fn(),
@@ -6,7 +6,7 @@ import type { ChatConversation } from '@tuturuuu/internal-api';
6
6
  import type {
7
7
  AiAgentTestResponse,
8
8
  SaveAiAgentPayload,
9
- } from '@tuturuuu/internal-api/infrastructure';
9
+ } from '@tuturuuu/internal-api/infrastructure/ai';
10
10
  import {
11
11
  deployAiAgentChannel,
12
12
  listAiAgents,
@@ -14,7 +14,7 @@ import {
14
14
  rotateAiAgentChannelSecret,
15
15
  saveAiAgent,
16
16
  testAiAgentChannel,
17
- } from '@tuturuuu/internal-api/infrastructure';
17
+ } from '@tuturuuu/internal-api/infrastructure/ai';
18
18
  import { useTranslations } from 'next-intl';
19
19
  import { useMemo, useState } from 'react';
20
20
  import { Badge } from '../badge';
@@ -1,5 +1,5 @@
1
1
  import type { ChatConversation } from '@tuturuuu/internal-api';
2
- import type { AiAgentDefinition } from '@tuturuuu/internal-api/infrastructure';
2
+ import type { AiAgentDefinition } from '@tuturuuu/internal-api/infrastructure/ai';
3
3
  import { ROOT_WORKSPACE_ID } from '@tuturuuu/utils/constants';
4
4
  import { describe, expect, it } from 'vitest';
5
5
  import {
@@ -6,7 +6,7 @@ import type {
6
6
  AiAgentChannelConfig,
7
7
  AiAgentDefinition,
8
8
  SaveAiAgentPayload,
9
- } from '@tuturuuu/internal-api/infrastructure';
9
+ } from '@tuturuuu/internal-api/infrastructure/ai';
10
10
  import { cn } from '@tuturuuu/utils/format';
11
11
  import { useTranslations } from 'next-intl';
12
12
  import type { ReactNode } from 'react';
@@ -19,14 +19,14 @@ import type {
19
19
  AiAgentZaloPersonalPhoneSyncResult,
20
20
  AiAgentZaloPersonalQrLoginSession,
21
21
  AiAgentZaloPersonalQrLoginStatus,
22
- } from '@tuturuuu/internal-api/infrastructure';
22
+ } from '@tuturuuu/internal-api/infrastructure/ai';
23
23
  import {
24
24
  abortAiAgentZaloPersonalQrLogin,
25
25
  getAiAgentZaloPersonalQrLoginStatus,
26
26
  getAiAgentZaloPersonalStatus,
27
27
  runAiAgentZaloPersonalAction,
28
28
  startAiAgentZaloPersonalQrLogin,
29
- } from '@tuturuuu/internal-api/infrastructure';
29
+ } from '@tuturuuu/internal-api/infrastructure/ai';
30
30
  import Image from 'next/image';
31
31
  import { useTranslations } from 'next-intl';
32
32
  import type { ReactNode } from 'react';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
4
- import { CheckIcon, MinusIcon } from '@tuturuuu/icons';
4
+ import { CheckIcon, MinusIcon } from '@tuturuuu/icons/lucide-static';
5
5
  import { cn } from '@tuturuuu/utils/format';
6
6
  import type * as React from 'react';
7
7
 
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { TextIcon } from '@tuturuuu/icons';
3
+ import { TextIcon } from '@tuturuuu/icons/lucide-static';
4
4
  import { forwardRef, useMemo, useState } from 'react';
5
5
  import { HexColorPicker } from 'react-colorful';
6
6
  import { useDomResolvedTheme } from '../../hooks/use-dom-resolved-theme';
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { SearchIcon } from '@tuturuuu/icons';
3
+ import { SearchIcon } from '@tuturuuu/icons/lucide-static';
4
4
  import { cn } from '@tuturuuu/utils/format';
5
5
  import { Command as CommandPrimitive } from 'cmdk';
6
6
  import type * as React from 'react';
@@ -1,7 +1,11 @@
1
1
  'use client';
2
2
 
3
3
  import * as ContextMenuPrimitive from '@radix-ui/react-context-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
 
@@ -92,10 +92,13 @@ describe('SettingsDialogShell keyboard navigation', () => {
92
92
  renderShell();
93
93
 
94
94
  const dialog = screen.getByRole('dialog');
95
+ const className = dialog.getAttribute('class') ?? '';
95
96
 
96
97
  expect(dialog).toHaveClass('h-dvh');
97
98
  expect(dialog).toHaveClass('w-screen');
99
+ expect(dialog).toHaveClass('bg-background');
98
100
  expect(dialog).toHaveClass('rounded-none');
101
+ expect(className).not.toMatch(/animate-in|fade-in|zoom-in|slide-in/);
99
102
  expect(
100
103
  screen.getAllByRole('button', { name: 'settings.back_to_app' }).length
101
104
  ).toBeGreaterThan(0);
@@ -1,3 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import type { InternalApiWorkspaceSummary } from '@tuturuuu/types';
2
4
  import { describe, expect, it } from 'vitest';
3
5
  import { mergeWorkspaceSelectWorkspaces } from '../workspace-select-helpers';
@@ -36,4 +38,21 @@ describe('mergeWorkspaceSelectWorkspaces', () => {
36
38
  workspace,
37
39
  ]);
38
40
  });
41
+
42
+ it('keeps workspace fallback images outside Radix AvatarImage context', () => {
43
+ const workspaceSelectSource = readFileSync(
44
+ join(process.cwd(), 'src/components/ui/custom/workspace-select.tsx'),
45
+ 'utf8'
46
+ );
47
+ const workspaceIconSource = workspaceSelectSource.slice(
48
+ workspaceSelectSource.indexOf('function WorkspaceIcon'),
49
+ workspaceSelectSource.indexOf('export function WorkspaceSelect')
50
+ );
51
+
52
+ expect(workspaceIconSource).toContain('<AvatarFallback');
53
+ expect(workspaceIconSource).toContain('<Image');
54
+ expect(workspaceIconSource).not.toMatch(
55
+ /<AvatarFallback[\s\S]*<AvatarImage/u
56
+ );
57
+ });
39
58
  });