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 = []; // Add system status fields first addSystemStatusFields(properties, fields); // 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; } }); // Mark lookup copy fields markLookupCopies(fields); // Add any missing system fields addSystemFields(properties, fields); return { fields, spacers }; }, isCategory: field => field.type === "CATEGORY", isCheckBox: field => field.type === "CHECK_BOX", isCreatedTime: field => field.type === "CREATED_TIME", isCreator: field => field.type === "CREATOR", isDate: field => field.type === "DATE", isDatetime: field => field.type === "DATETIME", isDropDown: field => field.type === "DROP_DOWN", isFile: field => field.type === "FILE", isGroup: field => field.type === "GROUP", isGroupSelect: field => field.type === "GROUP_SELECT", isInGroup: field => 'group' in field, isInSubtable: field => 'table' in field, isLink: field => field.type === "LINK", isLookup: field => 'lookup' in field, isLookupCopy: field => field.isLookupCopy === true, isModifier: field => field.type === "MODIFIER", isMultiLineText: field => field.type === "MULTI_LINE_TEXT", isMultiSelect: field => field.type === "MULTI_SELECT", isNotInSubtable: field => !('table' in field), isNumber: field => field.type === "NUMBER", isOrganizationSelect: field => field.type === "ORGANIZATION_SELECT", isRadioButton: field => field.type === "RADIO_BUTTON", isRecordNumber: field => field.type === "RECORD_NUMBER", isReferenceTable: field => field.type === "REFERENCE_TABLE", isRichText: field => field.type === "RICH_TEXT", isSingleLineText: field => field.type === "SINGLE_LINE_TEXT", isStatus: field => field.type === "STATUS", isStatusAssignee: field => field.type === "STATUS_ASSIGNEE", isSubtable: field => field.type === "SUBTABLE", isTime: field => field.type === "TIME", isUpdatedTime: field => field.type === "UPDATED_TIME", isUserSelect: field => field.type === "USER_SELECT", };