@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.
- package/CHANGELOG.md +40 -0
- package/biome.json +1 -1
- package/package.json +73 -71
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +1 -1
- package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
- package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
- package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
- package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/carousel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
- package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
- package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/color-picker.tsx +1 -1
- package/src/components/ui/command.tsx +1 -1
- package/src/components/ui/context-menu.tsx +5 -1
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
- package/src/components/ui/custom/combobox.test.tsx +195 -0
- package/src/components/ui/custom/combobox.tsx +273 -156
- package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
- package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
- package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
- package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
- package/src/components/ui/custom/theme-toggle.tsx +1 -1
- package/src/components/ui/custom/workspace-select.tsx +8 -3
- package/src/components/ui/dialog.test.tsx +52 -0
- package/src/components/ui/dialog.tsx +6 -2
- package/src/components/ui/dropdown-menu.tsx +5 -1
- package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
- package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
- package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
- package/src/components/ui/finance/debts/debts-page.tsx +15 -2
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
- package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/utils.ts +3 -1
- package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
- package/src/components/ui/finance/transactions/form-types.ts +1 -0
- package/src/components/ui/finance/transactions/form.tsx +2 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
- package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
- package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
- package/src/components/ui/finance/wallets/form.test.tsx +51 -3
- package/src/components/ui/finance/wallets/form.tsx +15 -4
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
- package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
- package/src/components/ui/input-otp.tsx +1 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
- package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
- package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
- package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
- package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
- package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
- package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
- package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
- package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
- package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
- package/src/components/ui/navigation-menu.tsx +1 -1
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/radio-group.tsx +1 -1
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/ui/storefront/cart-popover.tsx +61 -0
- package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
- package/src/components/ui/storefront/cart-summary.tsx +93 -154
- package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
- package/src/components/ui/storefront/listing-card.tsx +1 -1
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +1 -1
- package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
- package/src/components/ui/storefront/storefront-surface.tsx +101 -166
- package/src/components/ui/storefront/types.ts +4 -0
- package/src/components/ui/storefront/utils.ts +6 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/background-color-extension.ts +62 -0
- package/src/components/ui/text-editor/color-controls.tsx +284 -0
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +8 -2
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
- package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
- package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +112 -43
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +397 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +103 -13
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +26 -4
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +5 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
- package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
- package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
- package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
- package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
- package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
- package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
- package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
- package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +203 -2
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
- package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
- package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
- package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +464 -975
- package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
- package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
- package/src/components/ui/tu-do/shared/board-views.tsx +587 -75
- package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
- package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
- package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +2 -1
- package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
- package/src/declarations.d.ts +1 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
- package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
- package/src/hooks/use-calendar-sync.tsx +247 -243
- package/src/hooks/use-calendar.tsx +323 -138
- package/src/hooks/use-task-actions.ts +24 -0
- package/src/hooks/use-user-workspace-config.ts +75 -0
- package/src/hooks/use-workspace-currency.ts +8 -3
- 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
|
|
360
|
-
|
|
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
|
|
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={
|
|
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
|
-
|
|
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';
|
|
@@ -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,7 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
|
|
4
|
-
import {
|
|
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
|
});
|