@tuturuuu/ui 0.1.0 → 0.3.1
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 +71 -0
- package/package.json +82 -70
- package/src/components/ui/__tests__/avatar.test.tsx +8 -5
- package/src/components/ui/calendar-app/components/calendar-connections-compact.tsx +414 -0
- package/src/components/ui/calendar-app/components/calendar-connections-manager.tsx +5 -1
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +529 -0
- package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +26 -1429
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +711 -0
- package/src/components/ui/chart.test.tsx +29 -0
- package/src/components/ui/chart.tsx +12 -3
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +43 -13
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +138 -74
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +70 -0
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +60 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +13 -5
- package/src/components/ui/chat/chat-sidebar-panel.test.tsx +110 -0
- package/src/components/ui/chat/chat-sidebar-panel.tsx +13 -3
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +24 -1
- package/src/components/ui/custom/__tests__/tuturuuu-logo.test.ts +12 -3
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +39 -0
- package/src/components/ui/custom/common-footer.tsx +16 -1
- package/src/components/ui/custom/production-indicator.tsx +1 -1
- package/src/components/ui/custom/settings/sidebar-settings.tsx +1 -1
- package/src/components/ui/custom/settings/task-settings.tsx +18 -0
- package/src/components/ui/custom/settings-dialog-shell.tsx +38 -23
- package/src/components/ui/custom/sidebar-context-compile-graph.test.ts +60 -0
- package/src/components/ui/custom/sidebar-context.tsx +61 -61
- package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +123 -0
- package/src/components/ui/custom/tuturuuu-logo-urls.ts +6 -0
- package/src/components/ui/custom/tuturuuu-logo.tsx +25 -7
- package/src/components/ui/custom/workspace-select-helpers.ts +20 -0
- package/src/components/ui/custom/workspace-select.tsx +33 -12
- package/src/components/ui/finance/invoices/components/invoice-checkout-summary.tsx +7 -1
- package/src/components/ui/finance/invoices/components/invoice-payment-settings.tsx +3 -0
- package/src/components/ui/finance/invoices/components/invoice-products-permission-warning.tsx +58 -0
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +12 -20
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +10 -9
- package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +10 -5
- package/src/components/ui/finance/invoices/hooks.ts +75 -20
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +137 -0
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +86 -37
- package/src/components/ui/finance/invoices/product-selection.test.tsx +8 -26
- package/src/components/ui/finance/invoices/product-selection.tsx +2 -10
- package/src/components/ui/finance/invoices/standard-invoice.tsx +88 -26
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +154 -46
- package/src/components/ui/finance/invoices/utils.test.ts +50 -0
- package/src/components/ui/finance/invoices/utils.ts +75 -17
- package/src/components/ui/finance/shared/finance-display-amount.tsx +3 -1
- package/src/components/ui/finance/shared/finance-permission-warning-dialog.test.tsx +34 -0
- package/src/components/ui/finance/shared/finance-permission-warning-dialog.tsx +157 -0
- package/src/components/ui/finance/transactions/form-basic-tab.tsx +8 -0
- package/src/components/ui/finance/transactions/form-more-tab.tsx +8 -0
- package/src/components/ui/finance/transactions/form-types.ts +2 -0
- package/src/components/ui/finance/transactions/form.test.tsx +43 -0
- package/src/components/ui/finance/transactions/form.tsx +60 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +27 -0
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +13 -1
- package/src/components/ui/finance/transactions/transactions-infinite-page.tsx +4 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +23 -1
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +5 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +9 -1
- package/src/components/ui/legacy/calendar/event-modal.tsx +146 -2
- package/src/components/ui/legacy/calendar/event-preview-popover.tsx +200 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +76 -0
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +13 -1
- package/src/components/ui/legacy/meet/page.test.ts +180 -0
- package/src/components/ui/legacy/meet/page.tsx +87 -39
- package/src/components/ui/legacy/meet/planId/page.tsx +10 -4
- package/src/components/ui/text-editor/__tests__/task-mention-chip.test.tsx +203 -6
- package/src/components/ui/text-editor/task-mention-chip.tsx +29 -7
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +79 -25
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-external-workspaces.test.tsx +392 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.test.tsx +57 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.tsx +106 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +106 -161
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-assignees.ts +96 -150
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-labels.ts +63 -79
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-projects.ts +64 -83
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +115 -155
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-utils.ts +319 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +8 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +63 -37
- package/src/components/ui/tu-do/boards/boardId/kanban/kanban-column-collapse.ts +16 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +46 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +5 -3
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +19 -7
- package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +181 -2
- package/src/components/ui/tu-do/boards/boardId/menus/index.ts +1 -0
- package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +463 -0
- package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-utils.ts +109 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +4 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardCheckbox.tsx +6 -3
- package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardDates.tsx +26 -9
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-checkbox-style.ts +39 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.test.ts +43 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +33 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.test.ts +31 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.test.tsx +124 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.tsx +88 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +151 -76
- package/src/components/ui/tu-do/boards/boardId/task-card/task-scheduling-badge.tsx +174 -0
- package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +34 -13
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +54 -1
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +158 -0
- package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +5 -2
- package/src/components/ui/tu-do/shared/board-client.tsx +12 -2
- package/src/components/ui/tu-do/shared/board-views.tsx +195 -328
- package/src/components/ui/tu-do/shared/list-view.tsx +18 -8
- package/src/components/ui/tu-do/shared/task-due-date-visibility.test.ts +72 -0
- package/src/components/ui/tu-do/shared/task-due-date-visibility.ts +38 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/__tests__/use-task-realtime-sync.test.tsx +37 -9
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +6 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +89 -70
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +2 -2
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +33 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +74 -3
- package/src/hooks/__tests__/use-task-actions.test.tsx +118 -0
- package/src/hooks/__tests__/use-user-config.test.tsx +65 -0
- package/src/hooks/__tests__/use-workspace-presence.test.tsx +1 -1
- package/src/hooks/use-calendar-sync.tsx +22 -277
- package/src/hooks/use-calendar.tsx +95 -525
- package/src/hooks/use-task-actions.ts +43 -117
- package/src/hooks/use-user-config.ts +1 -1
- package/src/hooks/use-workspace-config.ts +6 -2
- package/src/hooks/use-workspace-presence.ts +1 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-bar.tsx +0 -94
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import type { Workspace } from '@tuturuuu/types';
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { SmartCalendar } from './smart-calendar';
|
|
8
|
+
|
|
9
|
+
vi.mock('@tuturuuu/ui/hooks/use-calendar', () => ({
|
|
10
|
+
CalendarProvider: ({ children }: { children: ReactNode }) => (
|
|
11
|
+
<div data-testid="calendar-provider">{children}</div>
|
|
12
|
+
),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('./settings/settings-context', () => ({
|
|
16
|
+
CalendarSettingsProvider: ({ children }: { children: ReactNode }) => (
|
|
17
|
+
<div data-testid="settings-provider">{children}</div>
|
|
18
|
+
),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('./calendar-content', () => ({
|
|
22
|
+
CalendarContent: ({ extras }: { extras?: ReactNode }) => (
|
|
23
|
+
<div data-testid="calendar-content">
|
|
24
|
+
<div data-testid="header-extras">{extras}</div>
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('../../calendar-app/components/calendar-connections-unified', () => ({
|
|
30
|
+
default: ({ wsId }: { wsId: string }) => (
|
|
31
|
+
<span data-testid="connections-manager">{wsId}</span>
|
|
32
|
+
),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const baseProps = {
|
|
36
|
+
locale: 'en',
|
|
37
|
+
t: (key: string) => key,
|
|
38
|
+
useQuery: vi.fn(),
|
|
39
|
+
useQueryClient: vi.fn(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('SmartCalendar', () => {
|
|
43
|
+
it('renders the compact connections manager before existing header extras', () => {
|
|
44
|
+
render(
|
|
45
|
+
<SmartCalendar
|
|
46
|
+
{...baseProps}
|
|
47
|
+
workspace={{ id: 'workspace-1' } as Workspace}
|
|
48
|
+
extras={<span data-testid="custom-extra">extra</span>}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId('connections-manager').textContent).toBe(
|
|
53
|
+
'workspace-1'
|
|
54
|
+
);
|
|
55
|
+
expect(
|
|
56
|
+
Array.from(screen.getByTestId('header-extras').children).map((element) =>
|
|
57
|
+
element.getAttribute('data-testid')
|
|
58
|
+
)
|
|
59
|
+
).toEqual(['connections-manager', 'custom-extra']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('keeps existing header extras when the manager is disabled', () => {
|
|
63
|
+
render(
|
|
64
|
+
<SmartCalendar
|
|
65
|
+
{...baseProps}
|
|
66
|
+
workspace={{ id: 'workspace-1' } as Workspace}
|
|
67
|
+
disabled
|
|
68
|
+
showConnectionsManager={false}
|
|
69
|
+
extras={<span data-testid="custom-extra">extra</span>}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(screen.queryByTestId('connections-manager')).toBeNull();
|
|
74
|
+
expect(screen.getByTestId('custom-extra').textContent).toBe('extra');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from '@tuturuuu/types';
|
|
7
7
|
import { CalendarProvider } from '@tuturuuu/ui/hooks/use-calendar';
|
|
8
8
|
import type { CalendarView } from '../../../../hooks/use-view-transition';
|
|
9
|
+
import CalendarConnectionsUnified from '../../calendar-app/components/calendar-connections-unified';
|
|
9
10
|
import { CalendarContent } from './calendar-content';
|
|
10
11
|
import {
|
|
11
12
|
type CalendarSettings,
|
|
@@ -26,6 +27,7 @@ export const SmartCalendar = ({
|
|
|
26
27
|
overlay,
|
|
27
28
|
initialSettings,
|
|
28
29
|
onSaveSettings,
|
|
30
|
+
showConnectionsManager = true,
|
|
29
31
|
}: {
|
|
30
32
|
t: any;
|
|
31
33
|
locale: string;
|
|
@@ -46,12 +48,22 @@ export const SmartCalendar = ({
|
|
|
46
48
|
overlay?: React.ReactNode;
|
|
47
49
|
initialSettings?: Partial<CalendarSettings>;
|
|
48
50
|
onSaveSettings?: (settings: CalendarSettings) => Promise<void>;
|
|
51
|
+
showConnectionsManager?: boolean;
|
|
49
52
|
}) => {
|
|
50
53
|
const handleSaveSettings = async (newSettings: CalendarSettings) => {
|
|
51
54
|
if (onSaveSettings) {
|
|
52
55
|
await onSaveSettings(newSettings);
|
|
53
56
|
}
|
|
54
57
|
};
|
|
58
|
+
const headerExtras =
|
|
59
|
+
showConnectionsManager && workspace?.id ? (
|
|
60
|
+
<>
|
|
61
|
+
<CalendarConnectionsUnified wsId={workspace.id} />
|
|
62
|
+
{extras}
|
|
63
|
+
</>
|
|
64
|
+
) : (
|
|
65
|
+
extras
|
|
66
|
+
);
|
|
55
67
|
|
|
56
68
|
return (
|
|
57
69
|
<CalendarProvider
|
|
@@ -73,7 +85,7 @@ export const SmartCalendar = ({
|
|
|
73
85
|
enableHeader={enableHeader}
|
|
74
86
|
experimentalGoogleToken={experimentalGoogleToken}
|
|
75
87
|
externalState={externalState}
|
|
76
|
-
extras={
|
|
88
|
+
extras={headerExtras}
|
|
77
89
|
overlay={overlay}
|
|
78
90
|
/>
|
|
79
91
|
</CalendarSettingsProvider>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getMeetTogetherPlansData } from './page';
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
createAdminClient: vi.fn(),
|
|
6
|
+
createClient: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock('@tuturuuu/supabase/next/server', () => ({
|
|
10
|
+
createAdminClient: (...args: Parameters<typeof mocks.createAdminClient>) =>
|
|
11
|
+
mocks.createAdminClient(...args),
|
|
12
|
+
createClient: (...args: Parameters<typeof mocks.createClient>) =>
|
|
13
|
+
mocks.createClient(...args),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('next-intl/server', () => ({
|
|
17
|
+
getLocale: vi.fn().mockResolvedValue('en'),
|
|
18
|
+
getTranslations: vi.fn().mockResolvedValue((key: string) => key),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
type QueryCall = {
|
|
22
|
+
column?: string;
|
|
23
|
+
table: string;
|
|
24
|
+
type: 'select' | 'eq' | 'neq' | 'order' | 'in';
|
|
25
|
+
value?: unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function createBuilder(table: string, data: unknown[], calls: QueryCall[]) {
|
|
29
|
+
const builder = Object.assign(Promise.resolve({ data, error: null }), {
|
|
30
|
+
eq: vi.fn((column: string, value: unknown) => {
|
|
31
|
+
calls.push({ column, table, type: 'eq', value });
|
|
32
|
+
return builder;
|
|
33
|
+
}),
|
|
34
|
+
in: vi.fn((column: string, value: unknown) => {
|
|
35
|
+
calls.push({ column, table, type: 'in', value });
|
|
36
|
+
return builder;
|
|
37
|
+
}),
|
|
38
|
+
neq: vi.fn((column: string, value: unknown) => {
|
|
39
|
+
calls.push({ column, table, type: 'neq', value });
|
|
40
|
+
return builder;
|
|
41
|
+
}),
|
|
42
|
+
order: vi.fn((column: string, value: unknown) => {
|
|
43
|
+
calls.push({ column, table, type: 'order', value });
|
|
44
|
+
return builder;
|
|
45
|
+
}),
|
|
46
|
+
select: vi.fn((column: string) => {
|
|
47
|
+
calls.push({ column, table, type: 'select' });
|
|
48
|
+
return builder;
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return builder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('MeetTogether plans data', () => {
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
vi.clearAllMocks();
|
|
58
|
+
mocks.createClient.mockResolvedValue({
|
|
59
|
+
auth: {
|
|
60
|
+
getUser: vi.fn().mockResolvedValue({
|
|
61
|
+
data: { user: { id: 'user-1' } },
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('consolidates personal workspace plans with previously interacted plans', async () => {
|
|
68
|
+
const calls: QueryCall[] = [];
|
|
69
|
+
const personalPlan = {
|
|
70
|
+
created_at: '2026-01-01T00:00:00.000Z',
|
|
71
|
+
creator_id: 'user-1',
|
|
72
|
+
id: 'personal-created',
|
|
73
|
+
ws_id: 'personal-ws',
|
|
74
|
+
};
|
|
75
|
+
const interactedPlan = {
|
|
76
|
+
created_at: '2026-01-03T00:00:00.000Z',
|
|
77
|
+
creator_id: 'other-user',
|
|
78
|
+
id: 'team-interacted',
|
|
79
|
+
ws_id: 'team-ws',
|
|
80
|
+
};
|
|
81
|
+
const participant = {
|
|
82
|
+
display_name: 'User',
|
|
83
|
+
is_guest: false,
|
|
84
|
+
plan_id: 'team-interacted',
|
|
85
|
+
timeblock_count: 1,
|
|
86
|
+
user_id: 'user-1',
|
|
87
|
+
};
|
|
88
|
+
const builders = [
|
|
89
|
+
createBuilder('meet_together_plans', [personalPlan], calls),
|
|
90
|
+
createBuilder(
|
|
91
|
+
'meet_together_user_timeblocks',
|
|
92
|
+
[interactedPlan, personalPlan],
|
|
93
|
+
calls
|
|
94
|
+
),
|
|
95
|
+
createBuilder('meet_together_users', [participant], calls),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
mocks.createAdminClient.mockResolvedValue({
|
|
99
|
+
from: vi.fn(() => builders.shift()),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const result = await getMeetTogetherPlansData({
|
|
103
|
+
scope: 'personal-consolidated',
|
|
104
|
+
wsId: 'personal-ws',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(result.data.map((plan) => plan.id)).toEqual([
|
|
108
|
+
'team-interacted',
|
|
109
|
+
'personal-created',
|
|
110
|
+
]);
|
|
111
|
+
expect(result.totalCount).toBe(2);
|
|
112
|
+
expect(result.data[0]?.participants).toEqual([participant]);
|
|
113
|
+
expect(calls).toEqual(
|
|
114
|
+
expect.arrayContaining([
|
|
115
|
+
{
|
|
116
|
+
column: 'ws_id',
|
|
117
|
+
table: 'meet_together_plans',
|
|
118
|
+
type: 'eq',
|
|
119
|
+
value: 'personal-ws',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
column: 'user_id',
|
|
123
|
+
table: 'meet_together_user_timeblocks',
|
|
124
|
+
type: 'eq',
|
|
125
|
+
value: 'user-1',
|
|
126
|
+
},
|
|
127
|
+
])
|
|
128
|
+
);
|
|
129
|
+
expect(calls).not.toEqual(
|
|
130
|
+
expect.arrayContaining([
|
|
131
|
+
expect.objectContaining({
|
|
132
|
+
column: 'meet_together_plans.ws_id',
|
|
133
|
+
table: 'meet_together_user_timeblocks',
|
|
134
|
+
type: 'eq',
|
|
135
|
+
}),
|
|
136
|
+
])
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('keeps team workspace plans scoped to the active workspace', async () => {
|
|
141
|
+
const calls: QueryCall[] = [];
|
|
142
|
+
const builders = [
|
|
143
|
+
createBuilder('meet_together_plans', [], calls),
|
|
144
|
+
createBuilder('meet_together_user_timeblocks', [], calls),
|
|
145
|
+
createBuilder('meet_together_users', [], calls),
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
mocks.createAdminClient.mockResolvedValue({
|
|
149
|
+
from: vi.fn(() => builders.shift()),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await getMeetTogetherPlansData({
|
|
153
|
+
scope: 'workspace',
|
|
154
|
+
wsId: 'team-ws',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(calls).toEqual(
|
|
158
|
+
expect.arrayContaining([
|
|
159
|
+
{
|
|
160
|
+
column: 'ws_id',
|
|
161
|
+
table: 'meet_together_plans',
|
|
162
|
+
type: 'eq',
|
|
163
|
+
value: 'team-ws',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
column: 'meet_together_plans.ws_id',
|
|
167
|
+
table: 'meet_together_user_timeblocks',
|
|
168
|
+
type: 'eq',
|
|
169
|
+
value: 'team-ws',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
column: 'meet_together_plans.creator_id',
|
|
173
|
+
table: 'meet_together_user_timeblocks',
|
|
174
|
+
type: 'neq',
|
|
175
|
+
value: 'user-1',
|
|
176
|
+
},
|
|
177
|
+
])
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -27,8 +27,18 @@ export interface MeetTogetherPlanWithParticipants extends MeetTogetherPlan {
|
|
|
27
27
|
}>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
type MeetTogetherPlanRow = Record<string, unknown> & {
|
|
31
|
+
created_at?: string | null;
|
|
32
|
+
creator_id?: string | null;
|
|
33
|
+
description?: string | null;
|
|
34
|
+
id?: string | null;
|
|
35
|
+
name?: string | null;
|
|
36
|
+
ws_id?: string | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
30
39
|
// Server component props type
|
|
31
40
|
interface MeetTogetherPageProps {
|
|
41
|
+
scope?: MeetTogetherPlansScope;
|
|
32
42
|
wsId?: string;
|
|
33
43
|
path?: string;
|
|
34
44
|
searchParams?: Promise<{
|
|
@@ -38,7 +48,10 @@ interface MeetTogetherPageProps {
|
|
|
38
48
|
}>;
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
export type MeetTogetherPlansScope = 'workspace' | 'personal-consolidated';
|
|
52
|
+
|
|
41
53
|
export async function MeetTogetherPage({
|
|
54
|
+
scope = 'workspace',
|
|
42
55
|
wsId,
|
|
43
56
|
path,
|
|
44
57
|
searchParams,
|
|
@@ -55,7 +68,7 @@ export async function MeetTogetherPage({
|
|
|
55
68
|
data: plans,
|
|
56
69
|
user,
|
|
57
70
|
totalCount,
|
|
58
|
-
} = await
|
|
71
|
+
} = await getMeetTogetherPlansData({ scope, wsId, page, pageSize });
|
|
59
72
|
const totalPages = Math.ceil(totalCount / pageSize);
|
|
60
73
|
|
|
61
74
|
return (
|
|
@@ -222,11 +235,13 @@ export async function MeetTogetherPage({
|
|
|
222
235
|
);
|
|
223
236
|
}
|
|
224
237
|
|
|
225
|
-
async function
|
|
238
|
+
export async function getMeetTogetherPlansData({
|
|
239
|
+
scope = 'workspace',
|
|
226
240
|
wsId,
|
|
227
241
|
page = 1,
|
|
228
242
|
pageSize = 9,
|
|
229
243
|
}: {
|
|
244
|
+
scope?: MeetTogetherPlansScope;
|
|
230
245
|
wsId?: string;
|
|
231
246
|
page?: number;
|
|
232
247
|
pageSize?: number;
|
|
@@ -241,47 +256,80 @@ async function getData({
|
|
|
241
256
|
|
|
242
257
|
const sbAdmin = await createAdminClient();
|
|
243
258
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
259
|
+
const planMap = new Map<string, MeetTogetherPlan>();
|
|
260
|
+
const addPlans = (plans: MeetTogetherPlanRow[] | null | undefined) => {
|
|
261
|
+
plans?.forEach((plan) => {
|
|
262
|
+
const planId = plan.id ?? undefined;
|
|
263
|
+
|
|
264
|
+
if (planId && !planMap.has(planId)) {
|
|
265
|
+
planMap.set(planId, {
|
|
266
|
+
...plan,
|
|
267
|
+
agenda_content: plan.agenda_content ?? undefined,
|
|
268
|
+
created_at: plan.created_at ?? undefined,
|
|
269
|
+
creator_id: plan.creator_id ?? undefined,
|
|
270
|
+
description: plan.description ?? undefined,
|
|
271
|
+
id: planId,
|
|
272
|
+
name: plan.name ?? undefined,
|
|
273
|
+
ws_id: plan.ws_id ?? undefined,
|
|
274
|
+
} as MeetTogetherPlan);
|
|
275
|
+
}
|
|
258
276
|
});
|
|
277
|
+
};
|
|
259
278
|
|
|
260
|
-
if (wsId) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
279
|
+
if (scope === 'personal-consolidated' && wsId) {
|
|
280
|
+
const personalPlansQuery = sbAdmin
|
|
281
|
+
.from('meet_together_plans')
|
|
282
|
+
.select('*')
|
|
283
|
+
.eq('creator_id', user.id)
|
|
284
|
+
.eq('ws_id', wsId)
|
|
285
|
+
.order('created_at', { ascending: false });
|
|
286
|
+
|
|
287
|
+
const interactedPlansQuery = sbAdmin
|
|
288
|
+
.from('meet_together_user_timeblocks')
|
|
289
|
+
.select('...meet_together_plans!inner(*)')
|
|
290
|
+
.eq('user_id', user.id)
|
|
291
|
+
.order('created_at', {
|
|
292
|
+
ascending: false,
|
|
293
|
+
referencedTable: 'meet_together_plans',
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const [personalPlansResult, interactedPlansResult] = await Promise.all([
|
|
297
|
+
personalPlansQuery,
|
|
298
|
+
interactedPlansQuery,
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
addPlans(personalPlansResult.data);
|
|
302
|
+
addPlans(interactedPlansResult.data);
|
|
303
|
+
} else {
|
|
304
|
+
const createdPlansQuery = sbAdmin
|
|
305
|
+
.from('meet_together_plans')
|
|
306
|
+
.select('*')
|
|
307
|
+
.eq('creator_id', user.id)
|
|
308
|
+
.order('created_at', { ascending: false });
|
|
309
|
+
|
|
310
|
+
const joinedPlansQuery = sbAdmin
|
|
311
|
+
.from('meet_together_user_timeblocks')
|
|
312
|
+
.select('...meet_together_plans!inner(*)')
|
|
313
|
+
.eq('user_id', user.id)
|
|
314
|
+
.neq('meet_together_plans.creator_id', user.id)
|
|
315
|
+
.order('created_at', {
|
|
316
|
+
ascending: false,
|
|
317
|
+
referencedTable: 'meet_together_plans',
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (wsId) {
|
|
321
|
+
createdPlansQuery.eq('ws_id', wsId);
|
|
322
|
+
joinedPlansQuery.eq('meet_together_plans.ws_id', wsId);
|
|
323
|
+
}
|
|
269
324
|
|
|
270
|
-
|
|
271
|
-
|
|
325
|
+
const [createdPlansResult, joinedPlansResult] = await Promise.all([
|
|
326
|
+
createdPlansQuery,
|
|
327
|
+
joinedPlansQuery,
|
|
328
|
+
]);
|
|
272
329
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (!planMap.has(plan.id)) {
|
|
277
|
-
planMap.set(plan.id, plan);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
joinedPlans?.forEach((plan) => {
|
|
281
|
-
if (!planMap.has(plan.id)) {
|
|
282
|
-
planMap.set(plan.id, plan);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
330
|
+
addPlans(createdPlansResult.data);
|
|
331
|
+
addPlans(joinedPlansResult.data);
|
|
332
|
+
}
|
|
285
333
|
|
|
286
334
|
const allPlans = Array.from(planMap.values()).sort(
|
|
287
335
|
(a, b) =>
|
|
@@ -17,23 +17,29 @@ interface Props {
|
|
|
17
17
|
params: Promise<{
|
|
18
18
|
planId: string;
|
|
19
19
|
}>;
|
|
20
|
+
actorUserId?: string | null;
|
|
20
21
|
baseUrl: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export default async function MeetTogetherPlanDetailsPage({
|
|
25
|
+
actorUserId,
|
|
24
26
|
params,
|
|
25
27
|
baseUrl,
|
|
26
28
|
}: Props) {
|
|
27
29
|
const { planId } = await params;
|
|
28
30
|
|
|
29
31
|
const platformUser = await getCurrentUser();
|
|
30
|
-
const plan = await getPlan(planId);
|
|
31
|
-
const users: PlanUser[] = await getUsers(planId);
|
|
32
|
-
const polls = await getPollsForPlan(planId);
|
|
33
|
-
const timeblocks = await getTimeBlocks(planId);
|
|
32
|
+
const plan = await getPlan(planId, { actorUserId });
|
|
34
33
|
|
|
35
34
|
if (!plan) return notFound();
|
|
36
35
|
|
|
36
|
+
const canonicalPlanId = plan.id;
|
|
37
|
+
if (!canonicalPlanId) return notFound();
|
|
38
|
+
|
|
39
|
+
const users: PlanUser[] = await getUsers(canonicalPlanId);
|
|
40
|
+
const polls = await getPollsForPlan(canonicalPlanId);
|
|
41
|
+
const timeblocks = await getTimeBlocks(canonicalPlanId);
|
|
42
|
+
|
|
37
43
|
return (
|
|
38
44
|
<div className="flex min-h-screen w-full flex-col items-center">
|
|
39
45
|
<Suspense fallback={null}>
|