Skip to content

Commit a8c8ecd

Browse files
authored
Merge pull request #95 from objectstack-ai/copilot/optimize-components-according-objectql
2 parents f504d1b + d82029d commit a8c8ecd

File tree

2 files changed

+149
-7
lines changed

2 files changed

+149
-7
lines changed

packages/plugin-object/src/ObjectForm.tsx

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,57 @@ export const ObjectForm: React.FC<ObjectFormProps> = ({
128128
};
129129

130130
// Add field-specific properties
131-
if (field.type === 'select' || field.type === 'lookup') {
131+
if (field.type === 'select' || field.type === 'lookup' || field.type === 'master_detail') {
132132
formField.options = field.options || [];
133+
formField.multiple = field.multiple;
133134
}
134135

135-
if (field.type === 'number' || field.type === 'currency') {
136+
if (field.type === 'number' || field.type === 'currency' || field.type === 'percent') {
136137
formField.min = field.min;
137138
formField.max = field.max;
138139
formField.step = field.precision ? Math.pow(10, -field.precision) : undefined;
139140
}
140141

141-
if (field.type === 'text' || field.type === 'textarea') {
142-
formField.maxLength = field.maxLength;
142+
if (field.type === 'text' || field.type === 'textarea' || field.type === 'markdown' || field.type === 'html') {
143+
formField.maxLength = field.max_length;
144+
formField.minLength = field.min_length;
145+
}
146+
147+
if (field.type === 'file' || field.type === 'image') {
148+
formField.multiple = field.multiple;
149+
formField.accept = field.accept ? field.accept.join(',') : undefined;
150+
// Add validation hints for file size and dimensions
151+
if (field.max_size) {
152+
const sizeHint = `Max size: ${formatFileSize(field.max_size)}`;
153+
formField.description = formField.description
154+
? `${formField.description} (${sizeHint})`
155+
: sizeHint;
156+
}
157+
}
158+
159+
if (field.type === 'email') {
160+
formField.inputType = 'email';
161+
}
162+
163+
if (field.type === 'phone') {
164+
formField.inputType = 'tel';
165+
}
166+
167+
if (field.type === 'url') {
168+
formField.inputType = 'url';
169+
}
170+
171+
if (field.type === 'password') {
172+
formField.inputType = 'password';
173+
}
174+
175+
if (field.type === 'time') {
176+
formField.inputType = 'time';
177+
}
178+
179+
// Read-only fields for computed types
180+
if (field.type === 'formula' || field.type === 'summary' || field.type === 'auto_number') {
181+
formField.disabled = true;
143182
}
144183

145184
generatedFields.push(formField);
@@ -245,29 +284,92 @@ export const ObjectForm: React.FC<ObjectFormProps> = ({
245284
* `select`). If a field type is not explicitly mapped, the function falls
246285
* back to the generic `"input"` type.
247286
*
287+
* Updated to support all field types from @objectql/types v3.0.1:
288+
* text, textarea, markdown, html, select, date, datetime, time, number,
289+
* currency, percent, boolean, email, phone, url, image, file, location,
290+
* lookup, master_detail, password, formula, summary, auto_number, object,
291+
* vector, grid
292+
*
248293
* @param fieldType - The ObjectQL field type identifier to convert
249294
* (for example: `"text"`, `"number"`, `"date"`, `"lookup"`).
250295
* @returns The normalized form field type string used in the form schema
251296
* (for example: `"input"`, `"textarea"`, `"date-picker"`, `"select"`).
252297
*/
253298
function mapFieldTypeToFormType(fieldType: string): string {
254299
const typeMap: Record<string, string> = {
300+
// Text-based fields
255301
text: 'input',
256302
textarea: 'textarea',
303+
markdown: 'textarea', // Markdown editor (fallback to textarea)
304+
html: 'textarea', // Rich text editor (fallback to textarea)
305+
306+
// Numeric fields
257307
number: 'input',
258308
currency: 'input',
259309
percent: 'input',
310+
311+
// Date/Time fields
260312
date: 'date-picker',
261313
datetime: 'date-picker',
314+
time: 'input', // Time picker (fallback to input with type="time")
315+
316+
// Boolean
262317
boolean: 'switch',
318+
319+
// Selection fields
263320
select: 'select',
321+
lookup: 'select',
322+
master_detail: 'select',
323+
324+
// Contact fields
264325
email: 'input',
326+
phone: 'input',
265327
url: 'input',
328+
329+
// File fields
330+
file: 'file-upload',
331+
image: 'file-upload',
332+
333+
// Special fields
266334
password: 'input',
267-
lookup: 'select',
268-
master_detail: 'select',
269-
fileupload: 'file-upload',
335+
location: 'input', // Location/map field (fallback to input)
336+
337+
// Auto-generated/computed fields (typically read-only)
338+
formula: 'input',
339+
summary: 'input',
340+
auto_number: 'input',
341+
342+
// Complex data types
343+
object: 'input', // JSON object (fallback to input)
344+
vector: 'input', // Vector/embedding data (fallback to input)
345+
grid: 'input', // Grid/table data (fallback to input)
270346
};
271347

272348
return typeMap[fieldType] || 'input';
273349
}
350+
351+
/**
352+
* Formats file size in bytes to human-readable string
353+
* @param bytes - File size in bytes (must be non-negative)
354+
* @returns Formatted string (e.g., "5 MB", "1.5 GB")
355+
*/
356+
function formatFileSize(bytes: number): string {
357+
if (bytes < 0 || !Number.isFinite(bytes)) {
358+
return '0 B';
359+
}
360+
361+
if (bytes === 0) {
362+
return '0 B';
363+
}
364+
365+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
366+
let size = bytes;
367+
let unitIndex = 0;
368+
369+
while (size >= 1024 && unitIndex < units.length - 1) {
370+
size /= 1024;
371+
unitIndex++;
372+
}
373+
374+
return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
375+
}

packages/plugin-object/src/ObjectTable.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,46 @@ export const ObjectTable: React.FC<ObjectTableProps> = ({
9696
accessorKey: fieldName,
9797
};
9898

99+
// Add field type-specific formatting hints
100+
if (field.type === 'date' || field.type === 'datetime') {
101+
column.type = 'date';
102+
} else if (field.type === 'boolean') {
103+
column.type = 'boolean';
104+
} else if (field.type === 'number' || field.type === 'currency' || field.type === 'percent') {
105+
column.type = 'number';
106+
} else if (field.type === 'image' || field.type === 'file') {
107+
// For file/image fields, display the name or count
108+
column.cell = (value: any) => {
109+
if (!value) return '-';
110+
if (Array.isArray(value)) {
111+
const count = value.length;
112+
const fileType = field.type === 'image' ? 'image' : 'file';
113+
return count === 1 ? `1 ${fileType}` : `${count} ${fileType}s`;
114+
}
115+
return value.name || value.original_name || 'File';
116+
};
117+
} else if (field.type === 'lookup' || field.type === 'master_detail') {
118+
// For relationship fields, display the name property if available
119+
column.cell = (value: any) => {
120+
if (!value) return '-';
121+
if (typeof value === 'object' && value !== null) {
122+
// Try common display properties first
123+
if (value.name) return value.name;
124+
if (value.label) return value.label;
125+
if (value._id) return value._id;
126+
// Fallback to object type indicator
127+
return '[Object]';
128+
}
129+
return String(value);
130+
};
131+
} else if (field.type === 'url') {
132+
// For URL fields, make them clickable
133+
column.cell = (value: any) => {
134+
if (!value) return '-';
135+
return value; // The table renderer should handle URL formatting
136+
};
137+
}
138+
99139
generatedColumns.push(column);
100140
}
101141
});

0 commit comments

Comments
 (0)