@tuturuuu/ui 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/biome.json +1 -1
- package/package.json +8 -8
- package/src/components/ui/calendar-app/components/calendar-connections.tsx +17 -13
- package/src/components/ui/calendar-app/components/connected-accounts-dialog.tsx +2 -5
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +2 -5
- package/src/components/ui/calendar.test.tsx +24 -0
- package/src/components/ui/calendar.tsx +1 -0
- package/src/components/ui/date-time-picker.tsx +352 -234
- package/src/components/ui/finance/categories-tags-tabs.tsx +23 -1
- package/src/components/ui/finance/command/finance-command-actions.test.tsx +48 -0
- package/src/components/ui/finance/command/finance-command-actions.tsx +200 -0
- package/src/components/ui/finance/command/finance-command-provider.test.tsx +151 -0
- package/src/components/ui/finance/command/finance-command-provider.tsx +250 -0
- package/src/components/ui/finance/command/finance-command-results.tsx +262 -0
- package/src/components/ui/finance/invoices/pending-invoices-table.tsx +22 -9
- package/src/components/ui/finance/shared/quick-actions.tsx +39 -90
- package/src/components/ui/finance/tags/tag-manager.tsx +24 -5
- package/src/components/ui/finance/transactions/form-basic-tab.tsx +33 -49
- package/src/components/ui/finance/transactions/form-types.ts +3 -0
- package/src/components/ui/finance/transactions/form.test.tsx +105 -22
- package/src/components/ui/finance/transactions/form.tsx +116 -20
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +13 -6
- package/src/components/ui/finance/transactions/transaction-edit-dialog.test.tsx +25 -1
- package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +16 -3
- package/src/components/ui/finance/transactions/transactionId/transaction-details-client-page.tsx +3 -0
- package/src/components/ui/finance/transactions/transactionId/transaction-details-page.tsx +3 -0
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +6 -0
- package/src/components/ui/finance/transactions/transactions-infinite-page.tsx +20 -2
- package/src/components/ui/finance/transactions/transactions-page.tsx +4 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +7 -2
- package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +7 -2
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +38 -1
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +5 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +18 -2
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +3 -0
- package/src/components/ui/finance/wallets/wallets-page.tsx +3 -0
- package/src/components/ui/legacy/calendar/settings/google-calendar-settings.tsx +2 -9
- package/src/components/ui/optional-time-picker.tsx +95 -0
- package/src/components/ui/quick-command-center.test.tsx +90 -0
- package/src/components/ui/quick-command-center.tsx +190 -0
- package/src/components/ui/storefront/cart-summary.tsx +18 -27
- package/src/components/ui/storefront/hero-panel.tsx +22 -13
- package/src/components/ui/storefront/storefront-surface.test.tsx +8 -4
- package/src/components/ui/storefront/storefront-surface.tsx +84 -41
- package/src/components/ui/storefront/types.ts +2 -0
- package/src/components/ui/storefront/utils.ts +21 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.test.ts +171 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/task-api.ts +200 -36
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +21 -2
|
@@ -82,6 +82,14 @@ export function TransactionsInfinitePage({
|
|
|
82
82
|
shallow: true,
|
|
83
83
|
})
|
|
84
84
|
);
|
|
85
|
+
const [tool, setTool] = useQueryState(
|
|
86
|
+
'tool',
|
|
87
|
+
parseAsString.withDefault('').withOptions({
|
|
88
|
+
shallow: true,
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
const importOpen = tool === 'import';
|
|
92
|
+
const exportOpen = tool === 'export';
|
|
85
93
|
|
|
86
94
|
const handleSearch = async (query: string) => {
|
|
87
95
|
await setQ(query || '');
|
|
@@ -143,7 +151,12 @@ export function TransactionsInfinitePage({
|
|
|
143
151
|
|
|
144
152
|
<div className="flex w-full flex-col gap-2 sm:flex-row md:w-auto">
|
|
145
153
|
{/* Import button */}
|
|
146
|
-
<Dialog
|
|
154
|
+
<Dialog
|
|
155
|
+
open={importOpen}
|
|
156
|
+
onOpenChange={(nextOpen) =>
|
|
157
|
+
setTool(nextOpen ? 'import' : tool === 'import' ? null : tool)
|
|
158
|
+
}
|
|
159
|
+
>
|
|
147
160
|
<DialogTrigger asChild>
|
|
148
161
|
<Button
|
|
149
162
|
variant="outline"
|
|
@@ -161,7 +174,12 @@ export function TransactionsInfinitePage({
|
|
|
161
174
|
|
|
162
175
|
{/* Export button */}
|
|
163
176
|
{canExport && exportContent && (
|
|
164
|
-
<Dialog
|
|
177
|
+
<Dialog
|
|
178
|
+
open={exportOpen}
|
|
179
|
+
onOpenChange={(nextOpen) =>
|
|
180
|
+
setTool(nextOpen ? 'export' : tool === 'export' ? null : tool)
|
|
181
|
+
}
|
|
182
|
+
>
|
|
165
183
|
<DialogTrigger asChild>
|
|
166
184
|
<Button
|
|
167
185
|
variant="outline"
|
|
@@ -26,6 +26,7 @@ interface Props {
|
|
|
26
26
|
};
|
|
27
27
|
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
28
28
|
openCreateDialog?: boolean;
|
|
29
|
+
initialCreateMode?: 'transaction' | 'transfer';
|
|
29
30
|
showTransactionTypeFilter?: boolean;
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -36,6 +37,7 @@ export default async function TransactionsPage({
|
|
|
36
37
|
workspace,
|
|
37
38
|
permissionRequestUser,
|
|
38
39
|
openCreateDialog = false,
|
|
40
|
+
initialCreateMode = 'transaction',
|
|
39
41
|
showTransactionTypeFilter = false,
|
|
40
42
|
}: Props) {
|
|
41
43
|
const [t, resolvedWorkspace, resolvedPermissions, resolvedCurrency] =
|
|
@@ -102,11 +104,13 @@ export default async function TransactionsPage({
|
|
|
102
104
|
createTitle={t('ws-transactions.create')}
|
|
103
105
|
createDescription={t('ws-transactions.create_description')}
|
|
104
106
|
defaultOpen={openCreateDialog}
|
|
107
|
+
initialMode={initialCreateMode}
|
|
105
108
|
wsId={wsId}
|
|
106
109
|
canCreateTransactions={canCreateTransactions}
|
|
107
110
|
canChangeFinanceWallets={canChangeFinanceWallets}
|
|
108
111
|
canSetFinanceWalletsOnCreate={canSetFinanceWalletsOnCreate}
|
|
109
112
|
canCreateConfidentialTransactions={canCreateConfidentialTransactions}
|
|
113
|
+
timezone={resolvedWorkspace.timezone}
|
|
110
114
|
permissionRequestUser={permissionRequestUser}
|
|
111
115
|
/>
|
|
112
116
|
<Separator className="my-4" />
|
|
@@ -30,7 +30,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@tuturuuu/ui/tabs';
|
|
|
30
30
|
import { cn } from '@tuturuuu/utils/format';
|
|
31
31
|
import Link from 'next/link';
|
|
32
32
|
import { useLocale, useTranslations } from 'next-intl';
|
|
33
|
-
import { useMemo, useState } from 'react';
|
|
33
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
34
34
|
import { invalidateWalletMutationQueries } from '../query-invalidation';
|
|
35
35
|
import { WalletCheckpointAdjustmentDialog } from './wallet-checkpoint-adjustment-dialog';
|
|
36
36
|
import { WalletCheckpointAmount } from './wallet-checkpoint-amount';
|
|
@@ -63,17 +63,19 @@ type WindowRow =
|
|
|
63
63
|
|
|
64
64
|
export function WalletCheckpointHistoryDialog({
|
|
65
65
|
canCreateTransactions,
|
|
66
|
+
defaultOpen = false,
|
|
66
67
|
financePrefix = '/finance',
|
|
67
68
|
wsId,
|
|
68
69
|
}: {
|
|
69
70
|
canCreateTransactions: boolean;
|
|
71
|
+
defaultOpen?: boolean;
|
|
70
72
|
financePrefix?: string;
|
|
71
73
|
wsId: string;
|
|
72
74
|
}) {
|
|
73
75
|
const t = useTranslations('wallet-checkpoints');
|
|
74
76
|
const locale = useLocale();
|
|
75
77
|
const queryClient = useQueryClient();
|
|
76
|
-
const [open, setOpen] = useState(
|
|
78
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
77
79
|
const [search, setSearch] = useState('');
|
|
78
80
|
const [currency, setCurrency] = useState(ALL);
|
|
79
81
|
const [status, setStatus] = useState<typeof ALL | CheckpointStatus>(ALL);
|
|
@@ -84,6 +86,9 @@ export function WalletCheckpointHistoryDialog({
|
|
|
84
86
|
queryFn: () => getWalletCheckpointHistory(wsId, { limit: 100 }),
|
|
85
87
|
enabled: open,
|
|
86
88
|
});
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (defaultOpen) setOpen(true);
|
|
91
|
+
}, [defaultOpen]);
|
|
87
92
|
const formatDate = (value: string) =>
|
|
88
93
|
new Intl.DateTimeFormat(locale, {
|
|
89
94
|
dateStyle: 'medium',
|
|
@@ -29,7 +29,7 @@ import { toast } from '@tuturuuu/ui/sonner';
|
|
|
29
29
|
import { cn } from '@tuturuuu/utils/format';
|
|
30
30
|
import { useTranslations } from 'next-intl';
|
|
31
31
|
import type { ReactNode } from 'react';
|
|
32
|
-
import { useMemo, useState } from 'react';
|
|
32
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
33
33
|
import { useFinanceBalanceMode } from '../../shared/use-finance-balance-mode';
|
|
34
34
|
import {
|
|
35
35
|
getWalletBalanceTone,
|
|
@@ -51,22 +51,27 @@ type WalletInput = {
|
|
|
51
51
|
export function WalletTotalCheckDialog({
|
|
52
52
|
canUpdateWallets,
|
|
53
53
|
currency,
|
|
54
|
+
defaultOpen = false,
|
|
54
55
|
wsId,
|
|
55
56
|
}: {
|
|
56
57
|
canUpdateWallets: boolean;
|
|
57
58
|
currency: string;
|
|
59
|
+
defaultOpen?: boolean;
|
|
58
60
|
wsId: string;
|
|
59
61
|
}) {
|
|
60
62
|
const t = useTranslations('wallet-checkpoints');
|
|
61
63
|
const queryClient = useQueryClient();
|
|
62
64
|
const { isAuditedMode } = useFinanceBalanceMode();
|
|
63
|
-
const [open, setOpen] = useState(
|
|
65
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
64
66
|
const [values, setValues] = useState<Record<string, string>>({});
|
|
65
67
|
const walletsQuery = useQuery({
|
|
66
68
|
enabled: open && canUpdateWallets,
|
|
67
69
|
queryFn: () => listWallets(wsId),
|
|
68
70
|
queryKey: ['wallets', wsId, 'all-wallet-check'],
|
|
69
71
|
});
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (defaultOpen) setOpen(true);
|
|
74
|
+
}, [defaultOpen]);
|
|
70
75
|
const wallets = useMemo<WalletInput[]>(
|
|
71
76
|
() =>
|
|
72
77
|
(walletsQuery.data ?? []).flatMap((wallet) => {
|
|
@@ -50,7 +50,7 @@ describe('WalletDetailsActions', () => {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it('prefills card payments as transfers into the credit wallet', () => {
|
|
53
|
-
render(<WalletDetailsActions {...baseProps} />);
|
|
53
|
+
render(<WalletDetailsActions {...baseProps} timezone="Asia/Ho_Chi_Minh" />);
|
|
54
54
|
|
|
55
55
|
fireEvent.click(screen.getByText('wallet-data-table.credit_payment'));
|
|
56
56
|
|
|
@@ -63,6 +63,9 @@ describe('WalletDetailsActions', () => {
|
|
|
63
63
|
initialTransfer: expect.objectContaining({
|
|
64
64
|
destination_wallet_id: 'wallet-1',
|
|
65
65
|
}),
|
|
66
|
+
preferInitialWalletSelection: false,
|
|
67
|
+
refreshPageOnFinish: true,
|
|
68
|
+
timezone: 'Asia/Ho_Chi_Minh',
|
|
66
69
|
})
|
|
67
70
|
);
|
|
68
71
|
});
|
|
@@ -81,6 +84,8 @@ describe('WalletDetailsActions', () => {
|
|
|
81
84
|
categoryKind: 'expense',
|
|
82
85
|
origin_wallet_id: 'wallet-1',
|
|
83
86
|
}),
|
|
87
|
+
preferInitialWalletSelection: true,
|
|
88
|
+
refreshPageOnFinish: true,
|
|
84
89
|
})
|
|
85
90
|
);
|
|
86
91
|
});
|
|
@@ -99,6 +104,38 @@ describe('WalletDetailsActions', () => {
|
|
|
99
104
|
categoryKind: 'income',
|
|
100
105
|
origin_wallet_id: 'wallet-1',
|
|
101
106
|
}),
|
|
107
|
+
preferInitialWalletSelection: true,
|
|
108
|
+
refreshPageOnFinish: true,
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('prefills standard wallet transactions as source-wallet transactions', () => {
|
|
114
|
+
render(
|
|
115
|
+
<WalletDetailsActions
|
|
116
|
+
{...baseProps}
|
|
117
|
+
wallet={
|
|
118
|
+
{
|
|
119
|
+
id: 'wallet-1',
|
|
120
|
+
name: 'Cash',
|
|
121
|
+
type: 'STANDARD',
|
|
122
|
+
} as never
|
|
123
|
+
}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
fireEvent.click(screen.getByText('ws-transactions.singular'));
|
|
128
|
+
|
|
129
|
+
const props = mocks.transactionForm.mock.calls.at(-1)?.[0];
|
|
130
|
+
|
|
131
|
+
expect(props).toEqual(
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
initialMode: 'transaction',
|
|
134
|
+
initialTransaction: expect.objectContaining({
|
|
135
|
+
origin_wallet_id: 'wallet-1',
|
|
136
|
+
}),
|
|
137
|
+
preferInitialWalletSelection: true,
|
|
138
|
+
refreshPageOnFinish: true,
|
|
102
139
|
})
|
|
103
140
|
);
|
|
104
141
|
});
|
|
@@ -35,6 +35,7 @@ interface WalletDetailsActionsProps {
|
|
|
35
35
|
canSetFinanceWalletsOnCreate?: boolean;
|
|
36
36
|
canDeleteWallets: boolean;
|
|
37
37
|
isPersonalWorkspace: boolean;
|
|
38
|
+
timezone?: string | null;
|
|
38
39
|
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -50,6 +51,7 @@ export function WalletDetailsActions({
|
|
|
50
51
|
canSetFinanceWalletsOnCreate,
|
|
51
52
|
canDeleteWallets,
|
|
52
53
|
isPersonalWorkspace,
|
|
54
|
+
timezone,
|
|
53
55
|
permissionRequestUser,
|
|
54
56
|
}: WalletDetailsActionsProps) {
|
|
55
57
|
const t = useTranslations();
|
|
@@ -208,6 +210,9 @@ export function WalletDetailsActions({
|
|
|
208
210
|
canCreateConfidentialTransactions={
|
|
209
211
|
canCreateConfidentialTransactions
|
|
210
212
|
}
|
|
213
|
+
timezone={timezone}
|
|
214
|
+
preferInitialWalletSelection={transactionAction !== 'payment'}
|
|
215
|
+
refreshPageOnFinish
|
|
211
216
|
permissionRequestUser={permissionRequestUser}
|
|
212
217
|
/>
|
|
213
218
|
}
|
|
@@ -30,6 +30,7 @@ const mocks = vi.hoisted(() => {
|
|
|
30
30
|
getWorkspace: vi.fn(),
|
|
31
31
|
getWorkspaceConfig: vi.fn(),
|
|
32
32
|
headers: vi.fn(),
|
|
33
|
+
infiniteTransactionsList: vi.fn((_props: unknown) => null),
|
|
33
34
|
notFound: vi.fn(() => {
|
|
34
35
|
throw new Error('notFound');
|
|
35
36
|
}),
|
|
@@ -81,7 +82,9 @@ vi.mock('@tuturuuu/ui/custom/feature-summary', () => ({
|
|
|
81
82
|
}));
|
|
82
83
|
|
|
83
84
|
vi.mock('@tuturuuu/ui/finance/transactions/infinite-transactions-list', () => ({
|
|
84
|
-
InfiniteTransactionsList: (
|
|
85
|
+
InfiniteTransactionsList: (
|
|
86
|
+
...args: Parameters<typeof mocks.infiniteTransactionsList>
|
|
87
|
+
) => mocks.infiniteTransactionsList(...args),
|
|
85
88
|
}));
|
|
86
89
|
|
|
87
90
|
vi.mock('@tuturuuu/ui/separator', () => ({
|
|
@@ -129,7 +132,10 @@ describe('wallet details page', () => {
|
|
|
129
132
|
vi.resetModules();
|
|
130
133
|
vi.clearAllMocks();
|
|
131
134
|
mocks.getTranslations.mockResolvedValue((key: string) => key);
|
|
132
|
-
mocks.getWorkspace.mockResolvedValue({
|
|
135
|
+
mocks.getWorkspace.mockResolvedValue({
|
|
136
|
+
personal: false,
|
|
137
|
+
timezone: 'Asia/Ho_Chi_Minh',
|
|
138
|
+
});
|
|
133
139
|
mocks.getWorkspaceConfig.mockResolvedValue('USD');
|
|
134
140
|
mocks.getPermissions.mockResolvedValue({
|
|
135
141
|
withoutPermission: vi.fn(() => false),
|
|
@@ -208,6 +214,16 @@ describe('wallet details page', () => {
|
|
|
208
214
|
expect(actionProps).toEqual(
|
|
209
215
|
expect.objectContaining({
|
|
210
216
|
initialAction: 'payment',
|
|
217
|
+
timezone: 'Asia/Ho_Chi_Minh',
|
|
218
|
+
walletId: 'wallet-1',
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
expect(mocks.infiniteTransactionsList).toHaveBeenCalled();
|
|
222
|
+
const listProps = mocks.infiniteTransactionsList.mock.calls[0]?.[0];
|
|
223
|
+
|
|
224
|
+
expect(listProps).toEqual(
|
|
225
|
+
expect.objectContaining({
|
|
226
|
+
timezone: 'Asia/Ho_Chi_Minh',
|
|
211
227
|
walletId: 'wallet-1',
|
|
212
228
|
})
|
|
213
229
|
);
|
|
@@ -51,6 +51,7 @@ interface Props {
|
|
|
51
51
|
permissions?: PermissionsResult;
|
|
52
52
|
workspace?: {
|
|
53
53
|
personal?: boolean | null;
|
|
54
|
+
timezone?: string | null;
|
|
54
55
|
};
|
|
55
56
|
permissionRequestUser?: FinancePermissionRequestUser | null;
|
|
56
57
|
}
|
|
@@ -152,6 +153,7 @@ export default async function WalletDetailsPage({
|
|
|
152
153
|
canSetFinanceWalletsOnCreate={canSetFinanceWalletsOnCreate}
|
|
153
154
|
canDeleteWallets={canDeleteWallets}
|
|
154
155
|
isPersonalWorkspace={!!resolvedWorkspace.personal}
|
|
156
|
+
timezone={resolvedWorkspace.timezone}
|
|
155
157
|
permissionRequestUser={permissionRequestUser}
|
|
156
158
|
/>
|
|
157
159
|
</div>
|
|
@@ -314,6 +316,7 @@ export default async function WalletDetailsPage({
|
|
|
314
316
|
wsId={wsId}
|
|
315
317
|
walletId={walletId}
|
|
316
318
|
currency={currency}
|
|
319
|
+
timezone={resolvedWorkspace.timezone}
|
|
317
320
|
canCreateTransactions={canCreateTransactions}
|
|
318
321
|
canCreateConfidentialTransactions={canCreateConfidentialTransactions}
|
|
319
322
|
canUpdateTransactions={canUpdateTransactions}
|
|
@@ -20,6 +20,7 @@ interface Props {
|
|
|
20
20
|
searchParams: {
|
|
21
21
|
create?: string;
|
|
22
22
|
q?: string;
|
|
23
|
+
tool?: string;
|
|
23
24
|
};
|
|
24
25
|
currency?: string;
|
|
25
26
|
financePrefix?: string;
|
|
@@ -90,11 +91,13 @@ export default async function WalletsPage({
|
|
|
90
91
|
wsId={wsId}
|
|
91
92
|
financePrefix={financePrefix}
|
|
92
93
|
canCreateTransactions={canCreateTransactions}
|
|
94
|
+
defaultOpen={searchParams.tool === 'checkpoint-history'}
|
|
93
95
|
/>
|
|
94
96
|
<WalletTotalCheckDialog
|
|
95
97
|
wsId={wsId}
|
|
96
98
|
currency={resolvedCurrency ?? 'USD'}
|
|
97
99
|
canUpdateWallets={canUpdateWallets}
|
|
100
|
+
defaultOpen={searchParams.tool === 'all-wallet-check'}
|
|
98
101
|
/>
|
|
99
102
|
</div>
|
|
100
103
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Check, ExternalLink, Link, Loader2, RefreshCw } from '@tuturuuu/icons';
|
|
4
|
+
import { getGoogleCalendarAuthUrl } from '@tuturuuu/internal-api';
|
|
4
5
|
import { createClient } from '@tuturuuu/supabase/next/client';
|
|
5
6
|
import type { WorkspaceCalendarGoogleTokenClient } from '@tuturuuu/types';
|
|
6
7
|
import { Alert, AlertDescription } from '@tuturuuu/ui/alert';
|
|
@@ -119,15 +120,7 @@ export function GoogleCalendarSettings({
|
|
|
119
120
|
|
|
120
121
|
setIsGoogleAuthenticating(true);
|
|
121
122
|
try {
|
|
122
|
-
const
|
|
123
|
-
method: 'GET',
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if (!response.ok) {
|
|
127
|
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const { authUrl } = await response.json();
|
|
123
|
+
const { authUrl } = await getGoogleCalendarAuthUrl(wsId);
|
|
131
124
|
window.location.href = authUrl;
|
|
132
125
|
} catch (error) {
|
|
133
126
|
console.error('Error initiating Google auth:', error);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { DateTimePicker } from '@tuturuuu/ui/date-time-picker';
|
|
4
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
5
|
+
import {
|
|
6
|
+
buildDateInTimezone,
|
|
7
|
+
getDatePartsInTimezone,
|
|
8
|
+
} from '@tuturuuu/utils/task-date-timezone';
|
|
9
|
+
import type { ReactNode } from 'react';
|
|
10
|
+
|
|
11
|
+
export interface OptionalTimePickerProps {
|
|
12
|
+
date?: Date;
|
|
13
|
+
setDate: (date: Date | undefined) => void;
|
|
14
|
+
includeTime: boolean;
|
|
15
|
+
setIncludeTime: (includeTime: boolean) => void;
|
|
16
|
+
includeTimeLabel: ReactNode;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
allowClear?: boolean;
|
|
19
|
+
showFooterControls?: boolean;
|
|
20
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
21
|
+
align?: 'start' | 'center' | 'end';
|
|
22
|
+
collisionPadding?: number;
|
|
23
|
+
className?: string;
|
|
24
|
+
preferences?: {
|
|
25
|
+
weekStartsOn?: 0 | 1 | 6;
|
|
26
|
+
timezone?: string;
|
|
27
|
+
timeFormat?: '12h' | '24h';
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function startOfDay(date: Date, timezone?: string) {
|
|
32
|
+
if (!timezone) {
|
|
33
|
+
const next = new Date(date);
|
|
34
|
+
next.setHours(0, 0, 0, 0);
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const parts = getDatePartsInTimezone(date, timezone);
|
|
39
|
+
return buildDateInTimezone(
|
|
40
|
+
parts.year,
|
|
41
|
+
parts.month,
|
|
42
|
+
parts.day,
|
|
43
|
+
0,
|
|
44
|
+
0,
|
|
45
|
+
timezone
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function OptionalTimePicker({
|
|
50
|
+
date,
|
|
51
|
+
setDate,
|
|
52
|
+
includeTime,
|
|
53
|
+
setIncludeTime,
|
|
54
|
+
includeTimeLabel,
|
|
55
|
+
disabled = false,
|
|
56
|
+
allowClear = true,
|
|
57
|
+
showFooterControls = true,
|
|
58
|
+
side = 'bottom',
|
|
59
|
+
align = 'start',
|
|
60
|
+
collisionPadding = 16,
|
|
61
|
+
className,
|
|
62
|
+
preferences,
|
|
63
|
+
}: OptionalTimePickerProps) {
|
|
64
|
+
const handleDateChange = (nextDate: Date | undefined) => {
|
|
65
|
+
if (!nextDate || includeTime) {
|
|
66
|
+
setDate(nextDate);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setDate(startOfDay(nextDate, preferences?.timezone));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className={cn('w-full', className)}>
|
|
75
|
+
<DateTimePicker
|
|
76
|
+
date={date}
|
|
77
|
+
setDate={handleDateChange}
|
|
78
|
+
showTimeSelect={includeTime}
|
|
79
|
+
allowClear={allowClear}
|
|
80
|
+
showFooterControls={showFooterControls}
|
|
81
|
+
disabled={disabled}
|
|
82
|
+
side={side}
|
|
83
|
+
align={align}
|
|
84
|
+
collisionPadding={collisionPadding}
|
|
85
|
+
preferences={preferences}
|
|
86
|
+
timeToggle={{
|
|
87
|
+
checked: includeTime,
|
|
88
|
+
disabled,
|
|
89
|
+
label: includeTimeLabel,
|
|
90
|
+
onCheckedChange: setIncludeTime,
|
|
91
|
+
}}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { QuickCommandCenter } from './quick-command-center';
|
|
4
|
+
|
|
5
|
+
describe('QuickCommandCenter', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
globalThis.ResizeObserver = class ResizeObserver {
|
|
8
|
+
disconnect() {}
|
|
9
|
+
observe() {}
|
|
10
|
+
unobserve() {}
|
|
11
|
+
};
|
|
12
|
+
Element.prototype.scrollIntoView = vi.fn();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders grouped commands and activates digit shortcuts', () => {
|
|
16
|
+
const first = vi.fn();
|
|
17
|
+
const second = vi.fn();
|
|
18
|
+
|
|
19
|
+
render(
|
|
20
|
+
<QuickCommandCenter
|
|
21
|
+
digitShortcuts
|
|
22
|
+
emptyLabel="No commands"
|
|
23
|
+
groups={[
|
|
24
|
+
{
|
|
25
|
+
heading: 'Create',
|
|
26
|
+
id: 'create',
|
|
27
|
+
items: [
|
|
28
|
+
{
|
|
29
|
+
id: 'transaction',
|
|
30
|
+
onSelect: first,
|
|
31
|
+
title: 'New transaction',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'wallet',
|
|
35
|
+
onSelect: second,
|
|
36
|
+
title: 'New wallet',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
onOpenChange={() => undefined}
|
|
42
|
+
open
|
|
43
|
+
placeholder="Search commands"
|
|
44
|
+
title="Quick command center"
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(screen.getByText('New transaction')).toBeVisible();
|
|
49
|
+
expect(screen.getByText('New wallet')).toBeVisible();
|
|
50
|
+
|
|
51
|
+
fireEvent.keyDown(window, { key: '2' });
|
|
52
|
+
|
|
53
|
+
expect(first).not.toHaveBeenCalled();
|
|
54
|
+
expect(second).toHaveBeenCalledTimes(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('filters commands by search text', () => {
|
|
58
|
+
render(
|
|
59
|
+
<QuickCommandCenter
|
|
60
|
+
emptyLabel="No commands"
|
|
61
|
+
groups={[
|
|
62
|
+
{
|
|
63
|
+
heading: 'Create',
|
|
64
|
+
id: 'create',
|
|
65
|
+
items: [
|
|
66
|
+
{
|
|
67
|
+
id: 'transaction',
|
|
68
|
+
onSelect: vi.fn(),
|
|
69
|
+
title: 'New transaction',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'wallet',
|
|
73
|
+
onSelect: vi.fn(),
|
|
74
|
+
title: 'New wallet',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
]}
|
|
79
|
+
onOpenChange={() => undefined}
|
|
80
|
+
open
|
|
81
|
+
placeholder="Search commands"
|
|
82
|
+
searchValue="wallet"
|
|
83
|
+
title="Quick command center"
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(screen.getByText('New wallet')).toBeVisible();
|
|
88
|
+
expect(screen.queryByText('New transaction')).not.toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
});
|