@tuturuuu/ui 0.38.0 → 0.39.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 +27 -0
- package/package.json +6 -6
- package/src/components/ui/finance/invoices/attendance-calendar.test.tsx +294 -0
- package/src/components/ui/finance/invoices/attendance-calendar.tsx +14 -6
- package/src/components/ui/finance/invoices/components/invoice-history-actor.test.tsx +341 -0
- package/src/components/ui/finance/invoices/components/invoice-history-date.test.tsx +173 -0
- package/src/components/ui/finance/invoices/components/invoice-history-error.test.tsx +269 -0
- package/src/components/ui/finance/invoices/components/invoice-history-recovery.tsx +47 -0
- package/src/components/ui/finance/invoices/components/invoice-user-history-accordion.tsx +43 -10
- package/src/components/ui/finance/invoices/components/subscription-create-action.tsx +42 -0
- package/src/components/ui/finance/invoices/hooks/customer-search-scope.test.tsx +404 -0
- package/src/components/ui/finance/invoices/hooks/invoice-actor-lifetime-key.ts +11 -0
- package/src/components/ui/finance/invoices/hooks/use-infinite-user-invoices.ts +48 -0
- package/src/components/ui/finance/invoices/hooks/use-invoice-customer-search.test.tsx +6 -1
- package/src/components/ui/finance/invoices/hooks/use-invoice-customer-search.ts +58 -6
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.test.tsx +34 -0
- package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +8 -2
- package/src/components/ui/finance/invoices/hooks/use-subscription-create.ts +219 -0
- package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-context.ts +59 -0
- package/src/components/ui/finance/invoices/hooks/use-user-groups.ts +38 -0
- package/src/components/ui/finance/invoices/hooks.ts +3 -100
- package/src/components/ui/finance/invoices/invoiceId/compact-invoice-template.tsx +2 -7
- package/src/components/ui/finance/invoices/invoiceId/format-invoice-template-date.ts +13 -0
- package/src/components/ui/finance/invoices/invoiceId/full-invoice-template.tsx +2 -7
- package/src/components/ui/finance/invoices/invoiceId/invoice-card-date.test.tsx +169 -0
- package/src/components/ui/finance/invoices/invoiceId/invoice-card-storage.test.tsx +120 -0
- package/src/components/ui/finance/invoices/invoiceId/invoice-card.tsx +25 -62
- package/src/components/ui/finance/invoices/invoiceId/invoice-details-page.tsx +1 -0
- package/src/components/ui/finance/invoices/invoiceId/invoice-png-lifetime.test.tsx +438 -0
- package/src/components/ui/finance/invoices/invoiceId/use-invoice-png-export.ts +184 -0
- package/src/components/ui/finance/invoices/subscription-admission.test.ts +77 -0
- package/src/components/ui/finance/invoices/subscription-admission.ts +101 -0
- package/src/components/ui/finance/invoices/subscription-billing-groups.test.ts +162 -1
- package/src/components/ui/finance/invoices/subscription-billing-groups.ts +50 -9
- package/src/components/ui/finance/invoices/subscription-context-actor.test.tsx +246 -0
- package/src/components/ui/finance/invoices/subscription-create-scope.test.tsx +562 -0
- package/src/components/ui/finance/invoices/subscription-invoice.test.tsx +88 -6
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +76 -135
- package/src/components/ui/finance/invoices/types.ts +3 -3
- package/src/components/ui/finance/invoices/user-groups-actor.test.tsx +334 -0
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +47 -10
- package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +95 -1
- package/src/components/ui/legacy/calendar/agenda-view.tsx +4 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +33 -369
- package/src/components/ui/legacy/calendar/all-day-event-layout.test.ts +94 -0
- package/src/components/ui/legacy/calendar/all-day-event-layout.ts +322 -0
- package/src/components/ui/legacy/calendar/calendar-views.test.tsx +58 -27
- package/src/components/ui/legacy/calendar/event-card.test.tsx +25 -1
- package/src/components/ui/legacy/calendar/event-card.tsx +10 -12
- package/src/components/ui/legacy/calendar/event-modal-header.tsx +17 -4
- package/src/components/ui/legacy/calendar/event-modal.tsx +26 -1554
- package/src/components/ui/legacy/calendar/legacy-event-modal.tsx +1559 -0
- package/src/components/ui/legacy/calendar/month-calendar.tsx +5 -2
- package/src/components/ui/legacy/calendar/native-recurrence-context-menu.tsx +21 -0
- package/src/components/ui/legacy/calendar/native-recurrence-dialog.test.tsx +245 -0
- package/src/components/ui/legacy/calendar/native-recurrence-dialog.tsx +95 -0
- package/src/components/ui/legacy/calendar/native-recurrence-fields.tsx +137 -0
- package/src/components/ui/legacy/calendar/native-recurrence-form.tsx +340 -0
- package/src/components/ui/legacy/calendar/native-recurrence-model.test.ts +234 -0
- package/src/components/ui/legacy/calendar/native-recurrence-model.ts +245 -0
- package/src/components/ui/legacy/calendar/native-recurrence-pattern-fields.test.tsx +142 -0
- package/src/components/ui/legacy/calendar/native-recurrence-pattern-fields.tsx +178 -0
- package/src/components/ui/legacy/calendar/native-recurrence-pattern-model.test.ts +205 -0
- package/src/components/ui/legacy/calendar/native-recurrence-provider-routing.test.tsx +255 -0
- package/src/components/ui/legacy/calendar/past-event-treatment.test.ts +137 -0
- package/src/components/ui/legacy/calendar/past-event-treatment.ts +25 -0
- package/src/components/ui/legacy/calendar/provider-readonly-dialog.test.tsx +52 -0
- package/src/components/ui/legacy/calendar/provider-readonly-dialog.tsx +48 -0
- package/src/components/ui/legacy/calendar/use-calendar-draft-save.test.tsx +1 -1
- package/src/components/ui/legacy/calendar/use-calendar-draft-save.tsx +1 -1
- package/src/components/ui/legacy/calendar/use-provider-recurrence-operation.test.tsx +185 -0
- package/src/components/ui/legacy/calendar/use-provider-recurrence-operation.ts +186 -0
- package/src/components/ui/text-editor/editor.tsx +3 -0
- package/src/components/ui/text-editor/fixed-toolbar-contents.test.tsx +59 -0
- package/src/components/ui/text-editor/fixed-toolbar-contents.tsx +156 -0
- package/src/components/ui/text-editor/tool-bar.tsx +18 -74
- package/src/hooks/__tests__/calendar-event-write-payload-readonly.test.ts +40 -0
- package/src/hooks/__tests__/native-calendar-mutation-guard.test.ts +45 -0
- package/src/hooks/__tests__/supabase-provider.test.ts +101 -7
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +27 -0
- package/src/hooks/__tests__/use-local-storage.test.tsx +42 -0
- package/src/hooks/__tests__/use-native-calendar-occurrences.test.tsx +140 -0
- package/src/hooks/__tests__/use-yjs-collaboration.test.tsx +39 -0
- package/src/hooks/calendar-connection-types.ts +49 -0
- package/src/hooks/calendar-event-update-queue-types.ts +12 -0
- package/src/hooks/calendar-event-write-payload.ts +33 -0
- package/src/hooks/collaboration-document-hydration.ts +35 -0
- package/src/hooks/supabase-provider.ts +39 -41
- package/src/hooks/use-calendar-sync.tsx +41 -62
- package/src/hooks/use-calendar.tsx +24 -38
- package/src/hooks/use-local-storage.ts +9 -11
- package/src/hooks/use-native-calendar-occurrences.ts +85 -0
- package/src/hooks/use-yjs-collaboration.ts +23 -0
package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx
CHANGED
|
@@ -29,7 +29,16 @@ import { toast } from '@tuturuuu/ui/sonner';
|
|
|
29
29
|
import { Textarea } from '@tuturuuu/ui/textarea';
|
|
30
30
|
import { formatCurrency } from '@tuturuuu/utils/format';
|
|
31
31
|
import { useTranslations } from 'next-intl';
|
|
32
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
32
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
33
|
+
|
|
34
|
+
type ReconciliationRequest = {
|
|
35
|
+
wsId: string;
|
|
36
|
+
walletId: string;
|
|
37
|
+
checkpointId: string;
|
|
38
|
+
payload: { category_id?: string; description: string };
|
|
39
|
+
onCreated: () => void;
|
|
40
|
+
onOpenChange: (open: boolean) => void;
|
|
41
|
+
};
|
|
33
42
|
|
|
34
43
|
const NO_CATEGORY = 'none';
|
|
35
44
|
|
|
@@ -57,6 +66,10 @@ export function WalletCheckpointAdjustmentDialog({
|
|
|
57
66
|
wsId: string;
|
|
58
67
|
}) {
|
|
59
68
|
const t = useTranslations('wallet-checkpoints');
|
|
69
|
+
const activeTarget = useRef({ wsId, walletId, checkpointId, open });
|
|
70
|
+
useLayoutEffect(() => {
|
|
71
|
+
activeTarget.current = { wsId, walletId, checkpointId, open };
|
|
72
|
+
}, [wsId, walletId, checkpointId, open]);
|
|
60
73
|
const [categoryId, setCategoryId] = useState(NO_CATEGORY);
|
|
61
74
|
const [description, setDescription] = useState(
|
|
62
75
|
t('reconciliation_description', {
|
|
@@ -129,17 +142,28 @@ export function WalletCheckpointAdjustmentDialog({
|
|
|
129
142
|
[checkedAt]
|
|
130
143
|
);
|
|
131
144
|
const mutation = useMutation({
|
|
132
|
-
mutationFn: () =>
|
|
133
|
-
createWalletCheckpointReconciliation(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
mutationFn: (request: ReconciliationRequest) =>
|
|
146
|
+
createWalletCheckpointReconciliation(
|
|
147
|
+
request.wsId,
|
|
148
|
+
request.walletId,
|
|
149
|
+
request.checkpointId,
|
|
150
|
+
request.payload
|
|
151
|
+
),
|
|
152
|
+
onSuccess: (result, request) => {
|
|
153
|
+
// Invalidate the submitted target even if the dialog has since moved.
|
|
154
|
+
request.onCreated();
|
|
155
|
+
const current = activeTarget.current;
|
|
156
|
+
if (
|
|
157
|
+
!current.open ||
|
|
158
|
+
current.wsId !== request.wsId ||
|
|
159
|
+
current.walletId !== request.walletId ||
|
|
160
|
+
current.checkpointId !== request.checkpointId
|
|
161
|
+
)
|
|
162
|
+
return;
|
|
138
163
|
toast.success(
|
|
139
164
|
result.created ? t('reconciliation_created') : t('reconciliation_clean')
|
|
140
165
|
);
|
|
141
|
-
|
|
142
|
-
onOpenChange(false);
|
|
166
|
+
request.onOpenChange(false);
|
|
143
167
|
},
|
|
144
168
|
onError: (error) => {
|
|
145
169
|
toast.error(
|
|
@@ -208,7 +232,20 @@ export function WalletCheckpointAdjustmentDialog({
|
|
|
208
232
|
</Button>
|
|
209
233
|
<Button
|
|
210
234
|
disabled={mutation.isPending || !categoryInitialized}
|
|
211
|
-
onClick={() =>
|
|
235
|
+
onClick={() =>
|
|
236
|
+
mutation.mutate({
|
|
237
|
+
wsId,
|
|
238
|
+
walletId,
|
|
239
|
+
checkpointId,
|
|
240
|
+
payload: {
|
|
241
|
+
category_id:
|
|
242
|
+
categoryId === NO_CATEGORY ? undefined : categoryId,
|
|
243
|
+
description,
|
|
244
|
+
},
|
|
245
|
+
onCreated,
|
|
246
|
+
onOpenChange,
|
|
247
|
+
})
|
|
248
|
+
}
|
|
212
249
|
>
|
|
213
250
|
{mutation.isPending ? t('creating') : t('reconcile')}
|
|
214
251
|
</Button>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
act,
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
waitFor,
|
|
8
|
+
} from '@testing-library/react';
|
|
3
9
|
import type { ReactElement, ReactNode } from 'react';
|
|
4
10
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
11
|
import { WalletCheckpointAdjustmentDialog } from './wallet-checkpoint-adjustment-dialog';
|
|
@@ -385,6 +391,94 @@ describe('wallet checkpoint UI', () => {
|
|
|
385
391
|
});
|
|
386
392
|
});
|
|
387
393
|
|
|
394
|
+
it('submits the category visible at click time despite a subsequent edit', async () => {
|
|
395
|
+
mocks.defaultReconciliationCategoryId = 'category-reconcile';
|
|
396
|
+
mocks.listTransactionCategories.mockResolvedValue([
|
|
397
|
+
{ id: 'category-reconcile', name: 'Audit' },
|
|
398
|
+
]);
|
|
399
|
+
renderWithQueryClient(
|
|
400
|
+
<WalletCheckpointAdjustmentDialog
|
|
401
|
+
wsId="ws-1"
|
|
402
|
+
walletId="wallet-1"
|
|
403
|
+
checkpointId="checkpoint-1"
|
|
404
|
+
walletName="Cash"
|
|
405
|
+
checkedAt="2026-06-11T10:00:00.000Z"
|
|
406
|
+
currency="USD"
|
|
407
|
+
variance={-12.34}
|
|
408
|
+
open
|
|
409
|
+
onOpenChange={vi.fn()}
|
|
410
|
+
onCreated={vi.fn()}
|
|
411
|
+
/>
|
|
412
|
+
);
|
|
413
|
+
const select = screen.getByRole('combobox');
|
|
414
|
+
await waitFor(() => expect(select).toHaveValue('category-reconcile'));
|
|
415
|
+
const button = screen.getByRole('button', { name: 'reconcile' });
|
|
416
|
+
await act(async () => {
|
|
417
|
+
fireEvent.click(button);
|
|
418
|
+
fireEvent.change(select, { target: { value: 'none' } });
|
|
419
|
+
});
|
|
420
|
+
await waitFor(() =>
|
|
421
|
+
expect(mocks.createWalletCheckpointReconciliation).toHaveBeenCalledWith(
|
|
422
|
+
'ws-1',
|
|
423
|
+
'wallet-1',
|
|
424
|
+
'checkpoint-1',
|
|
425
|
+
expect.objectContaining({ category_id: 'category-reconcile' })
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('keeps a queued reconciliation and completion bound to its original target', async () => {
|
|
431
|
+
const oldCreated = vi.fn();
|
|
432
|
+
const newCreated = vi.fn();
|
|
433
|
+
const oldClose = vi.fn();
|
|
434
|
+
const newClose = vi.fn();
|
|
435
|
+
const client = new QueryClient({
|
|
436
|
+
defaultOptions: { queries: { retry: false } },
|
|
437
|
+
});
|
|
438
|
+
const dialog = (
|
|
439
|
+
wallet: string,
|
|
440
|
+
checkpoint: string,
|
|
441
|
+
created: () => void,
|
|
442
|
+
close: (open: boolean) => void
|
|
443
|
+
) => (
|
|
444
|
+
<QueryClientProvider client={client}>
|
|
445
|
+
<WalletCheckpointAdjustmentDialog
|
|
446
|
+
wsId="ws-1"
|
|
447
|
+
walletId={wallet}
|
|
448
|
+
checkpointId={checkpoint}
|
|
449
|
+
walletName="Cash"
|
|
450
|
+
checkedAt="2026-06-11T10:00:00.000Z"
|
|
451
|
+
currency="USD"
|
|
452
|
+
variance={-12.34}
|
|
453
|
+
open
|
|
454
|
+
onOpenChange={close}
|
|
455
|
+
onCreated={created}
|
|
456
|
+
/>
|
|
457
|
+
</QueryClientProvider>
|
|
458
|
+
);
|
|
459
|
+
const view = render(
|
|
460
|
+
dialog('wallet-1', 'checkpoint-1', oldCreated, oldClose)
|
|
461
|
+
);
|
|
462
|
+
const button = screen.getByRole('button', { name: 'reconcile' });
|
|
463
|
+
await waitFor(() => expect(button).not.toBeDisabled());
|
|
464
|
+
await act(async () => {
|
|
465
|
+
fireEvent.click(button);
|
|
466
|
+
view.rerender(dialog('wallet-2', 'checkpoint-2', newCreated, newClose));
|
|
467
|
+
});
|
|
468
|
+
await waitFor(() =>
|
|
469
|
+
expect(mocks.createWalletCheckpointReconciliation).toHaveBeenCalledWith(
|
|
470
|
+
'ws-1',
|
|
471
|
+
'wallet-1',
|
|
472
|
+
'checkpoint-1',
|
|
473
|
+
expect.any(Object)
|
|
474
|
+
)
|
|
475
|
+
);
|
|
476
|
+
await waitFor(() => expect(oldCreated).toHaveBeenCalledOnce());
|
|
477
|
+
expect(newCreated).not.toHaveBeenCalled();
|
|
478
|
+
expect(oldClose).not.toHaveBeenCalled();
|
|
479
|
+
expect(newClose).not.toHaveBeenCalled();
|
|
480
|
+
});
|
|
481
|
+
|
|
388
482
|
it('submits no reconciliation category after clearing the configured default', async () => {
|
|
389
483
|
mocks.defaultReconciliationCategoryId = 'category-reconcile';
|
|
390
484
|
mocks.listTransactionCategories.mockResolvedValue([
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { Button } from '../../button';
|
|
28
28
|
import { Input } from '../../input';
|
|
29
29
|
import { calendarDraftDate } from './calendar-period';
|
|
30
|
+
import { pastEventTreatment } from './past-event-treatment';
|
|
30
31
|
import { useCalendarSettings } from './settings/settings-context';
|
|
31
32
|
|
|
32
33
|
dayjs.extend(utc);
|
|
@@ -72,6 +73,7 @@ function EventCard({
|
|
|
72
73
|
onOpen: (id: string) => void;
|
|
73
74
|
}) {
|
|
74
75
|
const { settings } = useCalendarSettings();
|
|
76
|
+
const { preservePastEventOpacity } = useCalendar();
|
|
75
77
|
const isAllDay = isAllDayEvent(event);
|
|
76
78
|
|
|
77
79
|
return (
|
|
@@ -80,7 +82,8 @@ function EventCard({
|
|
|
80
82
|
onClick={() => onOpen(event.id)}
|
|
81
83
|
style={calendarEventStyle(event)}
|
|
82
84
|
className={cn(
|
|
83
|
-
'group flex w-full cursor-pointer items-start gap-3 rounded-lg border-l-[3px] px-3 py-2.5 text-left transition-colors hover:ring-1 hover:ring-current'
|
|
85
|
+
'group relative flex w-full cursor-pointer items-start gap-3 rounded-lg border-l-[3px] px-3 py-2.5 text-left transition-colors hover:ring-1 hover:ring-current',
|
|
86
|
+
pastEventTreatment(event, preservePastEventOpacity)
|
|
84
87
|
)}
|
|
85
88
|
>
|
|
86
89
|
{/* Time column */}
|