This commit is contained in:
hsueh chiahao
2025-10-10 09:39:55 +08:00
parent dfedbf2ef6
commit a5dead4259
5 changed files with 580 additions and 306 deletions

360
fields.js
View File

@@ -1,160 +1,224 @@
import {
FIELD_TYPES,
LAYOUT_TYPES,
OPTION_SORTABLE_TYPES,
EXCLUDED_GROUP_TYPES
} from './constants.js';
/**
* Sorts field options by index for fields that support options
* @param {Object} field - Field object
* @returns {Array} Sorted option labels
*/
const sortFieldOptions = (field) => {
if (!field.options) return [];
return Object.values(field.options)
.sort((a, b) => Number(a.index) - Number(b.index))
.map(option => option.label);
};
/**
* Checks if a field should have its options sorted
* @param {string} fieldType - Type of the field
* @returns {boolean} True if options should be sorted
*/
const shouldSortOptions = (fieldType) => OPTION_SORTABLE_TYPES.includes(fieldType);
/**
* Adds system status fields to the fields array if they are enabled
* @param {Array} properties - All property objects
* @param {Array} fields - Array to add fields to
*/
const addSystemStatusFields = (properties, fields) => {
const propertyList = Object.values(properties);
[FIELD_TYPES.STATUS, FIELD_TYPES.STATUS_ASSIGNEE, FIELD_TYPES.CATEGORY].forEach(type => {
const field = propertyList.find(f => f.type === type);
if (field && field.enabled) {
fields.push(field);
}
});
};
/**
* Processes fields in a regular row layout
* @param {Array} rowFields - Fields in the row
* @param {Object} properties - Property mapping
* @param {Array} fields - Array to add processed fields to
* @param {Array} spacers - Array to add spacer elements to
*/
const processRowFields = (rowFields, properties, fields, spacers) => {
for (const field of rowFields) {
if ([LAYOUT_TYPES.LABEL, LAYOUT_TYPES.HR].includes(field.type)) continue;
if (field.type === LAYOUT_TYPES.SPACER) {
spacers.push(field);
continue;
}
const fieldProperty = properties[field.code];
if ([FIELD_TYPES.SUBTABLE, FIELD_TYPES.GROUP].includes(fieldProperty.type)) continue;
const fieldToAdd = shouldSortOptions(fieldProperty.type)
? { ...fieldProperty, sortedOptions: sortFieldOptions(fieldProperty) }
: fieldProperty;
fields.push(fieldToAdd);
}
};
/**
* Processes subtable fields
* @param {Object} layout - Subtable layout object
* @param {Object} properties - Property mapping
* @param {Array} fields - Array to add processed fields to
*/
const processSubtableFields = (layout, properties, fields) => {
const tableCode = layout.code;
const tableField = properties[tableCode];
if (tableField.type !== FIELD_TYPES.SUBTABLE) return;
const tableFields = {};
layout.fields.forEach(({ code: fieldCode }) => {
const subField = tableField.fields[fieldCode];
const processedField = shouldSortOptions(subField.type)
? { ...subField, table: tableCode, sortedOptions: sortFieldOptions(subField) }
: { ...subField, table: tableCode };
tableFields[fieldCode] = processedField;
fields.push(processedField);
});
fields.push({ ...tableField, fields: tableFields });
};
/**
* Processes group fields
* @param {Object} layout - Group layout object
* @param {Object} properties - Property mapping
* @param {Array} fields - Array to add processed fields to
* @param {Array} spacers - Array to add spacer elements to
*/
const processGroupFields = (layout, properties, fields, spacers) => {
const groupCode = layout.code;
const groupField = properties[groupCode];
if (groupField.type !== FIELD_TYPES.GROUP) return;
const groupFields = {};
layout.layout.forEach(row => {
row.fields.forEach(field => {
if ([LAYOUT_TYPES.LABEL, LAYOUT_TYPES.HR].includes(field.type)) return;
if (field.type === LAYOUT_TYPES.SPACER) {
spacers.push({ ...field, group: groupCode });
return;
}
const groupSubField = properties[field.code];
if (EXCLUDED_GROUP_TYPES.includes(groupSubField.type)) return;
const processedField = shouldSortOptions(groupSubField.type)
? { ...groupSubField, group: groupCode, sortedOptions: sortFieldOptions(groupSubField) }
: { ...groupSubField, group: groupCode };
groupFields[field.code] = processedField;
fields.push(processedField);
});
});
fields.push({ ...groupField, fields: groupFields });
};
/**
* Marks lookup copy fields in the fields array
* @param {Array} fields - Array of fields to process
*/
const markLookupCopies = (fields) => {
const lookupCopyTypes = [
FIELD_TYPES.SINGLE_LINE_TEXT, FIELD_TYPES.NUMBER, FIELD_TYPES.MULTI_LINE_TEXT,
FIELD_TYPES.RICH_TEXT, FIELD_TYPES.LINK, FIELD_TYPES.CHECK_BOX,
FIELD_TYPES.RADIO_BUTTON, FIELD_TYPES.DROP_DOWN, FIELD_TYPES.MULTI_SELECT,
FIELD_TYPES.DATE, FIELD_TYPES.TIME, FIELD_TYPES.DATETIME,
FIELD_TYPES.USER_SELECT, FIELD_TYPES.ORGANIZATION_SELECT,
FIELD_TYPES.GROUP_SELECT, FIELD_TYPES.CALC, FIELD_TYPES.RECORD_NUMBER
];
fields.forEach(field => {
if (field.lookup && Array.isArray(field.lookup.fieldMappings)) {
field.lookup.fieldMappings.forEach(mapping => {
const targetField = fields.find(f =>
lookupCopyTypes.includes(f.type) && f.code === mapping.field
);
if (targetField) {
targetField.isLookupCopy = true;
}
});
}
});
};
/**
* Adds system fields that aren't already in the fields array
* @param {Array} properties - Property objects
* @param {Array} fields - Array to add fields to
*/
const addSystemFields = (properties, fields) => {
const propertyList = Object.values(properties);
const systemFieldTypes = [
FIELD_TYPES.RECORD_NUMBER,
FIELD_TYPES.CREATOR,
FIELD_TYPES.CREATED_TIME,
FIELD_TYPES.MODIFIER,
FIELD_TYPES.UPDATED_TIME
];
systemFieldTypes.forEach(type => {
const field = propertyList.find(f => f.type === type);
if (field && !fields.some(f => f.type === type)) {
fields.push(field);
}
});
};
export const kintonePrettyFields = {
/**
* Generates processed field and spacer arrays from form properties and layout
* @param {Object} properties - Field properties object
* @param {Array} layouts - Layout definitions
* @returns {Object} Object containing fields and spacers arrays
*/
generateFields: (properties, layouts) => {
const fields = [];
const spacers = [];
const isStatus = field => field.type === "STATUS";
const isStatusAssignee = field => field.type === "STATUS_ASSIGNEE";
const isCategory = field => field.type === "CATEGORY";
// Add system status fields first
addSystemStatusFields(properties, fields);
const propertyList = Object.values(properties)
const statusField = propertyList.find(isStatus);
if (statusField && statusField.enabled) {
fields.push(statusField);
}
const statusAssigneeField = propertyList.find(isStatusAssignee);
if (statusAssigneeField && statusAssigneeField.enabled) {
fields.push(statusAssigneeField);
}
const categoryField = propertyList.find(isCategory);
if (categoryField && categoryField.enabled) {
fields.push(categoryField);
}
for (const r of layouts) {
if (r.type === "ROW") {
for (const f of r.fields) {
if (f.type === "LABEL" || f.type === "HR") continue;
if (f.type === "SPACER") {
spacers.push(f);
continue;
}
const ee = properties[f.code];
if (ee.type !== "SUBTABLE" && ee.type !== "GROUP") {
if (ee.type === "CHECK_BOX" || ee.type === "DROP_DOWN" || ee.type === "MULTI_SELECT" || ee.type === "RADIO_BUTTON") {
const sortedOptions = Object.values(ee.options).sort((a, b) => Number(a.index) - Number(b.index)).map(o => o.label);
fields.push({
...ee,
sortedOptions
});
continue;
}
fields.push(ee);
}
}
} else if (r.type === "SUBTABLE") {
const tableCode = r.code;
const tableField = properties[tableCode];
if (tableField.type !== "SUBTABLE") continue;
const tableFields = {};
for (const { code: fieldCode } of r.fields) {
const subf = tableField.fields[fieldCode];
if (subf.type === "CHECK_BOX" || subf.type === "DROP_DOWN" || subf.type === "MULTI_SELECT" || subf.type === "RADIO_BUTTON") {
const sortedOptions = Object.values(subf.options).sort((a, b) => Number(a.index) - Number(b.index)).map(o => o.label);
tableFields[fieldCode] = {
...subf,
table: tableCode,
sortedOptions
};
fields.push({
...subf,
table: tableCode,
sortedOptions
});
continue;
}
tableFields[fieldCode] = {
...subf,
table: tableCode
};
fields.push({
...subf,
table: tableCode
});
}
fields.push({
...tableField,
fields: tableFields
});
} else if (r.type === "GROUP") {
const groupCode = r.code;
const groupField = properties[groupCode];
if (groupField.type !== "GROUP") continue;
const groupFields = {};
for (const row of r.layout) {
for (const f of row.fields) {
if (f.type === "LABEL" || f.type === "HR") continue;
if (f.type === "SPACER") {
spacers.push({
...f,
group: groupCode
});
continue;
}
const gSubf = properties[f.code];
if (gSubf.type !== "CATEGORY" && gSubf.type !== "STATUS" && gSubf.type !== "STATUS_ASSIGNEE" && gSubf.type !== "SUBTABLE" && gSubf.type !== "GROUP") {
if (gSubf.type === "CHECK_BOX" || gSubf.type === "DROP_DOWN" || gSubf.type === "MULTI_SELECT" || gSubf.type === "RADIO_BUTTON") {
const sortedOptions = Object.values(gSubf.options).sort((a, b) => Number(a.index) - Number(b.index)).map(o => o.label);
groupFields[f.code] = {
...gSubf,
group: groupCode,
sortedOptions
};
fields.push({
...gSubf,
group: groupCode,
sortedOptions
});
continue;
}
groupFields[f.code] = {
...gSubf,
group: groupCode
};
fields.push({
...gSubf,
group: groupCode
});
}
}
}
fields.push({
...groupField,
fields: groupFields
});
// Process each layout item
layouts.forEach(layout => {
switch (layout.type) {
case LAYOUT_TYPES.ROW:
processRowFields(layout.fields, properties, fields, spacers);
break;
case LAYOUT_TYPES.SUBTABLE:
processSubtableFields(layout, properties, fields);
break;
case LAYOUT_TYPES.GROUP:
processGroupFields(layout, properties, fields, spacers);
break;
}
}
});
// lookup copy
const flatFields = fields;
for (const f of flatFields) {
if ('lookup' in f && f.lookup !== null) {
for (const mapping of f.lookup.fieldMappings) {
const target = flatFields.find(ee => ["SINGLE_LINE_TEXT", "NUMBER", "MULTI_LINE_TEXT", "RICH_TEXT", "LINK", "CHECK_BOX", "RADIO_BUTTON", "DROP_DOWN", "MULTI_SELECT", "DATE", "TIME", "DATETIME", "USER_SELECT", "ORGANIZATION_SELECT", "GROUP_SELECT", "CALC", "RECORD_NUMBER"].includes(ee.type) && ee.code === mapping.field);
if (target) {
target.isLookupCopy = true;
}
}
}
}
// Mark lookup copy fields
markLookupCopies(fields);
const recordNumberField = propertyList.find(f => f.type === "RECORD_NUMBER");
if (recordNumberField && !fields.some(f => f.type === "RECORD_NUMBER")) fields.push(recordNumberField);
const creatorField = propertyList.find(f => f.type === "CREATOR");
if (creatorField && !fields.some(f => f.type === "CREATOR")) fields.push(creatorField);
const createdTimeField = propertyList.find(f => f.type === "CREATED_TIME");
if (createdTimeField && !fields.some(f => f.type === "CREATED_TIME")) fields.push(createdTimeField);
const modifierField = propertyList.find(f => f.type === "MODIFIER");
if (modifierField && !fields.some(f => f.type === "MODIFIER")) fields.push(modifierField);
const updatedTimeField = propertyList.find(f => f.type === "UPDATED_TIME");
if (updatedTimeField && !fields.some(f => f.type === "UPDATED_TIME")) fields.push(updatedTimeField);
// Add any missing system fields
addSystemFields(properties, fields);
return { fields, spacers };
},