@vendure/dashboard 3.4.3-master-202509260228 → 3.5.0-minor-202509261210

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 (74) hide show
  1. package/dist/plugin/api/api-extensions.js +11 -14
  2. package/dist/plugin/api/metrics.resolver.d.ts +2 -2
  3. package/dist/plugin/api/metrics.resolver.js +2 -2
  4. package/dist/plugin/config/metrics-strategies.d.ts +9 -9
  5. package/dist/plugin/config/metrics-strategies.js +6 -6
  6. package/dist/plugin/constants.d.ts +2 -0
  7. package/dist/plugin/constants.js +3 -1
  8. package/dist/plugin/dashboard.plugin.js +13 -0
  9. package/dist/plugin/service/metrics.service.d.ts +3 -3
  10. package/dist/plugin/service/metrics.service.js +37 -53
  11. package/dist/plugin/types.d.ts +9 -12
  12. package/dist/plugin/types.js +7 -11
  13. package/dist/vite/vite-plugin-vendure-dashboard.js +2 -2
  14. package/package.json +4 -4
  15. package/src/app/routes/_authenticated/_collections/collections.tsx +7 -2
  16. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +15 -2
  17. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +14 -2
  18. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +10 -0
  19. package/src/app/routes/_authenticated/_products/components/product-option-group-badge.tsx +19 -0
  20. package/src/app/routes/_authenticated/_products/components/product-options-table.tsx +111 -0
  21. package/src/app/routes/_authenticated/_products/product-option-groups.graphql.ts +103 -0
  22. package/src/app/routes/_authenticated/_products/products.graphql.ts +13 -1
  23. package/src/app/routes/_authenticated/_products/products.tsx +27 -3
  24. package/src/app/routes/_authenticated/_products/products_.$id.tsx +26 -9
  25. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +181 -0
  26. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +208 -0
  27. package/src/app/routes/_authenticated/_zones/components/zone-countries-sheet.tsx +4 -1
  28. package/src/app/routes/_authenticated/index.tsx +41 -24
  29. package/src/lib/components/data-display/json.tsx +16 -1
  30. package/src/lib/components/data-input/index.ts +3 -0
  31. package/src/lib/components/data-input/slug-input.tsx +296 -0
  32. package/src/lib/components/data-table/add-filter-menu.tsx +13 -6
  33. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +38 -1
  34. package/src/lib/components/data-table/data-table-context.tsx +91 -0
  35. package/src/lib/components/data-table/data-table-filter-badge.tsx +9 -5
  36. package/src/lib/components/data-table/data-table-view-options.tsx +17 -8
  37. package/src/lib/components/data-table/data-table.tsx +146 -94
  38. package/src/lib/components/data-table/global-views-bar.tsx +97 -0
  39. package/src/lib/components/data-table/global-views-sheet.tsx +11 -0
  40. package/src/lib/components/data-table/manage-global-views-button.tsx +26 -0
  41. package/src/lib/components/data-table/my-views-button.tsx +47 -0
  42. package/src/lib/components/data-table/refresh-button.tsx +12 -3
  43. package/src/lib/components/data-table/save-view-button.tsx +45 -0
  44. package/src/lib/components/data-table/save-view-dialog.tsx +113 -0
  45. package/src/lib/components/data-table/use-generated-columns.tsx +3 -1
  46. package/src/lib/components/data-table/user-views-sheet.tsx +11 -0
  47. package/src/lib/components/data-table/views-sheet.tsx +297 -0
  48. package/src/lib/components/date-range-picker.tsx +184 -0
  49. package/src/lib/components/shared/paginated-list-data-table.tsx +59 -32
  50. package/src/lib/components/ui/button.tsx +1 -1
  51. package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +29 -2
  52. package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +10 -7
  53. package/src/lib/framework/dashboard-widget/metrics-widget/metrics-widget.graphql.ts +9 -3
  54. package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +19 -75
  55. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +33 -0
  56. package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +319 -9
  57. package/src/lib/framework/document-introspection/add-custom-fields.ts +60 -31
  58. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +1 -159
  59. package/src/lib/framework/document-introspection/include-only-selected-list-fields.spec.ts +1840 -0
  60. package/src/lib/framework/document-introspection/include-only-selected-list-fields.ts +940 -0
  61. package/src/lib/framework/document-introspection/testing-utils.ts +161 -0
  62. package/src/lib/framework/extension-api/display-component-extensions.tsx +2 -0
  63. package/src/lib/framework/extension-api/types/data-table.ts +62 -4
  64. package/src/lib/framework/extension-api/types/navigation.ts +16 -0
  65. package/src/lib/framework/form-engine/utils.ts +34 -0
  66. package/src/lib/framework/page/list-page.tsx +289 -4
  67. package/src/lib/framework/page/use-extended-router.tsx +59 -17
  68. package/src/lib/graphql/api.ts +4 -2
  69. package/src/lib/graphql/graphql-env.d.ts +13 -10
  70. package/src/lib/hooks/use-extended-list-query.ts +5 -0
  71. package/src/lib/hooks/use-saved-views.ts +230 -0
  72. package/src/lib/index.ts +15 -0
  73. package/src/lib/types/saved-views.ts +39 -0
  74. package/src/lib/utils/saved-views-utils.ts +40 -0
