@eventcatalog/core 3.5.2 → 3.6.1
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/README.md +1 -1
- package/dist/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-YVX5C6L3.js → chunk-FCIJEGOL.js} +1 -1
- package/dist/{chunk-WO3AKJVB.js → chunk-N2VBSHPU.js} +1 -1
- package/dist/{chunk-OKWCSRLE.js → chunk-OFHFRJ42.js} +1 -1
- package/dist/{chunk-YOFNY2RC.js → chunk-SI6IEUYS.js} +1 -1
- package/dist/{chunk-YTZSPYJN.js → chunk-XRLZZXIS.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/astro.config.mjs +2 -1
- package/eventcatalog/src/components/EnvironmentDropdown.tsx +1 -1
- package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +477 -0
- package/eventcatalog/src/components/MDX/components.tsx +2 -0
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +23 -11
- package/eventcatalog/src/components/Search/SearchModal.tsx +17 -2
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +12 -6
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +37 -16
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +816 -0
- package/eventcatalog/src/components/Tables/Discover/FilterComponents.tsx +161 -0
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +565 -0
- package/eventcatalog/src/components/Tables/Discover/index.ts +4 -0
- package/eventcatalog/src/components/Tables/columns/ContainersTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/DomainTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/FlowTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/MessageTableColumns.tsx +1 -1
- package/eventcatalog/src/components/Tables/columns/ServiceTableColumns.tsx +54 -64
- package/eventcatalog/src/components/Tables/columns/SharedColumns.tsx +15 -30
- package/eventcatalog/src/enterprise/plans/index.astro +125 -98
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +1 -1
- package/eventcatalog/src/pages/api/sidebar-data.json.ts +22 -0
- package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -1
- package/eventcatalog/src/pages/discover/[type]/index.astro +360 -41
- package/eventcatalog/src/pages/docs/custom/feature.astro +45 -39
- package/eventcatalog/src/remark-plugins/resource-ref.ts +51 -0
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +1 -1
- package/eventcatalog/src/stores/sidebar-store/state.ts +25 -22
- package/package.json +3 -2
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flexRender,
|
|
3
|
+
getCoreRowModel,
|
|
4
|
+
getFacetedMinMaxValues,
|
|
5
|
+
getFacetedRowModel,
|
|
6
|
+
getFacetedUniqueValues,
|
|
7
|
+
getFilteredRowModel,
|
|
8
|
+
getPaginationRowModel,
|
|
9
|
+
useReactTable,
|
|
10
|
+
type ColumnFiltersState,
|
|
11
|
+
} from '@tanstack/react-table';
|
|
12
|
+
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, SearchX, X, Search, Users } from 'lucide-react';
|
|
13
|
+
import { UserIcon } from '@heroicons/react/24/outline';
|
|
14
|
+
import { useMemo, useState } from 'react';
|
|
15
|
+
import type { TableConfiguration } from '@types';
|
|
16
|
+
import { isSameVersion } from '@utils/collections/util';
|
|
17
|
+
import { FilterDropdown, CheckboxItem } from './FilterComponents';
|
|
18
|
+
import DebouncedInput from '../DebouncedInput';
|
|
19
|
+
import { getDiscoverColumns } from './columns';
|
|
20
|
+
|
|
21
|
+
export type CollectionType = 'events' | 'commands' | 'queries' | 'services' | 'domains' | 'flows' | 'containers';
|
|
22
|
+
|
|
23
|
+
export interface DiscoverTableData {
|
|
24
|
+
collection: string;
|
|
25
|
+
domains?: Array<{ id: string; name: string; version: string }>;
|
|
26
|
+
owners?: Array<{ id: string; name: string; type?: 'user' | 'team' }>;
|
|
27
|
+
hasSpecifications?: boolean;
|
|
28
|
+
hasOwners?: boolean;
|
|
29
|
+
hasRepository?: boolean;
|
|
30
|
+
isDeprecated?: boolean;
|
|
31
|
+
hasDataDependencies?: boolean;
|
|
32
|
+
data: {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
summary?: string;
|
|
36
|
+
version: string;
|
|
37
|
+
latestVersion?: string;
|
|
38
|
+
draft?: boolean | { title?: string; message: string };
|
|
39
|
+
badges?: Array<{
|
|
40
|
+
id?: string;
|
|
41
|
+
content: string;
|
|
42
|
+
backgroundColor?: string;
|
|
43
|
+
textColor?: string;
|
|
44
|
+
}>;
|
|
45
|
+
producers?: Array<any>;
|
|
46
|
+
consumers?: Array<any>;
|
|
47
|
+
receives?: Array<any>;
|
|
48
|
+
sends?: Array<any>;
|
|
49
|
+
services?: Array<any>;
|
|
50
|
+
servicesThatWriteToContainer?: Array<any>;
|
|
51
|
+
servicesThatReadFromContainer?: Array<any>;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PropertyOption {
|
|
56
|
+
id: string;
|
|
57
|
+
label: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface DiscoverTableProps<T extends DiscoverTableData> {
|
|
61
|
+
data: T[];
|
|
62
|
+
collectionType: CollectionType;
|
|
63
|
+
collectionLabel: string;
|
|
64
|
+
domains?: Array<{ id: string; name: string; version: string }>;
|
|
65
|
+
owners?: Array<{ id: string; name: string; type?: 'user' | 'team' }>;
|
|
66
|
+
producers?: Array<{ id: string; name: string }>;
|
|
67
|
+
consumers?: Array<{ id: string; name: string }>;
|
|
68
|
+
propertyOptions?: PropertyOption[];
|
|
69
|
+
tableConfiguration?: TableConfiguration;
|
|
70
|
+
showDomainsFilter?: boolean;
|
|
71
|
+
showOwnersFilter?: boolean;
|
|
72
|
+
showProducersFilter?: boolean;
|
|
73
|
+
showConsumersFilter?: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function DiscoverTable<T extends DiscoverTableData>({
|
|
77
|
+
data: initialData,
|
|
78
|
+
collectionType,
|
|
79
|
+
collectionLabel,
|
|
80
|
+
domains = [],
|
|
81
|
+
owners = [],
|
|
82
|
+
producers = [],
|
|
83
|
+
consumers = [],
|
|
84
|
+
propertyOptions = [],
|
|
85
|
+
tableConfiguration,
|
|
86
|
+
showDomainsFilter = true,
|
|
87
|
+
showOwnersFilter = true,
|
|
88
|
+
showProducersFilter = false,
|
|
89
|
+
showConsumersFilter = false,
|
|
90
|
+
}: DiscoverTableProps<T>) {
|
|
91
|
+
// Generate columns inside the React component to avoid hydration issues
|
|
92
|
+
const columns = useMemo(
|
|
93
|
+
() => getDiscoverColumns(collectionType, tableConfiguration ?? { columns: {} }),
|
|
94
|
+
[collectionType, tableConfiguration]
|
|
95
|
+
);
|
|
96
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
|
97
|
+
const [globalFilter, setGlobalFilter] = useState('');
|
|
98
|
+
const [tableFilter, setTableFilter] = useState('');
|
|
99
|
+
const [showOnlyLatest, setShowOnlyLatest] = useState(true);
|
|
100
|
+
const [onlyShowDrafts, setOnlyShowDrafts] = useState(false);
|
|
101
|
+
const [selectedDomains, setSelectedDomains] = useState<string[]>([]);
|
|
102
|
+
const [selectedOwners, setSelectedOwners] = useState<string[]>([]);
|
|
103
|
+
const [selectedProducers, setSelectedProducers] = useState<string[]>([]);
|
|
104
|
+
const [selectedConsumers, setSelectedConsumers] = useState<string[]>([]);
|
|
105
|
+
const [selectedBadges, setSelectedBadges] = useState<string[]>([]);
|
|
106
|
+
const [selectedProperties, setSelectedProperties] = useState<string[]>([]);
|
|
107
|
+
|
|
108
|
+
// Collect unique badges from all items
|
|
109
|
+
const allBadges = useMemo(() => {
|
|
110
|
+
const badgeMap = new Map<string, { content: string; backgroundColor?: string; textColor?: string; count: number }>();
|
|
111
|
+
initialData.forEach((item) => {
|
|
112
|
+
const badges = item.data?.badges || [];
|
|
113
|
+
badges.forEach((badge: any) => {
|
|
114
|
+
const existing = badgeMap.get(badge.content);
|
|
115
|
+
if (existing) {
|
|
116
|
+
existing.count++;
|
|
117
|
+
} else {
|
|
118
|
+
badgeMap.set(badge.content, {
|
|
119
|
+
content: badge.content,
|
|
120
|
+
backgroundColor: badge.backgroundColor,
|
|
121
|
+
textColor: badge.textColor,
|
|
122
|
+
count: 1,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
return Array.from(badgeMap.values()).sort((a, b) => b.count - a.count);
|
|
128
|
+
}, [initialData]);
|
|
129
|
+
|
|
130
|
+
// Filter data based on all filter states
|
|
131
|
+
const filteredData = useMemo(() => {
|
|
132
|
+
return initialData.filter((row) => {
|
|
133
|
+
// Check if item is a draft
|
|
134
|
+
const isDraft = row.data.draft === true || (typeof row.data.draft === 'object' && row.data.draft !== null);
|
|
135
|
+
|
|
136
|
+
// Draft filter
|
|
137
|
+
if (onlyShowDrafts && !isDraft) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (onlyShowDrafts) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Latest version filter
|
|
146
|
+
if (showOnlyLatest && !isSameVersion(row.data.version, row.data.latestVersion)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Domain filter
|
|
151
|
+
if (selectedDomains.length > 0) {
|
|
152
|
+
const itemDomains = row.domains || [];
|
|
153
|
+
const hasMatchingDomain = itemDomains.some((d) => selectedDomains.includes(d.id));
|
|
154
|
+
if (!hasMatchingDomain) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Owner filter
|
|
160
|
+
if (selectedOwners.length > 0) {
|
|
161
|
+
const itemOwners = row.owners || [];
|
|
162
|
+
const hasMatchingOwner = itemOwners.some((o) => selectedOwners.includes(o.id));
|
|
163
|
+
if (!hasMatchingOwner) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Producer filter
|
|
169
|
+
if (selectedProducers.length > 0) {
|
|
170
|
+
const itemProducers = row.data.producers || [];
|
|
171
|
+
const hasMatchingProducer = itemProducers.some((p: any) => selectedProducers.includes(p.data?.id || p.id));
|
|
172
|
+
if (!hasMatchingProducer) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Consumer filter
|
|
178
|
+
if (selectedConsumers.length > 0) {
|
|
179
|
+
const itemConsumers = row.data.consumers || [];
|
|
180
|
+
const hasMatchingConsumer = itemConsumers.some((c: any) => selectedConsumers.includes(c.data?.id || c.id));
|
|
181
|
+
if (!hasMatchingConsumer) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Badge filter
|
|
187
|
+
if (selectedBadges.length > 0) {
|
|
188
|
+
const itemBadges = row.data?.badges || [];
|
|
189
|
+
const hasMatchingBadge = itemBadges.some((badge: any) => selectedBadges.includes(badge.content));
|
|
190
|
+
if (!hasMatchingBadge) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Property filters
|
|
196
|
+
if (selectedProperties.length > 0) {
|
|
197
|
+
for (const prop of selectedProperties) {
|
|
198
|
+
// Generic property checks
|
|
199
|
+
if (prop === 'hasSpecifications' && !row.hasSpecifications) return false;
|
|
200
|
+
if (prop === 'hasOwners' && !row.hasOwners) return false;
|
|
201
|
+
if (prop === 'hasRepository' && !row.hasRepository) return false;
|
|
202
|
+
if (prop === 'hasDataDependencies' && !row.hasDataDependencies) return false;
|
|
203
|
+
if (prop === 'isDeprecated' && !row.isDeprecated) return false;
|
|
204
|
+
|
|
205
|
+
// Message-specific checks
|
|
206
|
+
if (prop === 'hasProducers') {
|
|
207
|
+
const producers = row.data.producers || [];
|
|
208
|
+
if (producers.length === 0) return false;
|
|
209
|
+
}
|
|
210
|
+
if (prop === 'hasConsumers') {
|
|
211
|
+
const consumers = row.data.consumers || [];
|
|
212
|
+
if (consumers.length === 0) return false;
|
|
213
|
+
}
|
|
214
|
+
if (prop === 'hasMessages') {
|
|
215
|
+
const sends = row.data.sends || [];
|
|
216
|
+
const receives = row.data.receives || [];
|
|
217
|
+
if (sends.length === 0 && receives.length === 0) return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Service-specific checks
|
|
221
|
+
if (prop === 'hasServices') {
|
|
222
|
+
const services = row.data.services || [];
|
|
223
|
+
if (services.length === 0) return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Container-specific checks
|
|
227
|
+
if (prop === 'hasWriters') {
|
|
228
|
+
const writers = row.data.servicesThatWriteToContainer || [];
|
|
229
|
+
if (writers.length === 0) return false;
|
|
230
|
+
}
|
|
231
|
+
if (prop === 'hasReaders') {
|
|
232
|
+
const readers = row.data.servicesThatReadFromContainer || [];
|
|
233
|
+
if (readers.length === 0) return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Global search filter (sidebar)
|
|
239
|
+
if (globalFilter) {
|
|
240
|
+
const searchLower = globalFilter.toLowerCase();
|
|
241
|
+
const nameMatch = row.data.name.toLowerCase().includes(searchLower);
|
|
242
|
+
const summaryMatch = row.data.summary?.toLowerCase().includes(searchLower);
|
|
243
|
+
if (!nameMatch && !summaryMatch) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Table filter (header)
|
|
249
|
+
if (tableFilter) {
|
|
250
|
+
const searchLower = tableFilter.toLowerCase();
|
|
251
|
+
const nameMatch = row.data.name.toLowerCase().includes(searchLower);
|
|
252
|
+
const summaryMatch = row.data.summary?.toLowerCase().includes(searchLower);
|
|
253
|
+
if (!nameMatch && !summaryMatch) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return true;
|
|
259
|
+
});
|
|
260
|
+
}, [
|
|
261
|
+
initialData,
|
|
262
|
+
showOnlyLatest,
|
|
263
|
+
onlyShowDrafts,
|
|
264
|
+
selectedDomains,
|
|
265
|
+
selectedOwners,
|
|
266
|
+
selectedProducers,
|
|
267
|
+
selectedConsumers,
|
|
268
|
+
selectedBadges,
|
|
269
|
+
selectedProperties,
|
|
270
|
+
globalFilter,
|
|
271
|
+
tableFilter,
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
const table = useReactTable({
|
|
275
|
+
data: filteredData,
|
|
276
|
+
columns,
|
|
277
|
+
onColumnFiltersChange: setColumnFilters,
|
|
278
|
+
getCoreRowModel: getCoreRowModel(),
|
|
279
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
280
|
+
getFacetedRowModel: getFacetedRowModel(),
|
|
281
|
+
getFacetedUniqueValues: getFacetedUniqueValues(),
|
|
282
|
+
getFacetedMinMaxValues: getFacetedMinMaxValues(),
|
|
283
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
284
|
+
state: {
|
|
285
|
+
columnFilters,
|
|
286
|
+
columnVisibility: Object.fromEntries(
|
|
287
|
+
Object.entries(tableConfiguration?.columns ?? {}).map(([key, value]) => [key, value.visible ?? true])
|
|
288
|
+
),
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const totalResults = table.getPrePaginationRowModel().rows.length;
|
|
293
|
+
const hasResults = table.getRowModel().rows.length > 0;
|
|
294
|
+
|
|
295
|
+
// Count items per domain for the filter
|
|
296
|
+
const domainCounts = useMemo(() => {
|
|
297
|
+
const counts: Record<string, number> = {};
|
|
298
|
+
initialData.forEach((item) => {
|
|
299
|
+
const itemDomains = item.domains || [];
|
|
300
|
+
itemDomains.forEach((d) => {
|
|
301
|
+
counts[d.id] = (counts[d.id] || 0) + 1;
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
return counts;
|
|
305
|
+
}, [initialData]);
|
|
306
|
+
|
|
307
|
+
// Count items per owner for the filter
|
|
308
|
+
const ownerCounts = useMemo(() => {
|
|
309
|
+
const counts: Record<string, number> = {};
|
|
310
|
+
initialData.forEach((item) => {
|
|
311
|
+
const itemOwners = item.owners || [];
|
|
312
|
+
itemOwners.forEach((o) => {
|
|
313
|
+
counts[o.id] = (counts[o.id] || 0) + 1;
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
return counts;
|
|
317
|
+
}, [initialData]);
|
|
318
|
+
|
|
319
|
+
// Count items per producer for the filter
|
|
320
|
+
const producerCounts = useMemo(() => {
|
|
321
|
+
const counts: Record<string, number> = {};
|
|
322
|
+
initialData.forEach((item) => {
|
|
323
|
+
const itemProducers = item.data.producers || [];
|
|
324
|
+
itemProducers.forEach((p: any) => {
|
|
325
|
+
const id = p.data?.id || p.id;
|
|
326
|
+
if (id) counts[id] = (counts[id] || 0) + 1;
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
return counts;
|
|
330
|
+
}, [initialData]);
|
|
331
|
+
|
|
332
|
+
// Count items per consumer for the filter
|
|
333
|
+
const consumerCounts = useMemo(() => {
|
|
334
|
+
const counts: Record<string, number> = {};
|
|
335
|
+
initialData.forEach((item) => {
|
|
336
|
+
const itemConsumers = item.data.consumers || [];
|
|
337
|
+
itemConsumers.forEach((c: any) => {
|
|
338
|
+
const id = c.data?.id || c.id;
|
|
339
|
+
if (id) counts[id] = (counts[id] || 0) + 1;
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
return counts;
|
|
343
|
+
}, [initialData]);
|
|
344
|
+
|
|
345
|
+
const toggleDomain = (domainId: string) => {
|
|
346
|
+
setSelectedDomains((prev) => (prev.includes(domainId) ? prev.filter((id) => id !== domainId) : [...prev, domainId]));
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const toggleOwner = (ownerId: string) => {
|
|
350
|
+
setSelectedOwners((prev) => (prev.includes(ownerId) ? prev.filter((id) => id !== ownerId) : [...prev, ownerId]));
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const toggleProducer = (producerId: string) => {
|
|
354
|
+
setSelectedProducers((prev) => (prev.includes(producerId) ? prev.filter((id) => id !== producerId) : [...prev, producerId]));
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const toggleConsumer = (consumerId: string) => {
|
|
358
|
+
setSelectedConsumers((prev) => (prev.includes(consumerId) ? prev.filter((id) => id !== consumerId) : [...prev, consumerId]));
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const toggleBadge = (badgeContent: string) => {
|
|
362
|
+
setSelectedBadges((prev) => (prev.includes(badgeContent) ? prev.filter((b) => b !== badgeContent) : [...prev, badgeContent]));
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const toggleProperty = (propertyId: string) => {
|
|
366
|
+
setSelectedProperties((prev) => (prev.includes(propertyId) ? prev.filter((p) => p !== propertyId) : [...prev, propertyId]));
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const clearAllFilters = () => {
|
|
370
|
+
setSelectedDomains([]);
|
|
371
|
+
setSelectedOwners([]);
|
|
372
|
+
setSelectedProducers([]);
|
|
373
|
+
setSelectedConsumers([]);
|
|
374
|
+
setSelectedBadges([]);
|
|
375
|
+
setSelectedProperties([]);
|
|
376
|
+
setShowOnlyLatest(true);
|
|
377
|
+
setOnlyShowDrafts(false);
|
|
378
|
+
setGlobalFilter('');
|
|
379
|
+
setTableFilter('');
|
|
380
|
+
setColumnFilters([]);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const activeFilterCount =
|
|
384
|
+
selectedDomains.length +
|
|
385
|
+
selectedOwners.length +
|
|
386
|
+
selectedProducers.length +
|
|
387
|
+
selectedConsumers.length +
|
|
388
|
+
selectedBadges.length +
|
|
389
|
+
selectedProperties.length +
|
|
390
|
+
(!showOnlyLatest ? 1 : 0) +
|
|
391
|
+
(onlyShowDrafts ? 1 : 0);
|
|
392
|
+
|
|
393
|
+
// Get selected domain names for display
|
|
394
|
+
const selectedDomainNames = selectedDomains.map((id) => domains.find((d) => d.id === id)?.name || id);
|
|
395
|
+
|
|
396
|
+
// Get selected owner names for display
|
|
397
|
+
const selectedOwnerNames = selectedOwners.map((id) => owners.find((o) => o.id === id)?.name || id);
|
|
398
|
+
|
|
399
|
+
// Get selected producer names for display
|
|
400
|
+
const selectedProducerNames = selectedProducers.map((id) => producers.find((p) => p.id === id)?.name || id);
|
|
401
|
+
|
|
402
|
+
// Get selected consumer names for display
|
|
403
|
+
const selectedConsumerNames = selectedConsumers.map((id) => consumers.find((c) => c.id === id)?.name || id);
|
|
404
|
+
|
|
405
|
+
// Filter producers/consumers to only show those with count > 0
|
|
406
|
+
const filteredProducers = producers.filter((p) => (producerCounts[p.id] || 0) > 0);
|
|
407
|
+
const filteredConsumers = consumers.filter((c) => (consumerCounts[c.id] || 0) > 0);
|
|
408
|
+
|
|
409
|
+
// Get selected property labels for display
|
|
410
|
+
const selectedPropertyLabels = selectedProperties.map((id) => propertyOptions.find((p) => p.id === id)?.label || id);
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<div className="flex gap-8 items-start">
|
|
414
|
+
{/* Filter Sidebar */}
|
|
415
|
+
<div className="w-72 flex-shrink-0 space-y-6 p-4 bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))] border border-[rgb(var(--ec-page-border))] rounded-xl">
|
|
416
|
+
{/* Search */}
|
|
417
|
+
<DebouncedInput
|
|
418
|
+
type="text"
|
|
419
|
+
value={globalFilter}
|
|
420
|
+
onChange={(value) => setGlobalFilter(String(value))}
|
|
421
|
+
placeholder={`Search ${collectionLabel.toLowerCase()}...`}
|
|
422
|
+
className="w-full px-3 py-2 text-sm bg-[rgb(var(--ec-input-bg))] text-[rgb(var(--ec-input-text))] border border-[rgb(var(--ec-page-border))] rounded-lg placeholder:text-[rgb(var(--ec-input-placeholder))] focus:outline-none focus:ring-2 focus:ring-[rgb(var(--ec-accent)/0.2)] focus:border-[rgb(var(--ec-accent))] transition-colors"
|
|
423
|
+
/>
|
|
424
|
+
|
|
425
|
+
{/* Message Filters Section */}
|
|
426
|
+
{(showProducersFilter || showConsumersFilter) && (filteredProducers.length > 0 || filteredConsumers.length > 0) && (
|
|
427
|
+
<div className="space-y-2">
|
|
428
|
+
<h3 className="text-[11px] font-bold uppercase tracking-widest text-[rgb(var(--ec-page-text-muted))]">
|
|
429
|
+
Message Filters
|
|
430
|
+
</h3>
|
|
431
|
+
|
|
432
|
+
{/* Producers Filter */}
|
|
433
|
+
{showProducersFilter && filteredProducers.length > 0 && (
|
|
434
|
+
<div>
|
|
435
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Producers</label>
|
|
436
|
+
<FilterDropdown
|
|
437
|
+
label="Select producers..."
|
|
438
|
+
selectedItems={selectedProducerNames}
|
|
439
|
+
onClear={() => setSelectedProducers([])}
|
|
440
|
+
onRemoveItem={(name) => {
|
|
441
|
+
const producer = filteredProducers.find((p) => p.name === name);
|
|
442
|
+
if (producer) toggleProducer(producer.id);
|
|
443
|
+
}}
|
|
444
|
+
>
|
|
445
|
+
{filteredProducers.map((producer) => (
|
|
446
|
+
<CheckboxItem
|
|
447
|
+
key={producer.id}
|
|
448
|
+
label={producer.name}
|
|
449
|
+
checked={selectedProducers.includes(producer.id)}
|
|
450
|
+
onChange={() => toggleProducer(producer.id)}
|
|
451
|
+
count={producerCounts[producer.id] || 0}
|
|
452
|
+
/>
|
|
453
|
+
))}
|
|
454
|
+
</FilterDropdown>
|
|
455
|
+
</div>
|
|
456
|
+
)}
|
|
457
|
+
|
|
458
|
+
{/* Consumers Filter */}
|
|
459
|
+
{showConsumersFilter && filteredConsumers.length > 0 && (
|
|
460
|
+
<div>
|
|
461
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Consumers</label>
|
|
462
|
+
<FilterDropdown
|
|
463
|
+
label="Select consumers..."
|
|
464
|
+
selectedItems={selectedConsumerNames}
|
|
465
|
+
onClear={() => setSelectedConsumers([])}
|
|
466
|
+
onRemoveItem={(name) => {
|
|
467
|
+
const consumer = filteredConsumers.find((c) => c.name === name);
|
|
468
|
+
if (consumer) toggleConsumer(consumer.id);
|
|
469
|
+
}}
|
|
470
|
+
>
|
|
471
|
+
{filteredConsumers.map((consumer) => (
|
|
472
|
+
<CheckboxItem
|
|
473
|
+
key={consumer.id}
|
|
474
|
+
label={consumer.name}
|
|
475
|
+
checked={selectedConsumers.includes(consumer.id)}
|
|
476
|
+
onChange={() => toggleConsumer(consumer.id)}
|
|
477
|
+
count={consumerCounts[consumer.id] || 0}
|
|
478
|
+
/>
|
|
479
|
+
))}
|
|
480
|
+
</FilterDropdown>
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
</div>
|
|
484
|
+
)}
|
|
485
|
+
|
|
486
|
+
{/* Catalog Filters Section */}
|
|
487
|
+
<div className="space-y-2">
|
|
488
|
+
<h3 className="text-[11px] font-bold uppercase tracking-widest text-[rgb(var(--ec-page-text-muted))]">
|
|
489
|
+
Catalog Filters
|
|
490
|
+
</h3>
|
|
491
|
+
|
|
492
|
+
{/* Domains Filter */}
|
|
493
|
+
{showDomainsFilter && domains.length > 0 && (
|
|
494
|
+
<div>
|
|
495
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Domains</label>
|
|
496
|
+
<FilterDropdown
|
|
497
|
+
label="Select domains..."
|
|
498
|
+
selectedItems={selectedDomainNames}
|
|
499
|
+
onClear={() => setSelectedDomains([])}
|
|
500
|
+
onRemoveItem={(name) => {
|
|
501
|
+
const domain = domains.find((d) => d.name === name);
|
|
502
|
+
if (domain) toggleDomain(domain.id);
|
|
503
|
+
}}
|
|
504
|
+
>
|
|
505
|
+
{domains.map((domain) => (
|
|
506
|
+
<CheckboxItem
|
|
507
|
+
key={domain.id}
|
|
508
|
+
label={domain.name}
|
|
509
|
+
checked={selectedDomains.includes(domain.id)}
|
|
510
|
+
onChange={() => toggleDomain(domain.id)}
|
|
511
|
+
count={domainCounts[domain.id] || 0}
|
|
512
|
+
/>
|
|
513
|
+
))}
|
|
514
|
+
</FilterDropdown>
|
|
515
|
+
</div>
|
|
516
|
+
)}
|
|
517
|
+
|
|
518
|
+
{/* Owners Filter */}
|
|
519
|
+
{showOwnersFilter && owners.length > 0 && (
|
|
520
|
+
<div>
|
|
521
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Owners</label>
|
|
522
|
+
<FilterDropdown
|
|
523
|
+
label="Select owners..."
|
|
524
|
+
selectedItems={selectedOwnerNames}
|
|
525
|
+
onClear={() => setSelectedOwners([])}
|
|
526
|
+
onRemoveItem={(name) => {
|
|
527
|
+
const owner = owners.find((o) => o.name === name);
|
|
528
|
+
if (owner) toggleOwner(owner.id);
|
|
529
|
+
}}
|
|
530
|
+
>
|
|
531
|
+
{/* Users section */}
|
|
532
|
+
{owners.filter((o) => o.type !== 'team').length > 0 && (
|
|
533
|
+
<>
|
|
534
|
+
<div className="px-2 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))] flex items-center gap-1.5">
|
|
535
|
+
<UserIcon className="w-3 h-3" />
|
|
536
|
+
Users
|
|
537
|
+
</div>
|
|
538
|
+
{owners
|
|
539
|
+
.filter((o) => o.type !== 'team')
|
|
540
|
+
.map((owner) => (
|
|
541
|
+
<CheckboxItem
|
|
542
|
+
key={owner.id}
|
|
543
|
+
label={owner.name}
|
|
544
|
+
checked={selectedOwners.includes(owner.id)}
|
|
545
|
+
onChange={() => toggleOwner(owner.id)}
|
|
546
|
+
count={ownerCounts[owner.id] || 0}
|
|
547
|
+
/>
|
|
548
|
+
))}
|
|
549
|
+
</>
|
|
550
|
+
)}
|
|
551
|
+
{/* Teams section */}
|
|
552
|
+
{owners.filter((o) => o.type === 'team').length > 0 && (
|
|
553
|
+
<>
|
|
554
|
+
<div className="px-2 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-[rgb(var(--ec-page-text-muted))] flex items-center gap-1.5 mt-2 border-t border-[rgb(var(--ec-page-border))] pt-2">
|
|
555
|
+
<Users className="w-3 h-3" />
|
|
556
|
+
Teams
|
|
557
|
+
</div>
|
|
558
|
+
{owners
|
|
559
|
+
.filter((o) => o.type === 'team')
|
|
560
|
+
.map((owner) => (
|
|
561
|
+
<CheckboxItem
|
|
562
|
+
key={owner.id}
|
|
563
|
+
label={owner.name}
|
|
564
|
+
checked={selectedOwners.includes(owner.id)}
|
|
565
|
+
onChange={() => toggleOwner(owner.id)}
|
|
566
|
+
count={ownerCounts[owner.id] || 0}
|
|
567
|
+
/>
|
|
568
|
+
))}
|
|
569
|
+
</>
|
|
570
|
+
)}
|
|
571
|
+
</FilterDropdown>
|
|
572
|
+
</div>
|
|
573
|
+
)}
|
|
574
|
+
|
|
575
|
+
{/* Badges Filter */}
|
|
576
|
+
{allBadges.length > 0 && (
|
|
577
|
+
<div>
|
|
578
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Badges</label>
|
|
579
|
+
<FilterDropdown
|
|
580
|
+
label="Select badges..."
|
|
581
|
+
selectedItems={selectedBadges}
|
|
582
|
+
onClear={() => setSelectedBadges([])}
|
|
583
|
+
onRemoveItem={(badge) => toggleBadge(badge)}
|
|
584
|
+
>
|
|
585
|
+
{allBadges.map((badge) => (
|
|
586
|
+
<CheckboxItem
|
|
587
|
+
key={badge.content}
|
|
588
|
+
label={badge.content}
|
|
589
|
+
checked={selectedBadges.includes(badge.content)}
|
|
590
|
+
onChange={() => toggleBadge(badge.content)}
|
|
591
|
+
count={badge.count}
|
|
592
|
+
/>
|
|
593
|
+
))}
|
|
594
|
+
</FilterDropdown>
|
|
595
|
+
</div>
|
|
596
|
+
)}
|
|
597
|
+
|
|
598
|
+
{/* Properties Filter */}
|
|
599
|
+
{propertyOptions.length > 0 && (
|
|
600
|
+
<div>
|
|
601
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Properties</label>
|
|
602
|
+
<FilterDropdown
|
|
603
|
+
label="Select properties..."
|
|
604
|
+
selectedItems={selectedPropertyLabels}
|
|
605
|
+
onClear={() => setSelectedProperties([])}
|
|
606
|
+
onRemoveItem={(label) => {
|
|
607
|
+
const prop = propertyOptions.find((p) => p.label === label);
|
|
608
|
+
if (prop) toggleProperty(prop.id);
|
|
609
|
+
}}
|
|
610
|
+
>
|
|
611
|
+
{propertyOptions.map((option) => (
|
|
612
|
+
<CheckboxItem
|
|
613
|
+
key={option.id}
|
|
614
|
+
label={option.label}
|
|
615
|
+
checked={selectedProperties.includes(option.id)}
|
|
616
|
+
onChange={() => toggleProperty(option.id)}
|
|
617
|
+
/>
|
|
618
|
+
))}
|
|
619
|
+
</FilterDropdown>
|
|
620
|
+
</div>
|
|
621
|
+
)}
|
|
622
|
+
|
|
623
|
+
{/* Version Filter */}
|
|
624
|
+
<div>
|
|
625
|
+
<label className="block text-xs font-medium text-[rgb(var(--ec-page-text-muted))] mb-1.5">Version</label>
|
|
626
|
+
<FilterDropdown
|
|
627
|
+
label="Select version..."
|
|
628
|
+
selectedItems={[...(showOnlyLatest ? ['Latest only'] : []), ...(onlyShowDrafts ? ['Drafts only'] : [])]}
|
|
629
|
+
onClear={() => {
|
|
630
|
+
setShowOnlyLatest(true);
|
|
631
|
+
setOnlyShowDrafts(false);
|
|
632
|
+
}}
|
|
633
|
+
onRemoveItem={(item) => {
|
|
634
|
+
if (item === 'Latest only') setShowOnlyLatest(false);
|
|
635
|
+
if (item === 'Drafts only') setOnlyShowDrafts(false);
|
|
636
|
+
}}
|
|
637
|
+
>
|
|
638
|
+
<CheckboxItem
|
|
639
|
+
label="Latest version only"
|
|
640
|
+
checked={showOnlyLatest}
|
|
641
|
+
onChange={() => setShowOnlyLatest(!showOnlyLatest)}
|
|
642
|
+
/>
|
|
643
|
+
<CheckboxItem label="Drafts only" checked={onlyShowDrafts} onChange={() => setOnlyShowDrafts(!onlyShowDrafts)} />
|
|
644
|
+
</FilterDropdown>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
{/* Results & Clear */}
|
|
649
|
+
<div className="flex items-center justify-between pt-4 mt-2 border-t border-[rgb(var(--ec-page-border))]">
|
|
650
|
+
<span className="text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
651
|
+
<span className="font-semibold text-[rgb(var(--ec-page-text))]">{totalResults}</span> results
|
|
652
|
+
</span>
|
|
653
|
+
{activeFilterCount > 0 && (
|
|
654
|
+
<button onClick={clearAllFilters} className="text-xs font-medium text-[rgb(var(--ec-accent))] hover:underline">
|
|
655
|
+
Clear all
|
|
656
|
+
</button>
|
|
657
|
+
)}
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
{/* Main Table */}
|
|
662
|
+
<div className="flex-1 min-w-0">
|
|
663
|
+
{/* Table Header */}
|
|
664
|
+
<div className="flex items-center justify-between mb-5">
|
|
665
|
+
<h2 className="text-xl font-bold text-[rgb(var(--ec-page-text))]">
|
|
666
|
+
{collectionLabel}{' '}
|
|
667
|
+
<span className="text-base text-[rgb(var(--ec-page-text-muted))] font-normal ml-1">({totalResults})</span>
|
|
668
|
+
</h2>
|
|
669
|
+
<div className="relative">
|
|
670
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[rgb(var(--ec-icon-color))]" />
|
|
671
|
+
<input
|
|
672
|
+
type="text"
|
|
673
|
+
value={tableFilter}
|
|
674
|
+
onChange={(e) => setTableFilter(e.target.value)}
|
|
675
|
+
placeholder="Filter..."
|
|
676
|
+
className="pl-9 pr-3 py-1.5 text-sm w-48 bg-[rgb(var(--ec-input-bg))] text-[rgb(var(--ec-input-text))] border border-[rgb(var(--ec-page-border))] rounded-lg placeholder:text-[rgb(var(--ec-input-placeholder))] focus:outline-none focus:ring-2 focus:ring-[rgb(var(--ec-accent)/0.2)] focus:border-[rgb(var(--ec-accent))] transition-colors"
|
|
677
|
+
/>
|
|
678
|
+
{tableFilter && (
|
|
679
|
+
<button
|
|
680
|
+
onClick={() => setTableFilter('')}
|
|
681
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))]"
|
|
682
|
+
>
|
|
683
|
+
<X className="w-3.5 h-3.5" />
|
|
684
|
+
</button>
|
|
685
|
+
)}
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
{/* Table */}
|
|
690
|
+
<div className="rounded-xl border border-[rgb(var(--ec-page-border))] overflow-hidden shadow-sm">
|
|
691
|
+
<table className="min-w-full divide-y divide-[rgb(var(--ec-page-border))]">
|
|
692
|
+
<thead className="bg-[rgb(var(--ec-content-hover))] sticky top-0 z-10 border-b-2 border-[rgb(var(--ec-page-border))]">
|
|
693
|
+
{table.getHeaderGroups().map((headerGroup, index) => (
|
|
694
|
+
<tr key={`${headerGroup}-${index}`}>
|
|
695
|
+
{headerGroup.headers.map((header) => (
|
|
696
|
+
<th
|
|
697
|
+
key={`${header.id}`}
|
|
698
|
+
className="px-4 py-3.5 text-left text-[11px] font-bold text-[rgb(var(--ec-page-text-muted))] uppercase tracking-widest"
|
|
699
|
+
>
|
|
700
|
+
<div className="flex flex-col gap-2">
|
|
701
|
+
<div>{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}</div>
|
|
702
|
+
</div>
|
|
703
|
+
</th>
|
|
704
|
+
))}
|
|
705
|
+
</tr>
|
|
706
|
+
))}
|
|
707
|
+
</thead>
|
|
708
|
+
|
|
709
|
+
<tbody className="bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))] divide-y divide-[rgb(var(--ec-page-border)/0.5)]">
|
|
710
|
+
{hasResults ? (
|
|
711
|
+
table.getRowModel().rows.map((row, index) => (
|
|
712
|
+
<tr
|
|
713
|
+
key={`${row.id}-${index}`}
|
|
714
|
+
className={`group hover:bg-[rgb(var(--ec-accent)/0.04)] transition-all duration-150 border-l-2 border-transparent hover:border-[rgb(var(--ec-accent))] ${
|
|
715
|
+
index % 2 === 1 ? 'bg-[rgb(var(--ec-page-bg)/0.5)]' : ''
|
|
716
|
+
}`}
|
|
717
|
+
>
|
|
718
|
+
{row.getVisibleCells().map((cell) => (
|
|
719
|
+
<td
|
|
720
|
+
key={cell.id}
|
|
721
|
+
className={`px-4 py-4 text-sm text-[rgb(var(--ec-page-text))] ${cell.column.columnDef.meta?.className || ''}`}
|
|
722
|
+
>
|
|
723
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
724
|
+
</td>
|
|
725
|
+
))}
|
|
726
|
+
</tr>
|
|
727
|
+
))
|
|
728
|
+
) : (
|
|
729
|
+
<tr>
|
|
730
|
+
<td colSpan={table.getAllColumns().length} className="px-4 py-12 text-center">
|
|
731
|
+
<div className="flex flex-col items-center justify-center text-[rgb(var(--ec-page-text-muted))]">
|
|
732
|
+
<SearchX className="w-10 h-10 text-[rgb(var(--ec-icon-color))] mb-3 opacity-50" />
|
|
733
|
+
<p className="text-sm font-medium text-[rgb(var(--ec-page-text-muted))]">No results found</p>
|
|
734
|
+
<p className="text-xs text-[rgb(var(--ec-icon-color))] mt-1">Try adjusting your search or filters</p>
|
|
735
|
+
{activeFilterCount > 0 && (
|
|
736
|
+
<button onClick={clearAllFilters} className="mt-3 text-sm text-[rgb(var(--ec-accent))] hover:underline">
|
|
737
|
+
Clear all filters
|
|
738
|
+
</button>
|
|
739
|
+
)}
|
|
740
|
+
</div>
|
|
741
|
+
</td>
|
|
742
|
+
</tr>
|
|
743
|
+
)}
|
|
744
|
+
</tbody>
|
|
745
|
+
</table>
|
|
746
|
+
</div>
|
|
747
|
+
|
|
748
|
+
{/* Pagination */}
|
|
749
|
+
<div className="flex items-center justify-between px-1 py-4">
|
|
750
|
+
<div className="text-sm text-[rgb(var(--ec-page-text-muted))]">
|
|
751
|
+
{totalResults > 0 && (
|
|
752
|
+
<span>
|
|
753
|
+
Showing <span className="font-medium text-[rgb(var(--ec-page-text))]">{table.getRowModel().rows.length}</span> of{' '}
|
|
754
|
+
<span className="font-medium text-[rgb(var(--ec-page-text))]">{totalResults}</span> results
|
|
755
|
+
</span>
|
|
756
|
+
)}
|
|
757
|
+
</div>
|
|
758
|
+
<div className="flex items-center gap-2">
|
|
759
|
+
<div className="flex items-center rounded-lg border border-[rgb(var(--ec-page-border))] bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))]">
|
|
760
|
+
<button
|
|
761
|
+
className="p-2 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors rounded-l-lg"
|
|
762
|
+
onClick={() => table.setPageIndex(0)}
|
|
763
|
+
disabled={!table.getCanPreviousPage()}
|
|
764
|
+
title="First page"
|
|
765
|
+
>
|
|
766
|
+
<ChevronsLeft className="w-4 h-4" />
|
|
767
|
+
</button>
|
|
768
|
+
<button
|
|
769
|
+
className="p-2 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors border-l border-[rgb(var(--ec-page-border))]"
|
|
770
|
+
onClick={() => table.previousPage()}
|
|
771
|
+
disabled={!table.getCanPreviousPage()}
|
|
772
|
+
title="Previous page"
|
|
773
|
+
>
|
|
774
|
+
<ChevronLeft className="w-4 h-4" />
|
|
775
|
+
</button>
|
|
776
|
+
<span className="px-3 py-2 text-sm text-[rgb(var(--ec-page-text-muted))] border-l border-r border-[rgb(var(--ec-page-border))] min-w-[100px] text-center">
|
|
777
|
+
Page{' '}
|
|
778
|
+
<span className="font-medium text-[rgb(var(--ec-page-text))]">{table.getState().pagination.pageIndex + 1}</span>{' '}
|
|
779
|
+
of <span className="font-medium text-[rgb(var(--ec-page-text))]">{table.getPageCount() || 1}</span>
|
|
780
|
+
</span>
|
|
781
|
+
<button
|
|
782
|
+
className="p-2 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors border-r border-[rgb(var(--ec-page-border))]"
|
|
783
|
+
onClick={() => table.nextPage()}
|
|
784
|
+
disabled={!table.getCanNextPage()}
|
|
785
|
+
title="Next page"
|
|
786
|
+
>
|
|
787
|
+
<ChevronRight className="w-4 h-4" />
|
|
788
|
+
</button>
|
|
789
|
+
<button
|
|
790
|
+
className="p-2 text-[rgb(var(--ec-icon-color))] hover:text-[rgb(var(--ec-page-text))] hover:bg-[rgb(var(--ec-content-hover))] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent transition-colors rounded-r-lg"
|
|
791
|
+
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
792
|
+
disabled={!table.getCanNextPage()}
|
|
793
|
+
title="Last page"
|
|
794
|
+
>
|
|
795
|
+
<ChevronsRight className="w-4 h-4" />
|
|
796
|
+
</button>
|
|
797
|
+
</div>
|
|
798
|
+
<select
|
|
799
|
+
value={table.getState().pagination.pageSize}
|
|
800
|
+
onChange={(e) => {
|
|
801
|
+
table.setPageSize(Number(e.target.value));
|
|
802
|
+
}}
|
|
803
|
+
className="px-3 py-2 text-sm text-[rgb(var(--ec-page-text-muted))] bg-[rgb(var(--ec-card-bg,var(--ec-page-bg)))] border border-[rgb(var(--ec-page-border))] rounded-lg hover:border-[rgb(var(--ec-icon-color))] focus:outline-none focus:ring-2 focus:ring-[rgb(var(--ec-accent)/0.2)] transition-colors"
|
|
804
|
+
>
|
|
805
|
+
{[10, 20, 30, 40, 50].map((pageSize) => (
|
|
806
|
+
<option key={pageSize} value={pageSize}>
|
|
807
|
+
{pageSize} per page
|
|
808
|
+
</option>
|
|
809
|
+
))}
|
|
810
|
+
</select>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
);
|
|
816
|
+
}
|