Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 109 additions & 7 deletions packages/plugin-object/src/ObjectForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,57 @@ export const ObjectForm: React.FC<ObjectFormProps> = ({
};

// Add field-specific properties
if (field.type === 'select' || field.type === 'lookup') {
if (field.type === 'select' || field.type === 'lookup' || field.type === 'master_detail') {
formField.options = field.options || [];
formField.multiple = field.multiple;
}

if (field.type === 'number' || field.type === 'currency') {
if (field.type === 'number' || field.type === 'currency' || field.type === 'percent') {
formField.min = field.min;
formField.max = field.max;
formField.step = field.precision ? Math.pow(10, -field.precision) : undefined;
}

if (field.type === 'text' || field.type === 'textarea') {
formField.maxLength = field.maxLength;
if (field.type === 'text' || field.type === 'textarea' || field.type === 'markdown' || field.type === 'html') {
formField.maxLength = field.max_length;
formField.minLength = field.min_length;
}

if (field.type === 'file' || field.type === 'image') {
formField.multiple = field.multiple;
formField.accept = field.accept ? field.accept.join(',') : undefined;
// Add validation hints for file size and dimensions
if (field.max_size) {
const sizeHint = `Max size: ${formatFileSize(field.max_size)}`;
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String concatenation for description updates could lead to duplicate size hints if this logic runs multiple times. Consider checking if the description already contains the size hint before appending, or use a more structured approach to store metadata separately from user-facing descriptions.

Suggested change
const sizeHint = `Max size: ${formatFileSize(field.max_size)}`;
const sizeHint = `(Max size: ${formatFileSize(field.max_size)})`;
const currentDescription = formField.description || '';
if (!currentDescription.includes(sizeHint)) {
formField.description = currentDescription
? `${currentDescription} ${sizeHint}`
: sizeHint;
}

Copilot uses AI. Check for mistakes.
formField.description = formField.description
? `${formField.description} (${sizeHint})`
: sizeHint;
}
}

if (field.type === 'email') {
formField.inputType = 'email';
}

if (field.type === 'phone') {
formField.inputType = 'tel';
}

if (field.type === 'url') {
formField.inputType = 'url';
}

if (field.type === 'password') {
formField.inputType = 'password';
}

if (field.type === 'time') {
formField.inputType = 'time';
}

// Read-only fields for computed types
if (field.type === 'formula' || field.type === 'summary' || field.type === 'auto_number') {
formField.disabled = true;
}

generatedFields.push(formField);
Expand Down Expand Up @@ -245,29 +284,92 @@ export const ObjectForm: React.FC<ObjectFormProps> = ({
* `select`). If a field type is not explicitly mapped, the function falls
* back to the generic `"input"` type.
*
* Updated to support all field types from @objectql/types v3.0.1:
* text, textarea, markdown, html, select, date, datetime, time, number,
* currency, percent, boolean, email, phone, url, image, file, location,
* lookup, master_detail, password, formula, summary, auto_number, object,
* vector, grid
*
* @param fieldType - The ObjectQL field type identifier to convert
* (for example: `"text"`, `"number"`, `"date"`, `"lookup"`).
* @returns The normalized form field type string used in the form schema
* (for example: `"input"`, `"textarea"`, `"date-picker"`, `"select"`).
*/
function mapFieldTypeToFormType(fieldType: string): string {
const typeMap: Record<string, string> = {
// Text-based fields
text: 'input',
textarea: 'textarea',
markdown: 'textarea', // Markdown editor (fallback to textarea)
html: 'textarea', // Rich text editor (fallback to textarea)

// Numeric fields
number: 'input',
currency: 'input',
percent: 'input',

// Date/Time fields
date: 'date-picker',
datetime: 'date-picker',
time: 'input', // Time picker (fallback to input with type="time")

// Boolean
boolean: 'switch',

// Selection fields
select: 'select',
lookup: 'select',
master_detail: 'select',

// Contact fields
email: 'input',
phone: 'input',
url: 'input',

// File fields
file: 'file-upload',
image: 'file-upload',

// Special fields
password: 'input',
lookup: 'select',
master_detail: 'select',
fileupload: 'file-upload',
location: 'input', // Location/map field (fallback to input)

// Auto-generated/computed fields (typically read-only)
formula: 'input',
summary: 'input',
auto_number: 'input',

// Complex data types
object: 'input', // JSON object (fallback to input)
vector: 'input', // Vector/embedding data (fallback to input)
grid: 'input', // Grid/table data (fallback to input)
};

return typeMap[fieldType] || 'input';
}

/**
* Formats file size in bytes to human-readable string
* @param bytes - File size in bytes (must be non-negative)
* @returns Formatted string (e.g., "5 MB", "1.5 GB")
*/
function formatFileSize(bytes: number): string {
if (bytes < 0 || !Number.isFinite(bytes)) {
return '0 B';
}

if (bytes === 0) {
return '0 B';
}
Comment on lines +362 to +363
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatFileSize function uses toFixed() which returns a string, then interpolates it into another string. This can result in trailing zeros (e.g., '5.0 MB'). Consider using Number(size.toFixed(...)) or format the number differently to avoid unnecessary decimal points for whole numbers.

Suggested change
return '0 B';
}
const decimals = unitIndex > 0 ? 1 : 0;
const formattedSize = Number(size.toFixed(decimals));
return `${formattedSize} ${units[unitIndex]}`;

Copilot uses AI. Check for mistakes.

const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;

while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}

return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
}
40 changes: 40 additions & 0 deletions packages/plugin-object/src/ObjectTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,46 @@
accessorKey: fieldName,
};

// Add field type-specific formatting hints
if (field.type === 'date' || field.type === 'datetime') {
column.type = 'date';
} else if (field.type === 'boolean') {
column.type = 'boolean';
} else if (field.type === 'number' || field.type === 'currency' || field.type === 'percent') {
column.type = 'number';
} else if (field.type === 'image' || field.type === 'file') {
// For file/image fields, display the name or count
column.cell = (value: any) => {
if (!value) return '-';
if (Array.isArray(value)) {
const count = value.length;
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pluralization logic is incorrect. When value.length is 1, it will display 'file(s)' instead of 'file'. Consider using conditional logic: ${value.length} ${field.type}${value.length !== 1 ? 's' : ''}

Suggested change
const count = value.length;
return `${value.length} ${field.type}${value.length !== 1 ? 's' : ''}`;

Copilot uses AI. Check for mistakes.
const fileType = field.type === 'image' ? 'image' : 'file';
return count === 1 ? `1 ${fileType}` : `${count} ${fileType}s`;
}
return value.name || value.original_name || 'File';
};
} else if (field.type === 'lookup' || field.type === 'master_detail') {
// For relationship fields, display the name property if available
column.cell = (value: any) => {
if (!value) return '-';
if (typeof value === 'object' && value !== null) {

Check warning

Code scanning / CodeQL

Comparison between inconvertible types Warning

Variable 'value' is of type date, object or regular expression, but it is compared to
an expression
of type null.

Copilot Autofix

AI 9 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

// Try common display properties first
if (value.name) return value.name;
if (value.label) return value.label;
if (value._id) return value._id;
// Fallback to object type indicator
return '[Object]';
}
return String(value);
};
} else if (field.type === 'url') {
// For URL fields, make them clickable
column.cell = (value: any) => {
if (!value) return '-';
return value; // The table renderer should handle URL formatting
};
}

generatedColumns.push(column);
}
});
Expand Down