@@ -7,10 +7,11 @@ import {
7
7
  DropdownMenuItem,
8
8
  DropdownMenuTrigger,
9
9
  } from '@/vdb/components/ui/dropdown-menu.js';
10
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/vdb/components/ui/tooltip.js';
10
11
  import { Trans } from '@/vdb/lib/trans.js';
11
12
  import { camelCaseToTitleCase } from '@/vdb/lib/utils.js';
12
13
  import { Column, ColumnDef } from '@tanstack/react-table';
13
- import { PlusCircle } from 'lucide-react';
14
+ import { FilterIcon } from 'lucide-react';
14
15
  import { useState } from 'react';
15
16
 
16
17
  export interface AddFilterMenuProps {
@@ -26,12 +27,18 @@ export function AddFilterMenu({ columns }: Readonly<AddFilterMenuProps>) {
26
27
  return (
27
28
  <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
28
29
  <DropdownMenu>
29
- <DropdownMenuTrigger asChild>
30
- <Button variant="outline" size="sm" className="h-8 border-dashed">
31
- <PlusCircle className="mr-2 h-4 w-4" />
30
+ <Tooltip>
31
+ <TooltipTrigger asChild>
32
+ <DropdownMenuTrigger asChild>
33
+ <Button variant="outline" size="icon">
34
+ <FilterIcon />
35
+ </Button>
36
+ </DropdownMenuTrigger>
37
+ </TooltipTrigger>
38
+ <TooltipContent>
32
39
  <Trans>Add filter</Trans>
33
- </Button>
34
- </DropdownMenuTrigger>
40
+ </TooltipContent>
41
+ </Tooltip>
35
42
  <DropdownMenuContent align="end" className="w-[200px]">
36
43
  {filterableColumns.map(column => (
37
44
  <DropdownMenuItem
@@ -16,6 +16,13 @@ import {
16
16
  } from '../ui/alert-dialog.js';
17
17
  import { DropdownMenuItem } from '../ui/dropdown-menu.js';
18
18
 
19
+ /**
20
+ * @description
21
+ *
22
+ * @docsCategory list-views
23
+ * @docsPage bulk-actions
24
+ * @since 3.4.0
25
+ */
19
26
  export interface DataTableBulkActionItemProps {
20
27
  label: React.ReactNode;
21
28
  icon?: LucideIcon;
@@ -25,6 +32,36 @@ export interface DataTableBulkActionItemProps {
25
32
  requiresPermission?: string[];
26
33
  }
27
34
 
35
+ /**
36
+ * @description
37
+ * A component that should be used to implement any bulk actions for list pages & data tables.
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * import { DataTableBulkActionItem, Trans } from '\@vendure/dashboard';
42
+ * import { Check } from 'lucide-react';
43
+ *
44
+ * export const MyBulkAction: BulkActionComponent<any> = ({ selection, table }) => {
45
+ *
46
+ * return (
47
+ * <DataTableBulkActionItem
48
+ * requiresPermission={['ReadMyCustomEntity']}
49
+ * onClick={() => {
50
+ * console.log('Selected items:', selection);
51
+ * }}
52
+ * label={<Trans>Delete</Trans>}
53
+ * confirmationText={<Trans>Are you sure?</Trans>}
54
+ * icon={Check}
55
+ * className="text-destructive"
56
+ * />
57
+ * );
58
+ * }
59
+ * ```
60
+ *
61
+ * @docsCategory list-views
62
+ * @docsPage bulk-actions
63
+ * @since 3.4.0
64
+ */
28
65
  export function DataTableBulkActionItem({
29
66
  label,
30
67
  icon: Icon,
@@ -32,7 +69,7 @@ export function DataTableBulkActionItem({
32
69
  className,
33
70
  onClick,
34
71
  requiresPermission,
35
- }: DataTableBulkActionItemProps) {
72
+ }: Readonly<DataTableBulkActionItemProps>) {
36
73
  const [isOpen, setIsOpen] = useState(false);
37
74
  const { hasPermissions } = usePermissions();
38
75
  const userHasPermission = hasPermissions(requiresPermission ?? []);
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
4
+ import React, { createContext, ReactNode, useContext } from 'react';
5
+
6
+ interface DataTableContextValue {
7
+ columnFilters: ColumnFiltersState;
8
+ setColumnFilters: React.Dispatch<React.SetStateAction<ColumnFiltersState>>;
9
+ searchTerm: string;
10
+ setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
11
+ sorting: SortingState;
12
+ setSorting: React.Dispatch<React.SetStateAction<SortingState>>;
13
+ pageId?: string;
14
+ onFilterChange?: (table: Table<any>, filters: ColumnFiltersState) => void;
15
+ onSearchTermChange?: (searchTerm: string) => void;
16
+ onRefresh?: () => void;
17
+ isLoading?: boolean;
18
+ table?: Table<any>;
19
+ handleApplyView: (filters: ColumnFiltersState, searchTerm?: string) => void;
20
+ }
21
+
22
+ const DataTableContext = createContext<DataTableContextValue | undefined>(undefined);
23
+
24
+ export interface DataTableProviderProps {
25
+ children: ReactNode;
26
+ columnFilters: ColumnFiltersState;
27
+ setColumnFilters: React.Dispatch<React.SetStateAction<ColumnFiltersState>>;
28
+ searchTerm: string;
29
+ setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
30
+ sorting: SortingState;
31
+ setSorting: React.Dispatch<React.SetStateAction<SortingState>>;
32
+ pageId?: string;
33
+ onFilterChange?: (table: Table<any>, filters: ColumnFiltersState) => void;
34
+ onSearchTermChange?: (searchTerm: string) => void;
35
+ onRefresh?: () => void;
36
+ isLoading?: boolean;
37
+ table?: Table<any>;
38
+ }
39
+
40
+ export function DataTableProvider({
41
+ children,
42
+ columnFilters,
43
+ setColumnFilters,
44
+ searchTerm,
45
+ setSearchTerm,
46
+ sorting,
47
+ setSorting,
48
+ pageId,
49
+ onFilterChange,
50
+ onSearchTermChange,
51
+ onRefresh,
52
+ isLoading,
53
+ table,
54
+ }: DataTableProviderProps) {
55
+ const handleApplyView = (filters: ColumnFiltersState, viewSearchTerm?: string) => {
56
+ setColumnFilters(filters);
57
+ if (viewSearchTerm !== undefined && onSearchTermChange) {
58
+ setSearchTerm(viewSearchTerm);
59
+ onSearchTermChange(viewSearchTerm);
60
+ }
61
+ if (onFilterChange && table) {
62
+ onFilterChange(table, filters);
63
+ }
64
+ };
65
+
66
+ const value: DataTableContextValue = {
67
+ columnFilters,
68
+ setColumnFilters,
69
+ searchTerm,
70
+ setSearchTerm,
71
+ sorting,
72
+ setSorting,
73
+ pageId,
74
+ onFilterChange,
75
+ onSearchTermChange,
76
+ onRefresh,
77
+ isLoading,
78
+ table,
79
+ handleApplyView,
80
+ };
81
+
82
+ return <DataTableContext.Provider value={value}>{children}</DataTableContext.Provider>;
83
+ }
84
+
85
+ export function useDataTableContext() {
86
+ const context = useContext(DataTableContext);
87
+ if (context === undefined) {
88
+ throw new Error('useDataTableContext must be used within a DataTableProvider');
89
+ }
90
+ return context;
91
+ }
@@ -1,5 +1,5 @@
1
1
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
2
- import { CircleX, Filter } from 'lucide-react';
2
+ import { Filter, XIcon } from 'lucide-react';
3
3
  import { Badge } from '../ui/badge.js';
4
4
  import { HumanReadableOperator, Operator } from './human-readable-operator.js';
5
5
  import { ColumnDataType } from './types.js';
@@ -17,16 +17,20 @@ export function DataTableFilterBadge({
17
17
  }) {
18
18
  const [operator, value] = Object.entries(filter.value as Record<string, unknown>)[0];
19
19
  return (
20
- <Badge key={filter.id} className="flex gap-1 items-center" variant="secondary">
20
+ <Badge
21
+ key={filter.id}
22
+ className="flex gap-1 items-center font-mono cursor-pointer "
23
+ variant="outline"
24
+ onClick={() => onRemove(filter)}
25
+ >
21
26
  <Filter size="12" className="opacity-50" />
22
27
  <div>{filter.id}</div>
23
28
  <div className="text-muted-foreground">
24
29
  <HumanReadableOperator operator={operator as Operator} mode="short" />
25
30
  </div>
26
31
  <FilterValue value={value} dataType={dataType} currencyCode={currencyCode} />
27
- <button className="cursor-pointer" onClick={() => onRemove(filter)}>
28
- <CircleX size="14" />
29
- </button>
32
+
33
+ <XIcon className="h-4" />
30
34
  </Badge>
31
35
  );
32
36
  }
@@ -17,6 +17,7 @@ import {
17
17
  DropdownMenuTrigger,
18
18
  } from '@/vdb/components/ui/dropdown-menu.js';
19
19
  import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
20
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/vdb/components/ui/tooltip.js';
20
21
  import { usePage } from '@/vdb/hooks/use-page.js';
21
22
  import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
22
23
  import { Trans } from '@/vdb/lib/trans.js';
@@ -78,12 +79,18 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
78
79
  return (
79
80
  <div className="flex items-center gap-2">
80
81
  <DropdownMenu modal={false}>
81
- <DropdownMenuTrigger asChild>
82
- <Button variant="ghost" size="sm" className="ml-auto hidden h-8 lg:flex">
83
- <Settings2 />
84
- <Trans>Columns</Trans>
85
- </Button>
86
- </DropdownMenuTrigger>
82
+ <Tooltip>
83
+ <TooltipTrigger asChild>
84
+ <DropdownMenuTrigger asChild>
85
+ <Button variant="outline" size="sm" className="ml-auto hidden h-8 lg:flex">
86
+ <Settings2 />
87
+ </Button>
88
+ </DropdownMenuTrigger>
89
+ </TooltipTrigger>
90
+ <TooltipContent>
91
+ <Trans>Column settings</Trans>
92
+ </TooltipContent>
93
+ </Tooltip>
87
94
  <DropdownMenuContent align="end" className="overflow-auto">
88
95
  <ScrollArea className="max-h-[60vh]" type="always">
89
96
  <DndContext
@@ -100,7 +107,7 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
100
107
  <DropdownMenuCheckboxItem
101
108
  className="capitalize"
102
109
  checked={column.getIsVisible()}
103
- onCheckedChange={value => column.toggleVisibility(!!value)}
110
+ onCheckedChange={value => column.toggleVisibility(value)}
104
111
  onSelect={e => e.preventDefault()}
105
112
  >
106
113
  {column.id}
@@ -110,7 +117,9 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
110
117
  </SortableContext>
111
118
  </DndContext>
112
119
  <DropdownMenuSeparator />
113
- <DropdownMenuItem onClick={handleReset}>Reset</DropdownMenuItem>
120
+ <DropdownMenuItem onClick={handleReset}>
121
+ <Trans>Reset</Trans>
122
+ </DropdownMenuItem>
114
123
  </ScrollArea>
115
124
  </DropdownMenuContent>
116
125
  </DropdownMenu>
@@ -2,12 +2,19 @@
2
2
 
3
3
  import { DataTablePagination } from '@/vdb/components/data-table/data-table-pagination.js';
4
4
  import { DataTableViewOptions } from '@/vdb/components/data-table/data-table-view-options.js';
5
+ import { GlobalViewsBar } from '@/vdb/components/data-table/global-views-bar.js';
6
+ import { MyViewsButton } from '@/vdb/components/data-table/my-views-button.js';
5
7
  import { RefreshButton } from '@/vdb/components/data-table/refresh-button.js';
8
+ import { SaveViewButton } from '@/vdb/components/data-table/save-view-button.js';
9
+ import { Button } from '@/vdb/components/ui/button.js';
6
10
  import { Input } from '@/vdb/components/ui/input.js';
7
11
  import { Skeleton } from '@/vdb/components/ui/skeleton.js';
8
12
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/vdb/components/ui/table.js';
9
13
  import { BulkAction } from '@/vdb/framework/extension-api/types/index.js';
10
14
  import { useChannel } from '@/vdb/hooks/use-channel.js';
15
+ import { usePage } from '@/vdb/hooks/use-page.js';
16
+ import { useSavedViews } from '@/vdb/hooks/use-saved-views.js';
17
+ import { Trans, useLingui } from '@/vdb/lib/trans.js';
11
18
  import {
12
19
  ColumnDef,
13
20
  ColumnFilter,
@@ -25,6 +32,7 @@ import { RowSelectionState, TableOptions } from '@tanstack/table-core';
25
32
  import React, { Suspense, useEffect } from 'react';
26
33
  import { AddFilterMenu } from './add-filter-menu.js';
27
34
  import { DataTableBulkActions } from './data-table-bulk-actions.js';
35
+ import { DataTableProvider } from './data-table-context.js';
28
36
  import { DataTableFacetedFilter, DataTableFacetedFilterOption } from './data-table-faceted-filter.js';
29
37
  import { DataTableFilterBadge } from './data-table-filter-badge.js';
30
38
 
@@ -106,7 +114,12 @@ export function DataTable<TData>({
106
114
  }: Readonly<DataTableProps<TData>>) {
107
115
  const [sorting, setSorting] = React.useState<SortingState>(sortingInitialState || []);
108
116
  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(filtersInitialState || []);
117
+ const [searchTerm, setSearchTerm] = React.useState<string>('');
109
118
  const { activeChannel } = useChannel();
119
+ const { pageId } = usePage();
120
+ const savedViewsResult = useSavedViews();
121
+ const globalViews = pageId && onFilterChange ? savedViewsResult.globalViews : [];
122
+ const { i18n } = useLingui();
110
123
  const [pagination, setPagination] = React.useState<PaginationState>({
111
124
  pageIndex: (page ?? 1) - 1,
112
125
  pageSize: itemsPerPage ?? 10,
@@ -175,19 +188,37 @@ export function DataTable<TData>({
175
188
  }, [columnVisibility]);
176
189
 
177
190
  const visibleColumnCount = Object.values(columnVisibility).filter(Boolean).length;
191
+
192
+ const handleSearchChange = (value: string) => {
193
+ setSearchTerm(value);
194
+ onSearchTermChange?.(value);
195
+ };
196
+
178
197
  return (
179
- <>
180
- <div className="flex justify-between items-start">
181
- <div className="flex flex-col space-y-2">
182
- <div className="flex items-center justify-start gap-2">
198
+ <DataTableProvider
199
+ columnFilters={columnFilters}
200
+ setColumnFilters={setColumnFilters}
201
+ searchTerm={searchTerm}
202
+ setSearchTerm={setSearchTerm}
203
+ sorting={sorting}
204
+ setSorting={setSorting}
205
+ pageId={pageId}
206
+ onFilterChange={onFilterChange}
207
+ onSearchTermChange={onSearchTermChange}
208
+ onRefresh={onRefresh}
209
+ isLoading={isLoading}
210
+ table={table}
211
+ >
212
+ <div className="space-y-2">
213
+ <div className="flex items-center justify-between gap-2">
214
+ <div className="flex items-center gap-2">
183
215
  {onSearchTermChange && (
184
- <div className="flex items-center">
185
- <Input
186
- placeholder="Filter..."
187
- onChange={event => onSearchTermChange(event.target.value)}
188
- className="max-w-sm w-md"
189
- />
190
- </div>
216
+ <Input
217
+ placeholder={i18n.t('Filter...')}
218
+ value={searchTerm}
219
+ onChange={event => handleSearchChange(event.target.value)}
220
+ className="w-64"
221
+ />
191
222
  )}
192
223
  <Suspense>
193
224
  {Object.entries(facetedFilters ?? {}).map(([key, filter]) => (
@@ -201,99 +232,120 @@ export function DataTable<TData>({
201
232
  ))}
202
233
  </Suspense>
203
234
  {onFilterChange && <AddFilterMenu columns={table.getAllColumns()} />}
235
+ {pageId && onFilterChange && <MyViewsButton />}
204
236
  </div>
205
- <div className="flex gap-1">
206
- {columnFilters
207
- .filter(f => !facetedFilters?.[f.id])
208
- .map(f => {
209
- const column = table.getColumn(f.id);
210
- const currency = activeChannel?.defaultCurrencyCode ?? 'USD';
211
- return (
212
- <DataTableFilterBadge
213
- key={f.id}
214
- filter={f}
215
- currencyCode={currency}
216
- dataType={
217
- (column?.columnDef.meta as any)?.fieldInfo?.type ?? 'String'
218
- }
219
- onRemove={() =>
220
- setColumnFilters(old => old.filter(x => x.id !== f.id))
221
- }
222
- />
223
- );
224
- })}
237
+ <div className="flex items-center gap-2">
238
+ {pageId && onFilterChange && <SaveViewButton />}
239
+ {!disableViewOptions && <DataTableViewOptions table={table} />}
240
+ {onRefresh && <RefreshButton onRefresh={onRefresh} isLoading={isLoading ?? false} />}
225
241
  </div>
226
242
  </div>
227
- <div className="flex items-center justify-start gap-2">
228
- {!disableViewOptions && <DataTableViewOptions table={table} />}
229
- {onRefresh && <RefreshButton onRefresh={onRefresh} isLoading={isLoading ?? false} />}
230
- </div>
231
- </div>
232
243
 
233
- <div className="rounded-md border my-2 relative">
234
- <Table>
235
- <TableHeader>
236
- {table.getHeaderGroups().map(headerGroup => (
237
- <TableRow key={headerGroup.id}>
238
- {headerGroup.headers.map(header => {
244
+ {(pageId && onFilterChange && globalViews.length > 0) ||
245
+ columnFilters.filter(f => !facetedFilters?.[f.id]).length > 0 ? (
246
+ <div className="flex items-center justify-between bg-muted/40 rounded border border-border p-2">
247
+ <div className="flex items-center">
248
+ {pageId && onFilterChange && <GlobalViewsBar />}
249
+ </div>
250
+ <div className="flex gap-1 items-center">
251
+ {columnFilters
252
+ .filter(f => !facetedFilters?.[f.id])
253
+ .map(f => {
254
+ const column = table.getColumn(f.id);
255
+ const currency = activeChannel?.defaultCurrencyCode ?? 'USD';
239
256
  return (
240
- <TableHead key={header.id}>
241
- {header.isPlaceholder
242
- ? null
243
- : flexRender(
244
- header.column.columnDef.header,
245
- header.getContext(),
246
- )}
247
- </TableHead>
257
+ <DataTableFilterBadge
258
+ key={f.id}
259
+ filter={f}
260
+ currencyCode={currency}
261
+ dataType={
262
+ (column?.columnDef.meta as any)?.fieldInfo?.type ?? 'String'
263
+ }
264
+ onRemove={() =>
265
+ setColumnFilters(old => old.filter(x => x.id !== f.id))
266
+ }
267
+ />
248
268
  );
249
269
  })}
250
- </TableRow>
251
- ))}
252
- </TableHeader>
253
- <TableBody>
254
- {isLoading && !data?.length ? (
255
- Array.from({ length: pagination.pageSize }).map((_, index) => (
256
- <TableRow
257
- key={`skeleton-${index}`}
258
- className="animate-in fade-in duration-100"
270
+ {columnFilters.filter(f => !facetedFilters?.[f.id]).length > 0 && (
271
+ <Button
272
+ variant="ghost"
273
+ size="sm"
274
+ onClick={() => setColumnFilters([])}
275
+ className="text-xs opacity-60 hover:opacity-100"
259
276
  >
260
- {Array.from({ length: visibleColumnCount }).map((_, cellIndex) => (
261
- <TableCell
262
- key={`skeleton-cell-${index}-${cellIndex}`}
263
- className="h-12"
264
- >
265
- <Skeleton className="h-4 my-2 w-full" />
266
- </TableCell>
267
- ))}
277
+ <Trans>Clear all</Trans>
278
+ </Button>
279
+ )}
280
+ </div>
281
+ </div>
282
+ ) : null}
283
+
284
+ <div className="rounded-md border my-2 relative shadow-sm">
285
+ <Table>
286
+ <TableHeader className="bg-muted/50">
287
+ {table.getHeaderGroups().map(headerGroup => (
288
+ <TableRow key={headerGroup.id}>
289
+ {headerGroup.headers.map(header => {
290
+ return (
291
+ <TableHead key={header.id}>
292
+ {header.isPlaceholder
293
+ ? null
294
+ : flexRender(
295
+ header.column.columnDef.header,
296
+ header.getContext(),
297
+ )}
298
+ </TableHead>
299
+ );
300
+ })}
268
301
  </TableRow>
269
- ))
270
- ) : table.getRowModel().rows?.length ? (
271
- table.getRowModel().rows.map(row => (
272
- <TableRow
273
- key={row.id}
274
- data-state={row.getIsSelected() && 'selected'}
275
- className="animate-in fade-in duration-100"
276
- >
277
- {row.getVisibleCells().map(cell => (
278
- <TableCell key={cell.id} className="h-12">
279
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
280
- </TableCell>
281
- ))}
302
+ ))}
303
+ </TableHeader>
304
+ <TableBody>
305
+ {isLoading && !data?.length ? (
306
+ Array.from({ length: pagination.pageSize }).map((_, index) => (
307
+ <TableRow
308
+ key={`skeleton-${index}`}
309
+ className="animate-in fade-in duration-100"
310
+ >
311
+ {Array.from({ length: visibleColumnCount }).map((_, cellIndex) => (
312
+ <TableCell
313
+ key={`skeleton-cell-${index}-${cellIndex}`}
314
+ className="h-12"
315
+ >
316
+ <Skeleton className="h-4 my-2 w-full" />
317
+ </TableCell>
318
+ ))}
319
+ </TableRow>
320
+ ))
321
+ ) : table.getRowModel().rows?.length ? (
322
+ table.getRowModel().rows.map(row => (
323
+ <TableRow
324
+ key={row.id}
325
+ data-state={row.getIsSelected() && 'selected'}
326
+ className="animate-in fade-in duration-100"
327
+ >
328
+ {row.getVisibleCells().map(cell => (
329
+ <TableCell key={cell.id} className="h-12">
330
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
331
+ </TableCell>
332
+ ))}
333
+ </TableRow>
334
+ ))
335
+ ) : (
336
+ <TableRow className="animate-in fade-in duration-100">
337
+ <TableCell colSpan={columns.length} className="h-24 text-center">
338
+ <Trans>No results</Trans>
339
+ </TableCell>
282
340
  </TableRow>
283
- ))
284
- ) : (
285
- <TableRow className="animate-in fade-in duration-100">
286
- <TableCell colSpan={columns.length} className="h-24 text-center">
287
- No results.
288
- </TableCell>
289
- </TableRow>
290
- )}
291
- {children}
292
- </TableBody>
293
- </Table>
294
- <DataTableBulkActions bulkActions={bulkActions ?? []} table={table} />
341
+ )}
342
+ {children}
343
+ </TableBody>
344
+ </Table>
345
+ <DataTableBulkActions bulkActions={bulkActions ?? []} table={table} />
346
+ </div>
347
+ {onPageChange && totalItems != null && <DataTablePagination table={table} />}
295
348
  </div>
296
- {onPageChange && totalItems != null && <DataTablePagination table={table} />}
297
- </>
349
+ </DataTableProvider>
298
350
  );
299
351
  }