@vendure/dashboard 3.5.0-minor-202510071456 → 3.5.0-minor-202510201346
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/dist/plugin/dashboard.plugin.js +1 -1
- package/dist/vite/utils/ast-utils.spec.js +3 -3
- package/dist/vite/vite-plugin-hmr.d.ts +8 -0
- package/dist/vite/vite-plugin-hmr.js +34 -0
- package/dist/vite/vite-plugin-theme.js +6 -6
- package/dist/vite/vite-plugin-transform-index.js +6 -1
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
- package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
- package/package.json +17 -5
- package/src/app/app-providers.tsx +4 -1
- package/src/app/common/map-faceted-filter-fields.ts +21 -0
- package/src/app/main.tsx +3 -1
- package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
- package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
- package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
- package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
- package/src/app/routes/_authenticated/_orders/orders.tsx +2 -0
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +2 -0
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_products/products.tsx +6 -2
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
- package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
- package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
- package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
- package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
- package/src/app/routes/login.tsx +2 -2
- package/src/i18n/locales/ar.po +420 -289
- package/src/i18n/locales/cs.po +420 -289
- package/src/i18n/locales/de.po +420 -289
- package/src/i18n/locales/en.po +420 -289
- package/src/i18n/locales/es.po +420 -289
- package/src/i18n/locales/fa.po +420 -289
- package/src/i18n/locales/fr.po +468 -337
- package/src/i18n/locales/he.po +420 -289
- package/src/i18n/locales/hr.po +420 -289
- package/src/i18n/locales/it.po +420 -289
- package/src/i18n/locales/ja.po +420 -289
- package/src/i18n/locales/nb.po +420 -289
- package/src/i18n/locales/ne.po +420 -289
- package/src/i18n/locales/pl.po +420 -289
- package/src/i18n/locales/pt_BR.po +420 -289
- package/src/i18n/locales/pt_PT.po +420 -289
- package/src/i18n/locales/ru.po +420 -289
- package/src/i18n/locales/sv.po +420 -289
- package/src/i18n/locales/tr.po +420 -289
- package/src/i18n/locales/uk.po +420 -289
- package/src/i18n/locales/zh_Hans.po +420 -289
- package/src/i18n/locales/zh_Hant.po +420 -289
- package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
- package/src/lib/components/data-input/affixed-input.tsx +5 -2
- package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
- package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
- package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
- package/src/lib/components/data-input/datetime-input.tsx +27 -13
- package/src/lib/components/data-input/default-relation-input.tsx +18 -12
- package/src/lib/components/data-input/money-input.stories.tsx +88 -0
- package/src/lib/components/data-input/number-input.stories.tsx +103 -0
- package/src/lib/components/data-input/number-input.tsx +10 -4
- package/src/lib/components/data-input/password-form-input.stories.tsx +65 -0
- package/src/lib/components/data-input/{password-input.tsx → password-form-input.tsx} +1 -1
- package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
- package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
- package/src/lib/components/data-input/slug-input.tsx +9 -10
- package/src/lib/components/data-input/text-input.stories.tsx +52 -0
- package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
- package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
- package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
- package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
- package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
- package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
- package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
- package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
- package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
- package/src/lib/components/data-table/data-table.stories.tsx +249 -0
- package/src/lib/components/data-table/data-table.tsx +37 -9
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
- package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
- package/src/lib/components/layout/nav-user.tsx +19 -13
- package/src/lib/components/login/login-form.tsx +39 -123
- package/src/lib/components/shared/alerts.tsx +29 -17
- package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
- package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
- package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
- package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
- package/src/lib/components/shared/customer-group-selector.tsx +5 -2
- package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
- package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
- package/src/lib/components/shared/facet-value-selector.tsx +130 -34
- package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
- package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
- package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
- package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
- package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
- package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
- package/src/lib/components/shared/vendure-image.tsx +6 -7
- package/src/lib/components/ui/accordion.stories.tsx +33 -0
- package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
- package/src/lib/components/ui/alert.stories.tsx +35 -0
- package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
- package/src/lib/components/ui/badge.stories.tsx +28 -0
- package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
- package/src/lib/components/ui/button.stories.tsx +38 -0
- package/src/lib/components/ui/calendar.stories.tsx +22 -0
- package/src/lib/components/ui/card.stories.tsx +28 -0
- package/src/lib/components/ui/carousel.stories.tsx +34 -0
- package/src/lib/components/ui/checkbox.stories.tsx +31 -0
- package/src/lib/components/ui/collapsible.stories.tsx +39 -0
- package/src/lib/components/ui/command.stories.tsx +44 -0
- package/src/lib/components/ui/context-menu.stories.tsx +38 -0
- package/src/lib/components/ui/dialog.stories.tsx +52 -0
- package/src/lib/components/ui/drawer.stories.tsx +50 -0
- package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
- package/src/lib/components/ui/hover-card.stories.tsx +38 -0
- package/src/lib/components/ui/input-group.tsx +148 -0
- package/src/lib/components/ui/input-otp.stories.tsx +30 -0
- package/src/lib/components/ui/input.stories.tsx +38 -0
- package/src/lib/components/ui/label.stories.tsx +24 -0
- package/src/lib/components/ui/menubar.stories.tsx +53 -0
- package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
- package/src/lib/components/ui/pagination.stories.tsx +51 -0
- package/src/lib/components/ui/password-input.stories.tsx +32 -0
- package/src/lib/components/ui/password-input.tsx +29 -0
- package/src/lib/components/ui/popover.stories.tsx +33 -0
- package/src/lib/components/ui/progress.stories.tsx +27 -0
- package/src/lib/components/ui/radio-group.stories.tsx +34 -0
- package/src/lib/components/ui/resizable.stories.tsx +32 -0
- package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
- package/src/lib/components/ui/select.stories.tsx +36 -0
- package/src/lib/components/ui/separator.stories.tsx +35 -0
- package/src/lib/components/ui/sheet.stories.tsx +50 -0
- package/src/lib/components/ui/sidebar-context.ts +16 -0
- package/src/lib/components/ui/sidebar.tsx +2 -13
- package/src/lib/components/ui/skeleton.stories.tsx +26 -0
- package/src/lib/components/ui/slider.stories.tsx +37 -0
- package/src/lib/components/ui/switch.stories.tsx +31 -0
- package/src/lib/components/ui/table.stories.tsx +52 -0
- package/src/lib/components/ui/tabs.stories.tsx +29 -0
- package/src/lib/components/ui/textarea.stories.tsx +32 -0
- package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
- package/src/lib/components/ui/toggle.stories.tsx +39 -0
- package/src/lib/components/ui/tooltip.stories.tsx +30 -0
- package/src/lib/components/ui/tooltip.tsx +2 -2
- package/src/lib/framework/alert/alert-extensions.tsx +0 -11
- package/src/lib/framework/alert/alert-item.tsx +14 -19
- package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
- package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
- package/src/lib/framework/component-registry/component-registry.tsx +3 -14
- package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
- package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
- package/src/lib/framework/defaults.ts +9 -13
- package/src/lib/framework/extension-api/input-component-extensions.tsx +8 -3
- package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
- package/src/lib/framework/extension-api/types/alerts.ts +12 -6
- package/src/lib/framework/extension-api/types/data-table.ts +5 -2
- package/src/lib/framework/extension-api/types/login.ts +0 -21
- package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
- package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
- package/src/lib/framework/page/detail-page.stories.tsx +151 -0
- package/src/lib/framework/page/list-page.stories.tsx +217 -0
- package/src/lib/framework/page/list-page.tsx +8 -1
- package/src/lib/graphql/api.ts +18 -1
- package/src/lib/graphql/graphql-env.d.ts +1 -1
- package/src/lib/hooks/use-alerts.ts +84 -0
- package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
- package/src/lib/index.ts +14 -1
- package/src/lib/providers/alerts-provider.tsx +60 -0
- package/src/lib/providers/theme-provider.tsx +6 -3
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { CopyableText } from '@/vdb/components/shared/copyable-text.js';
|
|
2
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
|
|
3
|
+
import { usePageBlock } from '@/vdb/hooks/use-page-block.js';
|
|
4
|
+
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
5
|
+
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
6
|
+
import { cn } from '@/vdb/lib/utils.js';
|
|
7
|
+
import React, { useEffect, useState } from 'react';
|
|
8
|
+
import { DevModeButton } from '../../framework/layout-engine/dev-mode-button.js';
|
|
9
|
+
|
|
10
|
+
// Singleton state for hover tracking
|
|
11
|
+
let globalHoveredColumnId: string | null = null;
|
|
12
|
+
const columnHoverListeners: Set<(id: string | null) => void> = new Set();
|
|
13
|
+
|
|
14
|
+
const setGlobalHoveredColumnId = (id: string | null) => {
|
|
15
|
+
globalHoveredColumnId = id;
|
|
16
|
+
columnHoverListeners.forEach(listener => listener(id));
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface ColumnHeaderWrapperProps {
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
columnId: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ColumnHeaderWrapper({ children, columnId }: Readonly<ColumnHeaderWrapperProps>) {
|
|
25
|
+
const { settings } = useUserSettings();
|
|
26
|
+
const page = usePage();
|
|
27
|
+
const pageBlock = usePageBlock({ optional: true });
|
|
28
|
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
29
|
+
const [hoveredId, setHoveredId] = useState<string | null>(globalHoveredColumnId);
|
|
30
|
+
const blockId = pageBlock?.blockId ?? null;
|
|
31
|
+
const pageId = page.pageId;
|
|
32
|
+
const isHovered = hoveredId === columnId;
|
|
33
|
+
|
|
34
|
+
// Subscribe to global hover changes
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const listener = (newHoveredId: string | null) => {
|
|
37
|
+
setHoveredId(newHoveredId);
|
|
38
|
+
};
|
|
39
|
+
columnHoverListeners.add(listener);
|
|
40
|
+
return () => {
|
|
41
|
+
columnHoverListeners.delete(listener);
|
|
42
|
+
};
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const setHoverId = (id: string | null) => {
|
|
46
|
+
setGlobalHoveredColumnId(id);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const handleMouseEnter = () => {
|
|
50
|
+
setHoverId(columnId);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleMouseLeave = () => {
|
|
54
|
+
setHoverId(null);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (settings.devMode) {
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
className={cn(
|
|
61
|
+
'ring-2 ring-transparent rounded-md transition-all delay-50 relative min-h-8 flex flex-col justify-center',
|
|
62
|
+
isHovered || isPopoverOpen ? 'ring-dev-mode ring-offset-1 ring-offset-background' : '',
|
|
63
|
+
)}
|
|
64
|
+
onMouseEnter={handleMouseEnter}
|
|
65
|
+
onMouseLeave={handleMouseLeave}
|
|
66
|
+
>
|
|
67
|
+
<div
|
|
68
|
+
className={cn(
|
|
69
|
+
`absolute right-0 top-0 transition-all delay-50 z-10`,
|
|
70
|
+
isHovered || isPopoverOpen ? 'visible' : 'invisible',
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
|
74
|
+
<PopoverTrigger asChild>
|
|
75
|
+
<DevModeButton className={`h-5 w-5`} />
|
|
76
|
+
</PopoverTrigger>
|
|
77
|
+
<PopoverContent className="w-48 p-3">
|
|
78
|
+
<div className="space-y-2">
|
|
79
|
+
<div className="space-y-1">
|
|
80
|
+
{pageId && (
|
|
81
|
+
<div className="text-xs">
|
|
82
|
+
<div className="text-muted-foreground mb-0.5">pageId</div>
|
|
83
|
+
<CopyableText text={pageId} />
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
{blockId && (
|
|
87
|
+
<div className="text-xs">
|
|
88
|
+
<div className="text-muted-foreground mb-0.5">blockId</div>
|
|
89
|
+
<CopyableText text={blockId} />
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
<div className="text-xs">
|
|
93
|
+
<div className="text-muted-foreground mb-0.5">column</div>
|
|
94
|
+
<CopyableText text={columnId} />
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</PopoverContent>
|
|
99
|
+
</Popover>
|
|
100
|
+
</div>
|
|
101
|
+
{children}
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return children;
|
|
106
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { usePermissions } from '@/vdb/hooks/use-permissions.js';
|
|
2
|
-
import { Trans } from '@lingui/react/macro';
|
|
3
2
|
import { cn } from '@/vdb/lib/utils.js';
|
|
3
|
+
import { Trans } from '@lingui/react/macro';
|
|
4
4
|
import { LucideIcon } from 'lucide-react';
|
|
5
5
|
import { useState } from 'react';
|
|
6
6
|
import {
|
|
@@ -30,6 +30,7 @@ export interface DataTableBulkActionItemProps {
|
|
|
30
30
|
onClick: () => void;
|
|
31
31
|
className?: string;
|
|
32
32
|
requiresPermission?: string[];
|
|
33
|
+
disabled?: boolean;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -63,13 +64,14 @@ export interface DataTableBulkActionItemProps {
|
|
|
63
64
|
* @since 3.4.0
|
|
64
65
|
*/
|
|
65
66
|
export function DataTableBulkActionItem({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
label,
|
|
68
|
+
icon: Icon,
|
|
69
|
+
confirmationText,
|
|
70
|
+
className,
|
|
71
|
+
onClick,
|
|
72
|
+
requiresPermission,
|
|
73
|
+
disabled,
|
|
74
|
+
}: Readonly<DataTableBulkActionItemProps>) {
|
|
73
75
|
const [isOpen, setIsOpen] = useState(false);
|
|
74
76
|
const { hasPermissions } = usePermissions();
|
|
75
77
|
const userHasPermission = hasPermissions(requiresPermission ?? []);
|
|
@@ -100,7 +102,7 @@ export function DataTableBulkActionItem({
|
|
|
100
102
|
return (
|
|
101
103
|
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
|
102
104
|
<AlertDialogTrigger asChild>
|
|
103
|
-
<DropdownMenuItem onClick={handleClick} disabled={!userHasPermission}>
|
|
105
|
+
<DropdownMenuItem onClick={handleClick} disabled={!userHasPermission || disabled}>
|
|
104
106
|
{Icon && <Icon className={cn('mr-1 h-4 w-4', className)} />}
|
|
105
107
|
<span className={cn('text-sm', className)}>
|
|
106
108
|
<Trans>{label}</Trans>
|
|
@@ -19,9 +19,9 @@ interface DataTableBulkActionsProps<TData> {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function DataTableBulkActions<TData>({
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
table,
|
|
23
|
+
bulkActions,
|
|
24
|
+
}: Readonly<DataTableBulkActionsProps<TData>>) {
|
|
25
25
|
const allBulkActions = useAllBulkActions(bulkActions);
|
|
26
26
|
|
|
27
27
|
// Cache to store selected items across page changes
|
|
@@ -60,7 +60,7 @@ export function DataTableBulkActions<TData>({
|
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<div
|
|
63
|
-
className="flex items-center gap-4 px-8 py-2 animate-in fade-in duration-200 fixed transform -translate-x-1/2
|
|
63
|
+
className="flex items-center gap-4 px-8 py-2 animate-in fade-in duration-200 fixed transform -translate-x-1/2 shadow-2xl bg-background rounded-md border z-50"
|
|
64
64
|
style={{
|
|
65
65
|
height: 'auto',
|
|
66
66
|
maxHeight: '60px',
|
|
@@ -3,6 +3,7 @@ import { useDynamicTranslations } from '@/vdb/hooks/use-dynamic-translations.js'
|
|
|
3
3
|
import { ColumnDef, HeaderContext } from '@tanstack/table-core';
|
|
4
4
|
import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
|
+
import { ColumnHeaderWrapper } from './column-header-wrapper.js';
|
|
6
7
|
|
|
7
8
|
export interface DataTableColumnHeaderProps {
|
|
8
9
|
customConfig: Partial<ColumnDef<any>>;
|
|
@@ -29,19 +30,21 @@ export function DataTableColumnHeader({ headerContext, customConfig }: Readonly<
|
|
|
29
30
|
const nextSort = columSort === 'asc' ? true : columSort === 'desc' ? undefined : false;
|
|
30
31
|
|
|
31
32
|
return (
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
<ColumnHeaderWrapper columnId={column.id}>
|
|
34
|
+
<div className="flex items-center">
|
|
35
|
+
{isSortable && (
|
|
36
|
+
<Button size="icon-sm" variant="ghost" onClick={() => column.toggleSorting(nextSort)}>
|
|
37
|
+
{columSort === 'desc' ? (
|
|
38
|
+
<ArrowUp />
|
|
39
|
+
) : columSort === 'asc' ? (
|
|
40
|
+
<ArrowDown />
|
|
41
|
+
) : (
|
|
42
|
+
<ArrowUpDown className="opacity-50" />
|
|
43
|
+
)}
|
|
44
|
+
</Button>
|
|
45
|
+
)}
|
|
46
|
+
<div>{display}</div>
|
|
47
|
+
</div>
|
|
48
|
+
</ColumnHeaderWrapper>
|
|
46
49
|
);
|
|
47
50
|
}
|
|
@@ -69,6 +69,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
|
|
69
69
|
setResolvedOptions(options);
|
|
70
70
|
}
|
|
71
71
|
}, [optionsFn]);
|
|
72
|
+
const isBoolean = (column?.columnDef?.meta as any)?.fieldInfo.type === 'Boolean';
|
|
72
73
|
|
|
73
74
|
return (
|
|
74
75
|
<Popover>
|
|
@@ -108,7 +109,7 @@ export function DataTableFacetedFilter<TData, TValue>({
|
|
|
108
109
|
</PopoverTrigger>
|
|
109
110
|
<PopoverContent className="w-[200px] p-0" align="start">
|
|
110
111
|
<Command>
|
|
111
|
-
<CommandInput placeholder={title} />
|
|
112
|
+
{resolvedOptions.length > 2 ? <CommandInput placeholder={title} /> : null}
|
|
112
113
|
<CommandList>
|
|
113
114
|
<CommandEmpty>No results found.</CommandEmpty>
|
|
114
115
|
<CommandGroup>
|
|
@@ -118,26 +119,47 @@ export function DataTableFacetedFilter<TData, TValue>({
|
|
|
118
119
|
<CommandItem
|
|
119
120
|
key={option.value}
|
|
120
121
|
onSelect={() => {
|
|
121
|
-
if (
|
|
122
|
-
|
|
122
|
+
if (isBoolean) {
|
|
123
|
+
// Radio button behavior: single selection only
|
|
124
|
+
if (isSelected) {
|
|
125
|
+
// Deselect if clicking the same option
|
|
126
|
+
column?.setFilterValue(undefined);
|
|
127
|
+
} else {
|
|
128
|
+
// Select only this option
|
|
129
|
+
column?.setFilterValue({ eq: option.value });
|
|
130
|
+
}
|
|
123
131
|
} else {
|
|
124
|
-
|
|
132
|
+
// Checkbox behavior: multi-selection
|
|
133
|
+
if (isSelected) {
|
|
134
|
+
selectedValues.delete(option.value);
|
|
135
|
+
} else {
|
|
136
|
+
selectedValues.add(option.value);
|
|
137
|
+
}
|
|
138
|
+
const filterValues = Array.from(selectedValues);
|
|
139
|
+
column?.setFilterValue(
|
|
140
|
+
filterValues.length ? filterValues : undefined,
|
|
141
|
+
);
|
|
125
142
|
}
|
|
126
|
-
const filterValues = Array.from(selectedValues);
|
|
127
|
-
column?.setFilterValue(
|
|
128
|
-
filterValues.length ? filterValues : undefined,
|
|
129
|
-
);
|
|
130
143
|
}}
|
|
131
144
|
>
|
|
132
145
|
<div
|
|
133
146
|
className={cn(
|
|
134
|
-
'mr-2 flex h-4 w-4 items-center justify-center
|
|
147
|
+
'mr-2 flex h-4 w-4 items-center justify-center border border-primary',
|
|
148
|
+
isBoolean ? 'rounded-full' : 'rounded-sm',
|
|
135
149
|
isSelected
|
|
136
150
|
? 'bg-primary text-primary-foreground'
|
|
137
|
-
:
|
|
151
|
+
: isBoolean
|
|
152
|
+
? '' // Empty circle for unselected radio buttons
|
|
153
|
+
: 'opacity-50 [&_svg]:invisible', // Grey checkbox for unselected checkboxes
|
|
138
154
|
)}
|
|
139
155
|
>
|
|
140
|
-
|
|
156
|
+
{isBoolean ? (
|
|
157
|
+
isSelected && (
|
|
158
|
+
<div className="h-2 w-2 rounded-full bg-primary-foreground" />
|
|
159
|
+
)
|
|
160
|
+
) : (
|
|
161
|
+
<Check />
|
|
162
|
+
)}
|
|
141
163
|
</div>
|
|
142
164
|
{option.icon && (
|
|
143
165
|
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Dialog } from '@/vdb/components/ui/dialog.js';
|
|
2
|
+
import { Column } from '@tanstack/react-table';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { DataTableFilterBadge } from './data-table-filter-badge.js';
|
|
5
|
+
import { DataTableFilterDialog } from './data-table-filter-dialog.js';
|
|
6
|
+
import { ColumnDataType } from './types.js';
|
|
7
|
+
|
|
8
|
+
export function DataTableFilterBadgeEditable({
|
|
9
|
+
filter,
|
|
10
|
+
column,
|
|
11
|
+
onRemove,
|
|
12
|
+
dataType,
|
|
13
|
+
currencyCode,
|
|
14
|
+
}: {
|
|
15
|
+
filter: any;
|
|
16
|
+
column: Column<any> | undefined;
|
|
17
|
+
onRemove: (filter: any) => void;
|
|
18
|
+
dataType: ColumnDataType;
|
|
19
|
+
currencyCode: string;
|
|
20
|
+
}) {
|
|
21
|
+
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
25
|
+
<DataTableFilterBadge
|
|
26
|
+
filter={filter}
|
|
27
|
+
onRemove={onRemove}
|
|
28
|
+
onClick={() => setIsDialogOpen(true)}
|
|
29
|
+
dataType={dataType}
|
|
30
|
+
currencyCode={currencyCode}
|
|
31
|
+
/>
|
|
32
|
+
{column && <DataTableFilterDialog column={column} onEnter={() => setIsDialogOpen(false)} />}
|
|
33
|
+
</Dialog>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -7,33 +7,40 @@ import { ColumnDataType } from './types.js';
|
|
|
7
7
|
export function DataTableFilterBadge({
|
|
8
8
|
filter,
|
|
9
9
|
onRemove,
|
|
10
|
+
onClick,
|
|
10
11
|
dataType,
|
|
11
12
|
currencyCode,
|
|
12
13
|
}: {
|
|
13
14
|
filter: any;
|
|
14
15
|
onRemove: (filter: any) => void;
|
|
16
|
+
onClick?: (filter: any) => void;
|
|
15
17
|
dataType: ColumnDataType;
|
|
16
18
|
currencyCode: string;
|
|
17
19
|
}) {
|
|
18
20
|
const [operator, value] = Object.entries(filter.value as Record<string, unknown>)[0];
|
|
19
21
|
return (
|
|
20
|
-
<Badge
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
<Badge key={filter.id} className="flex gap-2 flex-wrap items-center" variant="outline">
|
|
23
|
+
<div
|
|
24
|
+
className="flex gap-1 flex-wrap items-center cursor-pointer flex-1"
|
|
25
|
+
onClick={() => onClick?.(filter)}
|
|
26
|
+
>
|
|
27
|
+
<Filter size="12" className="opacity-50 flex-shrink-0" />
|
|
28
|
+
<div
|
|
29
|
+
className="@xs:overflow-hidden @xs:text-ellipsis @xs:whitespace-nowrap"
|
|
30
|
+
title={filter.id}
|
|
31
|
+
>
|
|
32
|
+
{filter.id}
|
|
33
|
+
</div>
|
|
34
|
+
<div className="text-muted-foreground flex-shrink-0">
|
|
35
|
+
<HumanReadableOperator operator={operator as Operator} mode="short" />
|
|
36
|
+
</div>
|
|
37
|
+
<div className="@xs:overflow-hidden @xs:text-ellipsis @xs:whitespace-nowrap flex flex-col @xl:flex-row @2xl:gap-1">
|
|
38
|
+
<FilterValue value={value} dataType={dataType} currencyCode={currencyCode} />
|
|
39
|
+
</div>
|
|
29
40
|
</div>
|
|
30
|
-
<
|
|
31
|
-
<
|
|
32
|
-
</
|
|
33
|
-
<div className="@xs:overflow-hidden @xs:text-ellipsis @xs:whitespace-nowrap flex flex-col @xl:flex-row @2xl:gap-1">
|
|
34
|
-
<FilterValue value={value} dataType={dataType} currencyCode={currencyCode} />
|
|
35
|
-
</div>
|
|
36
|
-
<XIcon className="h-4 flex-shrink-0" />
|
|
41
|
+
<button className="border-l -mr-2" onClick={() => onRemove(filter)}>
|
|
42
|
+
<XIcon className="h-4 flex-shrink-0 cursor-pointer" />
|
|
43
|
+
</button>
|
|
37
44
|
</Badge>
|
|
38
45
|
);
|
|
39
46
|
}
|
|
@@ -2,14 +2,13 @@ import { Button } from '@/vdb/components/ui/button.js';
|
|
|
2
2
|
import {
|
|
3
3
|
DialogClose,
|
|
4
4
|
DialogContent,
|
|
5
|
-
DialogDescription,
|
|
6
5
|
DialogFooter,
|
|
7
6
|
DialogHeader,
|
|
8
7
|
DialogTitle,
|
|
9
8
|
} from '@/vdb/components/ui/dialog.js';
|
|
10
9
|
import { Trans } from '@lingui/react/macro';
|
|
11
10
|
import { Column } from '@tanstack/react-table';
|
|
12
|
-
import { useState } from 'react';
|
|
11
|
+
import { useEffect, useState } from 'react';
|
|
13
12
|
import { DataTableBooleanFilter } from './filters/data-table-boolean-filter.js';
|
|
14
13
|
import { DataTableDateTimeFilter } from './filters/data-table-datetime-filter.js';
|
|
15
14
|
import { DataTableIdFilter } from './filters/data-table-id-filter.js';
|
|
@@ -19,21 +18,38 @@ import { ColumnDataType } from './types.js';
|
|
|
19
18
|
|
|
20
19
|
export interface DataTableFilterDialogProps {
|
|
21
20
|
column: Column<any>;
|
|
21
|
+
onEnter?: () => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function DataTableFilterDialog({ column }: Readonly<DataTableFilterDialogProps>) {
|
|
24
|
+
export function DataTableFilterDialog({ column, onEnter }: Readonly<DataTableFilterDialogProps>) {
|
|
25
25
|
const columnFilter = column.getFilterValue() as Record<string, string> | undefined;
|
|
26
26
|
const [filter, setFilter] = useState(columnFilter);
|
|
27
27
|
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setFilter(columnFilter);
|
|
30
|
+
}, [columnFilter]);
|
|
31
|
+
|
|
28
32
|
const columnDataType = (column.columnDef.meta as any)?.fieldInfo?.type as ColumnDataType;
|
|
29
33
|
const columnId = column.id;
|
|
34
|
+
const isEmpty = !filter || Object.keys(filter).length === 0;
|
|
35
|
+
const setFilterOnColumn = () => {
|
|
36
|
+
column.setFilterValue(filter);
|
|
37
|
+
setFilter(undefined);
|
|
38
|
+
};
|
|
39
|
+
const handleEnter = (e: React.KeyboardEvent<any>) => {
|
|
40
|
+
if (e.key === 'Enter') {
|
|
41
|
+
if (!isEmpty) {
|
|
42
|
+
setFilterOnColumn();
|
|
43
|
+
onEnter?.();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
30
47
|
return (
|
|
31
|
-
<DialogContent>
|
|
48
|
+
<DialogContent onKeyDown={handleEnter}>
|
|
32
49
|
<DialogHeader>
|
|
33
50
|
<DialogTitle>
|
|
34
51
|
<Trans>Filter by {columnId}</Trans>
|
|
35
52
|
</DialogTitle>
|
|
36
|
-
<DialogDescription></DialogDescription>
|
|
37
53
|
</DialogHeader>
|
|
38
54
|
{columnDataType === 'String' ? (
|
|
39
55
|
<DataTableStringFilter value={filter} onChange={e => setFilter(e)} />
|
|
@@ -50,7 +66,11 @@ export function DataTableFilterDialog({ column }: Readonly<DataTableFilterDialog
|
|
|
50
66
|
) : null}
|
|
51
67
|
<DialogFooter className="sm:justify-end">
|
|
52
68
|
{columnFilter && (
|
|
53
|
-
<Button
|
|
69
|
+
<Button
|
|
70
|
+
type="button"
|
|
71
|
+
variant="secondary"
|
|
72
|
+
onClick={() => column.setFilterValue(undefined)}
|
|
73
|
+
>
|
|
54
74
|
<Trans>Clear filter</Trans>
|
|
55
75
|
</Button>
|
|
56
76
|
)}
|
|
@@ -58,9 +78,9 @@ export function DataTableFilterDialog({ column }: Readonly<DataTableFilterDialog
|
|
|
58
78
|
<Button
|
|
59
79
|
type="button"
|
|
60
80
|
variant="secondary"
|
|
81
|
+
disabled={isEmpty}
|
|
61
82
|
onClick={() => {
|
|
62
|
-
|
|
63
|
-
setFilter(undefined);
|
|
83
|
+
setFilterOnColumn();
|
|
64
84
|
}}
|
|
65
85
|
>
|
|
66
86
|
<Trans>Apply filter</Trans>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Trans } from '@lingui/react/macro';
|
|
1
2
|
import { Table } from '@tanstack/react-table';
|
|
2
3
|
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
|
3
4
|
|
|
@@ -17,7 +18,9 @@ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<T
|
|
|
17
18
|
</div>
|
|
18
19
|
<div className="flex items-center space-x-6 lg:space-x-8">
|
|
19
20
|
<div className="flex items-center space-x-2">
|
|
20
|
-
<p className="text-sm font-medium">
|
|
21
|
+
<p className="hidden md:block text-sm font-medium">
|
|
22
|
+
<Trans>Rows per page</Trans>
|
|
23
|
+
</p>
|
|
21
24
|
<Select
|
|
22
25
|
value={`${table.getState().pagination.pageSize}`}
|
|
23
26
|
onValueChange={value => {
|
|
@@ -36,8 +39,12 @@ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<T
|
|
|
36
39
|
</SelectContent>
|
|
37
40
|
</Select>
|
|
38
41
|
</div>
|
|
39
|
-
<div className="flex
|
|
40
|
-
|
|
42
|
+
<div className=" flex items-center justify-center text-sm font-medium">
|
|
43
|
+
<span className="hidden md:block w-[100px] ">
|
|
44
|
+
<Trans>
|
|
45
|
+
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
|
|
46
|
+
</Trans>
|
|
47
|
+
</span>
|
|
41
48
|
</div>
|
|
42
49
|
<div className="flex items-center space-x-2">
|
|
43
50
|
<Button
|
|
@@ -47,7 +54,9 @@ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<T
|
|
|
47
54
|
onClick={() => table.setPageIndex(0)}
|
|
48
55
|
disabled={!table.getCanPreviousPage()}
|
|
49
56
|
>
|
|
50
|
-
<span className="sr-only">
|
|
57
|
+
<span className="sr-only">
|
|
58
|
+
<Trans>Go to first page</Trans>
|
|
59
|
+
</span>
|
|
51
60
|
<ChevronsLeft />
|
|
52
61
|
</Button>
|
|
53
62
|
<Button
|
|
@@ -57,9 +66,12 @@ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<T
|
|
|
57
66
|
onClick={() => table.previousPage()}
|
|
58
67
|
disabled={!table.getCanPreviousPage()}
|
|
59
68
|
>
|
|
60
|
-
<span className="sr-only">
|
|
69
|
+
<span className="sr-only">
|
|
70
|
+
<Trans>Go to previous page</Trans>
|
|
71
|
+
</span>
|
|
61
72
|
<ChevronLeft />
|
|
62
73
|
</Button>
|
|
74
|
+
<span className="md:hidden">{table.getState().pagination.pageIndex + 1}</span>
|
|
63
75
|
<Button
|
|
64
76
|
variant="outline"
|
|
65
77
|
type="button"
|
|
@@ -67,7 +79,9 @@ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<T
|
|
|
67
79
|
onClick={() => table.nextPage()}
|
|
68
80
|
disabled={!table.getCanNextPage()}
|
|
69
81
|
>
|
|
70
|
-
<span className="sr-only">
|
|
82
|
+
<span className="sr-only">
|
|
83
|
+
<Trans>Go to next page</Trans>
|
|
84
|
+
</span>
|
|
71
85
|
<ChevronRight />
|
|
72
86
|
</Button>
|
|
73
87
|
<Button
|
|
@@ -77,7 +91,9 @@ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<T
|
|
|
77
91
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
78
92
|
disabled={!table.getCanNextPage()}
|
|
79
93
|
>
|
|
80
|
-
<span className="sr-only">
|
|
94
|
+
<span className="sr-only">
|
|
95
|
+
<Trans>Go to last page</Trans>
|
|
96
|
+
</span>
|
|
81
97
|
<ChevronsRight />
|
|
82
98
|
</Button>
|
|
83
99
|
</div>
|