@tuturuuu/ui 0.2.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 +53 -0
- package/package.json +79 -67
- 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/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/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/use-task-mutations.ts +6 -3
- 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) =>
|
|
@@ -40,8 +40,13 @@ import {
|
|
|
40
40
|
type ListPaginationState,
|
|
41
41
|
useProgressiveLoader,
|
|
42
42
|
} from '../../shared/progressive-loader-context';
|
|
43
|
+
import { getListTextColorClass } from '../../utils/taskColorUtils';
|
|
43
44
|
import { normalizeBoardText } from './board-text-utils';
|
|
44
45
|
import type { DragPreviewPosition } from './kanban/dnd/use-kanban-dnd';
|
|
46
|
+
import {
|
|
47
|
+
isClosedTaskListColumnCollapsed,
|
|
48
|
+
isKanbanColumnCollapsed,
|
|
49
|
+
} from './kanban/kanban-column-collapse';
|
|
45
50
|
import { ListActions } from './list-actions';
|
|
46
51
|
import { statusIcons } from './status-section';
|
|
47
52
|
import type { TaskFilters } from './task-filter';
|
|
@@ -168,6 +173,7 @@ interface BoardColumnProps {
|
|
|
168
173
|
workspaceId?: string;
|
|
169
174
|
wsId: string;
|
|
170
175
|
onExternalTasksCollapsedChange?: (collapsed: boolean) => void;
|
|
176
|
+
onTaskListCollapsedChange?: (listId: string, collapsed: boolean) => void;
|
|
171
177
|
}
|
|
172
178
|
|
|
173
179
|
export function BoardColumn({
|
|
@@ -193,6 +199,7 @@ export function BoardColumn({
|
|
|
193
199
|
workspaceId,
|
|
194
200
|
wsId,
|
|
195
201
|
onExternalTasksCollapsedChange,
|
|
202
|
+
onTaskListCollapsedChange,
|
|
196
203
|
}: BoardColumnProps) {
|
|
197
204
|
const t = useTranslations('common');
|
|
198
205
|
const tTasks = useTranslations('ws-tasks');
|
|
@@ -211,6 +218,8 @@ export function BoardColumn({
|
|
|
211
218
|
const [externalSortBy, setExternalSortBy] = useState<ExternalTaskSortBy>(
|
|
212
219
|
DEFAULT_EXTERNAL_TASK_SORT_BY
|
|
213
220
|
);
|
|
221
|
+
const isClosedCollapsed = isClosedTaskListColumnCollapsed(column);
|
|
222
|
+
const isColumnCollapsed = isKanbanColumnCollapsed(column);
|
|
214
223
|
const hasActiveFilters =
|
|
215
224
|
!!filters &&
|
|
216
225
|
(filters.labels.length > 0 ||
|
|
@@ -291,7 +300,7 @@ export function BoardColumn({
|
|
|
291
300
|
useEffect(() => {
|
|
292
301
|
if (
|
|
293
302
|
!isExternalStaging ||
|
|
294
|
-
|
|
303
|
+
isColumnCollapsed ||
|
|
295
304
|
!listState ||
|
|
296
305
|
listState.isInitialLoad ||
|
|
297
306
|
listState.isLoading ||
|
|
@@ -303,7 +312,7 @@ export function BoardColumn({
|
|
|
303
312
|
loadColumnPage(0);
|
|
304
313
|
}, [
|
|
305
314
|
externalOptionsSignature,
|
|
306
|
-
|
|
315
|
+
isColumnCollapsed,
|
|
307
316
|
isExternalStaging,
|
|
308
317
|
listState,
|
|
309
318
|
listState?.isInitialLoad,
|
|
@@ -315,7 +324,7 @@ export function BoardColumn({
|
|
|
315
324
|
// cache was cleared, refetch page 0 for this list so cards reappear.
|
|
316
325
|
useEffect(() => {
|
|
317
326
|
if (
|
|
318
|
-
|
|
327
|
+
isColumnCollapsed ||
|
|
319
328
|
!listState ||
|
|
320
329
|
listState.isLoading ||
|
|
321
330
|
hasActiveFilters
|
|
@@ -334,7 +343,7 @@ export function BoardColumn({
|
|
|
334
343
|
recoveryRequestedRef.current = false;
|
|
335
344
|
}, [
|
|
336
345
|
hasActiveFilters,
|
|
337
|
-
|
|
346
|
+
isColumnCollapsed,
|
|
338
347
|
listState,
|
|
339
348
|
listState?.isLoading,
|
|
340
349
|
listState?.totalCount,
|
|
@@ -481,22 +490,45 @@ export function BoardColumn({
|
|
|
481
490
|
});
|
|
482
491
|
};
|
|
483
492
|
|
|
484
|
-
if (
|
|
493
|
+
if (isColumnCollapsed) {
|
|
494
|
+
const collapsedListName = translateListName(column.name);
|
|
495
|
+
const expandLabel = isExternalCollapsed
|
|
496
|
+
? tTasks('expand_external_tasks')
|
|
497
|
+
: tTasks('expand_task_list', { name: collapsedListName });
|
|
498
|
+
const collapsedTextColor = isExternalCollapsed
|
|
499
|
+
? 'text-dynamic-cyan'
|
|
500
|
+
: getListTextColorClass(column.color as SupportedColor);
|
|
501
|
+
|
|
485
502
|
return (
|
|
486
503
|
<Card
|
|
487
504
|
ref={composedRef}
|
|
488
505
|
style={style}
|
|
489
506
|
className={cn(
|
|
490
|
-
'group flex h-full w-14 shrink-0 snap-start flex-col items-center rounded-xl border border-
|
|
491
|
-
'touch-none select-none overflow-hidden hover:shadow-md'
|
|
507
|
+
'group flex h-full w-14 shrink-0 snap-start flex-col items-center rounded-xl border border-dashed transition-all duration-200',
|
|
508
|
+
'touch-none select-none overflow-hidden hover:shadow-md',
|
|
509
|
+
isExternalCollapsed
|
|
510
|
+
? 'border-dynamic-cyan/45 bg-dynamic-cyan/[0.035]'
|
|
511
|
+
: colorClass
|
|
492
512
|
)}
|
|
493
513
|
>
|
|
494
514
|
<button
|
|
495
515
|
type="button"
|
|
496
|
-
className=
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
516
|
+
className={cn(
|
|
517
|
+
'flex h-full w-full flex-col items-center gap-3 rounded-xl px-1 py-3 transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
518
|
+
isExternalCollapsed
|
|
519
|
+
? 'text-dynamic-cyan hover:bg-dynamic-cyan/10 focus-visible:ring-dynamic-cyan/40'
|
|
520
|
+
: `${collapsedTextColor} hover:bg-muted/40 focus-visible:ring-primary/40`
|
|
521
|
+
)}
|
|
522
|
+
title={expandLabel}
|
|
523
|
+
aria-label={expandLabel}
|
|
524
|
+
onClick={() => {
|
|
525
|
+
if (isExternalCollapsed) {
|
|
526
|
+
onExternalTasksCollapsedChange?.(false);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
onTaskListCollapsedChange?.(column.id, false);
|
|
531
|
+
}}
|
|
500
532
|
>
|
|
501
533
|
<ChevronRight className="h-4 w-4 shrink-0" />
|
|
502
534
|
<Badge
|
|
@@ -509,7 +541,7 @@ export function BoardColumn({
|
|
|
509
541
|
className="max-h-48 truncate font-medium text-[11px]"
|
|
510
542
|
style={{ writingMode: 'vertical-rl' }}
|
|
511
543
|
>
|
|
512
|
-
{
|
|
544
|
+
{collapsedListName}
|
|
513
545
|
</span>
|
|
514
546
|
</button>
|
|
515
547
|
</Card>
|
|
@@ -688,19 +720,41 @@ export function BoardColumn({
|
|
|
688
720
|
</Button>
|
|
689
721
|
</>
|
|
690
722
|
) : (
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
723
|
+
<>
|
|
724
|
+
{isClosedCollapsed || column.status === 'closed' ? (
|
|
725
|
+
<Button
|
|
726
|
+
type="button"
|
|
727
|
+
variant="ghost"
|
|
728
|
+
size="xs"
|
|
729
|
+
className={cn(
|
|
730
|
+
'h-7 w-7 p-0 hover:bg-muted/40',
|
|
731
|
+
getListTextColorClass(column.color as SupportedColor)
|
|
732
|
+
)}
|
|
733
|
+
title={tTasks('collapse_task_list', {
|
|
734
|
+
name: translateListName(column.name),
|
|
735
|
+
})}
|
|
736
|
+
aria-label={tTasks('collapse_task_list', {
|
|
737
|
+
name: translateListName(column.name),
|
|
738
|
+
})}
|
|
739
|
+
onClick={() => onTaskListCollapsedChange?.(column.id, true)}
|
|
740
|
+
>
|
|
741
|
+
<ChevronLeft className="h-3.5 w-3.5" />
|
|
742
|
+
</Button>
|
|
743
|
+
) : null}
|
|
744
|
+
<ListActions
|
|
745
|
+
listId={column.id}
|
|
746
|
+
listName={column.name}
|
|
747
|
+
listStatus={column.status}
|
|
748
|
+
listColor={column.color as SupportedColor}
|
|
749
|
+
tasks={tasks}
|
|
750
|
+
boardId={boardId}
|
|
751
|
+
wsId={wsId}
|
|
752
|
+
onUpdate={handleUpdate}
|
|
753
|
+
onSelectAll={handleSelectAll}
|
|
754
|
+
isEditOpen={isEditOpen}
|
|
755
|
+
onEditOpenChange={setIsEditOpen}
|
|
756
|
+
/>
|
|
757
|
+
</>
|
|
704
758
|
)}
|
|
705
759
|
</div>
|
|
706
760
|
</div>
|