Skip to content

Commit b35e529

Browse files
Copilothotlong
andcommitted
feat: Integrate @object-ui/fields widgets into object-agrid cell rendering
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent a52d526 commit b35e529

4 files changed

Lines changed: 326 additions & 155 deletions

File tree

packages/plugin-aggrid/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"dependencies": {
3535
"@object-ui/components": "workspace:*",
3636
"@object-ui/core": "workspace:*",
37+
"@object-ui/fields": "workspace:*",
3738
"@object-ui/react": "workspace:*",
3839
"@object-ui/types": "workspace:*",
3940
"@object-ui/data-objectstack": "workspace:*"

packages/plugin-aggrid/src/ObjectAgGridImpl.tsx

Lines changed: 52 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type {
2424
import type { DataSource, FieldMetadata, ObjectSchemaMetadata } from '@object-ui/types';
2525
import type { ObjectAgGridImplProps } from './object-aggrid.types';
2626
import { FIELD_TYPE_TO_FILTER_TYPE } from './object-aggrid.types';
27+
import { createFieldCellRenderer, createFieldCellEditor } from './field-renderers';
2728

2829
/**
2930
* ObjectAgGridImpl - Metadata-driven AG Grid implementation
@@ -438,166 +439,62 @@ function getFilterType(field: FieldMetadata): string | boolean {
438439

439440
/**
440441
* Apply field type-specific formatting to column definition
442+
* Uses field widgets from @object-ui/fields for consistent rendering
441443
*/
442444
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;
445+
// Define field types that should use field widgets for rendering
446+
const fieldWidgetTypes = [
447+
'text', 'textarea', 'number', 'currency', 'percent',
448+
'boolean', 'select', 'date', 'datetime', 'time',
449+
'email', 'phone', 'url', 'password', 'color',
450+
'rating', 'image', 'avatar', 'lookup', 'slider', 'code'
451+
];
452+
453+
// Use field widget renderer if the type is supported
454+
if (fieldWidgetTypes.includes(field.type)) {
455+
colDef.cellRenderer = createFieldCellRenderer(field);
456+
457+
// Add cell editor for editable fields
458+
if (colDef.editable) {
459+
colDef.cellEditor = createFieldCellEditor(field);
551460

552-
case 'number': {
553-
const precision = (field as any).precision;
554-
if (precision !== undefined) {
461+
// Configure editor based on field type
462+
if (['date', 'datetime', 'select', 'lookup', 'color'].includes(field.type)) {
463+
colDef.cellEditorPopup = true;
464+
}
465+
}
466+
} else {
467+
// Fallback to simple rendering for unsupported types
468+
switch (field.type) {
469+
case 'lookup':
470+
case 'master_detail':
555471
colDef.valueFormatter = (params: any) => {
556-
if (params.value == null) return '';
557-
return Number(params.value).toFixed(precision);
472+
if (!params.value) return '';
473+
// Handle lookup values - could be an object or just an ID
474+
if (typeof params.value === 'object') {
475+
return params.value.name || params.value.label || params.value.id || '';
476+
}
477+
return String(params.value);
478+
};
479+
break;
480+
481+
case 'object':
482+
colDef.cellRenderer = () => '<span class="text-gray-500 italic">[Object]</span>';
483+
break;
484+
485+
case 'vector':
486+
colDef.cellRenderer = () => '<span class="text-gray-500 italic">[Vector]</span>';
487+
break;
488+
489+
case 'grid':
490+
colDef.cellRenderer = () => '<span class="text-gray-500 italic">[Grid]</span>';
491+
break;
492+
493+
default:
494+
// Default text rendering
495+
colDef.valueFormatter = (params: any) => {
496+
return params.value != null ? String(params.value) : '';
558497
};
559-
}
560-
break;
561498
}
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;
602499
}
603500
}

0 commit comments

Comments
 (0)