@redsift/ds-mcp-server 12.3.1-muiv6-alpha.2
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 +203 -0
- package/consumer-instructions/.cursorrules +80 -0
- package/consumer-instructions/.windsurfrules +80 -0
- package/consumer-instructions/CLAUDE.md +87 -0
- package/consumer-instructions/redsift-design-system.instructions.md +87 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/example.tsx +750 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/with-empty-state.tsx +111 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/with-error.tsx +122 -0
- package/data/demos/patterns/crossfiltered-datagrid-page/with-loading.tsx +88 -0
- package/data/demos/patterns/datagrid-page/example.tsx +521 -0
- package/data/demos/patterns/datagrid-page/with-empty-state.tsx +80 -0
- package/data/demos/patterns/datagrid-page/with-error.tsx +96 -0
- package/data/demos/patterns/datagrid-page/with-loading.tsx +57 -0
- package/data/demos/patterns/drilldown-datagrid-page/example.tsx +715 -0
- package/data/demos/patterns/drilldown-datagrid-page/with-empty-state.tsx +113 -0
- package/data/demos/patterns/drilldown-datagrid-page/with-error.tsx +125 -0
- package/data/demos/patterns/drilldown-datagrid-page/with-loading.tsx +90 -0
- package/data/demos/patterns/server-datagrid-page/example.tsx +757 -0
- package/data/demos/patterns/server-datagrid-page/with-empty-state.tsx +107 -0
- package/data/demos/patterns/server-datagrid-page/with-error.tsx +107 -0
- package/data/demos/patterns/server-datagrid-page/with-loading.tsx +63 -0
- package/data/docs/components/charts/Arc.json +179 -0
- package/data/docs/components/charts/Arcs.json +104 -0
- package/data/docs/components/charts/Axis.json +269 -0
- package/data/docs/components/charts/Bar.json +236 -0
- package/data/docs/components/charts/BarChart.json +852 -0
- package/data/docs/components/charts/BarChartBars.json +18 -0
- package/data/docs/components/charts/BarChartGroupedTooltip.json +9 -0
- package/data/docs/components/charts/BarChartOverlay.json +34 -0
- package/data/docs/components/charts/BarChartSection.json +94 -0
- package/data/docs/components/charts/BaseBarChart.json +852 -0
- package/data/docs/components/charts/ChartContainerTitle.json +574 -0
- package/data/docs/components/charts/DataPoint.json +160 -0
- package/data/docs/components/charts/Dot.json +191 -0
- package/data/docs/components/charts/EmptyBarChart.json +852 -0
- package/data/docs/components/charts/EmptyLineChart.json +753 -0
- package/data/docs/components/charts/EmptyPieChart.json +826 -0
- package/data/docs/components/charts/EmptyScatterPlot.json +802 -0
- package/data/docs/components/charts/Legend.json +510 -0
- package/data/docs/components/charts/LegendItem.json +128 -0
- package/data/docs/components/charts/Line.json +69 -0
- package/data/docs/components/charts/LineChart.json +753 -0
- package/data/docs/components/charts/LoadingBarChart.json +852 -0
- package/data/docs/components/charts/LoadingLineChart.json +753 -0
- package/data/docs/components/charts/LoadingPieChart.json +826 -0
- package/data/docs/components/charts/LoadingScatterPlot.json +802 -0
- package/data/docs/components/charts/PieChart.json +826 -0
- package/data/docs/components/charts/RenderedLineChart.json +753 -0
- package/data/docs/components/charts/RenderedLinearBarChart.json +823 -0
- package/data/docs/components/charts/RenderedOrdinalBarChart.json +852 -0
- package/data/docs/components/charts/RenderedPieChart.json +826 -0
- package/data/docs/components/charts/RenderedScatterPlot.json +802 -0
- package/data/docs/components/charts/ScatterPlot.json +802 -0
- package/data/docs/components/charts/getAxisType.json +9 -0
- package/data/docs/components/charts/getComponentPosition.json +9 -0
- package/data/docs/components/dashboard/ChartEmptyState.json +42 -0
- package/data/docs/components/dashboard/Dashboard.json +26 -0
- package/data/docs/components/dashboard/DataCard.json +300 -0
- package/data/docs/components/dashboard/DataCardBody.json +431 -0
- package/data/docs/components/dashboard/DataCardHeader.json +304 -0
- package/data/docs/components/dashboard/DataCardListbox.json +529 -0
- package/data/docs/components/dashboard/DataRow.json +539 -0
- package/data/docs/components/dashboard/DrilldownItem.json +342 -0
- package/data/docs/components/dashboard/FilterableBarChart.json +83 -0
- package/data/docs/components/dashboard/FilterableDataGrid.json +83 -0
- package/data/docs/components/dashboard/FilterablePieChart.json +83 -0
- package/data/docs/components/dashboard/FilterableScatterPlot.json +83 -0
- package/data/docs/components/dashboard/PdfDocument.json +58 -0
- package/data/docs/components/dashboard/PdfExportButton.json +458 -0
- package/data/docs/components/dashboard/TimeSeriesBarChart.json +172 -0
- package/data/docs/components/dashboard/WithFilters.json +83 -0
- package/data/docs/components/design-system/ActiveDescendantListbox.json +521 -0
- package/data/docs/components/design-system/Alert.json +349 -0
- package/data/docs/components/design-system/AppBar.json +64 -0
- package/data/docs/components/design-system/AppContainer.json +67 -0
- package/data/docs/components/design-system/AppContent.json +566 -0
- package/data/docs/components/design-system/AppSidePanel.json +87 -0
- package/data/docs/components/design-system/Badge.json +293 -0
- package/data/docs/components/design-system/BaseBreadcrumbs.json +298 -0
- package/data/docs/components/design-system/BaseGrid.json +543 -0
- package/data/docs/components/design-system/BaseSkeleton.json +338 -0
- package/data/docs/components/design-system/BreadcrumbItem.json +231 -0
- package/data/docs/components/design-system/Breadcrumbs.json +298 -0
- package/data/docs/components/design-system/Button.json +402 -0
- package/data/docs/components/design-system/ButtonGroup.json +415 -0
- package/data/docs/components/design-system/ButtonLink.json +568 -0
- package/data/docs/components/design-system/Card.json +328 -0
- package/data/docs/components/design-system/CardActions.json +431 -0
- package/data/docs/components/design-system/CardBody.json +431 -0
- package/data/docs/components/design-system/CardHeader.json +428 -0
- package/data/docs/components/design-system/Checkbox.json +426 -0
- package/data/docs/components/design-system/CheckboxGroup.json +382 -0
- package/data/docs/components/design-system/ConditionalWrapper.json +40 -0
- package/data/docs/components/design-system/DetailedCard.json +401 -0
- package/data/docs/components/design-system/DetailedCardCollapsibleSectionItems.json +29 -0
- package/data/docs/components/design-system/DetailedCardHeader.json +37 -0
- package/data/docs/components/design-system/DetailedCardSection.json +90 -0
- package/data/docs/components/design-system/DetailedCardSectionItem.json +109 -0
- package/data/docs/components/design-system/Flexbox.json +523 -0
- package/data/docs/components/design-system/FocusWithinGroup.json +9 -0
- package/data/docs/components/design-system/Grid.json +543 -0
- package/data/docs/components/design-system/GridItem.json +388 -0
- package/data/docs/components/design-system/Heading.json +390 -0
- package/data/docs/components/design-system/Icon.json +325 -0
- package/data/docs/components/design-system/IconButton.json +371 -0
- package/data/docs/components/design-system/IconButtonLink.json +588 -0
- package/data/docs/components/design-system/Item.json +554 -0
- package/data/docs/components/design-system/Link.json +552 -0
- package/data/docs/components/design-system/LinkButton.json +397 -0
- package/data/docs/components/design-system/Listbox.json +529 -0
- package/data/docs/components/design-system/Number.json +773 -0
- package/data/docs/components/design-system/NumberField.json +594 -0
- package/data/docs/components/design-system/Pill.json +378 -0
- package/data/docs/components/design-system/ProgressBar.json +121 -0
- package/data/docs/components/design-system/RadarSvgLinearGradient.json +9 -0
- package/data/docs/components/design-system/Radio.json +415 -0
- package/data/docs/components/design-system/RadioGroup.json +382 -0
- package/data/docs/components/design-system/RenderedListboxItem.json +18 -0
- package/data/docs/components/design-system/RovingTabindexListbox.json +521 -0
- package/data/docs/components/design-system/Shield.json +360 -0
- package/data/docs/components/design-system/SideNavigationMenu.json +144 -0
- package/data/docs/components/design-system/SideNavigationMenuBar.json +89 -0
- package/data/docs/components/design-system/SideNavigationMenuItem.json +319 -0
- package/data/docs/components/design-system/Skeleton.json +338 -0
- package/data/docs/components/design-system/SkeletonCircle.json +338 -0
- package/data/docs/components/design-system/SkeletonText.json +371 -0
- package/data/docs/components/design-system/Spinner.json +291 -0
- package/data/docs/components/design-system/Switch.json +415 -0
- package/data/docs/components/design-system/SwitchGroup.json +382 -0
- package/data/docs/components/design-system/Text.json +418 -0
- package/data/docs/components/design-system/TextArea.json +501 -0
- package/data/docs/components/design-system/TextField.json +539 -0
- package/data/docs/components/design-system/sizeToDimension.json +9 -0
- package/data/docs/components/pickers/BaseCombobox.json +320 -0
- package/data/docs/components/pickers/BaseComboboxContent.json +453 -0
- package/data/docs/components/pickers/BaseMenuButton.json +240 -0
- package/data/docs/components/pickers/BaseMenuButtonContent.json +442 -0
- package/data/docs/components/pickers/BaseSelect.json +258 -0
- package/data/docs/components/pickers/Combobox.json +320 -0
- package/data/docs/components/pickers/ComboboxContent.json +453 -0
- package/data/docs/components/pickers/ComboboxContentFooter.json +431 -0
- package/data/docs/components/pickers/ComboboxContentHeader.json +431 -0
- package/data/docs/components/pickers/ComboboxContentListbox.json +541 -0
- package/data/docs/components/pickers/ComboboxTrigger.json +336 -0
- package/data/docs/components/pickers/Item.json +554 -0
- package/data/docs/components/pickers/MenuButton.json +240 -0
- package/data/docs/components/pickers/MenuButtonContent.json +442 -0
- package/data/docs/components/pickers/MenuButtonContentFooter.json +431 -0
- package/data/docs/components/pickers/MenuButtonContentHeader.json +431 -0
- package/data/docs/components/pickers/MenuButtonContentMenu.json +523 -0
- package/data/docs/components/pickers/MenuButtonTrigger.json +287 -0
- package/data/docs/components/pickers/Select.json +258 -0
- package/data/docs/components/pickers/SelectContent.json +442 -0
- package/data/docs/components/pickers/SelectTrigger.json +298 -0
- package/data/docs/components/popovers/BaseDialog.json +114 -0
- package/data/docs/components/popovers/BaseDialogContent.json +21 -0
- package/data/docs/components/popovers/BasePopover.json +171 -0
- package/data/docs/components/popovers/BaseToggletip.json +184 -0
- package/data/docs/components/popovers/BaseTooltip.json +121 -0
- package/data/docs/components/popovers/Button.json +402 -0
- package/data/docs/components/popovers/ButtonLink.json +568 -0
- package/data/docs/components/popovers/Dialog.json +114 -0
- package/data/docs/components/popovers/DialogContent.json +21 -0
- package/data/docs/components/popovers/DialogContentActions.json +442 -0
- package/data/docs/components/popovers/DialogContentBody.json +442 -0
- package/data/docs/components/popovers/DialogContentHeader.json +76 -0
- package/data/docs/components/popovers/DialogTrigger.json +276 -0
- package/data/docs/components/popovers/IconButton.json +371 -0
- package/data/docs/components/popovers/IconButtonLink.json +588 -0
- package/data/docs/components/popovers/Link.json +552 -0
- package/data/docs/components/popovers/LinkButton.json +397 -0
- package/data/docs/components/popovers/Popover.json +171 -0
- package/data/docs/components/popovers/PopoverContent.json +442 -0
- package/data/docs/components/popovers/PopoverTrigger.json +276 -0
- package/data/docs/components/popovers/Toast.json +145 -0
- package/data/docs/components/popovers/ToastContainer.json +122 -0
- package/data/docs/components/popovers/Toggletip.json +184 -0
- package/data/docs/components/popovers/ToggletipContent.json +402 -0
- package/data/docs/components/popovers/ToggletipTrigger.json +276 -0
- package/data/docs/components/popovers/Tooltip.json +121 -0
- package/data/docs/components/popovers/TooltipContent.json +402 -0
- package/data/docs/components/popovers/TooltipTrigger.json +276 -0
- package/data/docs/components/products/Dialog.json +106 -0
- package/data/docs/components/products/MenuButton.json +232 -0
- package/data/docs/components/products/PulsedRadarImage.json +9 -0
- package/data/docs/components/products/RadarButton.json +402 -0
- package/data/docs/components/products/RadarItem.json +554 -0
- package/data/docs/components/table/ControlledPagination.json +9 -0
- package/data/docs/components/table/DataGrid.json +93 -0
- package/data/docs/components/table/GridToolbarFilterSemanticField.json +69 -0
- package/data/docs/components/table/ServerSideControlledPagination.json +9 -0
- package/data/docs/components/table/StatefulDataGrid.json +117 -0
- package/data/docs/components/table/TextCell.json +118 -0
- package/data/docs/components/table/Toolbar.json +145 -0
- package/data/docs/components/table/ToolbarWrapper.json +9 -0
- package/data/docs/components-index.json +1206 -0
- package/data/docs/components.json +55694 -0
- package/data/docs/llms-full.txt +8012 -0
- package/data/docs/llms.txt +234 -0
- package/data/docs/patterns-catalog.md +359 -0
- package/data/docs/patterns.json +712 -0
- package/data/metadata.json +4 -0
- package/data/patterns/crossfiltered-datagrid-page.mdx +386 -0
- package/data/patterns/datagrid-page.mdx +214 -0
- package/data/patterns/drilldown-datagrid-page.mdx +291 -0
- package/data/patterns/server-datagrid-page.mdx +301 -0
- package/data/tokens/properties/components/dark-components.json +1108 -0
- package/data/tokens/properties/components/light-components.json +1108 -0
- package/data/tokens/properties/core/border-radius.json +3 -0
- package/data/tokens/properties/core/color.json +280 -0
- package/data/tokens/properties/core/layout.json +14 -0
- package/data/tokens/properties/core/typography.json +199 -0
- package/data/tokens/redsift-design-tokens.css +1391 -0
- package/dist/data-store.d.ts +47 -0
- package/dist/data-store.d.ts.map +1 -0
- package/dist/data-store.js +152 -0
- package/dist/data-store.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +14 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +137 -0
- package/dist/init.js.map +1 -0
- package/dist/paths.d.ts +30 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +53 -0
- package/dist/paths.js.map +1 -0
- package/dist/pattern-store.d.ts +41 -0
- package/dist/pattern-store.d.ts.map +1 -0
- package/dist/pattern-store.js +177 -0
- package/dist/pattern-store.js.map +1 -0
- package/dist/prompts.d.ts +14 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +762 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +14 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +482 -0
- package/dist/resources.js.map +1 -0
- package/dist/scaffold.d.ts +31 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +239 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/token-store.d.ts +70 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +196 -0
- package/dist/token-store.js.map +1 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +491 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { DataGrid, TextCell } from '@redsift/table';
|
|
3
|
+
import { Button, Flexbox, Icon, IconButtonLink, Pill } from '@redsift/design-system';
|
|
4
|
+
import { mdiArrowRight, mdiCheck, mdiClose, mdiShieldCheck, mdiLinkVariant, mdiLeaf } from '@redsift/icons';
|
|
5
|
+
import {
|
|
6
|
+
GridColDef,
|
|
7
|
+
GridFilterModel,
|
|
8
|
+
GridRenderCellParams,
|
|
9
|
+
GridRowSelectionModel,
|
|
10
|
+
GridSortModel,
|
|
11
|
+
} from '@mui/x-data-grid-pro';
|
|
12
|
+
import {
|
|
13
|
+
GridToolbarContainer,
|
|
14
|
+
GridToolbarColumnsButton,
|
|
15
|
+
GridToolbarDensitySelector,
|
|
16
|
+
GridToolbarFilterButton,
|
|
17
|
+
GridToolbarExport,
|
|
18
|
+
GridToolbarQuickFilter,
|
|
19
|
+
} from '@mui/x-data-grid-pro';
|
|
20
|
+
|
|
21
|
+
// -- Toolbar ----------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const CustomToolbar = () => (
|
|
24
|
+
<GridToolbarContainer>
|
|
25
|
+
<Flexbox gap="0">
|
|
26
|
+
<GridToolbarFilterButton />
|
|
27
|
+
<GridToolbarColumnsButton />
|
|
28
|
+
<GridToolbarDensitySelector />
|
|
29
|
+
<GridToolbarExport />
|
|
30
|
+
<Flexbox marginLeft="8px">
|
|
31
|
+
<GridToolbarQuickFilter />
|
|
32
|
+
</Flexbox>
|
|
33
|
+
</Flexbox>
|
|
34
|
+
</GridToolbarContainer>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// -- Tag options for the rsMultipleSelect column ----------------------------
|
|
38
|
+
|
|
39
|
+
const TAG_OPTIONS = [
|
|
40
|
+
{ value: 'production', label: 'Production' },
|
|
41
|
+
{ value: 'staging', label: 'Staging' },
|
|
42
|
+
{ value: 'legacy', label: 'Legacy' },
|
|
43
|
+
{ value: 'infrastructure', label: 'Infrastructure' },
|
|
44
|
+
{ value: 'cdn', label: 'CDN' },
|
|
45
|
+
{ value: 'api', label: 'API' },
|
|
46
|
+
{ value: 'auth', label: 'Auth' },
|
|
47
|
+
{ value: 'ca', label: 'CA' },
|
|
48
|
+
{ value: 'email', label: 'Email' },
|
|
49
|
+
{ value: 'ecommerce', label: 'Ecommerce' },
|
|
50
|
+
{ value: 'wildcard', label: 'Wildcard' },
|
|
51
|
+
{ value: 'monitoring', label: 'Monitoring' },
|
|
52
|
+
{ value: 'vpn', label: 'VPN' },
|
|
53
|
+
{ value: 'billing', label: 'Billing' },
|
|
54
|
+
{ value: 'ci', label: 'CI' },
|
|
55
|
+
{ value: 'cms', label: 'CMS' },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// -- Full dataset (lives "on the server") -----------------------------------
|
|
59
|
+
|
|
60
|
+
type Row = {
|
|
61
|
+
id: string;
|
|
62
|
+
label: string;
|
|
63
|
+
active: boolean;
|
|
64
|
+
status: string;
|
|
65
|
+
category: string;
|
|
66
|
+
score: number;
|
|
67
|
+
expiry: string;
|
|
68
|
+
tags: string[];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const DATABASE: Row[] = [
|
|
72
|
+
{
|
|
73
|
+
id: 'a3f8c1d2',
|
|
74
|
+
label: 'cdn.acme.com',
|
|
75
|
+
active: true,
|
|
76
|
+
status: 'active',
|
|
77
|
+
category: 'primary',
|
|
78
|
+
score: 92,
|
|
79
|
+
expiry: '2027-06-15T00:00:00Z',
|
|
80
|
+
tags: ['production', 'cdn'],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'b7e2d4f6',
|
|
84
|
+
label: 'api.globex.io',
|
|
85
|
+
active: true,
|
|
86
|
+
status: 'warning',
|
|
87
|
+
category: 'primary',
|
|
88
|
+
score: 74,
|
|
89
|
+
expiry: '2026-05-01T12:00:00Z',
|
|
90
|
+
tags: ['production', 'api'],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'c1a9b5e3',
|
|
94
|
+
label: '*.staging.acme.com',
|
|
95
|
+
active: true,
|
|
96
|
+
status: 'critical',
|
|
97
|
+
category: 'secondary',
|
|
98
|
+
score: 45,
|
|
99
|
+
expiry: '2026-03-28T23:59:59Z',
|
|
100
|
+
tags: ['staging'],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'd4f0c8a7',
|
|
104
|
+
label: 'mail.initech.dev',
|
|
105
|
+
active: false,
|
|
106
|
+
status: 'expired',
|
|
107
|
+
category: 'tertiary',
|
|
108
|
+
score: 12,
|
|
109
|
+
expiry: '2025-11-30T08:45:00Z',
|
|
110
|
+
tags: ['legacy', 'email'],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'e6b3d1f9',
|
|
114
|
+
label: 'Acme Root CA',
|
|
115
|
+
active: true,
|
|
116
|
+
status: 'active',
|
|
117
|
+
category: 'primary',
|
|
118
|
+
score: 100,
|
|
119
|
+
expiry: '2035-06-01T00:00:00Z',
|
|
120
|
+
tags: ['infrastructure'],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'f2c7a5e8',
|
|
124
|
+
label: 'Issuing CA G2',
|
|
125
|
+
active: true,
|
|
126
|
+
status: 'active',
|
|
127
|
+
category: 'secondary',
|
|
128
|
+
score: 98,
|
|
129
|
+
expiry: '2031-12-31T23:59:59Z',
|
|
130
|
+
tags: ['infrastructure', 'ca'],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'a8d4f6b1',
|
|
134
|
+
label: 'auth.globex.io',
|
|
135
|
+
active: false,
|
|
136
|
+
status: 'revoked',
|
|
137
|
+
category: 'primary',
|
|
138
|
+
score: 0,
|
|
139
|
+
expiry: '2026-09-10T17:30:00Z',
|
|
140
|
+
tags: ['production', 'auth'],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'b5e9c3a2',
|
|
144
|
+
label: 'shop.initech.dev',
|
|
145
|
+
active: true,
|
|
146
|
+
status: 'warning',
|
|
147
|
+
category: 'tertiary',
|
|
148
|
+
score: 63,
|
|
149
|
+
expiry: '2026-04-20T00:00:00Z',
|
|
150
|
+
tags: ['ecommerce'],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'c0f7d2b4',
|
|
154
|
+
label: 'Sectigo DV CA',
|
|
155
|
+
active: true,
|
|
156
|
+
status: 'active',
|
|
157
|
+
category: 'secondary',
|
|
158
|
+
score: 95,
|
|
159
|
+
expiry: '2030-01-01T00:00:00Z',
|
|
160
|
+
tags: ['infrastructure', 'ca'],
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'd9a1e5c6',
|
|
164
|
+
label: 'vault.initech.dev',
|
|
165
|
+
active: false,
|
|
166
|
+
status: 'expired',
|
|
167
|
+
category: 'primary',
|
|
168
|
+
score: 8,
|
|
169
|
+
expiry: '2024-07-22T14:00:00Z',
|
|
170
|
+
tags: ['legacy'],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'e3b8f0d7',
|
|
174
|
+
label: '*.acme.com',
|
|
175
|
+
active: true,
|
|
176
|
+
status: 'active',
|
|
177
|
+
category: 'primary',
|
|
178
|
+
score: 88,
|
|
179
|
+
expiry: '2027-08-05T12:00:00Z',
|
|
180
|
+
tags: ['production', 'wildcard'],
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'f6c2a4e1',
|
|
184
|
+
label: 'portal.globex.io',
|
|
185
|
+
active: true,
|
|
186
|
+
status: 'critical',
|
|
187
|
+
category: 'tertiary',
|
|
188
|
+
score: 51,
|
|
189
|
+
expiry: '2026-03-26T10:00:00Z',
|
|
190
|
+
tags: [],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'a1d7f3b9',
|
|
194
|
+
label: 'logs.initech.dev',
|
|
195
|
+
active: true,
|
|
196
|
+
status: 'active',
|
|
197
|
+
category: 'secondary',
|
|
198
|
+
score: 81,
|
|
199
|
+
expiry: '2028-02-14T00:00:00Z',
|
|
200
|
+
tags: ['production', 'monitoring'],
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'b4e8c6a3',
|
|
204
|
+
label: 'cms.acme.com',
|
|
205
|
+
active: false,
|
|
206
|
+
status: 'expired',
|
|
207
|
+
category: 'tertiary',
|
|
208
|
+
score: 5,
|
|
209
|
+
expiry: '2025-06-10T00:00:00Z',
|
|
210
|
+
tags: ['legacy', 'cms'],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'c7f2d0b5',
|
|
214
|
+
label: 'DigiCert EV CA',
|
|
215
|
+
active: true,
|
|
216
|
+
status: 'active',
|
|
217
|
+
category: 'secondary',
|
|
218
|
+
score: 99,
|
|
219
|
+
expiry: '2033-09-15T00:00:00Z',
|
|
220
|
+
tags: ['infrastructure', 'ca'],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'd0a3e9f1',
|
|
224
|
+
label: 'vpn.globex.io',
|
|
225
|
+
active: true,
|
|
226
|
+
status: 'warning',
|
|
227
|
+
category: 'primary',
|
|
228
|
+
score: 68,
|
|
229
|
+
expiry: '2026-04-12T00:00:00Z',
|
|
230
|
+
tags: ['production', 'vpn'],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: 'e5b1c4d8',
|
|
234
|
+
label: '*.initech.dev',
|
|
235
|
+
active: true,
|
|
236
|
+
status: 'active',
|
|
237
|
+
category: 'primary',
|
|
238
|
+
score: 85,
|
|
239
|
+
expiry: '2027-11-20T00:00:00Z',
|
|
240
|
+
tags: ['production', 'wildcard'],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'f8c6a2e7',
|
|
244
|
+
label: 'docs.acme.com',
|
|
245
|
+
active: true,
|
|
246
|
+
status: 'active',
|
|
247
|
+
category: 'tertiary',
|
|
248
|
+
score: 90,
|
|
249
|
+
expiry: '2028-07-01T00:00:00Z',
|
|
250
|
+
tags: ['production'],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: 'a2d5f9b0',
|
|
254
|
+
label: 'billing.globex.io',
|
|
255
|
+
active: false,
|
|
256
|
+
status: 'revoked',
|
|
257
|
+
category: 'primary',
|
|
258
|
+
score: 0,
|
|
259
|
+
expiry: '2026-01-15T00:00:00Z',
|
|
260
|
+
tags: ['production', 'billing'],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: 'b6e0c3a1',
|
|
264
|
+
label: 'staging.initech.dev',
|
|
265
|
+
active: true,
|
|
266
|
+
status: 'critical',
|
|
267
|
+
category: 'secondary',
|
|
268
|
+
score: 42,
|
|
269
|
+
expiry: '2026-03-27T00:00:00Z',
|
|
270
|
+
tags: ['staging', 'ci'],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'c9f4d7b2',
|
|
274
|
+
label: "Let's Encrypt R3",
|
|
275
|
+
active: true,
|
|
276
|
+
status: 'active',
|
|
277
|
+
category: 'secondary',
|
|
278
|
+
score: 97,
|
|
279
|
+
expiry: '2030-09-30T00:00:00Z',
|
|
280
|
+
tags: ['infrastructure', 'ca'],
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 'd3a8e1f5',
|
|
284
|
+
label: 'analytics.acme.com',
|
|
285
|
+
active: true,
|
|
286
|
+
status: 'warning',
|
|
287
|
+
category: 'tertiary',
|
|
288
|
+
score: 59,
|
|
289
|
+
expiry: '2026-04-05T00:00:00Z',
|
|
290
|
+
tags: ['production', 'monitoring'],
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 'e7b2c5d9',
|
|
294
|
+
label: 'sso.globex.io',
|
|
295
|
+
active: true,
|
|
296
|
+
status: 'active',
|
|
297
|
+
category: 'primary',
|
|
298
|
+
score: 91,
|
|
299
|
+
expiry: '2027-12-01T00:00:00Z',
|
|
300
|
+
tags: ['production', 'auth'],
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: 'f0c9a6e4',
|
|
304
|
+
label: 'wiki.initech.dev',
|
|
305
|
+
active: false,
|
|
306
|
+
status: 'expired',
|
|
307
|
+
category: 'tertiary',
|
|
308
|
+
score: 3,
|
|
309
|
+
expiry: '2024-12-31T00:00:00Z',
|
|
310
|
+
tags: ['legacy'],
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: 'a4d1f8b6',
|
|
314
|
+
label: 'proxy.acme.com',
|
|
315
|
+
active: true,
|
|
316
|
+
status: 'active',
|
|
317
|
+
category: 'primary',
|
|
318
|
+
score: 83,
|
|
319
|
+
expiry: '2028-03-22T00:00:00Z',
|
|
320
|
+
tags: ['production', 'cdn'],
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
id: 'b9e5c0a8',
|
|
324
|
+
label: 'dev.globex.io',
|
|
325
|
+
active: true,
|
|
326
|
+
status: 'critical',
|
|
327
|
+
category: 'secondary',
|
|
328
|
+
score: 38,
|
|
329
|
+
expiry: '2026-03-25T00:00:00Z',
|
|
330
|
+
tags: ['staging'],
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: 'c2f6d3b7',
|
|
334
|
+
label: 'GTS Root R1',
|
|
335
|
+
active: true,
|
|
336
|
+
status: 'active',
|
|
337
|
+
category: 'primary',
|
|
338
|
+
score: 100,
|
|
339
|
+
expiry: '2036-06-22T00:00:00Z',
|
|
340
|
+
tags: ['infrastructure'],
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
// -- Server-side filter logic -----------------------------------------------
|
|
345
|
+
|
|
346
|
+
const matchesFilter = (row: Row, item: GridFilterModel['items'][number]): boolean => {
|
|
347
|
+
const { field, operator, value } = item;
|
|
348
|
+
if (!operator) return true;
|
|
349
|
+
|
|
350
|
+
const raw = row[field as keyof Row];
|
|
351
|
+
|
|
352
|
+
// -- String operators --
|
|
353
|
+
if (typeof raw === 'string') {
|
|
354
|
+
const s = raw.toLowerCase();
|
|
355
|
+
const v = ((value as string) || '').toLowerCase();
|
|
356
|
+
switch (operator) {
|
|
357
|
+
case 'contains':
|
|
358
|
+
return s.includes(v);
|
|
359
|
+
case 'equals':
|
|
360
|
+
return s === v;
|
|
361
|
+
case 'startsWith':
|
|
362
|
+
return s.startsWith(v);
|
|
363
|
+
case 'endsWith':
|
|
364
|
+
return s.endsWith(v);
|
|
365
|
+
case 'isEmpty':
|
|
366
|
+
return s === '';
|
|
367
|
+
case 'isNotEmpty':
|
|
368
|
+
return s !== '';
|
|
369
|
+
case 'is':
|
|
370
|
+
return s === v;
|
|
371
|
+
case 'not':
|
|
372
|
+
return s !== v;
|
|
373
|
+
case 'isAnyOf':
|
|
374
|
+
return Array.isArray(value) && value.some((o: string) => o.toLowerCase() === s);
|
|
375
|
+
default:
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// -- Number operators --
|
|
381
|
+
if (typeof raw === 'number') {
|
|
382
|
+
const n = Number(value);
|
|
383
|
+
switch (operator) {
|
|
384
|
+
case '=':
|
|
385
|
+
return raw === n;
|
|
386
|
+
case '!=':
|
|
387
|
+
return raw !== n;
|
|
388
|
+
case '>':
|
|
389
|
+
return raw > n;
|
|
390
|
+
case '>=':
|
|
391
|
+
return raw >= n;
|
|
392
|
+
case '<':
|
|
393
|
+
return raw < n;
|
|
394
|
+
case '<=':
|
|
395
|
+
return raw <= n;
|
|
396
|
+
case 'isEmpty':
|
|
397
|
+
return false;
|
|
398
|
+
case 'isNotEmpty':
|
|
399
|
+
return true;
|
|
400
|
+
default:
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// -- Boolean operators --
|
|
406
|
+
if (typeof raw === 'boolean') {
|
|
407
|
+
if (operator === 'is') return String(raw) === value;
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// -- Array (tags) operators --
|
|
412
|
+
if (Array.isArray(raw)) {
|
|
413
|
+
switch (operator) {
|
|
414
|
+
case 'isAnyOf':
|
|
415
|
+
return Array.isArray(value) && value.some((v: string) => raw.includes(v));
|
|
416
|
+
case 'is':
|
|
417
|
+
return raw.includes(value as string);
|
|
418
|
+
case 'not':
|
|
419
|
+
return !raw.includes(value as string);
|
|
420
|
+
case 'isEmpty':
|
|
421
|
+
return raw.length === 0;
|
|
422
|
+
case 'isNotEmpty':
|
|
423
|
+
return raw.length > 0;
|
|
424
|
+
default:
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return true;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// -- Server-side sort logic -------------------------------------------------
|
|
433
|
+
|
|
434
|
+
const compareRows = (a: Row, b: Row, field: string, direction: 'asc' | 'desc'): number => {
|
|
435
|
+
let av = a[field as keyof Row];
|
|
436
|
+
let bv = b[field as keyof Row];
|
|
437
|
+
|
|
438
|
+
// Date fields — compare as timestamps
|
|
439
|
+
if (field === 'expiry') {
|
|
440
|
+
av = new Date(av as string).getTime() as unknown as string;
|
|
441
|
+
bv = new Date(bv as string).getTime() as unknown as string;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
let cmp = 0;
|
|
445
|
+
if (av == null && bv == null) cmp = 0;
|
|
446
|
+
else if (av == null) cmp = -1;
|
|
447
|
+
else if (bv == null) cmp = 1;
|
|
448
|
+
else if (typeof av === 'string' && typeof bv === 'string') cmp = av.localeCompare(bv);
|
|
449
|
+
else if (typeof av === 'number' && typeof bv === 'number') cmp = av - bv;
|
|
450
|
+
else if (typeof av === 'boolean' && typeof bv === 'boolean') cmp = Number(av) - Number(bv);
|
|
451
|
+
else cmp = String(av).localeCompare(String(bv));
|
|
452
|
+
|
|
453
|
+
return direction === 'desc' ? -cmp : cmp;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// -- Fake fetch: applies filter → sort → paginate on the "server" -----------
|
|
457
|
+
|
|
458
|
+
type FetchParams = {
|
|
459
|
+
page: number;
|
|
460
|
+
pageSize: number;
|
|
461
|
+
sortModel: GridSortModel;
|
|
462
|
+
filterModel: GridFilterModel;
|
|
463
|
+
quickFilterText?: string;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
type FetchResult = { rows: Row[]; totalRows: number };
|
|
467
|
+
|
|
468
|
+
const fakeFetch = ({ page, pageSize, sortModel, filterModel, quickFilterText }: FetchParams): Promise<FetchResult> =>
|
|
469
|
+
new Promise((resolve) => {
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
let filtered = [...DATABASE];
|
|
472
|
+
|
|
473
|
+
// Apply column filters
|
|
474
|
+
if (filterModel.items.length > 0) {
|
|
475
|
+
const logicOperator = filterModel.logicOperator || 'and';
|
|
476
|
+
filtered = filtered.filter((row) => {
|
|
477
|
+
const results = filterModel.items
|
|
478
|
+
.filter((it) => it.value !== undefined || ['isEmpty', 'isNotEmpty'].includes(it.operator || ''))
|
|
479
|
+
.map((it) => matchesFilter(row, it));
|
|
480
|
+
if (results.length === 0) return true;
|
|
481
|
+
return logicOperator === 'and' ? results.every(Boolean) : results.some(Boolean);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Apply quick filter (search across all string fields)
|
|
486
|
+
if (quickFilterText) {
|
|
487
|
+
const q = quickFilterText.toLowerCase();
|
|
488
|
+
filtered = filtered.filter((row) =>
|
|
489
|
+
[row.id, row.label, row.status, row.category, ...row.tags].some((v) => v.toLowerCase().includes(q))
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Apply sorting
|
|
494
|
+
if (sortModel.length > 0) {
|
|
495
|
+
filtered.sort((a, b) => {
|
|
496
|
+
for (const { field, sort } of sortModel) {
|
|
497
|
+
const cmp = compareRows(a, b, field, sort || 'asc');
|
|
498
|
+
if (cmp !== 0) return cmp;
|
|
499
|
+
}
|
|
500
|
+
return 0;
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const totalRows = filtered.length;
|
|
505
|
+
const start = page * pageSize;
|
|
506
|
+
const rows = filtered.slice(start, start + pageSize);
|
|
507
|
+
|
|
508
|
+
resolve({ rows, totalRows });
|
|
509
|
+
}, 400);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// -- Helpers ----------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
const statusColor = (v: string) => {
|
|
515
|
+
switch (v) {
|
|
516
|
+
case 'active':
|
|
517
|
+
return 'success';
|
|
518
|
+
case 'warning':
|
|
519
|
+
return 'warning';
|
|
520
|
+
case 'critical':
|
|
521
|
+
return 'error';
|
|
522
|
+
case 'expired':
|
|
523
|
+
return 'grey';
|
|
524
|
+
case 'revoked':
|
|
525
|
+
return 'error-dark';
|
|
526
|
+
default:
|
|
527
|
+
return 'grey';
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const categoryIcon = (v: string) => {
|
|
532
|
+
switch (v) {
|
|
533
|
+
case 'primary':
|
|
534
|
+
return { icon: mdiShieldCheck, color: 'success' };
|
|
535
|
+
case 'secondary':
|
|
536
|
+
return { icon: mdiLinkVariant, color: 'info' };
|
|
537
|
+
case 'tertiary':
|
|
538
|
+
return { icon: mdiLeaf, color: 'grey' };
|
|
539
|
+
default:
|
|
540
|
+
return { icon: mdiLeaf, color: 'grey' };
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const formatDate = (d: Date) => (isNaN(d.getTime()) ? '—' : d.toISOString().replace('T', ' ').slice(0, 19));
|
|
545
|
+
|
|
546
|
+
// -- Columns ----------------------------------------------------------------
|
|
547
|
+
|
|
548
|
+
const columns: GridColDef[] = [
|
|
549
|
+
{
|
|
550
|
+
field: 'id',
|
|
551
|
+
headerName: 'ID',
|
|
552
|
+
width: 100,
|
|
553
|
+
type: 'rsString' as string,
|
|
554
|
+
renderCell: ({ value }: GridRenderCellParams) => <TextCell style={{ fontFamily: 'monospace' }}>{value}</TextCell>,
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
field: 'label',
|
|
558
|
+
headerName: 'Label',
|
|
559
|
+
flex: 2,
|
|
560
|
+
type: 'rsString' as string,
|
|
561
|
+
renderCell: ({ value }: GridRenderCellParams) => <TextCell>{value}</TextCell>,
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
field: 'active',
|
|
565
|
+
headerName: 'Active',
|
|
566
|
+
width: 80,
|
|
567
|
+
type: 'boolean',
|
|
568
|
+
renderCell: ({ value }: GridRenderCellParams) =>
|
|
569
|
+
value ? (
|
|
570
|
+
<Icon icon={mdiCheck} color="success" size="small" />
|
|
571
|
+
) : (
|
|
572
|
+
<Icon icon={mdiClose} color="grey" size="small" />
|
|
573
|
+
),
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
field: 'status',
|
|
577
|
+
headerName: 'Status',
|
|
578
|
+
flex: 1,
|
|
579
|
+
type: 'rsSingleSelect' as string,
|
|
580
|
+
valueOptions: [
|
|
581
|
+
{ value: 'active', label: 'Active' },
|
|
582
|
+
{ value: 'warning', label: 'Warning' },
|
|
583
|
+
{ value: 'critical', label: 'Critical' },
|
|
584
|
+
{ value: 'expired', label: 'Expired' },
|
|
585
|
+
{ value: 'revoked', label: 'Revoked' },
|
|
586
|
+
],
|
|
587
|
+
renderCell: ({ value }: GridRenderCellParams) => <Pill color={statusColor(value)}>{value}</Pill>,
|
|
588
|
+
} as GridColDef,
|
|
589
|
+
{
|
|
590
|
+
field: 'category',
|
|
591
|
+
headerName: 'Category',
|
|
592
|
+
width: 110,
|
|
593
|
+
type: 'rsSingleSelect' as string,
|
|
594
|
+
valueOptions: [
|
|
595
|
+
{ value: 'primary', label: 'Primary' },
|
|
596
|
+
{ value: 'secondary', label: 'Secondary' },
|
|
597
|
+
{ value: 'tertiary', label: 'Tertiary' },
|
|
598
|
+
],
|
|
599
|
+
renderCell: ({ value }: GridRenderCellParams) => {
|
|
600
|
+
const cfg = categoryIcon(value);
|
|
601
|
+
return <Icon icon={cfg.icon} color={cfg.color} size="small" />;
|
|
602
|
+
},
|
|
603
|
+
} as GridColDef,
|
|
604
|
+
{
|
|
605
|
+
field: 'score',
|
|
606
|
+
headerName: 'Score',
|
|
607
|
+
width: 80,
|
|
608
|
+
type: 'rsNumber' as string,
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
field: 'expiry',
|
|
612
|
+
headerName: 'Expiry',
|
|
613
|
+
width: 180,
|
|
614
|
+
type: 'date',
|
|
615
|
+
valueGetter: (value: unknown) => new Date(value as string),
|
|
616
|
+
renderCell: ({ value }: GridRenderCellParams) => {
|
|
617
|
+
if (!value) return <TextCell>—</TextCell>;
|
|
618
|
+
const d = value as Date;
|
|
619
|
+
const isPast = d < new Date();
|
|
620
|
+
return <TextCell style={isPast ? { color: 'var(--redsift-color-red-n)' } : undefined}>{formatDate(d)}</TextCell>;
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
field: 'tags',
|
|
625
|
+
headerName: 'Tags',
|
|
626
|
+
flex: 2,
|
|
627
|
+
type: 'rsMultipleSelect' as string,
|
|
628
|
+
valueOptions: TAG_OPTIONS,
|
|
629
|
+
sortable: false,
|
|
630
|
+
renderCell: ({ value }: GridRenderCellParams) => {
|
|
631
|
+
const arr = value as string[];
|
|
632
|
+
if (!arr || arr.length === 0) return <TextCell>—</TextCell>;
|
|
633
|
+
return (
|
|
634
|
+
<Flexbox gap="4px" flexWrap="wrap" alignItems="center">
|
|
635
|
+
{arr.map((tag: string) => (
|
|
636
|
+
<Pill key={tag} color="blue">
|
|
637
|
+
{tag}
|
|
638
|
+
</Pill>
|
|
639
|
+
))}
|
|
640
|
+
</Flexbox>
|
|
641
|
+
);
|
|
642
|
+
},
|
|
643
|
+
} as GridColDef,
|
|
644
|
+
{
|
|
645
|
+
field: 'actions',
|
|
646
|
+
headerName: '',
|
|
647
|
+
type: 'actions',
|
|
648
|
+
width: 56,
|
|
649
|
+
hideable: false,
|
|
650
|
+
sortable: false,
|
|
651
|
+
filterable: false,
|
|
652
|
+
renderCell: ({ row }: GridRenderCellParams) => (
|
|
653
|
+
<IconButtonLink
|
|
654
|
+
href="/patterns/server-datagrid-page"
|
|
655
|
+
icon={mdiArrowRight}
|
|
656
|
+
color="info"
|
|
657
|
+
aria-label={`View ${row.label}`}
|
|
658
|
+
/>
|
|
659
|
+
),
|
|
660
|
+
},
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
// -- Component --------------------------------------------------------------
|
|
664
|
+
|
|
665
|
+
export default () => {
|
|
666
|
+
const [rows, setRows] = useState<Row[]>([]);
|
|
667
|
+
const [totalRows, setTotalRows] = useState(0);
|
|
668
|
+
const [loading, setLoading] = useState(true);
|
|
669
|
+
const [page, setPage] = useState(0);
|
|
670
|
+
const [pageSize, setPageSize] = useState(10);
|
|
671
|
+
const [sortModel, setSortModel] = useState<GridSortModel>([]);
|
|
672
|
+
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
|
|
673
|
+
const [selectionModel, setSelectionModel] = useState<GridRowSelectionModel>([]);
|
|
674
|
+
|
|
675
|
+
// Track the quick filter text extracted from filterModel
|
|
676
|
+
const quickFilterText = (filterModel as { quickFilterValues?: string[] }).quickFilterValues?.join(' ') || '';
|
|
677
|
+
|
|
678
|
+
// Debounce ref for filter changes
|
|
679
|
+
const debounceRef = useRef<ReturnType<typeof setTimeout>>();
|
|
680
|
+
|
|
681
|
+
const fetchData = useCallback(() => {
|
|
682
|
+
setLoading(true);
|
|
683
|
+
fakeFetch({ page, pageSize, sortModel, filterModel, quickFilterText }).then(({ rows: r, totalRows: t }) => {
|
|
684
|
+
setRows(r);
|
|
685
|
+
setTotalRows(t);
|
|
686
|
+
setLoading(false);
|
|
687
|
+
});
|
|
688
|
+
}, [page, pageSize, sortModel, filterModel, quickFilterText]);
|
|
689
|
+
|
|
690
|
+
// Fetch on mount and whenever params change
|
|
691
|
+
useEffect(() => {
|
|
692
|
+
fetchData();
|
|
693
|
+
}, [fetchData]);
|
|
694
|
+
|
|
695
|
+
// Debounced filter handler
|
|
696
|
+
const handleFilterModelChange = useCallback((model: GridFilterModel) => {
|
|
697
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
698
|
+
debounceRef.current = setTimeout(() => {
|
|
699
|
+
setFilterModel(model);
|
|
700
|
+
setPage(0); // Reset to first page on filter change
|
|
701
|
+
}, 300);
|
|
702
|
+
}, []);
|
|
703
|
+
|
|
704
|
+
const handleSortModelChange = useCallback((model: GridSortModel) => {
|
|
705
|
+
setSortModel(model);
|
|
706
|
+
setPage(0);
|
|
707
|
+
}, []);
|
|
708
|
+
|
|
709
|
+
return (
|
|
710
|
+
<div style={{ width: '100%' }}>
|
|
711
|
+
<Flexbox flexDirection="column" gap="0px">
|
|
712
|
+
{/* Bulk actions bar — visible when rows are selected */}
|
|
713
|
+
{selectionModel.length > 0 && (
|
|
714
|
+
<Flexbox gap="8px" alignItems="center" style={{ padding: '8px 0' }}>
|
|
715
|
+
<Pill color="info">{selectionModel.length} selected</Pill>
|
|
716
|
+
<Button variant="secondary" onClick={() => console.log('Selected:', selectionModel)}>
|
|
717
|
+
Log Selection
|
|
718
|
+
</Button>
|
|
719
|
+
<Button variant="secondary" color="error" onClick={() => console.log('Delete:', selectionModel)}>
|
|
720
|
+
Delete
|
|
721
|
+
</Button>
|
|
722
|
+
</Flexbox>
|
|
723
|
+
)}
|
|
724
|
+
|
|
725
|
+
<DataGrid
|
|
726
|
+
autoHeight
|
|
727
|
+
pagination
|
|
728
|
+
paginationMode="server"
|
|
729
|
+
sortingMode="server"
|
|
730
|
+
filterMode="server"
|
|
731
|
+
loading={loading}
|
|
732
|
+
rows={rows}
|
|
733
|
+
columns={columns}
|
|
734
|
+
rowCount={totalRows}
|
|
735
|
+
paginationModel={{ page, pageSize }}
|
|
736
|
+
pageSizeOptions={[10, 25, 50]}
|
|
737
|
+
onPaginationModelChange={(model) => {
|
|
738
|
+
if (model.pageSize !== pageSize) {
|
|
739
|
+
setPageSize(model.pageSize);
|
|
740
|
+
setPage(0);
|
|
741
|
+
} else {
|
|
742
|
+
setPage(model.page);
|
|
743
|
+
}
|
|
744
|
+
}}
|
|
745
|
+
sortModel={sortModel}
|
|
746
|
+
onSortModelChange={handleSortModelChange}
|
|
747
|
+
filterModel={filterModel}
|
|
748
|
+
onFilterModelChange={handleFilterModelChange}
|
|
749
|
+
checkboxSelection
|
|
750
|
+
rowSelectionModel={selectionModel}
|
|
751
|
+
onRowSelectionModelChange={setSelectionModel}
|
|
752
|
+
slots={{ toolbar: CustomToolbar }}
|
|
753
|
+
/>
|
|
754
|
+
</Flexbox>
|
|
755
|
+
</div>
|
|
756
|
+
);
|
|
757
|
+
};
|