/** * Resolves column types from backend attribute definitions. * * Pages define column configs with UI metadata (label, width, formatter, etc.) * but omit the `type` field. This utility merges the backend-provided attribute * type into each column, ensuring a single source of truth for column types. */ import type { ColumnConfig } from '../components/FormGenerator/FormGeneratorTable'; import type { AttributeType } from './attributeTypeMapper'; export interface AttributeLike { name: string; type?: string; label?: string; displayField?: string; frontendFormat?: string; frontendFormatLabels?: string[]; /** Backend-provided options. Accepts the canonical `{value,label}` array * (preferred), a legacy plain `string[]` (treated as value==label), or a * string reference (e.g. `"user.role"`) — only the array forms are merged * into `ColumnConfig.options`; references are ignored here. */ options?: Array<{ value: string | number; label: string }> | string[] | string; } /** * Merge backend attribute metadata into page-defined column configs. * * For each column, the following fields are resolved from the matching backend * attribute (by `key === attr.name`) when the column does not already define * them: `type`, `label`, `displayField`, `frontendFormat`, `frontendFormatLabels`. * * Pages must NOT hardcode display data (labels, value translations) — they * declare which columns to show, the backend declares the metadata. */ export function resolveColumnTypes( columns: ColumnConfig[], attributes: AttributeLike[], ): ColumnConfig[] { if (!attributes || attributes.length === 0) return columns; const attrMap = new Map(); for (const attr of attributes) { attrMap.set(attr.name, attr); } return columns.map((col) => { const attr = attrMap.get(col.key); if (!attr) return col; const merged: ColumnConfig = { ...col }; if (attr.type) { merged.type = attr.type as AttributeType; } if (attr.label && !col.label) { merged.label = attr.label; } if (attr.displayField && !col.displayField) { merged.displayField = attr.displayField; } if (attr.frontendFormat && !col.frontendFormat) { merged.frontendFormat = attr.frontendFormat; } if (attr.frontendFormatLabels && !col.frontendFormatLabels) { merged.frontendFormatLabels = attr.frontendFormatLabels; } if (Array.isArray(attr.options) && !col.options) { // Normalise legacy `string[]` to the canonical `{value,label}` shape. const opts = attr.options as Array; const normalised = opts.map((o) => typeof o === 'string' ? { value: o, label: o } : o, ); merged.options = normalised; } return merged; }); }