@tuturuuu/ui 0.25.0 → 0.25.3
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 +26 -0
- package/biome.json +1 -1
- package/package.json +13 -12
- package/src/components/ui/chat/chat-sidebar-items.tsx +25 -19
- package/src/components/ui/chat/chat-utils.test.ts +88 -2
- package/src/components/ui/chat/chat-workspace.tsx +1 -0
- package/src/components/ui/chat/composer-attachment-chip.tsx +128 -0
- package/src/components/ui/chat/external-message-content.test.ts +41 -0
- package/src/components/ui/chat/external-message-content.ts +28 -0
- package/src/components/ui/chat/message-bubble.tsx +4 -2
- package/src/components/ui/chat/message-composer.tsx +121 -33
- package/src/components/ui/chat/message-links.test.tsx +14 -0
- package/src/components/ui/chat/utils.ts +39 -3
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +2 -6
- package/src/components/ui/custom/__tests__/workspace-select-invitations.test.tsx +141 -0
- package/src/components/ui/custom/education/courses/course-row-actions.tsx +1 -1
- package/src/components/ui/custom/education/modules/course-module-row-actions.tsx +1 -1
- package/src/components/ui/custom/notification-popover-client.tsx +97 -57
- package/src/components/ui/custom/structure.test.tsx +55 -1
- package/src/components/ui/custom/structure.tsx +10 -1
- package/src/components/ui/custom/tables/custom-data-table.tsx +3 -2
- package/src/components/ui/custom/tables/data-table-column-header.tsx +4 -3
- package/src/components/ui/custom/tables/data-table-faceted-filter.tsx +4 -3
- package/src/components/ui/custom/tables/data-table-pagination.tsx +6 -6
- package/src/components/ui/custom/tables/data-table-toolbar.tsx +5 -5
- package/src/components/ui/custom/tables/data-table-view-options.tsx +4 -3
- package/src/components/ui/custom/tables/data-table.tsx +69 -30
- package/src/components/ui/custom/workspace-select-icon.tsx +58 -0
- package/src/components/ui/custom/workspace-select-invitations.tsx +202 -0
- package/src/components/ui/custom/workspace-select.tsx +43 -61
- package/src/components/ui/finance/invoices/columns.test.tsx +3 -3
- package/src/components/ui/finance/invoices/columns.tsx +4 -2
- package/src/components/ui/finance/invoices/pending-columns.tsx +1 -1
- package/src/components/ui/finance/invoices/row-actions.tsx +1 -1
- package/src/components/ui/finance/transactions/categories/columns.test.tsx +5 -5
- package/src/components/ui/finance/transactions/categories/columns.tsx +4 -2
- package/src/components/ui/finance/transactions/categories/row-actions.tsx +1 -1
- package/src/components/ui/finance/transactions/columns.test.tsx +5 -5
- package/src/components/ui/finance/transactions/columns.tsx +4 -2
- package/src/components/ui/finance/transactions/row-actions.tsx +1 -1
- package/src/components/ui/finance/wallets/columns.tsx +4 -2
- package/src/components/ui/finance/wallets/row-actions.tsx +1 -1
- package/src/hooks/use-notifications.ts +16 -2
|
@@ -22,6 +22,11 @@ import {
|
|
|
22
22
|
X,
|
|
23
23
|
XCircle,
|
|
24
24
|
} from '@tuturuuu/icons';
|
|
25
|
+
import { updateNotificationMetadata } from '@tuturuuu/internal-api';
|
|
26
|
+
import {
|
|
27
|
+
acceptWorkspaceInvite,
|
|
28
|
+
declineWorkspaceInvite,
|
|
29
|
+
} from '@tuturuuu/internal-api/workspaces';
|
|
25
30
|
import { Button } from '@tuturuuu/ui/button';
|
|
26
31
|
import {
|
|
27
32
|
dedupeNotifications,
|
|
@@ -61,6 +66,11 @@ interface NotificationPopoverClientProps {
|
|
|
61
66
|
archiveAllText?: string;
|
|
62
67
|
emptyArchiveText?: string;
|
|
63
68
|
loadingMoreText?: string;
|
|
69
|
+
retryText?: string;
|
|
70
|
+
acceptText?: string;
|
|
71
|
+
declineText?: string;
|
|
72
|
+
acceptedText?: string;
|
|
73
|
+
declinedText?: string;
|
|
64
74
|
/** Base URL for external redirect (e.g. 'https://tuturuuu.com'). When set, "View All" links to {webAppUrl}/{wsId}/notifications. */
|
|
65
75
|
webAppUrl?: string;
|
|
66
76
|
}
|
|
@@ -93,6 +103,11 @@ export default function NotificationPopoverClient({
|
|
|
93
103
|
archiveAllText = 'Archive all',
|
|
94
104
|
emptyArchiveText = 'No archived notifications yet.',
|
|
95
105
|
loadingMoreText = 'Loading more...',
|
|
106
|
+
retryText = 'Retry',
|
|
107
|
+
acceptText = 'Accept',
|
|
108
|
+
declineText = 'Decline',
|
|
109
|
+
acceptedText = 'Joined',
|
|
110
|
+
declinedText = 'Declined',
|
|
96
111
|
webAppUrl,
|
|
97
112
|
}: NotificationPopoverClientProps) {
|
|
98
113
|
const [open, setOpen] = useState(false);
|
|
@@ -111,11 +126,13 @@ export default function NotificationPopoverClient({
|
|
|
111
126
|
|
|
112
127
|
// Accurate unread count from dedicated endpoint
|
|
113
128
|
const { data: unreadCount = 0 } = useUnreadCount(wsIdForFiltering, {
|
|
129
|
+
cacheScope: userId,
|
|
114
130
|
enabled: Boolean(userId),
|
|
115
131
|
});
|
|
116
132
|
|
|
117
133
|
// Infinite scroll for inbox (unread) and archive (read)
|
|
118
134
|
const inboxQuery = useInfiniteNotifications({
|
|
135
|
+
cacheScope: userId,
|
|
119
136
|
wsId: wsIdForFiltering,
|
|
120
137
|
unreadOnly: true,
|
|
121
138
|
pageSize: 15,
|
|
@@ -123,6 +140,7 @@ export default function NotificationPopoverClient({
|
|
|
123
140
|
});
|
|
124
141
|
|
|
125
142
|
const archiveQuery = useInfiniteNotifications({
|
|
143
|
+
cacheScope: userId,
|
|
126
144
|
wsId: wsIdForFiltering,
|
|
127
145
|
readOnly: true,
|
|
128
146
|
pageSize: 15,
|
|
@@ -176,7 +194,9 @@ export default function NotificationPopoverClient({
|
|
|
176
194
|
<Button
|
|
177
195
|
variant="ghost"
|
|
178
196
|
size="icon"
|
|
179
|
-
|
|
197
|
+
aria-label={notificationsText}
|
|
198
|
+
title={notificationsText}
|
|
199
|
+
className="group relative flex size-10 flex-none transition-all"
|
|
180
200
|
>
|
|
181
201
|
<Bell className="h-6 w-6" />
|
|
182
202
|
{unreadCount > 0 && (
|
|
@@ -261,6 +281,11 @@ export default function NotificationPopoverClient({
|
|
|
261
281
|
noNotificationsText={noNotificationsText}
|
|
262
282
|
emptyArchiveText={emptyArchiveText}
|
|
263
283
|
loadingMoreText={loadingMoreText}
|
|
284
|
+
retryText={retryText}
|
|
285
|
+
acceptText={acceptText}
|
|
286
|
+
declineText={declineText}
|
|
287
|
+
acceptedText={acceptedText}
|
|
288
|
+
declinedText={declinedText}
|
|
264
289
|
markAsReadText={markAsReadText}
|
|
265
290
|
markAsUnreadText={markAsUnreadText}
|
|
266
291
|
onMarkAsRead={handleMarkAsRead}
|
|
@@ -293,6 +318,11 @@ function NotificationList({
|
|
|
293
318
|
noNotificationsText,
|
|
294
319
|
emptyArchiveText,
|
|
295
320
|
loadingMoreText,
|
|
321
|
+
retryText,
|
|
322
|
+
acceptText,
|
|
323
|
+
declineText,
|
|
324
|
+
acceptedText,
|
|
325
|
+
declinedText,
|
|
296
326
|
markAsReadText,
|
|
297
327
|
markAsUnreadText,
|
|
298
328
|
onMarkAsRead,
|
|
@@ -307,6 +337,11 @@ function NotificationList({
|
|
|
307
337
|
noNotificationsText: string;
|
|
308
338
|
emptyArchiveText: string;
|
|
309
339
|
loadingMoreText: string;
|
|
340
|
+
retryText: string;
|
|
341
|
+
acceptText: string;
|
|
342
|
+
declineText: string;
|
|
343
|
+
acceptedText: string;
|
|
344
|
+
declinedText: string;
|
|
310
345
|
markAsReadText: string;
|
|
311
346
|
markAsUnreadText: string;
|
|
312
347
|
onMarkAsRead: (id: string, isUnread: boolean) => void;
|
|
@@ -357,6 +392,21 @@ function NotificationList({
|
|
|
357
392
|
<p className="mt-1 text-foreground/40 text-xs">
|
|
358
393
|
{query.error instanceof Error ? query.error.message : 'Unknown error'}
|
|
359
394
|
</p>
|
|
395
|
+
<Button
|
|
396
|
+
className="mt-3"
|
|
397
|
+
disabled={query.isFetching}
|
|
398
|
+
onClick={() => query.refetch()}
|
|
399
|
+
size="sm"
|
|
400
|
+
type="button"
|
|
401
|
+
variant="outline"
|
|
402
|
+
>
|
|
403
|
+
{query.isFetching ? (
|
|
404
|
+
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
|
405
|
+
) : (
|
|
406
|
+
<RotateCcw className="mr-1.5 h-3.5 w-3.5" />
|
|
407
|
+
)}
|
|
408
|
+
{retryText}
|
|
409
|
+
</Button>
|
|
360
410
|
</div>
|
|
361
411
|
);
|
|
362
412
|
}
|
|
@@ -406,6 +456,10 @@ function NotificationList({
|
|
|
406
456
|
markAsUnreadText={markAsUnreadText}
|
|
407
457
|
queryClient={queryClient}
|
|
408
458
|
onActionComplete={onActionComplete}
|
|
459
|
+
acceptText={acceptText}
|
|
460
|
+
declineText={declineText}
|
|
461
|
+
acceptedText={acceptedText}
|
|
462
|
+
declinedText={declinedText}
|
|
409
463
|
/>
|
|
410
464
|
))}
|
|
411
465
|
|
|
@@ -431,6 +485,10 @@ interface NotificationCardProps {
|
|
|
431
485
|
markAsUnreadText: string;
|
|
432
486
|
queryClient: any;
|
|
433
487
|
onActionComplete?: () => void;
|
|
488
|
+
acceptText: string;
|
|
489
|
+
declineText: string;
|
|
490
|
+
acceptedText: string;
|
|
491
|
+
declinedText: string;
|
|
434
492
|
}
|
|
435
493
|
|
|
436
494
|
function getWorkspaceInviteWorkspaceId(notification: Notification) {
|
|
@@ -456,6 +514,10 @@ function NotificationCard({
|
|
|
456
514
|
markAsUnreadText,
|
|
457
515
|
queryClient,
|
|
458
516
|
onActionComplete,
|
|
517
|
+
acceptText,
|
|
518
|
+
declineText,
|
|
519
|
+
acceptedText,
|
|
520
|
+
declinedText,
|
|
459
521
|
}: NotificationCardProps) {
|
|
460
522
|
const isUnread = !notification.read_at;
|
|
461
523
|
const [processingAction, setProcessingAction] = useState<string | null>(null);
|
|
@@ -477,58 +539,34 @@ function NotificationCard({
|
|
|
477
539
|
break;
|
|
478
540
|
}
|
|
479
541
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}),
|
|
509
|
-
queryClient.refetchQueries({
|
|
510
|
-
queryKey: ['notifications'],
|
|
511
|
-
type: 'active',
|
|
512
|
-
}),
|
|
513
|
-
]);
|
|
514
|
-
|
|
515
|
-
toast.success(
|
|
516
|
-
accept
|
|
517
|
-
? 'Workspace invite accepted'
|
|
518
|
-
: 'Workspace invite declined'
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
onMarkAsRead(notification.id, true);
|
|
522
|
-
router.refresh();
|
|
523
|
-
onActionComplete?.();
|
|
524
|
-
} else {
|
|
525
|
-
toast.error('Failed to update notification');
|
|
526
|
-
}
|
|
527
|
-
} else {
|
|
528
|
-
const errorData = await res.json();
|
|
529
|
-
console.error('Failed to process invite:', errorData);
|
|
530
|
-
toast.error(errorData.error || 'Failed to process invite');
|
|
531
|
-
}
|
|
542
|
+
await (accept
|
|
543
|
+
? acceptWorkspaceInvite(targetWsId)
|
|
544
|
+
: declineWorkspaceInvite(targetWsId));
|
|
545
|
+
await updateNotificationMetadata(notification.id, {
|
|
546
|
+
action_taken: accept ? 'accepted' : 'declined',
|
|
547
|
+
action_timestamp: new Date().toISOString(),
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
await Promise.all([
|
|
551
|
+
queryClient.invalidateQueries({
|
|
552
|
+
queryKey: ['workspaces'],
|
|
553
|
+
refetchType: 'active',
|
|
554
|
+
}),
|
|
555
|
+
queryClient.invalidateQueries({
|
|
556
|
+
queryKey: ['notifications'],
|
|
557
|
+
refetchType: 'active',
|
|
558
|
+
}),
|
|
559
|
+
queryClient.refetchQueries({
|
|
560
|
+
queryKey: ['notifications'],
|
|
561
|
+
type: 'active',
|
|
562
|
+
}),
|
|
563
|
+
]);
|
|
564
|
+
|
|
565
|
+
toast.success(accept ? acceptedText : declinedText);
|
|
566
|
+
|
|
567
|
+
onMarkAsRead(notification.id, true);
|
|
568
|
+
router.refresh();
|
|
569
|
+
onActionComplete?.();
|
|
532
570
|
break;
|
|
533
571
|
}
|
|
534
572
|
default:
|
|
@@ -597,13 +635,15 @@ function NotificationCard({
|
|
|
597
635
|
{notification.data.action_taken === 'accepted' ? (
|
|
598
636
|
<>
|
|
599
637
|
<CheckCircle2 className="h-3 w-3 text-dynamic-green" />
|
|
600
|
-
<span className="font-medium text-dynamic-green">
|
|
638
|
+
<span className="font-medium text-dynamic-green">
|
|
639
|
+
{acceptedText}
|
|
640
|
+
</span>
|
|
601
641
|
</>
|
|
602
642
|
) : (
|
|
603
643
|
<>
|
|
604
644
|
<XCircle className="h-3 w-3 text-foreground/40" />
|
|
605
645
|
<span className="font-medium text-foreground/60">
|
|
606
|
-
|
|
646
|
+
{declinedText}
|
|
607
647
|
</span>
|
|
608
648
|
</>
|
|
609
649
|
)}
|
|
@@ -627,7 +667,7 @@ function NotificationCard({
|
|
|
627
667
|
) : (
|
|
628
668
|
<X className="h-3 w-3" />
|
|
629
669
|
)}
|
|
630
|
-
|
|
670
|
+
{declineText}
|
|
631
671
|
</Button>
|
|
632
672
|
<Button
|
|
633
673
|
size="sm"
|
|
@@ -644,7 +684,7 @@ function NotificationCard({
|
|
|
644
684
|
) : (
|
|
645
685
|
<Check className="h-3 w-3" />
|
|
646
686
|
)}
|
|
647
|
-
|
|
687
|
+
{acceptText}
|
|
648
688
|
</Button>
|
|
649
689
|
</div>
|
|
650
690
|
) : isTaskEntityNotification ? (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
3
|
import { describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { Structure } from './structure';
|
|
5
5
|
|
|
@@ -30,4 +30,58 @@ describe('Structure', () => {
|
|
|
30
30
|
'md:p-4'
|
|
31
31
|
);
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
it('keeps the account and notification controls in the collapsed footer', () => {
|
|
35
|
+
const { container } = render(
|
|
36
|
+
<Structure
|
|
37
|
+
actions={<span>Expanded account actions</span>}
|
|
38
|
+
isCollapsed
|
|
39
|
+
notificationPopover={<button type="button">Notifications</button>}
|
|
40
|
+
setIsCollapsed={vi.fn()}
|
|
41
|
+
userPopover={<button type="button">Account</button>}
|
|
42
|
+
>
|
|
43
|
+
<span>Page content</span>
|
|
44
|
+
</Structure>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const sidebar = container.querySelector('aside');
|
|
48
|
+
expect(sidebar).not.toBeNull();
|
|
49
|
+
const sidebarQueries = within(sidebar as HTMLElement);
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
sidebarQueries.getByRole('button', { name: 'Account' })
|
|
53
|
+
).toBeVisible();
|
|
54
|
+
expect(
|
|
55
|
+
sidebarQueries.getByRole('button', { name: 'Notifications' })
|
|
56
|
+
).toBeVisible();
|
|
57
|
+
expect(
|
|
58
|
+
sidebarQueries.queryByText('Expanded account actions')
|
|
59
|
+
).not.toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('renders the combined account actions only once when expanded', () => {
|
|
63
|
+
const { container } = render(
|
|
64
|
+
<Structure
|
|
65
|
+
actions={<span>Expanded account actions</span>}
|
|
66
|
+
isCollapsed={false}
|
|
67
|
+
notificationPopover={<button type="button">Notifications</button>}
|
|
68
|
+
setIsCollapsed={vi.fn()}
|
|
69
|
+
userPopover={<button type="button">Account</button>}
|
|
70
|
+
>
|
|
71
|
+
<span>Page content</span>
|
|
72
|
+
</Structure>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const sidebar = container.querySelector('aside');
|
|
76
|
+
expect(sidebar).not.toBeNull();
|
|
77
|
+
const sidebarQueries = within(sidebar as HTMLElement);
|
|
78
|
+
|
|
79
|
+
expect(sidebarQueries.getByText('Expanded account actions')).toBeVisible();
|
|
80
|
+
expect(
|
|
81
|
+
sidebarQueries.queryByRole('button', { name: 'Notifications' })
|
|
82
|
+
).not.toBeInTheDocument();
|
|
83
|
+
expect(
|
|
84
|
+
sidebarQueries.queryByRole('button', { name: 'Account' })
|
|
85
|
+
).not.toBeInTheDocument();
|
|
86
|
+
});
|
|
33
87
|
});
|
|
@@ -17,6 +17,7 @@ interface StructureProps {
|
|
|
17
17
|
sidebarContent?: ReactNode;
|
|
18
18
|
actions?: ReactNode;
|
|
19
19
|
userPopover?: ReactNode;
|
|
20
|
+
notificationPopover?: ReactNode;
|
|
20
21
|
sidebarUtility?: ReactNode;
|
|
21
22
|
feedbackButton?: ReactNode;
|
|
22
23
|
children: ReactNode;
|
|
@@ -40,6 +41,7 @@ export function Structure({
|
|
|
40
41
|
sidebarContent,
|
|
41
42
|
actions,
|
|
42
43
|
userPopover,
|
|
44
|
+
notificationPopover,
|
|
43
45
|
sidebarUtility,
|
|
44
46
|
feedbackButton,
|
|
45
47
|
children,
|
|
@@ -191,7 +193,14 @@ export function Structure({
|
|
|
191
193
|
isCollapsed ? 'justify-center' : ''
|
|
192
194
|
)}
|
|
193
195
|
>
|
|
194
|
-
{isCollapsed ?
|
|
196
|
+
{isCollapsed ? (
|
|
197
|
+
<div className="flex w-full flex-col items-center gap-1">
|
|
198
|
+
{userPopover}
|
|
199
|
+
{notificationPopover}
|
|
200
|
+
</div>
|
|
201
|
+
) : (
|
|
202
|
+
actions
|
|
203
|
+
)}
|
|
195
204
|
</div>
|
|
196
205
|
|
|
197
206
|
{!hideSizeToggle && (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { RowData } from '@tanstack/react-table';
|
|
3
4
|
import {
|
|
4
5
|
DataTable,
|
|
5
6
|
type DataTableProps,
|
|
@@ -8,7 +9,7 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
|
8
9
|
import { useTranslations } from 'next-intl';
|
|
9
10
|
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
|
10
11
|
|
|
11
|
-
function CustomDataTableInner<TData, TValue>({
|
|
12
|
+
function CustomDataTableInner<TData extends RowData, TValue>({
|
|
12
13
|
namespace,
|
|
13
14
|
hideToolbar,
|
|
14
15
|
hidePagination,
|
|
@@ -119,7 +120,7 @@ function CustomDataTableInner<TData, TValue>({
|
|
|
119
120
|
);
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
export function CustomDataTable<TData, TValue>(
|
|
123
|
+
export function CustomDataTable<TData extends RowData, TValue>(
|
|
123
124
|
props: DataTableProps<TData, TValue>
|
|
124
125
|
) {
|
|
125
126
|
return (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RowData } from '@tanstack/react-table';
|
|
2
2
|
import { ArrowDown, ArrowUp, ChevronDown, EyeOff } from '@tuturuuu/icons';
|
|
3
3
|
import { cn } from '@tuturuuu/utils/format';
|
|
4
4
|
import type React from 'react';
|
|
@@ -10,15 +10,16 @@ import {
|
|
|
10
10
|
DropdownMenuSeparator,
|
|
11
11
|
DropdownMenuTrigger,
|
|
12
12
|
} from '../../dropdown-menu';
|
|
13
|
+
import type { Column } from './data-table';
|
|
13
14
|
|
|
14
|
-
interface DataTableColumnHeaderProps<TData, TValue>
|
|
15
|
+
interface DataTableColumnHeaderProps<TData extends RowData, TValue>
|
|
15
16
|
extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
17
|
t: any;
|
|
17
18
|
column: Column<TData, TValue>;
|
|
18
19
|
title?: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function DataTableColumnHeader<TData, TValue>({
|
|
22
|
+
export function DataTableColumnHeader<TData extends RowData, TValue>({
|
|
22
23
|
t,
|
|
23
24
|
column,
|
|
24
25
|
title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RowData } from '@tanstack/react-table';
|
|
2
2
|
import { Check, PlusCircle } from '@tuturuuu/icons';
|
|
3
3
|
import { cn } from '@tuturuuu/utils/format';
|
|
4
4
|
import type * as React from 'react';
|
|
@@ -15,8 +15,9 @@ import {
|
|
|
15
15
|
} from '../../command';
|
|
16
16
|
import { Popover, PopoverContent, PopoverTrigger } from '../../popover';
|
|
17
17
|
import { Separator } from '../../separator';
|
|
18
|
+
import type { Column } from './data-table';
|
|
18
19
|
|
|
19
|
-
interface DataTableFacetedFilterProps<TData, TValue> {
|
|
20
|
+
interface DataTableFacetedFilterProps<TData extends RowData, TValue> {
|
|
20
21
|
column?: Column<TData, TValue>;
|
|
21
22
|
title?: string;
|
|
22
23
|
options: {
|
|
@@ -26,7 +27,7 @@ interface DataTableFacetedFilterProps<TData, TValue> {
|
|
|
26
27
|
}[];
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
export function DataTableFacetedFilter<TData, TValue>({
|
|
30
|
+
export function DataTableFacetedFilter<TData extends RowData, TValue>({
|
|
30
31
|
column,
|
|
31
32
|
title,
|
|
32
33
|
options,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { RowData } from '@tanstack/react-table';
|
|
4
4
|
import {
|
|
5
5
|
ArrowLeftToLine,
|
|
6
6
|
ArrowRightToLine,
|
|
@@ -17,8 +17,9 @@ import {
|
|
|
17
17
|
SelectValue,
|
|
18
18
|
} from '../../select';
|
|
19
19
|
import { Separator } from '../../separator';
|
|
20
|
+
import type { Table } from './data-table';
|
|
20
21
|
|
|
21
|
-
interface DataTablePaginationProps<TData> {
|
|
22
|
+
interface DataTablePaginationProps<TData extends RowData> {
|
|
22
23
|
table?: Table<TData>;
|
|
23
24
|
count?: number | null;
|
|
24
25
|
className?: string;
|
|
@@ -31,7 +32,7 @@ interface DataTablePaginationProps<TData> {
|
|
|
31
32
|
setParams?: (params: { page?: number; pageSize?: string }) => void;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export function DataTablePagination<TData>({
|
|
35
|
+
export function DataTablePagination<TData extends RowData>({
|
|
35
36
|
table,
|
|
36
37
|
count,
|
|
37
38
|
className,
|
|
@@ -46,9 +47,8 @@ export function DataTablePagination<TData>({
|
|
|
46
47
|
// When setParams is provided, we're in server-side pagination mode
|
|
47
48
|
const isServerSide = !!setParams;
|
|
48
49
|
|
|
49
|
-
const pageIndex =
|
|
50
|
-
|
|
51
|
-
const pageSize = pageSizeProp ?? table?.getState().pagination.pageSize ?? 10;
|
|
50
|
+
const pageIndex = pageIndexProp ?? table?.state.pagination.pageIndex ?? 0;
|
|
51
|
+
const pageSize = pageSizeProp ?? table?.state.pagination.pageSize ?? 10;
|
|
52
52
|
const pageCount = pageCountProp ?? table?.getPageCount() ?? 0;
|
|
53
53
|
|
|
54
54
|
// filter duplicate and sort sizes
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { RowData } from '@tanstack/react-table';
|
|
4
4
|
import { Download, RotateCcw, Upload } from '@tuturuuu/icons';
|
|
5
5
|
import { Dialog, DialogContent, DialogTrigger } from '@tuturuuu/ui/dialog';
|
|
6
6
|
import type { ReactNode } from 'react';
|
|
7
7
|
import { Button } from '../../button';
|
|
8
8
|
import SearchBar from '../search-bar';
|
|
9
|
+
import type { Table } from './data-table';
|
|
9
10
|
import { DataTableCreateButton } from './data-table-create-button';
|
|
10
11
|
import { DataTableRefreshButton } from './data-table-refresh-button';
|
|
11
12
|
import { DataTableViewOptions } from './data-table-view-options';
|
|
@@ -19,7 +20,7 @@ type DataTableTranslator = ((key: string) => string) & {
|
|
|
19
20
|
has?: (key: string) => boolean;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
interface DataTableToolbarProps<TData> {
|
|
23
|
+
interface DataTableToolbarProps<TData extends RowData> {
|
|
23
24
|
hasData: boolean;
|
|
24
25
|
newObjectTitle?: string;
|
|
25
26
|
editContent?: ReactNode;
|
|
@@ -42,7 +43,7 @@ interface DataTableToolbarProps<TData> {
|
|
|
42
43
|
resetParams: () => void;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
export function DataTableToolbar<TData>({
|
|
46
|
+
export function DataTableToolbar<TData extends RowData>({
|
|
46
47
|
hasData,
|
|
47
48
|
newObjectTitle,
|
|
48
49
|
editContent,
|
|
@@ -65,8 +66,7 @@ export function DataTableToolbar<TData>({
|
|
|
65
66
|
const isFiltered =
|
|
66
67
|
isFilteredProp !== undefined
|
|
67
68
|
? isFilteredProp
|
|
68
|
-
: table.
|
|
69
|
-
(defaultQuery?.length || 0) > 0;
|
|
69
|
+
: table.state.columnFilters.length > 0 || (defaultQuery?.length || 0) > 0;
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
72
|
<div className="flex flex-col items-start justify-between gap-2 lg:flex-row">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
|
|
4
|
-
import type {
|
|
4
|
+
import type { RowData } from '@tanstack/react-table';
|
|
5
5
|
import { Settings2, UserCog } from '@tuturuuu/icons';
|
|
6
6
|
import { Fragment } from 'react';
|
|
7
7
|
import { Button } from '../../button';
|
|
@@ -13,15 +13,16 @@ import {
|
|
|
13
13
|
DropdownMenuSeparator,
|
|
14
14
|
} from '../../dropdown-menu';
|
|
15
15
|
import { ScrollArea } from '../../scroll-area';
|
|
16
|
+
import type { Table } from './data-table';
|
|
16
17
|
|
|
17
|
-
interface DataTableViewOptionsProps<TData> {
|
|
18
|
+
interface DataTableViewOptionsProps<TData extends RowData> {
|
|
18
19
|
table: Table<TData>;
|
|
19
20
|
extraColumns?: any[];
|
|
20
21
|
namespace: string | undefined;
|
|
21
22
|
t?: any;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export function DataTableViewOptions<TData>({
|
|
25
|
+
export function DataTableViewOptions<TData extends RowData>({
|
|
25
26
|
t,
|
|
26
27
|
namespace,
|
|
27
28
|
table,
|