@vendure/dashboard 3.5.0-minor-202510071456 → 3.5.0-minor-202510161257

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.
Files changed (201) hide show
  1. package/dist/plugin/dashboard.plugin.js +1 -1
  2. package/dist/vite/utils/ast-utils.spec.js +3 -3
  3. package/dist/vite/vite-plugin-hmr.d.ts +8 -0
  4. package/dist/vite/vite-plugin-hmr.js +34 -0
  5. package/dist/vite/vite-plugin-theme.js +6 -6
  6. package/dist/vite/vite-plugin-transform-index.js +6 -1
  7. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
  8. package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
  9. package/package.json +17 -5
  10. package/src/app/app-providers.tsx +4 -1
  11. package/src/app/common/map-faceted-filter-fields.ts +21 -0
  12. package/src/app/main.tsx +3 -1
  13. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
  14. package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
  15. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
  16. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
  17. package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
  18. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  19. package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
  20. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
  21. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
  22. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
  23. package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
  24. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
  25. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
  26. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
  27. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
  28. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
  29. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
  30. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
  31. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
  32. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
  33. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
  34. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
  35. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
  36. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
  37. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
  38. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
  39. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
  40. package/src/app/routes/_authenticated/_products/products.tsx +6 -2
  41. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
  42. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
  43. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
  44. package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
  45. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
  46. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
  47. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
  48. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
  49. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
  50. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
  51. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
  52. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
  53. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
  54. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
  55. package/src/app/routes/login.tsx +2 -2
  56. package/src/i18n/locales/ar.po +420 -289
  57. package/src/i18n/locales/cs.po +420 -289
  58. package/src/i18n/locales/de.po +420 -289
  59. package/src/i18n/locales/en.po +420 -289
  60. package/src/i18n/locales/es.po +420 -289
  61. package/src/i18n/locales/fa.po +420 -289
  62. package/src/i18n/locales/fr.po +468 -337
  63. package/src/i18n/locales/he.po +420 -289
  64. package/src/i18n/locales/hr.po +420 -289
  65. package/src/i18n/locales/it.po +420 -289
  66. package/src/i18n/locales/ja.po +420 -289
  67. package/src/i18n/locales/nb.po +420 -289
  68. package/src/i18n/locales/ne.po +420 -289
  69. package/src/i18n/locales/pl.po +420 -289
  70. package/src/i18n/locales/pt_BR.po +420 -289
  71. package/src/i18n/locales/pt_PT.po +420 -289
  72. package/src/i18n/locales/ru.po +420 -289
  73. package/src/i18n/locales/sv.po +420 -289
  74. package/src/i18n/locales/tr.po +420 -289
  75. package/src/i18n/locales/uk.po +420 -289
  76. package/src/i18n/locales/zh_Hans.po +420 -289
  77. package/src/i18n/locales/zh_Hant.po +420 -289
  78. package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
  79. package/src/lib/components/data-input/affixed-input.tsx +5 -2
  80. package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
  81. package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
  82. package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
  83. package/src/lib/components/data-input/datetime-input.tsx +27 -13
  84. package/src/lib/components/data-input/default-relation-input.tsx +18 -12
  85. package/src/lib/components/data-input/money-input.stories.tsx +88 -0
  86. package/src/lib/components/data-input/number-input.stories.tsx +103 -0
  87. package/src/lib/components/data-input/number-input.tsx +10 -4
  88. package/src/lib/components/data-input/password-input.stories.tsx +65 -0
  89. package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
  90. package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
  91. package/src/lib/components/data-input/slug-input.tsx +9 -10
  92. package/src/lib/components/data-input/text-input.stories.tsx +52 -0
  93. package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
  94. package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
  95. package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
  96. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
  97. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  98. package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
  99. package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
  100. package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
  101. package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
  102. package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
  103. package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
  104. package/src/lib/components/data-table/data-table.stories.tsx +249 -0
  105. package/src/lib/components/data-table/data-table.tsx +37 -9
  106. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
  107. package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
  108. package/src/lib/components/layout/nav-user.tsx +19 -13
  109. package/src/lib/components/login/login-form.tsx +39 -123
  110. package/src/lib/components/shared/alerts.tsx +29 -17
  111. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
  112. package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
  113. package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
  114. package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
  115. package/src/lib/components/shared/customer-group-selector.tsx +5 -2
  116. package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
  117. package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
  118. package/src/lib/components/shared/facet-value-selector.tsx +130 -34
  119. package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
  120. package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
  121. package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
  122. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
  123. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
  124. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
  125. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
  126. package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
  127. package/src/lib/components/shared/vendure-image.tsx +6 -7
  128. package/src/lib/components/ui/accordion.stories.tsx +33 -0
  129. package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
  130. package/src/lib/components/ui/alert.stories.tsx +35 -0
  131. package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
  132. package/src/lib/components/ui/badge.stories.tsx +28 -0
  133. package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
  134. package/src/lib/components/ui/button.stories.tsx +38 -0
  135. package/src/lib/components/ui/calendar.stories.tsx +22 -0
  136. package/src/lib/components/ui/card.stories.tsx +28 -0
  137. package/src/lib/components/ui/carousel.stories.tsx +34 -0
  138. package/src/lib/components/ui/checkbox.stories.tsx +31 -0
  139. package/src/lib/components/ui/collapsible.stories.tsx +39 -0
  140. package/src/lib/components/ui/command.stories.tsx +44 -0
  141. package/src/lib/components/ui/context-menu.stories.tsx +38 -0
  142. package/src/lib/components/ui/dialog.stories.tsx +52 -0
  143. package/src/lib/components/ui/drawer.stories.tsx +50 -0
  144. package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
  145. package/src/lib/components/ui/hover-card.stories.tsx +38 -0
  146. package/src/lib/components/ui/input-group.tsx +148 -0
  147. package/src/lib/components/ui/input-otp.stories.tsx +30 -0
  148. package/src/lib/components/ui/input.stories.tsx +38 -0
  149. package/src/lib/components/ui/label.stories.tsx +24 -0
  150. package/src/lib/components/ui/menubar.stories.tsx +53 -0
  151. package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
  152. package/src/lib/components/ui/pagination.stories.tsx +51 -0
  153. package/src/lib/components/ui/password-input.stories.tsx +32 -0
  154. package/src/lib/components/ui/password-input.tsx +33 -0
  155. package/src/lib/components/ui/popover.stories.tsx +33 -0
  156. package/src/lib/components/ui/progress.stories.tsx +27 -0
  157. package/src/lib/components/ui/radio-group.stories.tsx +34 -0
  158. package/src/lib/components/ui/resizable.stories.tsx +32 -0
  159. package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
  160. package/src/lib/components/ui/select.stories.tsx +36 -0
  161. package/src/lib/components/ui/separator.stories.tsx +35 -0
  162. package/src/lib/components/ui/sheet.stories.tsx +50 -0
  163. package/src/lib/components/ui/sidebar-context.ts +16 -0
  164. package/src/lib/components/ui/sidebar.tsx +2 -13
  165. package/src/lib/components/ui/skeleton.stories.tsx +26 -0
  166. package/src/lib/components/ui/slider.stories.tsx +37 -0
  167. package/src/lib/components/ui/switch.stories.tsx +31 -0
  168. package/src/lib/components/ui/table.stories.tsx +52 -0
  169. package/src/lib/components/ui/tabs.stories.tsx +29 -0
  170. package/src/lib/components/ui/textarea.stories.tsx +32 -0
  171. package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
  172. package/src/lib/components/ui/toggle.stories.tsx +39 -0
  173. package/src/lib/components/ui/tooltip.stories.tsx +30 -0
  174. package/src/lib/components/ui/tooltip.tsx +2 -2
  175. package/src/lib/framework/alert/alert-extensions.tsx +0 -11
  176. package/src/lib/framework/alert/alert-item.tsx +14 -19
  177. package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
  178. package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
  179. package/src/lib/framework/component-registry/component-registry.tsx +3 -14
  180. package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
  181. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
  182. package/src/lib/framework/defaults.ts +9 -13
  183. package/src/lib/framework/extension-api/input-component-extensions.tsx +6 -1
  184. package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
  185. package/src/lib/framework/extension-api/types/alerts.ts +12 -6
  186. package/src/lib/framework/extension-api/types/data-table.ts +5 -2
  187. package/src/lib/framework/extension-api/types/login.ts +0 -21
  188. package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
  189. package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
  190. package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
  191. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
  192. package/src/lib/framework/page/detail-page.stories.tsx +151 -0
  193. package/src/lib/framework/page/list-page.stories.tsx +217 -0
  194. package/src/lib/framework/page/list-page.tsx +8 -1
  195. package/src/lib/graphql/api.ts +18 -1
  196. package/src/lib/graphql/graphql-env.d.ts +1 -1
  197. package/src/lib/hooks/use-alerts.ts +84 -0
  198. package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
  199. package/src/lib/index.ts +12 -5
  200. package/src/lib/providers/alerts-provider.tsx +60 -0
  201. 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
