@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
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { CreateDialogFeatureSummary } from '@tuturuuu/ui/finance/shared/create-dialog-feature-summary';
|
|
4
|
+
import {
|
|
5
|
+
type FinancePermissionRequestUser,
|
|
6
|
+
FinancePermissionWarningContent,
|
|
7
|
+
} from '@tuturuuu/ui/finance/shared/finance-permission-warning-dialog';
|
|
4
8
|
import { TransactionForm } from '@tuturuuu/ui/finance/transactions/form';
|
|
5
9
|
|
|
6
10
|
interface TransactionsCreateSummaryProps {
|
|
@@ -12,6 +16,7 @@ interface TransactionsCreateSummaryProps {
|
|
|
12
16
|
createTitle: string;
|
|
13
17
|
defaultOpen?: boolean;
|
|
14
18
|
description: string;
|
|
19
|
+
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
15
20
|
pluralTitle: string;
|
|
16
21
|
singularTitle: string;
|
|
17
22
|
wsId: string;
|
|
@@ -26,6 +31,7 @@ export function TransactionsCreateSummary({
|
|
|
26
31
|
createTitle,
|
|
27
32
|
defaultOpen = false,
|
|
28
33
|
description,
|
|
34
|
+
permissionRequestUser,
|
|
29
35
|
pluralTitle,
|
|
30
36
|
singularTitle,
|
|
31
37
|
wsId,
|
|
@@ -48,8 +54,14 @@ export function TransactionsCreateSummary({
|
|
|
48
54
|
canCreateConfidentialTransactions={
|
|
49
55
|
canCreateConfidentialTransactions
|
|
50
56
|
}
|
|
57
|
+
permissionRequestUser={permissionRequestUser}
|
|
51
58
|
/>
|
|
52
|
-
) :
|
|
59
|
+
) : (
|
|
60
|
+
<FinancePermissionWarningContent
|
|
61
|
+
missingPermissions={['create_transactions']}
|
|
62
|
+
user={permissionRequestUser}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
53
65
|
}
|
|
54
66
|
/>
|
|
55
67
|
);
|
|
@@ -6,6 +6,7 @@ import { Button } from '@tuturuuu/ui/button';
|
|
|
6
6
|
import SearchBar from '@tuturuuu/ui/custom/search-bar';
|
|
7
7
|
import { Dialog, DialogContent, DialogTrigger } from '@tuturuuu/ui/dialog';
|
|
8
8
|
import { DateRangeFilterWrapper } from '@tuturuuu/ui/finance/shared/date-range-filter-wrapper';
|
|
9
|
+
import type { FinancePermissionRequestUser } from '@tuturuuu/ui/finance/shared/finance-permission-warning-dialog';
|
|
9
10
|
import { CategoryFilterWrapper } from '@tuturuuu/ui/finance/transactions/category-filter-wrapper';
|
|
10
11
|
import { InfiniteTransactionsList } from '@tuturuuu/ui/finance/transactions/infinite-transactions-list';
|
|
11
12
|
import MoneyLoverImportDialog from '@tuturuuu/ui/finance/transactions/money-lover-import-dialog';
|
|
@@ -41,6 +42,7 @@ interface TransactionsInfinitePageProps {
|
|
|
41
42
|
canViewConfidentialCategory?: boolean;
|
|
42
43
|
/** Hide transaction creator (useful for personal workspaces) */
|
|
43
44
|
isPersonalWorkspace?: boolean;
|
|
45
|
+
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
44
46
|
showTransactionTypeFilter?: boolean;
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -62,6 +64,7 @@ export function TransactionsInfinitePage({
|
|
|
62
64
|
canViewConfidentialDescription,
|
|
63
65
|
canViewConfidentialCategory,
|
|
64
66
|
isPersonalWorkspace,
|
|
67
|
+
permissionRequestUser,
|
|
65
68
|
showTransactionTypeFilter,
|
|
66
69
|
}: TransactionsInfinitePageProps) {
|
|
67
70
|
const t = useTranslations();
|
|
@@ -206,6 +209,7 @@ export function TransactionsInfinitePage({
|
|
|
206
209
|
canViewConfidentialDescription={canViewConfidentialDescription}
|
|
207
210
|
canViewConfidentialCategory={canViewConfidentialCategory}
|
|
208
211
|
isPersonalWorkspace={isPersonalWorkspace}
|
|
212
|
+
permissionRequestUser={permissionRequestUser}
|
|
209
213
|
/>
|
|
210
214
|
</Suspense>
|
|
211
215
|
</div>
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { Button } from '@tuturuuu/ui/button';
|
|
2
|
+
import {
|
|
3
|
+
type FinancePermissionRequestUser,
|
|
4
|
+
FinancePermissionWarningDialog,
|
|
5
|
+
} from '@tuturuuu/ui/finance/shared/finance-permission-warning-dialog';
|
|
1
6
|
import ExportDialogContent from '@tuturuuu/ui/finance/transactions/export-dialog-content';
|
|
2
7
|
import { TransactionsCreateSummary } from '@tuturuuu/ui/finance/transactions/transactions-create-summary';
|
|
3
8
|
import { TransactionsInfinitePage } from '@tuturuuu/ui/finance/transactions/transactions-infinite-page';
|
|
@@ -19,6 +24,7 @@ interface Props {
|
|
|
19
24
|
personal?: boolean | null;
|
|
20
25
|
timezone?: string | null;
|
|
21
26
|
};
|
|
27
|
+
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
22
28
|
openCreateDialog?: boolean;
|
|
23
29
|
showTransactionTypeFilter?: boolean;
|
|
24
30
|
}
|
|
@@ -28,6 +34,7 @@ export default async function TransactionsPage({
|
|
|
28
34
|
permissions,
|
|
29
35
|
wsId,
|
|
30
36
|
workspace,
|
|
37
|
+
permissionRequestUser,
|
|
31
38
|
openCreateDialog = false,
|
|
32
39
|
showTransactionTypeFilter = false,
|
|
33
40
|
}: Props) {
|
|
@@ -71,7 +78,20 @@ export default async function TransactionsPage({
|
|
|
71
78
|
'view_confidential_category'
|
|
72
79
|
);
|
|
73
80
|
|
|
74
|
-
if (!canViewTransactions)
|
|
81
|
+
if (!canViewTransactions) {
|
|
82
|
+
return (
|
|
83
|
+
<FinancePermissionWarningDialog
|
|
84
|
+
defaultOpen
|
|
85
|
+
missingPermissions={['view_transactions']}
|
|
86
|
+
user={permissionRequestUser}
|
|
87
|
+
trigger={
|
|
88
|
+
<Button variant="outline">
|
|
89
|
+
{t('finance-permission-warning.open_request')}
|
|
90
|
+
</Button>
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
75
95
|
|
|
76
96
|
return (
|
|
77
97
|
<>
|
|
@@ -87,6 +107,7 @@ export default async function TransactionsPage({
|
|
|
87
107
|
canChangeFinanceWallets={canChangeFinanceWallets}
|
|
88
108
|
canSetFinanceWalletsOnCreate={canSetFinanceWalletsOnCreate}
|
|
89
109
|
canCreateConfidentialTransactions={canCreateConfidentialTransactions}
|
|
110
|
+
permissionRequestUser={permissionRequestUser}
|
|
90
111
|
/>
|
|
91
112
|
<Separator className="my-4" />
|
|
92
113
|
<TransactionsInfinitePage
|
|
@@ -109,6 +130,7 @@ export default async function TransactionsPage({
|
|
|
109
130
|
canViewConfidentialDescription={canViewConfidentialDescription}
|
|
110
131
|
canViewConfidentialCategory={canViewConfidentialCategory}
|
|
111
132
|
isPersonalWorkspace={!!resolvedWorkspace.personal}
|
|
133
|
+
permissionRequestUser={permissionRequestUser}
|
|
112
134
|
showTransactionTypeFilter={showTransactionTypeFilter}
|
|
113
135
|
/>
|
|
114
136
|
</>
|
|
@@ -4,6 +4,7 @@ import { Pencil, Plus } from '@tuturuuu/icons';
|
|
|
4
4
|
import type { Wallet } from '@tuturuuu/types/primitives/Wallet';
|
|
5
5
|
import { Button } from '@tuturuuu/ui/button';
|
|
6
6
|
import ModifiableDialogTrigger from '@tuturuuu/ui/custom/modifiable-dialog-trigger';
|
|
7
|
+
import type { FinancePermissionRequestUser } from '@tuturuuu/ui/finance/shared/finance-permission-warning-dialog';
|
|
7
8
|
import { TransactionForm } from '@tuturuuu/ui/finance/transactions/form';
|
|
8
9
|
import { WalletForm } from '@tuturuuu/ui/finance/wallets/form';
|
|
9
10
|
import { useTranslations } from 'next-intl';
|
|
@@ -21,6 +22,7 @@ interface WalletDetailsActionsProps {
|
|
|
21
22
|
canSetFinanceWalletsOnCreate?: boolean;
|
|
22
23
|
canDeleteWallets: boolean;
|
|
23
24
|
isPersonalWorkspace: boolean;
|
|
25
|
+
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export function WalletDetailsActions({
|
|
@@ -34,6 +36,7 @@ export function WalletDetailsActions({
|
|
|
34
36
|
canSetFinanceWalletsOnCreate,
|
|
35
37
|
canDeleteWallets,
|
|
36
38
|
isPersonalWorkspace,
|
|
39
|
+
permissionRequestUser,
|
|
37
40
|
}: WalletDetailsActionsProps) {
|
|
38
41
|
const t = useTranslations();
|
|
39
42
|
|
|
@@ -101,6 +104,7 @@ export function WalletDetailsActions({
|
|
|
101
104
|
canCreateConfidentialTransactions={
|
|
102
105
|
canCreateConfidentialTransactions
|
|
103
106
|
}
|
|
107
|
+
permissionRequestUser={permissionRequestUser}
|
|
104
108
|
/>
|
|
105
109
|
}
|
|
106
110
|
/>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { createAdminClient } from '@tuturuuu/supabase/next/server';
|
|
8
8
|
import type { Wallet } from '@tuturuuu/types';
|
|
9
9
|
import FeatureSummary from '@tuturuuu/ui/custom/feature-summary';
|
|
10
|
+
import type { FinancePermissionRequestUser } from '@tuturuuu/ui/finance/shared/finance-permission-warning-dialog';
|
|
10
11
|
import { InfiniteTransactionsList } from '@tuturuuu/ui/finance/transactions/infinite-transactions-list';
|
|
11
12
|
import { Separator } from '@tuturuuu/ui/separator';
|
|
12
13
|
import { Skeleton } from '@tuturuuu/ui/skeleton';
|
|
@@ -47,6 +48,7 @@ interface Props {
|
|
|
47
48
|
workspace?: {
|
|
48
49
|
personal?: boolean | null;
|
|
49
50
|
};
|
|
51
|
+
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export default async function WalletDetailsPage({
|
|
@@ -56,6 +58,7 @@ export default async function WalletDetailsPage({
|
|
|
56
58
|
internalApiOptions,
|
|
57
59
|
permissions,
|
|
58
60
|
workspace,
|
|
61
|
+
permissionRequestUser,
|
|
59
62
|
}: Props) {
|
|
60
63
|
const [t, resolvedWorkspace, resolvedPermissions, resolvedDefaultCurrency] =
|
|
61
64
|
await Promise.all([
|
|
@@ -164,6 +167,7 @@ export default async function WalletDetailsPage({
|
|
|
164
167
|
canSetFinanceWalletsOnCreate={canSetFinanceWalletsOnCreate}
|
|
165
168
|
canDeleteWallets={canDeleteWallets}
|
|
166
169
|
isPersonalWorkspace={!!resolvedWorkspace.personal}
|
|
170
|
+
permissionRequestUser={permissionRequestUser}
|
|
167
171
|
/>
|
|
168
172
|
</div>
|
|
169
173
|
<Separator className="my-4" />
|
|
@@ -320,6 +324,7 @@ export default async function WalletDetailsPage({
|
|
|
320
324
|
canViewConfidentialDescription={canViewConfidentialDescription}
|
|
321
325
|
canViewConfidentialCategory={canViewConfidentialCategory}
|
|
322
326
|
isPersonalWorkspace={!!resolvedWorkspace.personal}
|
|
327
|
+
permissionRequestUser={permissionRequestUser}
|
|
323
328
|
/>
|
|
324
329
|
</Suspense>
|
|
325
330
|
</div>
|
|
@@ -11,6 +11,7 @@ import { AgendaView } from './agenda-view';
|
|
|
11
11
|
import { CalendarHeader } from './calendar-header';
|
|
12
12
|
import { CalendarViewWithTrail } from './calendar-view-with-trail';
|
|
13
13
|
import { EventModal } from './event-modal';
|
|
14
|
+
import { EventPreviewPopover } from './event-preview-popover';
|
|
14
15
|
import { MonthCalendar } from './month-calendar';
|
|
15
16
|
import { useCalendarSettings } from './settings/settings-context';
|
|
16
17
|
import { WeekdayBar } from './weekday-bar';
|
|
@@ -664,7 +665,14 @@ export const CalendarContent = ({
|
|
|
664
665
|
)}
|
|
665
666
|
</div>
|
|
666
667
|
|
|
667
|
-
{disabled
|
|
668
|
+
{disabled
|
|
669
|
+
? null
|
|
670
|
+
: workspace && (
|
|
671
|
+
<>
|
|
672
|
+
<EventPreviewPopover />
|
|
673
|
+
<EventModal />
|
|
674
|
+
</>
|
|
675
|
+
)}
|
|
668
676
|
</div>
|
|
669
677
|
);
|
|
670
678
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
4
|
+
import { useQuery } from '@tanstack/react-query';
|
|
4
5
|
import { calendarEventsSchema } from '@tuturuuu/ai/calendar/events';
|
|
5
6
|
import { useObject } from '@tuturuuu/ai/object/core';
|
|
6
7
|
import {
|
|
@@ -24,6 +25,11 @@ import {
|
|
|
24
25
|
Unlock,
|
|
25
26
|
X,
|
|
26
27
|
} from '@tuturuuu/icons';
|
|
28
|
+
import {
|
|
29
|
+
type CalendarSourceInput,
|
|
30
|
+
type CalendarSourceOption,
|
|
31
|
+
getWorkspaceCalendarDefaultSource,
|
|
32
|
+
} from '@tuturuuu/internal-api';
|
|
27
33
|
import type { CalendarEvent } from '@tuturuuu/types/primitives/calendar-event';
|
|
28
34
|
import type { SupportedColor } from '@tuturuuu/types/primitives/SupportedColors';
|
|
29
35
|
import { Badge } from '@tuturuuu/ui/badge';
|
|
@@ -48,6 +54,13 @@ import { useCalendar } from '@tuturuuu/ui/hooks/use-calendar';
|
|
|
48
54
|
import { useForm } from '@tuturuuu/ui/hooks/use-form';
|
|
49
55
|
import { useToast } from '@tuturuuu/ui/hooks/use-toast';
|
|
50
56
|
import { ScrollArea } from '@tuturuuu/ui/scroll-area';
|
|
57
|
+
import {
|
|
58
|
+
Select,
|
|
59
|
+
SelectContent,
|
|
60
|
+
SelectItem,
|
|
61
|
+
SelectTrigger,
|
|
62
|
+
SelectValue,
|
|
63
|
+
} from '@tuturuuu/ui/select';
|
|
51
64
|
import { Separator } from '@tuturuuu/ui/separator';
|
|
52
65
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
|
|
53
66
|
import {
|
|
@@ -61,7 +74,7 @@ import dayjs from 'dayjs';
|
|
|
61
74
|
import ts from 'dayjs/plugin/timezone';
|
|
62
75
|
import utc from 'dayjs/plugin/utc';
|
|
63
76
|
import Image from 'next/image';
|
|
64
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
77
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
65
78
|
import { z } from 'zod';
|
|
66
79
|
import { Alert, AlertDescription, AlertTitle } from '../../alert';
|
|
67
80
|
import { AutosizeTextarea } from '../../custom/autosize-textarea';
|
|
@@ -89,6 +102,53 @@ const AIFormSchema = z.object({
|
|
|
89
102
|
smart_scheduling: z.boolean().default(true),
|
|
90
103
|
});
|
|
91
104
|
|
|
105
|
+
function sourceInputFromOption(
|
|
106
|
+
option?: CalendarSourceOption | null
|
|
107
|
+
): CalendarSourceInput | undefined {
|
|
108
|
+
if (!option) return undefined;
|
|
109
|
+
|
|
110
|
+
if (option.provider === 'tuturuuu') {
|
|
111
|
+
return {
|
|
112
|
+
provider: 'tuturuuu',
|
|
113
|
+
workspaceCalendarId: option.workspaceCalendarId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
provider: option.provider,
|
|
119
|
+
connectionId: option.connectionId,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function findEventSourceOption(
|
|
124
|
+
options: CalendarSourceOption[],
|
|
125
|
+
event: Partial<CalendarEvent>
|
|
126
|
+
) {
|
|
127
|
+
if (event.provider === 'google' || event.provider === 'microsoft') {
|
|
128
|
+
return options.find((option) => {
|
|
129
|
+
if (option.provider === 'tuturuuu') return false;
|
|
130
|
+
if (option.provider !== event.provider) return false;
|
|
131
|
+
if (
|
|
132
|
+
event.source_calendar_id &&
|
|
133
|
+
option.workspaceCalendarId === event.source_calendar_id
|
|
134
|
+
) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
option.externalCalendarId ===
|
|
140
|
+
(event.external_calendar_id ?? event.google_calendar_id)
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return options.find(
|
|
146
|
+
(option) =>
|
|
147
|
+
option.provider === 'tuturuuu' &&
|
|
148
|
+
option.workspaceCalendarId === event.source_calendar_id
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
92
152
|
export function EventModal() {
|
|
93
153
|
const { toast } = useToast();
|
|
94
154
|
const startPickerRef = useRef<HTMLButtonElement>(null);
|
|
@@ -118,6 +178,23 @@ export function EventModal() {
|
|
|
118
178
|
location: '',
|
|
119
179
|
locked: false,
|
|
120
180
|
});
|
|
181
|
+
const [selectedSourceId, setSelectedSourceId] = useState<string | null>(null);
|
|
182
|
+
|
|
183
|
+
const wsId = activeEvent?.ws_id || event.ws_id;
|
|
184
|
+
const { data: sourceData } = useQuery({
|
|
185
|
+
queryKey: ['calendar-default-source', wsId],
|
|
186
|
+
enabled: !!wsId,
|
|
187
|
+
queryFn: () => getWorkspaceCalendarDefaultSource(wsId as string),
|
|
188
|
+
staleTime: 30_000,
|
|
189
|
+
});
|
|
190
|
+
const sourceOptions = useMemo(
|
|
191
|
+
() => sourceData?.options ?? [],
|
|
192
|
+
[sourceData?.options]
|
|
193
|
+
);
|
|
194
|
+
const selectedSourceOption =
|
|
195
|
+
sourceOptions.find((option) => option.id === selectedSourceId) ??
|
|
196
|
+
sourceData?.defaultSource ??
|
|
197
|
+
sourceOptions[0];
|
|
121
198
|
|
|
122
199
|
// State for AI event generation
|
|
123
200
|
const [generatedEvents, setGeneratedEvents] =
|
|
@@ -239,10 +316,19 @@ export function EventModal() {
|
|
|
239
316
|
location: activeEvent.location || '',
|
|
240
317
|
locked: activeEvent.locked || false,
|
|
241
318
|
ws_id: activeEvent.ws_id,
|
|
319
|
+
provider: activeEvent.provider,
|
|
320
|
+
source_calendar_id: activeEvent.source_calendar_id,
|
|
321
|
+
external_calendar_id: activeEvent.external_calendar_id,
|
|
322
|
+
external_event_id: activeEvent.external_event_id,
|
|
242
323
|
google_event_id: activeEvent.google_event_id,
|
|
324
|
+
google_calendar_id: activeEvent.google_calendar_id,
|
|
243
325
|
};
|
|
244
326
|
|
|
245
327
|
setEvent(cleanEventData);
|
|
328
|
+
const sourceOption = findEventSourceOption(sourceOptions, cleanEventData);
|
|
329
|
+
setSelectedSourceId(
|
|
330
|
+
sourceOption?.id ?? sourceData?.defaultSource?.id ?? null
|
|
331
|
+
);
|
|
246
332
|
|
|
247
333
|
// Only check for all-day if this is an existing event (not a new one)
|
|
248
334
|
if (activeEvent.id !== 'new') {
|
|
@@ -270,9 +356,11 @@ export function EventModal() {
|
|
|
270
356
|
color: 'BLUE' as SupportedColor,
|
|
271
357
|
location: '',
|
|
272
358
|
locked: false,
|
|
359
|
+
ws_id: '',
|
|
273
360
|
};
|
|
274
361
|
|
|
275
362
|
setEvent(newEvent);
|
|
363
|
+
setSelectedSourceId(sourceData?.defaultSource?.id ?? null);
|
|
276
364
|
setIsAllDay(false);
|
|
277
365
|
|
|
278
366
|
// Reset AI form
|
|
@@ -282,7 +370,15 @@ export function EventModal() {
|
|
|
282
370
|
|
|
283
371
|
// Clear any error messages
|
|
284
372
|
setDateError(null);
|
|
285
|
-
}, [
|
|
373
|
+
}, [
|
|
374
|
+
activeEvent,
|
|
375
|
+
checkForOverlaps,
|
|
376
|
+
aiForm,
|
|
377
|
+
defaultNewEventTab,
|
|
378
|
+
isEditing,
|
|
379
|
+
sourceData?.defaultSource?.id,
|
|
380
|
+
sourceOptions,
|
|
381
|
+
]);
|
|
286
382
|
|
|
287
383
|
// Handle manual event save
|
|
288
384
|
const handleManualSave = async () => {
|
|
@@ -309,6 +405,7 @@ export function EventModal() {
|
|
|
309
405
|
color: event.color || 'BLUE',
|
|
310
406
|
location: event.location || '',
|
|
311
407
|
locked: event.locked || false,
|
|
408
|
+
source: sourceInputFromOption(selectedSourceOption),
|
|
312
409
|
};
|
|
313
410
|
|
|
314
411
|
if (activeEvent?.id === 'new') {
|
|
@@ -413,6 +510,7 @@ export function EventModal() {
|
|
|
413
510
|
color: eventData.color || 'BLUE',
|
|
414
511
|
location: eventData.location || '',
|
|
415
512
|
locked: eventData.locked || false,
|
|
513
|
+
source: sourceInputFromOption(selectedSourceOption),
|
|
416
514
|
};
|
|
417
515
|
|
|
418
516
|
const savedEvent = await addEvent(calendarEvent);
|
|
@@ -927,6 +1025,52 @@ export function EventModal() {
|
|
|
927
1025
|
onChange={(value) => setEvent({ ...event, title: value })}
|
|
928
1026
|
/>
|
|
929
1027
|
|
|
1028
|
+
<div className="space-y-2">
|
|
1029
|
+
<div className="flex items-center justify-between">
|
|
1030
|
+
<label
|
|
1031
|
+
htmlFor="event-source"
|
|
1032
|
+
className="font-medium text-sm"
|
|
1033
|
+
>
|
|
1034
|
+
Calendar source
|
|
1035
|
+
</label>
|
|
1036
|
+
{selectedSourceOption && (
|
|
1037
|
+
<Badge variant="secondary" className="capitalize">
|
|
1038
|
+
{selectedSourceOption.provider}
|
|
1039
|
+
</Badge>
|
|
1040
|
+
)}
|
|
1041
|
+
</div>
|
|
1042
|
+
<Select
|
|
1043
|
+
value={selectedSourceId ?? undefined}
|
|
1044
|
+
onValueChange={(value) => setSelectedSourceId(value)}
|
|
1045
|
+
disabled={sourceOptions.length === 0 || isSaving}
|
|
1046
|
+
>
|
|
1047
|
+
<SelectTrigger id="event-source" className="w-full">
|
|
1048
|
+
<SelectValue placeholder="Choose calendar source" />
|
|
1049
|
+
</SelectTrigger>
|
|
1050
|
+
<SelectContent>
|
|
1051
|
+
{sourceOptions.map((option) => (
|
|
1052
|
+
<SelectItem key={option.id} value={option.id}>
|
|
1053
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
1054
|
+
<span
|
|
1055
|
+
className="h-2.5 w-2.5 shrink-0 rounded-full"
|
|
1056
|
+
style={{
|
|
1057
|
+
backgroundColor: option.color ?? undefined,
|
|
1058
|
+
}}
|
|
1059
|
+
/>
|
|
1060
|
+
<span className="truncate">{option.label}</span>
|
|
1061
|
+
{option.provider !== 'tuturuuu' &&
|
|
1062
|
+
option.accountEmail && (
|
|
1063
|
+
<span className="text-muted-foreground text-xs">
|
|
1064
|
+
{option.accountEmail}
|
|
1065
|
+
</span>
|
|
1066
|
+
)}
|
|
1067
|
+
</div>
|
|
1068
|
+
</SelectItem>
|
|
1069
|
+
))}
|
|
1070
|
+
</SelectContent>
|
|
1071
|
+
</Select>
|
|
1072
|
+
</div>
|
|
1073
|
+
|
|
930
1074
|
{/* Date and Time Selection */}
|
|
931
1075
|
<div className="space-y-2">
|
|
932
1076
|
<div className="flex items-center justify-between">
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Calendar,
|
|
5
|
+
Clock,
|
|
6
|
+
Edit3,
|
|
7
|
+
ExternalLink,
|
|
8
|
+
Lock,
|
|
9
|
+
MapPin,
|
|
10
|
+
Trash2,
|
|
11
|
+
X,
|
|
12
|
+
} from '@tuturuuu/icons';
|
|
13
|
+
import { Badge } from '@tuturuuu/ui/badge';
|
|
14
|
+
import { Button } from '@tuturuuu/ui/button';
|
|
15
|
+
import { useCalendar } from '@tuturuuu/ui/hooks/use-calendar';
|
|
16
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
17
|
+
import { format } from 'date-fns';
|
|
18
|
+
import { useEffect, useRef } from 'react';
|
|
19
|
+
|
|
20
|
+
function formatEventTime(startAt?: string, endAt?: string) {
|
|
21
|
+
if (!startAt || !endAt) return '';
|
|
22
|
+
|
|
23
|
+
const start = new Date(startAt);
|
|
24
|
+
const end = new Date(endAt);
|
|
25
|
+
const sameDay = start.toDateString() === end.toDateString();
|
|
26
|
+
|
|
27
|
+
if (sameDay) {
|
|
28
|
+
return `${format(start, 'EEE, MMM d')} - ${format(start, 'p')} - ${format(end, 'p')}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return `${format(start, 'MMM d, p')} - ${format(end, 'MMM d, p')}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSourceLabel(event: {
|
|
35
|
+
provider?: string | null;
|
|
36
|
+
google_calendar_id?: string | null;
|
|
37
|
+
external_calendar_id?: string | null;
|
|
38
|
+
_calendarName?: string;
|
|
39
|
+
}) {
|
|
40
|
+
if (event._calendarName) return event._calendarName;
|
|
41
|
+
if (event.provider === 'google') {
|
|
42
|
+
return event.google_calendar_id || event.external_calendar_id || 'Google';
|
|
43
|
+
}
|
|
44
|
+
if (event.provider === 'microsoft') {
|
|
45
|
+
return event.external_calendar_id || 'Microsoft';
|
|
46
|
+
}
|
|
47
|
+
return 'Tuturuuu';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function EventPreviewPopover() {
|
|
51
|
+
const {
|
|
52
|
+
previewEvent,
|
|
53
|
+
isPreviewOpen,
|
|
54
|
+
closePreview,
|
|
55
|
+
openEventEditor,
|
|
56
|
+
deleteEvent,
|
|
57
|
+
readOnly,
|
|
58
|
+
} = useCalendar();
|
|
59
|
+
const popoverRef = useRef<HTMLDivElement>(null);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!isPreviewOpen) return;
|
|
63
|
+
|
|
64
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
65
|
+
if (
|
|
66
|
+
popoverRef.current &&
|
|
67
|
+
event.target instanceof Node &&
|
|
68
|
+
!popoverRef.current.contains(event.target)
|
|
69
|
+
) {
|
|
70
|
+
closePreview();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
75
|
+
if (event.key === 'Escape') closePreview();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
document.addEventListener('pointerdown', handlePointerDown);
|
|
79
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
80
|
+
return () => {
|
|
81
|
+
document.removeEventListener('pointerdown', handlePointerDown);
|
|
82
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
83
|
+
};
|
|
84
|
+
}, [closePreview, isPreviewOpen]);
|
|
85
|
+
|
|
86
|
+
if (!isPreviewOpen || !previewEvent) return null;
|
|
87
|
+
|
|
88
|
+
const provider = previewEvent.provider || 'tuturuuu';
|
|
89
|
+
const isSynced = provider === 'google' || provider === 'microsoft';
|
|
90
|
+
const sourceLabel = getSourceLabel(previewEvent);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
ref={popoverRef}
|
|
95
|
+
className={cn(
|
|
96
|
+
'fixed top-20 right-4 z-50 w-[min(380px,calc(100vw-2rem))]',
|
|
97
|
+
'rounded-lg border bg-popover p-4 text-popover-foreground shadow-xl'
|
|
98
|
+
)}
|
|
99
|
+
role="dialog"
|
|
100
|
+
aria-label="Event quick preview"
|
|
101
|
+
>
|
|
102
|
+
<div className="flex items-start gap-3">
|
|
103
|
+
<div
|
|
104
|
+
className="mt-1 h-3 w-3 shrink-0 rounded-full"
|
|
105
|
+
style={{ backgroundColor: previewEvent._calendarColor || undefined }}
|
|
106
|
+
/>
|
|
107
|
+
<div className="min-w-0 flex-1 space-y-3">
|
|
108
|
+
<div className="flex items-start justify-between gap-3">
|
|
109
|
+
<div className="min-w-0">
|
|
110
|
+
<h3 className="truncate font-semibold text-base">
|
|
111
|
+
{previewEvent.title || 'Untitled event'}
|
|
112
|
+
</h3>
|
|
113
|
+
<p className="text-muted-foreground text-sm">
|
|
114
|
+
{formatEventTime(previewEvent.start_at, previewEvent.end_at)}
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
<Button
|
|
118
|
+
type="button"
|
|
119
|
+
variant="ghost"
|
|
120
|
+
size="icon"
|
|
121
|
+
className="h-8 w-8 shrink-0"
|
|
122
|
+
onClick={closePreview}
|
|
123
|
+
>
|
|
124
|
+
<X className="h-4 w-4" />
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div className="space-y-2 text-sm">
|
|
129
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
130
|
+
<Calendar className="h-4 w-4" />
|
|
131
|
+
<span className="truncate">{sourceLabel}</span>
|
|
132
|
+
<Badge variant="secondary" className="ml-auto capitalize">
|
|
133
|
+
{provider}
|
|
134
|
+
</Badge>
|
|
135
|
+
</div>
|
|
136
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
137
|
+
<Clock className="h-4 w-4" />
|
|
138
|
+
<span>
|
|
139
|
+
{isSynced ? 'Synced with provider' : 'Local calendar'}
|
|
140
|
+
</span>
|
|
141
|
+
</div>
|
|
142
|
+
{previewEvent.locked && (
|
|
143
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
144
|
+
<Lock className="h-4 w-4" />
|
|
145
|
+
<span>Locked from auto scheduling</span>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
{previewEvent.location && (
|
|
149
|
+
<div className="flex items-center gap-2 text-muted-foreground">
|
|
150
|
+
<MapPin className="h-4 w-4" />
|
|
151
|
+
<span className="truncate">{previewEvent.location}</span>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{previewEvent.description && (
|
|
157
|
+
<p className="line-clamp-3 text-muted-foreground text-sm">
|
|
158
|
+
{previewEvent.description}
|
|
159
|
+
</p>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
<div className="flex items-center justify-end gap-2 pt-1">
|
|
163
|
+
{isSynced && (
|
|
164
|
+
<Badge variant="outline" className="gap-1">
|
|
165
|
+
<ExternalLink className="h-3 w-3" />
|
|
166
|
+
{provider}
|
|
167
|
+
</Badge>
|
|
168
|
+
)}
|
|
169
|
+
{!readOnly && (
|
|
170
|
+
<Button
|
|
171
|
+
type="button"
|
|
172
|
+
variant="ghost"
|
|
173
|
+
size="icon"
|
|
174
|
+
className="h-9 w-9 text-destructive hover:text-destructive"
|
|
175
|
+
onClick={async () => {
|
|
176
|
+
await deleteEvent(previewEvent.id);
|
|
177
|
+
closePreview();
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<Trash2 className="h-4 w-4" />
|
|
181
|
+
</Button>
|
|
182
|
+
)}
|
|
183
|
+
<Button
|
|
184
|
+
type="button"
|
|
185
|
+
size="sm"
|
|
186
|
+
className="gap-2"
|
|
187
|
+
onClick={() => {
|
|
188
|
+
openEventEditor(previewEvent.id);
|
|
189
|
+
closePreview();
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
<Edit3 className="h-4 w-4" />
|
|
193
|
+
Edit
|
|
194
|
+
</Button>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|