ui-nyla/src/utils/columnTypeResolver.ts
2026-04-26 22:53:39 +02:00

77 lines
2.8 KiB
TypeScript

/**
* 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<string, AttributeLike>();
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<string | { value: string | number; label: string }>;
const normalised = opts.map((o) =>
typeof o === 'string' ? { value: o, label: o } : o,
);
merged.options = normalised;
}
return merged;
});
}