- label,
67
- icon: Icon,
68
- confirmationText,
69
- className,
70
- onClick,
71
- requiresPermission,
72
- }: Readonly<DataTableBulkActionItemProps>) {
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
- table,
23
- bulkActions,
24
- }: Readonly<DataTableBulkActionsProps<TData>>) {
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 bg-white shadow-2xl rounded-md border z-50"
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
- <div className="flex items-center">
33
- {isSortable && (
34
- <Button size="icon-sm" variant="ghost" onClick={() => column.toggleSorting(nextSort)}>
35
- {columSort === 'desc' ? (
36
- <ArrowUp />
37
- ) : columSort === 'asc' ? (
38
- <ArrowDown />
39
- ) : (
40
- <ArrowUpDown className="opacity-50" />
41
- )}
42
- </Button>
43
- )}
44
- <div>{display}</div>
45
- </div>
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 (isSelected) {
122
- selectedValues.delete(option.value);
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
- selectedValues.add(option.value);
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 rounded-sm border border-primary',
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
- : 'opacity-50 [&_svg]:invisible',
151
+ : isBoolean
152
+ ? '' // Empty circle for unselected radio buttons
153
+ : 'opacity-50 [&_svg]:invisible', // Grey checkbox for unselected checkboxes
138
154
  )}
139
155
  >
140
- <Check />
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
- key={filter.id}
22
- className="flex gap-1 flex-wrap items-center font-mono cursor-pointer"
23
- variant="outline"
24
- onClick={() => onRemove(filter)}
25
- >
26
- <Filter size="12" className="opacity-50 flex-shrink-0" />
27
- <div className="@xs:overflow-hidden @xs:text-ellipsis @xs:whitespace-nowrap" title={filter.id}>
28
- {filter.id}
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
- <div className="text-muted-foreground flex-shrink-0">
31
- <HumanReadableOperator operator={operator as Operator} mode="short" />
32
- </div>
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 type="button" variant="secondary" onClick={e => column.setFilterValue(undefined)}>
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
- column.setFilterValue(filter);
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">Rows per page</p>
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 w-[100px] items-center justify-center text-sm font-medium">
40
- Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
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">Go to first page</span>
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">Go to previous page</span>
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">Go to next page</span>
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">Go to last page</span>
94
+ <span className="sr-only">
95
+ <Trans>Go to last page</Trans>
96
+ </span>
81
97
  <ChevronsRight />
82
98
  </Button>
83
99
  </div>