@@ -18,12 +18,11 @@ import type {
1818 StatusPanelDef ,
1919 GetContextMenuItemsParams ,
2020 MenuItemDef ,
21- IServerSideDatasource ,
22- IServerSideGetRowsParams
2321} from 'ag-grid-community' ;
24- import type { DataSource , FieldMetadata , ObjectSchemaMetadata } from '@object-ui/types' ;
22+ import type { FieldMetadata , ObjectSchemaMetadata } from '@object-ui/types' ;
2523import type { ObjectAgGridImplProps } from './object-aggrid.types' ;
2624import { FIELD_TYPE_TO_FILTER_TYPE } from './object-aggrid.types' ;
25+ import { createFieldCellRenderer , createFieldCellEditor } from './field-renderers' ;
2726
2827/**
2928 * ObjectAgGridImpl - Metadata-driven AG Grid implementation
@@ -61,7 +60,6 @@ export default function ObjectAgGridImpl({
6160 const [ error , setError ] = useState < Error | null > ( null ) ;
6261 const [ objectSchema , setObjectSchema ] = useState < ObjectSchemaMetadata | null > ( null ) ;
6362 const [ rowData , setRowData ] = useState < any [ ] > ( [ ] ) ;
64- const [ totalCount , setTotalCount ] = useState ( 0 ) ;
6563
6664 // Fetch object metadata
6765 useEffect ( ( ) => {
@@ -115,7 +113,6 @@ export default function ObjectAgGridImpl({
115113
116114 const result = await dataSource . find ( objectName , queryParams ) ;
117115 setRowData ( result . data || [ ] ) ;
118- setTotalCount ( result . total || 0 ) ;
119116 callbacks ?. onDataLoaded ?.( result . data || [ ] ) ;
120117 } catch ( err ) {
121118 const error = err instanceof Error ? err : new Error ( String ( err ) ) ;
@@ -416,15 +413,6 @@ export default function ObjectAgGridImpl({
416413 ) ;
417414}
418415
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-
428416/**
429417 * Get filter type based on field metadata
430418 */
@@ -438,166 +426,76 @@ function getFilterType(field: FieldMetadata): string | boolean {
438426
439427/**
440428 * Apply field type-specific formatting to column definition
429+ * Uses field widgets from @object-ui/fields for consistent rendering
441430 */
442431function 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 ) ;
551447
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' :
555457 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 ) : '' ;
558498 } ;
559- }
560- break ;
561499 }
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 ;
602500 }
603501}
0 commit comments