@object-ui/plugin-aggrid 0.5.0 → 2.0.0
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/.turbo/turbo-build.log +90 -12
- package/CHANGELOG.md +16 -0
- package/QUICKSTART.md +1 -1
- package/dist/AddressField-Bntpynvd.js +95 -0
- package/dist/AgGridImpl-3Mmf2qrR.js +229 -0
- package/dist/AutoNumberField-C1kBJaxh.js +8 -0
- package/dist/FileField-BDwbJvor.js +101 -0
- package/dist/FormulaField-BXNiyGoh.js +9 -0
- package/dist/GeolocationField-Df3yYcM9.js +141 -0
- package/dist/GridField-CcjQp4WM.js +29 -0
- package/dist/LocationField-BIfN5QIq.js +33 -0
- package/dist/MasterDetailField-CAEmxbIT.js +117 -0
- package/dist/ObjectAgGridImpl-EjifM4aY.js +28727 -0
- package/dist/ObjectField-BpkQpIF-.js +51 -0
- package/dist/QRCodeField-VCBewTDG.js +96 -0
- package/dist/RichTextField-CyQwSi2C.js +37 -0
- package/dist/SignatureField-Cr4tsEbj.js +96 -0
- package/dist/SummaryField-CnEJ_GZI.js +9 -0
- package/dist/UserField-DJjaVyrV.js +49 -0
- package/dist/VectorField-cPYmcKnV.js +25 -0
- package/dist/{index-CLKYMco3.js → index-B87wd1E0.js} +57 -33
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +225 -5
- package/dist/src/AgGridImpl.d.ts +5 -2
- package/dist/src/field-renderers.d.ts +67 -0
- package/dist/src/index.d.ts +5 -2
- package/dist/src/types.d.ts +48 -1
- package/package.json +8 -7
- package/src/AgGridImpl.tsx +95 -10
- package/src/ObjectAgGridImpl.tsx +67 -169
- package/src/field-renderers.test.tsx +383 -0
- package/src/field-renderers.tsx +224 -0
- package/src/index.tsx +30 -3
- package/src/types.ts +57 -1
- package/dist/AgGridImpl-BQ6tBvrq.js +0 -175
- package/dist/ObjectAgGridImpl-CGFeGvOH.js +0 -372
package/dist/src/AgGridImpl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ColDef, GridOptions } from 'ag-grid-community';
|
|
2
|
-
import { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig } from './types';
|
|
2
|
+
import { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig, TreeDataConfig, RowGroupingConfig, ExcelExportConfig } from './types';
|
|
3
3
|
export interface AgGridImplProps {
|
|
4
4
|
rowData?: any[];
|
|
5
5
|
columnDefs?: ColDef[];
|
|
@@ -23,9 +23,12 @@ export interface AgGridImplProps {
|
|
|
23
23
|
enableRangeSelection?: boolean;
|
|
24
24
|
enableCharts?: boolean;
|
|
25
25
|
contextMenu?: ContextMenuConfig;
|
|
26
|
+
treeData?: TreeDataConfig;
|
|
27
|
+
rowGrouping?: RowGroupingConfig;
|
|
28
|
+
excelExport?: ExcelExportConfig;
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* AgGridImpl - The heavy implementation that imports AG Grid
|
|
29
32
|
* This component is lazy-loaded to avoid including AG Grid in the initial bundle
|
|
30
33
|
*/
|
|
31
|
-
export default function AgGridImpl({ rowData, columnDefs, gridOptions, pagination, paginationPageSize, domLayout, animateRows, rowSelection, theme, height, className, editable, editType, singleClickEdit, stopEditingWhenCellsLoseFocus, exportConfig, statusBar, callbacks, columnConfig, enableRangeSelection, enableCharts, contextMenu, }: AgGridImplProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export default function AgGridImpl({ rowData, columnDefs, gridOptions, pagination, paginationPageSize, domLayout, animateRows, rowSelection, theme, height, className, editable, editType, singleClickEdit, stopEditingWhenCellsLoseFocus, exportConfig, statusBar, callbacks, columnConfig, enableRangeSelection, enableCharts, contextMenu, treeData, rowGrouping, excelExport, }: AgGridImplProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Root } from 'react-dom/client';
|
|
2
|
+
import { ICellRendererParams, ICellEditorParams } from 'ag-grid-community';
|
|
3
|
+
import { FieldMetadata } from '../../types/src';
|
|
4
|
+
/**
|
|
5
|
+
* AG Grid Cell Renderer using Field Widgets (Read-only mode)
|
|
6
|
+
*/
|
|
7
|
+
export declare class FieldWidgetCellRenderer {
|
|
8
|
+
eGui: HTMLDivElement;
|
|
9
|
+
root: Root | null;
|
|
10
|
+
init(params: ICellRendererParams & {
|
|
11
|
+
field: FieldMetadata;
|
|
12
|
+
}): void;
|
|
13
|
+
getGui(): HTMLDivElement;
|
|
14
|
+
refresh(params: ICellRendererParams & {
|
|
15
|
+
field: FieldMetadata;
|
|
16
|
+
}): boolean;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* AG Grid Cell Editor using Field Widgets (Edit mode)
|
|
21
|
+
*/
|
|
22
|
+
export declare class FieldWidgetCellEditor {
|
|
23
|
+
eGui: HTMLDivElement;
|
|
24
|
+
root: Root | null;
|
|
25
|
+
currentValue: unknown;
|
|
26
|
+
params: ICellEditorParams & {
|
|
27
|
+
field: FieldMetadata;
|
|
28
|
+
};
|
|
29
|
+
init(params: ICellEditorParams & {
|
|
30
|
+
field: FieldMetadata;
|
|
31
|
+
}): void;
|
|
32
|
+
getGui(): HTMLDivElement;
|
|
33
|
+
getValue(): unknown;
|
|
34
|
+
destroy(): void;
|
|
35
|
+
isPopup(): boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Factory function to create cell renderer with field metadata
|
|
39
|
+
*/
|
|
40
|
+
export declare function createFieldCellRenderer(field: FieldMetadata): {
|
|
41
|
+
new (): {
|
|
42
|
+
init(params: ICellRendererParams): void;
|
|
43
|
+
refresh(params: ICellRendererParams): boolean;
|
|
44
|
+
eGui: HTMLDivElement;
|
|
45
|
+
root: Root | null;
|
|
46
|
+
getGui(): HTMLDivElement;
|
|
47
|
+
destroy(): void;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Factory function to create cell editor with field metadata
|
|
52
|
+
*/
|
|
53
|
+
export declare function createFieldCellEditor(field: FieldMetadata): {
|
|
54
|
+
new (): {
|
|
55
|
+
init(params: ICellEditorParams): void;
|
|
56
|
+
eGui: HTMLDivElement;
|
|
57
|
+
root: Root | null;
|
|
58
|
+
currentValue: unknown;
|
|
59
|
+
params: ICellEditorParams & {
|
|
60
|
+
field: FieldMetadata;
|
|
61
|
+
};
|
|
62
|
+
getGui(): HTMLDivElement;
|
|
63
|
+
getValue(): unknown;
|
|
64
|
+
destroy(): void;
|
|
65
|
+
isPopup(): boolean;
|
|
66
|
+
};
|
|
67
|
+
};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig } from './types';
|
|
2
|
+
import { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig, TreeDataConfig, RowGroupingConfig, ExcelExportConfig } from './types';
|
|
3
3
|
import { DataSource } from '../../types/src';
|
|
4
|
-
export type { AgGridSchema, SimpleColumnDef, AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig } from './types';
|
|
4
|
+
export type { AgGridSchema, SimpleColumnDef, AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig, TreeDataConfig, RowGroupingConfig, ExcelExportConfig } from './types';
|
|
5
5
|
export type { ObjectAgGridSchema } from './object-aggrid.types';
|
|
6
6
|
export interface AgGridRendererProps {
|
|
7
7
|
schema: {
|
|
@@ -29,6 +29,9 @@ export interface AgGridRendererProps {
|
|
|
29
29
|
enableRangeSelection?: boolean;
|
|
30
30
|
enableCharts?: boolean;
|
|
31
31
|
contextMenu?: ContextMenuConfig;
|
|
32
|
+
treeData?: TreeDataConfig;
|
|
33
|
+
rowGrouping?: RowGroupingConfig;
|
|
34
|
+
excelExport?: ExcelExportConfig;
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
37
|
/**
|
package/dist/src/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface AgGridCallbacks {
|
|
|
7
7
|
onRowClicked?: (event: RowClickedEvent) => void;
|
|
8
8
|
onSelectionChanged?: (event: SelectionChangedEvent) => void;
|
|
9
9
|
onCellValueChanged?: (event: CellValueChangedEvent) => void;
|
|
10
|
-
onExport?: (data: any[], format: 'csv') => void;
|
|
10
|
+
onExport?: (data: any[], format: 'csv' | 'excel') => void;
|
|
11
11
|
onContextMenuAction?: (action: string, rowData: any) => void;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
@@ -19,6 +19,50 @@ export interface ExportConfig {
|
|
|
19
19
|
skipColumnHeaders?: boolean;
|
|
20
20
|
allColumns?: boolean;
|
|
21
21
|
onlySelected?: boolean;
|
|
22
|
+
format?: 'csv' | 'excel';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Tree data configuration
|
|
26
|
+
*/
|
|
27
|
+
export interface TreeDataConfig {
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/** Field that contains the hierarchy path (array of strings) */
|
|
30
|
+
pathField?: string;
|
|
31
|
+
/** Field that contains parent ID for parent-child relationships */
|
|
32
|
+
parentIdField?: string;
|
|
33
|
+
/** Field that contains the unique ID */
|
|
34
|
+
idField?: string;
|
|
35
|
+
/** Whether to expand all rows by default */
|
|
36
|
+
expandAll?: boolean;
|
|
37
|
+
/** Depth to expand to by default */
|
|
38
|
+
expandDepth?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Row grouping configuration
|
|
42
|
+
*/
|
|
43
|
+
export interface RowGroupingConfig {
|
|
44
|
+
enabled?: boolean;
|
|
45
|
+
/** Fields to group by */
|
|
46
|
+
groupByFields?: string[];
|
|
47
|
+
/** Whether to show group row count */
|
|
48
|
+
showRowCount?: boolean;
|
|
49
|
+
/** Whether to allow user to change grouping */
|
|
50
|
+
userGroupable?: boolean;
|
|
51
|
+
/** Aggregation functions for grouped columns */
|
|
52
|
+
aggregations?: Record<string, 'sum' | 'avg' | 'count' | 'min' | 'max' | 'first' | 'last'>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Excel export configuration
|
|
56
|
+
*/
|
|
57
|
+
export interface ExcelExportConfig {
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
fileName?: string;
|
|
60
|
+
sheetName?: string;
|
|
61
|
+
includeHeaders?: boolean;
|
|
62
|
+
includeGrouping?: boolean;
|
|
63
|
+
onlySelected?: boolean;
|
|
64
|
+
/** Custom column widths */
|
|
65
|
+
columnWidths?: Record<string, number>;
|
|
22
66
|
}
|
|
23
67
|
/**
|
|
24
68
|
* Status bar configuration
|
|
@@ -72,6 +116,9 @@ export interface AgGridSchema {
|
|
|
72
116
|
enableRangeSelection?: boolean;
|
|
73
117
|
enableCharts?: boolean;
|
|
74
118
|
contextMenu?: ContextMenuConfig;
|
|
119
|
+
treeData?: TreeDataConfig;
|
|
120
|
+
rowGrouping?: RowGroupingConfig;
|
|
121
|
+
excelExport?: ExcelExportConfig;
|
|
75
122
|
callbacks?: AgGridCallbacks;
|
|
76
123
|
theme?: 'alpine' | 'balham' | 'material' | 'quartz';
|
|
77
124
|
height?: number | string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-aggrid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "AG Grid data grid plugin for Object UI, powered by AG Grid Community",
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
"./style.css": "./dist/index.css"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@object-ui/components": "0.
|
|
29
|
-
"@object-ui/core": "0.
|
|
30
|
-
"@object-ui/
|
|
31
|
-
"@object-ui/
|
|
32
|
-
"@object-ui/
|
|
28
|
+
"@object-ui/components": "2.0.0",
|
|
29
|
+
"@object-ui/core": "2.0.0",
|
|
30
|
+
"@object-ui/fields": "2.0.0",
|
|
31
|
+
"@object-ui/react": "2.0.0",
|
|
32
|
+
"@object-ui/types": "2.0.0",
|
|
33
|
+
"@object-ui/data-objectstack": "2.0.0"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"ag-grid-react": "^32.0.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@types/react": "^19.2.
|
|
42
|
+
"@types/react": "^19.2.13",
|
|
42
43
|
"@types/react-dom": "^19.2.3",
|
|
43
44
|
"@vitejs/plugin-react": "^5.1.3",
|
|
44
45
|
"ag-grid-community": "^32.3.9",
|
package/src/AgGridImpl.tsx
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import React, { useMemo, useRef, useCallback } from 'react';
|
|
10
10
|
import { AgGridReact } from 'ag-grid-react';
|
|
11
11
|
import type { ColDef, GridOptions, GridReadyEvent, CellClickedEvent, RowClickedEvent, SelectionChangedEvent, CellValueChangedEvent, StatusPanelDef, GetContextMenuItemsParams, MenuItemDef } from 'ag-grid-community';
|
|
12
|
-
import type { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig } from './types';
|
|
12
|
+
import type { AgGridCallbacks, ExportConfig, StatusBarConfig, ColumnConfig, ContextMenuConfig, TreeDataConfig, RowGroupingConfig, ExcelExportConfig } from './types';
|
|
13
13
|
|
|
14
14
|
export interface AgGridImplProps {
|
|
15
15
|
rowData?: any[];
|
|
@@ -34,6 +34,9 @@ export interface AgGridImplProps {
|
|
|
34
34
|
enableRangeSelection?: boolean;
|
|
35
35
|
enableCharts?: boolean;
|
|
36
36
|
contextMenu?: ContextMenuConfig;
|
|
37
|
+
treeData?: TreeDataConfig;
|
|
38
|
+
rowGrouping?: RowGroupingConfig;
|
|
39
|
+
excelExport?: ExcelExportConfig;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -63,6 +66,9 @@ export default function AgGridImpl({
|
|
|
63
66
|
enableRangeSelection = false,
|
|
64
67
|
enableCharts = false,
|
|
65
68
|
contextMenu,
|
|
69
|
+
treeData,
|
|
70
|
+
rowGrouping,
|
|
71
|
+
excelExport,
|
|
66
72
|
}: AgGridImplProps) {
|
|
67
73
|
const gridRef = useRef<any>(null);
|
|
68
74
|
|
|
@@ -113,6 +119,31 @@ export default function AgGridImpl({
|
|
|
113
119
|
}
|
|
114
120
|
}, [exportConfig, callbacks, rowData]);
|
|
115
121
|
|
|
122
|
+
// Excel-compatible CSV Export handler
|
|
123
|
+
// Exports CSV format which can be opened directly in Excel
|
|
124
|
+
const handleExportExcel = useCallback(() => {
|
|
125
|
+
if (!gridRef.current?.api) return;
|
|
126
|
+
|
|
127
|
+
const fileName = excelExport?.fileName || exportConfig?.fileName || 'export.csv';
|
|
128
|
+
const includeHeaders = excelExport?.includeHeaders !== false;
|
|
129
|
+
|
|
130
|
+
const params = {
|
|
131
|
+
fileName,
|
|
132
|
+
skipColumnHeaders: !includeHeaders,
|
|
133
|
+
allColumns: true,
|
|
134
|
+
onlySelected: excelExport?.onlySelected || false,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
gridRef.current.api.exportDataAsCsv(params);
|
|
138
|
+
|
|
139
|
+
if (callbacks?.onExport) {
|
|
140
|
+
const data = excelExport?.onlySelected
|
|
141
|
+
? gridRef.current.api.getSelectedRows()
|
|
142
|
+
: rowData;
|
|
143
|
+
callbacks.onExport(data || [], 'excel');
|
|
144
|
+
}
|
|
145
|
+
}, [excelExport, exportConfig, callbacks, rowData]);
|
|
146
|
+
|
|
116
147
|
// Context Menu handler
|
|
117
148
|
const getContextMenuItems = useCallback((params: GetContextMenuItemsParams): (string | MenuItemDef)[] => {
|
|
118
149
|
if (!contextMenu?.enabled) return [];
|
|
@@ -127,6 +158,12 @@ export default function AgGridImpl({
|
|
|
127
158
|
icon: '<span>📥</span>',
|
|
128
159
|
action: () => handleExportCSV(),
|
|
129
160
|
});
|
|
161
|
+
} else if (item === 'export-excel') {
|
|
162
|
+
items.push({
|
|
163
|
+
name: 'Export Excel (CSV)',
|
|
164
|
+
icon: '<span>📊</span>',
|
|
165
|
+
action: () => handleExportExcel(),
|
|
166
|
+
});
|
|
130
167
|
} else if (item === 'autoSizeAll') {
|
|
131
168
|
items.push({
|
|
132
169
|
name: 'Auto-size All Columns',
|
|
@@ -170,7 +207,7 @@ export default function AgGridImpl({
|
|
|
170
207
|
}
|
|
171
208
|
|
|
172
209
|
return items;
|
|
173
|
-
}, [contextMenu, handleExportCSV, callbacks]);
|
|
210
|
+
}, [contextMenu, handleExportCSV, handleExportExcel, callbacks]);
|
|
174
211
|
|
|
175
212
|
// Event handlers
|
|
176
213
|
const handleCellClicked = useCallback((event: CellClickedEvent) => {
|
|
@@ -218,9 +255,20 @@ export default function AgGridImpl({
|
|
|
218
255
|
}
|
|
219
256
|
}
|
|
220
257
|
|
|
258
|
+
// Apply grouping
|
|
259
|
+
if (rowGrouping?.enabled && rowGrouping.groupByFields?.includes(col.field || '')) {
|
|
260
|
+
processed.rowGroup = true;
|
|
261
|
+
processed.hide = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Apply aggregation
|
|
265
|
+
if (rowGrouping?.aggregations && col.field && rowGrouping.aggregations[col.field]) {
|
|
266
|
+
processed.aggFunc = rowGrouping.aggregations[col.field];
|
|
267
|
+
}
|
|
268
|
+
|
|
221
269
|
return processed;
|
|
222
270
|
});
|
|
223
|
-
}, [columnDefs, editable, columnConfig]);
|
|
271
|
+
}, [columnDefs, editable, columnConfig, rowGrouping]);
|
|
224
272
|
|
|
225
273
|
// Merge grid options with props
|
|
226
274
|
const mergedGridOptions = useMemo(() => ({
|
|
@@ -237,6 +285,31 @@ export default function AgGridImpl({
|
|
|
237
285
|
enableRangeSelection,
|
|
238
286
|
enableCharts,
|
|
239
287
|
getContextMenuItems: contextMenu?.enabled ? getContextMenuItems : undefined,
|
|
288
|
+
// Tree data support
|
|
289
|
+
...(treeData?.enabled ? {
|
|
290
|
+
treeData: true,
|
|
291
|
+
getDataPath: treeData.pathField
|
|
292
|
+
? (data: any) => data[treeData.pathField as string]
|
|
293
|
+
: undefined,
|
|
294
|
+
autoGroupColumnDef: {
|
|
295
|
+
headerName: 'Hierarchy',
|
|
296
|
+
minWidth: 250,
|
|
297
|
+
cellRendererParams: {
|
|
298
|
+
suppressCount: false,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
groupDefaultExpanded: treeData.expandAll ? -1 : (treeData.expandDepth ?? 0),
|
|
302
|
+
} : {}),
|
|
303
|
+
// Row grouping
|
|
304
|
+
...(rowGrouping?.enabled ? {
|
|
305
|
+
groupDefaultExpanded: rowGrouping.groupByFields?.length ? 1 : 0,
|
|
306
|
+
autoGroupColumnDef: {
|
|
307
|
+
minWidth: 200,
|
|
308
|
+
cellRendererParams: {
|
|
309
|
+
suppressCount: !rowGrouping.showRowCount,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
} : {}),
|
|
240
313
|
// Default options for better UX
|
|
241
314
|
suppressCellFocus: !editable,
|
|
242
315
|
enableCellTextSelection: true,
|
|
@@ -265,6 +338,8 @@ export default function AgGridImpl({
|
|
|
265
338
|
enableCharts,
|
|
266
339
|
contextMenu,
|
|
267
340
|
getContextMenuItems,
|
|
341
|
+
treeData,
|
|
342
|
+
rowGrouping,
|
|
268
343
|
editable,
|
|
269
344
|
rowData.length,
|
|
270
345
|
handleCellClicked,
|
|
@@ -294,14 +369,24 @@ export default function AgGridImpl({
|
|
|
294
369
|
|
|
295
370
|
return (
|
|
296
371
|
<div className="ag-grid-container">
|
|
297
|
-
{exportConfig?.enabled && (
|
|
372
|
+
{(exportConfig?.enabled || excelExport?.enabled) && (
|
|
298
373
|
<div className="mb-2 flex gap-2">
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
374
|
+
{exportConfig?.enabled && (
|
|
375
|
+
<button
|
|
376
|
+
onClick={handleExportCSV}
|
|
377
|
+
className="px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
|
|
378
|
+
>
|
|
379
|
+
Export CSV
|
|
380
|
+
</button>
|
|
381
|
+
)}
|
|
382
|
+
{excelExport?.enabled && (
|
|
383
|
+
<button
|
|
384
|
+
onClick={handleExportExcel}
|
|
385
|
+
className="px-3 py-1.5 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-md transition-colors"
|
|
386
|
+
>
|
|
387
|
+
Export Excel (CSV)
|
|
388
|
+
</button>
|
|
389
|
+
)}
|
|
305
390
|
</div>
|
|
306
391
|
)}
|
|
307
392
|
<div
|
package/src/ObjectAgGridImpl.tsx
CHANGED
|
@@ -18,12 +18,11 @@ import type {
|
|
|
18
18
|
StatusPanelDef,
|
|
19
19
|
GetContextMenuItemsParams,
|
|
20
20
|
MenuItemDef,
|
|
21
|
-
IServerSideDatasource,
|
|
22
|
-
IServerSideGetRowsParams
|
|
23
21
|
} from 'ag-grid-community';
|
|
24
|
-
import type {
|
|
22
|
+
import type { FieldMetadata, ObjectSchemaMetadata } from '@object-ui/types';
|
|
25
23
|
import type { ObjectAgGridImplProps } from './object-aggrid.types';
|
|
26
24
|
import { FIELD_TYPE_TO_FILTER_TYPE } from './object-aggrid.types';
|
|
25
|
+
import { createFieldCellRenderer, createFieldCellEditor } from './field-renderers';
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
* ObjectAgGridImpl - Metadata-driven AG Grid implementation
|
|
@@ -61,7 +60,6 @@ export default function ObjectAgGridImpl({
|
|
|
61
60
|
const [error, setError] = useState<Error | null>(null);
|
|
62
61
|
const [objectSchema, setObjectSchema] = useState<ObjectSchemaMetadata | null>(null);
|
|
63
62
|
const [rowData, setRowData] = useState<any[]>([]);
|
|
64
|
-
const [totalCount, setTotalCount] = useState(0);
|
|
65
63
|
|
|
66
64
|
// Fetch object metadata
|
|
67
65
|
useEffect(() => {
|
|
@@ -115,7 +113,6 @@ export default function ObjectAgGridImpl({
|
|
|
115
113
|
|
|
116
114
|
const result = await dataSource.find(objectName, queryParams);
|
|
117
115
|
setRowData(result.data || []);
|
|
118
|
-
setTotalCount(result.total || 0);
|
|
119
116
|
callbacks?.onDataLoaded?.(result.data || []);
|
|
120
117
|
} catch (err) {
|
|
121
118
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -416,15 +413,6 @@ export default function ObjectAgGridImpl({
|
|
|
416
413
|
);
|
|
417
414
|
}
|
|
418
415
|
|
|
419
|
-
/**
|
|
420
|
-
* Escape HTML to prevent XSS attacks
|
|
421
|
-
*/
|
|
422
|
-
function escapeHtml(text: string): string {
|
|
423
|
-
const div = document.createElement('div');
|
|
424
|
-
div.textContent = text;
|
|
425
|
-
return div.innerHTML;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
416
|
/**
|
|
429
417
|
* Get filter type based on field metadata
|
|
430
418
|
*/
|
|
@@ -438,166 +426,76 @@ function getFilterType(field: FieldMetadata): string | boolean {
|
|
|
438
426
|
|
|
439
427
|
/**
|
|
440
428
|
* Apply field type-specific formatting to column definition
|
|
429
|
+
* Uses field widgets from @object-ui/fields for consistent rendering
|
|
441
430
|
*/
|
|
442
431
|
function applyFieldTypeFormatting(colDef: ColDef, field: FieldMetadata): void {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
style: 'currency',
|
|
459
|
-
currency,
|
|
460
|
-
minimumFractionDigits: precision,
|
|
461
|
-
maximumFractionDigits: precision,
|
|
462
|
-
}).format(params.value);
|
|
463
|
-
};
|
|
464
|
-
break;
|
|
465
|
-
|
|
466
|
-
case 'percent':
|
|
467
|
-
colDef.valueFormatter = (params: any) => {
|
|
468
|
-
if (params.value == null) return '';
|
|
469
|
-
const precision = (field as any).precision || 2;
|
|
470
|
-
return `${(params.value * 100).toFixed(precision)}%`;
|
|
471
|
-
};
|
|
472
|
-
break;
|
|
473
|
-
|
|
474
|
-
case 'date':
|
|
475
|
-
colDef.valueFormatter = (params: any) => {
|
|
476
|
-
if (!params.value) return '';
|
|
477
|
-
try {
|
|
478
|
-
const date = new Date(params.value);
|
|
479
|
-
if (isNaN(date.getTime())) return '';
|
|
480
|
-
return date.toLocaleDateString();
|
|
481
|
-
} catch {
|
|
482
|
-
return '';
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
break;
|
|
486
|
-
|
|
487
|
-
case 'datetime':
|
|
488
|
-
colDef.valueFormatter = (params: any) => {
|
|
489
|
-
if (!params.value) return '';
|
|
490
|
-
try {
|
|
491
|
-
const date = new Date(params.value);
|
|
492
|
-
if (isNaN(date.getTime())) return '';
|
|
493
|
-
return date.toLocaleString();
|
|
494
|
-
} catch {
|
|
495
|
-
return '';
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
break;
|
|
499
|
-
|
|
500
|
-
case 'time':
|
|
501
|
-
colDef.valueFormatter = (params: any) => {
|
|
502
|
-
if (!params.value) return '';
|
|
503
|
-
return params.value;
|
|
504
|
-
};
|
|
505
|
-
break;
|
|
506
|
-
|
|
507
|
-
case 'email':
|
|
508
|
-
colDef.cellRenderer = (params: any) => {
|
|
509
|
-
if (!params.value) return '';
|
|
510
|
-
const escaped = escapeHtml(params.value);
|
|
511
|
-
return `<a href="mailto:${escaped}" class="text-blue-600 hover:underline">${escaped}</a>`;
|
|
512
|
-
};
|
|
513
|
-
break;
|
|
514
|
-
|
|
515
|
-
case 'url':
|
|
516
|
-
colDef.cellRenderer = (params: any) => {
|
|
517
|
-
if (!params.value) return '';
|
|
518
|
-
const escaped = escapeHtml(params.value);
|
|
519
|
-
return `<a href="${escaped}" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:underline">${escaped}</a>`;
|
|
520
|
-
};
|
|
521
|
-
break;
|
|
522
|
-
|
|
523
|
-
case 'phone':
|
|
524
|
-
colDef.cellRenderer = (params: any) => {
|
|
525
|
-
if (!params.value) return '';
|
|
526
|
-
const escaped = escapeHtml(params.value);
|
|
527
|
-
return `<a href="tel:${escaped}" class="text-blue-600 hover:underline">${escaped}</a>`;
|
|
528
|
-
};
|
|
529
|
-
break;
|
|
530
|
-
|
|
531
|
-
case 'select':
|
|
532
|
-
colDef.valueFormatter = (params: any) => {
|
|
533
|
-
if (!params.value) return '';
|
|
534
|
-
const options = (field as any).options || [];
|
|
535
|
-
const option = options.find((opt: any) => opt.value === params.value);
|
|
536
|
-
return option?.label || params.value;
|
|
537
|
-
};
|
|
538
|
-
break;
|
|
539
|
-
|
|
540
|
-
case 'lookup':
|
|
541
|
-
case 'master_detail':
|
|
542
|
-
colDef.valueFormatter = (params: any) => {
|
|
543
|
-
if (!params.value) return '';
|
|
544
|
-
// Handle lookup values - could be an object or just an ID
|
|
545
|
-
if (typeof params.value === 'object') {
|
|
546
|
-
return params.value.name || params.value.label || params.value.id || '';
|
|
547
|
-
}
|
|
548
|
-
return String(params.value);
|
|
549
|
-
};
|
|
550
|
-
break;
|
|
432
|
+
// Define field types that should use field widgets for rendering
|
|
433
|
+
const fieldWidgetTypes = [
|
|
434
|
+
'text', 'textarea', 'number', 'currency', 'percent',
|
|
435
|
+
'boolean', 'select', 'date', 'datetime', 'time',
|
|
436
|
+
'email', 'phone', 'url', 'password', 'color',
|
|
437
|
+
'rating', 'image', 'avatar', 'lookup', 'slider', 'code'
|
|
438
|
+
];
|
|
439
|
+
|
|
440
|
+
// Use field widget renderer if the type is supported
|
|
441
|
+
if (fieldWidgetTypes.includes(field.type)) {
|
|
442
|
+
colDef.cellRenderer = createFieldCellRenderer(field);
|
|
443
|
+
|
|
444
|
+
// Add cell editor for editable fields
|
|
445
|
+
if (colDef.editable) {
|
|
446
|
+
colDef.cellEditor = createFieldCellEditor(field);
|
|
551
447
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
448
|
+
// Configure editor based on field type
|
|
449
|
+
if (['date', 'datetime', 'select', 'lookup', 'color'].includes(field.type)) {
|
|
450
|
+
colDef.cellEditorPopup = true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// Fallback to simple rendering for unsupported types
|
|
455
|
+
switch (field.type) {
|
|
456
|
+
case 'master_detail':
|
|
555
457
|
colDef.valueFormatter = (params: any) => {
|
|
556
|
-
if (params.value
|
|
557
|
-
|
|
458
|
+
if (!params.value) return '';
|
|
459
|
+
// Handle lookup values - could be an object or just an ID
|
|
460
|
+
if (typeof params.value === 'object') {
|
|
461
|
+
return params.value.name || params.value.label || params.value.id || '';
|
|
462
|
+
}
|
|
463
|
+
return String(params.value);
|
|
464
|
+
};
|
|
465
|
+
break;
|
|
466
|
+
|
|
467
|
+
case 'object':
|
|
468
|
+
colDef.cellRenderer = () => {
|
|
469
|
+
const span = document.createElement('span');
|
|
470
|
+
span.className = 'text-gray-500 italic';
|
|
471
|
+
span.textContent = '[Object]';
|
|
472
|
+
return span;
|
|
473
|
+
};
|
|
474
|
+
break;
|
|
475
|
+
|
|
476
|
+
case 'vector':
|
|
477
|
+
colDef.cellRenderer = () => {
|
|
478
|
+
const span = document.createElement('span');
|
|
479
|
+
span.className = 'text-gray-500 italic';
|
|
480
|
+
span.textContent = '[Vector]';
|
|
481
|
+
return span;
|
|
482
|
+
};
|
|
483
|
+
break;
|
|
484
|
+
|
|
485
|
+
case 'grid':
|
|
486
|
+
colDef.cellRenderer = () => {
|
|
487
|
+
const span = document.createElement('span');
|
|
488
|
+
span.className = 'text-gray-500 italic';
|
|
489
|
+
span.textContent = '[Grid]';
|
|
490
|
+
return span;
|
|
491
|
+
};
|
|
492
|
+
break;
|
|
493
|
+
|
|
494
|
+
default:
|
|
495
|
+
// Default text rendering
|
|
496
|
+
colDef.valueFormatter = (params: any) => {
|
|
497
|
+
return params.value != null ? String(params.value) : '';
|
|
558
498
|
};
|
|
559
|
-
}
|
|
560
|
-
break;
|
|
561
499
|
}
|
|
562
|
-
|
|
563
|
-
case 'color':
|
|
564
|
-
colDef.cellRenderer = (params: any) => {
|
|
565
|
-
if (!params.value) return '';
|
|
566
|
-
const escaped = escapeHtml(params.value);
|
|
567
|
-
return `<div class="flex items-center gap-2">
|
|
568
|
-
<div style="width: 16px; height: 16px; background-color: ${escaped}; border: 1px solid #ccc; border-radius: 2px;"></div>
|
|
569
|
-
<span>${escaped}</span>
|
|
570
|
-
</div>`;
|
|
571
|
-
};
|
|
572
|
-
break;
|
|
573
|
-
|
|
574
|
-
case 'rating':
|
|
575
|
-
colDef.cellRenderer = (params: any) => {
|
|
576
|
-
if (params.value == null) return '';
|
|
577
|
-
const max = (field as any).max || 5;
|
|
578
|
-
const stars = '⭐'.repeat(Math.min(params.value, max));
|
|
579
|
-
return stars;
|
|
580
|
-
};
|
|
581
|
-
break;
|
|
582
|
-
|
|
583
|
-
case 'image':
|
|
584
|
-
colDef.cellRenderer = (params: any) => {
|
|
585
|
-
if (!params.value) return '';
|
|
586
|
-
const url = typeof params.value === 'string' ? params.value : params.value.url;
|
|
587
|
-
if (!url) return '';
|
|
588
|
-
const escapedUrl = escapeHtml(url);
|
|
589
|
-
return `<img src="${escapedUrl}" alt="" style="width: 40px; height: 40px; object-fit: cover; border-radius: 4px;" />`;
|
|
590
|
-
};
|
|
591
|
-
break;
|
|
592
|
-
|
|
593
|
-
case 'avatar':
|
|
594
|
-
colDef.cellRenderer = (params: any) => {
|
|
595
|
-
if (!params.value) return '';
|
|
596
|
-
const url = typeof params.value === 'string' ? params.value : params.value.url;
|
|
597
|
-
if (!url) return '';
|
|
598
|
-
const escapedUrl = escapeHtml(url);
|
|
599
|
-
return `<img src="${escapedUrl}" alt="" style="width: 32px; height: 32px; object-fit: cover; border-radius: 50%;" />`;
|
|
600
|
-
};
|
|
601
|
-
break;
|
|
602
500
|
}
|
|
603
501
|
}
|