@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.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +90 -12
  2. package/CHANGELOG.md +16 -0
  3. package/QUICKSTART.md +1 -1
  4. package/dist/AddressField-Bntpynvd.js +95 -0
  5. package/dist/AgGridImpl-3Mmf2qrR.js +229 -0
  6. package/dist/AutoNumberField-C1kBJaxh.js +8 -0
  7. package/dist/FileField-BDwbJvor.js +101 -0
  8. package/dist/FormulaField-BXNiyGoh.js +9 -0
  9. package/dist/GeolocationField-Df3yYcM9.js +141 -0
  10. package/dist/GridField-CcjQp4WM.js +29 -0
  11. package/dist/LocationField-BIfN5QIq.js +33 -0
  12. package/dist/MasterDetailField-CAEmxbIT.js +117 -0
  13. package/dist/ObjectAgGridImpl-EjifM4aY.js +28727 -0
  14. package/dist/ObjectField-BpkQpIF-.js +51 -0
  15. package/dist/QRCodeField-VCBewTDG.js +96 -0
  16. package/dist/RichTextField-CyQwSi2C.js +37 -0
  17. package/dist/SignatureField-Cr4tsEbj.js +96 -0
  18. package/dist/SummaryField-CnEJ_GZI.js +9 -0
  19. package/dist/UserField-DJjaVyrV.js +49 -0
  20. package/dist/VectorField-cPYmcKnV.js +25 -0
  21. package/dist/{index-CLKYMco3.js → index-B87wd1E0.js} +57 -33
  22. package/dist/index.js +1 -1
  23. package/dist/index.umd.cjs +225 -5
  24. package/dist/src/AgGridImpl.d.ts +5 -2
  25. package/dist/src/field-renderers.d.ts +67 -0
  26. package/dist/src/index.d.ts +5 -2
  27. package/dist/src/types.d.ts +48 -1
  28. package/package.json +8 -7
  29. package/src/AgGridImpl.tsx +95 -10
  30. package/src/ObjectAgGridImpl.tsx +67 -169
  31. package/src/field-renderers.test.tsx +383 -0
  32. package/src/field-renderers.tsx +224 -0
  33. package/src/index.tsx +30 -3
  34. package/src/types.ts +57 -1
  35. package/dist/AgGridImpl-BQ6tBvrq.js +0 -175
  36. package/dist/ObjectAgGridImpl-CGFeGvOH.js +0 -372
@@ -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
+ };
@@ -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
  /**
@@ -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.5.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.5.0",
29
- "@object-ui/core": "0.5.0",
30
- "@object-ui/react": "0.5.0",
31
- "@object-ui/types": "0.5.0",
32
- "@object-ui/data-objectstack": "0.5.0"
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.10",
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",
@@ -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
- <button
300
- onClick={handleExportCSV}
301
- className="px-3 py-1.5 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors"
302
- >
303
- Export CSV
304
- </button>
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
@@ -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 { DataSource, FieldMetadata, ObjectSchemaMetadata } from '@object-ui/types';
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
- switch (field.type) {
444
- case 'boolean':
445
- colDef.cellRenderer = (params: any) => {
446
- if (params.value === true) return ' Yes';
447
- if (params.value === false) return ' No';
448
- return '';
449
- };
450
- break;
451
-
452
- case 'currency':
453
- colDef.valueFormatter = (params: any) => {
454
- if (params.value == null) return '';
455
- const currency = (field as any).currency || 'USD';
456
- const precision = (field as any).precision || 2;
457
- return new Intl.NumberFormat('en-US', {
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
- case 'number': {
553
- const precision = (field as any).precision;
554
- if (precision !== undefined) {
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 == null) return '';
557
- return Number(params.value).toFixed(precision);
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
  }