@mmlogic/components 0.1.4 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/mosterdcomponents.cjs.js +1 -1
- package/dist/cjs/mrd-boolean-field_16.cjs.entry.js +128 -29
- package/dist/collection/components/mrd-field/mrd-field.js +35 -16
- package/dist/collection/components/mrd-form/mrd-form.js +86 -2
- package/dist/collection/components/mrd-relation-field/mrd-relation-field.js +144 -13
- package/dist/collection/dev/api.js +198 -0
- package/dist/collection/dev/app.js +521 -0
- package/dist/collection/dev/auth.js +156 -0
- package/dist/collection/dev/example-data.js +256 -0
- package/dist/components/mrd-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-relation-field2.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/mosterdcomponents.js +1 -1
- package/dist/esm/mrd-boolean-field_16.entry.js +129 -30
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-5a453e03.entry.js +1 -0
- package/dist/types/components/mrd-field/mrd-field.d.ts +9 -0
- package/dist/types/components/mrd-form/mrd-form.d.ts +17 -0
- package/dist/types/components/mrd-relation-field/mrd-relation-field.d.ts +16 -1
- package/dist/types/components.d.ts +26 -2
- package/dist/types/types/client-layout.d.ts +1 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/p-cc7f5a78.entry.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Host, h } from "@stencil/core";
|
|
2
2
|
import { t } from "../../utils/i18n";
|
|
3
3
|
import { validateRequired } from "../../utils/validation";
|
|
4
|
-
import { ClientLayoutItemRelationDisplayType } from "../../types";
|
|
4
|
+
import { ClientLayoutItemRelationDisplayType, ClientLayoutItemRelationEditBehavior } from "../../types";
|
|
5
5
|
export class MrdRelationField {
|
|
6
6
|
constructor() {
|
|
7
7
|
this.name = '';
|
|
@@ -10,6 +10,8 @@ export class MrdRelationField {
|
|
|
10
10
|
this.disabled = false;
|
|
11
11
|
this.locale = navigator.language;
|
|
12
12
|
this.relatedClass = '';
|
|
13
|
+
/** When set, used instead of relatedClass for search queries (mostSignificantClass from API). */
|
|
14
|
+
this.mostSignificantClass = '';
|
|
13
15
|
this.displayType = ClientLayoutItemRelationDisplayType.SEARCH;
|
|
14
16
|
this.multiple = false;
|
|
15
17
|
this.dropdownValues = [];
|
|
@@ -17,6 +19,7 @@ export class MrdRelationField {
|
|
|
17
19
|
this.value = null;
|
|
18
20
|
this.searchQuery = '';
|
|
19
21
|
this.searchResults = [];
|
|
22
|
+
this.allRecords = [];
|
|
20
23
|
this.isLoading = false;
|
|
21
24
|
this.selectedItems = [];
|
|
22
25
|
this.showResults = false;
|
|
@@ -67,7 +70,7 @@ export class MrdRelationField {
|
|
|
67
70
|
this.isLoading = true;
|
|
68
71
|
this.showResults = true;
|
|
69
72
|
this.searchDebounce = setTimeout(() => {
|
|
70
|
-
this.mrdSearch.emit({ query, relatedClass: this.
|
|
73
|
+
this.mrdSearch.emit({ query, relatedClass: this.mostSignificantClass });
|
|
71
74
|
}, 300);
|
|
72
75
|
}
|
|
73
76
|
else {
|
|
@@ -125,6 +128,9 @@ export class MrdRelationField {
|
|
|
125
128
|
this.mrdBlur.emit({ name: this.name, value: val });
|
|
126
129
|
};
|
|
127
130
|
}
|
|
131
|
+
async setAllRecords(records) {
|
|
132
|
+
this.allRecords = records;
|
|
133
|
+
}
|
|
128
134
|
async setSearchResults(results) {
|
|
129
135
|
this.searchResults = results;
|
|
130
136
|
this.isLoading = false;
|
|
@@ -138,24 +144,41 @@ export class MrdRelationField {
|
|
|
138
144
|
var _a;
|
|
139
145
|
// Pre-fill selectedItems when value is passed as { id, label } objects
|
|
140
146
|
// (e.g. when editing an existing record fetched from the API).
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
if (this.value) {
|
|
148
|
+
if (Array.isArray(this.value)) {
|
|
149
|
+
if (this.value.length > 0 && typeof this.value[0] === 'object') {
|
|
150
|
+
this.selectedItems = this.value;
|
|
151
|
+
this.searchQuery = '';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (typeof this.value === 'object') {
|
|
155
|
+
this.selectedItems = [this.value];
|
|
156
|
+
this.searchQuery = (_a = this.value.label) !== null && _a !== void 0 ? _a : '';
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
}
|
|
160
|
+
componentDidLoad() {
|
|
161
|
+
// Only emit when there is no commonRelation dependency — the form orchestrates those.
|
|
162
|
+
if (this.editBehavior === ClientLayoutItemRelationEditBehavior.DROPDOWN && !this.commonRelation) {
|
|
163
|
+
// Defer to next tick so parent event listeners are registered after DOM patching
|
|
164
|
+
setTimeout(() => {
|
|
165
|
+
this.mrdFetchAll.emit({
|
|
166
|
+
name: this.name,
|
|
167
|
+
relatedClass: this.relatedClass,
|
|
168
|
+
mostSignificantClass: this.mostSignificantClass || undefined,
|
|
169
|
+
});
|
|
170
|
+
}, 0);
|
|
152
171
|
}
|
|
153
172
|
}
|
|
154
173
|
render() {
|
|
155
|
-
var _a, _b;
|
|
174
|
+
var _a, _b, _c, _d;
|
|
156
175
|
const hasError = !!this.error;
|
|
157
|
-
if (this.
|
|
176
|
+
if (this.editBehavior === ClientLayoutItemRelationEditBehavior.DROPDOWN) {
|
|
158
177
|
const currentValue = Array.isArray(this.value) ? ((_a = this.value[0]) !== null && _a !== void 0 ? _a : '') : ((_b = this.value) !== null && _b !== void 0 ? _b : '');
|
|
178
|
+
return (h(Host, null, h("div", { class: "mrd-relation-field" }, this.label && (h("label", { class: `mrd-relation-field__label${this.required ? ' mrd-relation-field__label--required' : ''}` }, this.label)), h("select", { class: `mrd-relation-field__select${hasError ? ' mrd-relation-field__select--error' : ''}`, name: this.name, required: this.required, disabled: this.disabled, onChange: this.handleDropdownChange }, h("option", { value: "" }, t('select_placeholder', this.locale)), this.allRecords.map(record => (h("option", { key: record.id, value: record.id, selected: record.id === currentValue }, record.label)))), hasError && h("span", { class: "mrd-relation-field__error" }, this.error))));
|
|
179
|
+
}
|
|
180
|
+
if (this.displayType === ClientLayoutItemRelationDisplayType.DROPDOWN) {
|
|
181
|
+
const currentValue = Array.isArray(this.value) ? ((_c = this.value[0]) !== null && _c !== void 0 ? _c : '') : ((_d = this.value) !== null && _d !== void 0 ? _d : '');
|
|
159
182
|
return (h(Host, null, h("div", { class: "mrd-relation-field" }, this.label && (h("label", { class: `mrd-relation-field__label${this.required ? ' mrd-relation-field__label--required' : ''}` }, this.label)), h("select", { class: `mrd-relation-field__select${hasError ? ' mrd-relation-field__select--error' : ''}`, name: this.name, required: this.required, disabled: this.disabled, onChange: this.handleDropdownChange }, h("option", { value: "" }, t('select_placeholder', this.locale)), this.dropdownValues.map(dv => (h("option", { key: dv.key, value: dv.key, selected: dv.key === currentValue }, dv.label)))), hasError && h("span", { class: "mrd-relation-field__error" }, this.error))));
|
|
160
183
|
}
|
|
161
184
|
// SEARCH mode
|
|
@@ -300,6 +323,26 @@ export class MrdRelationField {
|
|
|
300
323
|
"attribute": "related-class",
|
|
301
324
|
"defaultValue": "''"
|
|
302
325
|
},
|
|
326
|
+
"mostSignificantClass": {
|
|
327
|
+
"type": "string",
|
|
328
|
+
"mutable": false,
|
|
329
|
+
"complexType": {
|
|
330
|
+
"original": "string",
|
|
331
|
+
"resolved": "string",
|
|
332
|
+
"references": {}
|
|
333
|
+
},
|
|
334
|
+
"required": false,
|
|
335
|
+
"optional": false,
|
|
336
|
+
"docs": {
|
|
337
|
+
"tags": [],
|
|
338
|
+
"text": "When set, used instead of relatedClass for search queries (mostSignificantClass from API)."
|
|
339
|
+
},
|
|
340
|
+
"getter": false,
|
|
341
|
+
"setter": false,
|
|
342
|
+
"reflect": false,
|
|
343
|
+
"attribute": "most-significant-class",
|
|
344
|
+
"defaultValue": "''"
|
|
345
|
+
},
|
|
303
346
|
"displayType": {
|
|
304
347
|
"type": "string",
|
|
305
348
|
"mutable": false,
|
|
@@ -327,6 +370,51 @@ export class MrdRelationField {
|
|
|
327
370
|
"attribute": "display-type",
|
|
328
371
|
"defaultValue": "ClientLayoutItemRelationDisplayType.SEARCH"
|
|
329
372
|
},
|
|
373
|
+
"editBehavior": {
|
|
374
|
+
"type": "string",
|
|
375
|
+
"mutable": false,
|
|
376
|
+
"complexType": {
|
|
377
|
+
"original": "ClientLayoutItemRelationEditBehavior | null",
|
|
378
|
+
"resolved": "ClientLayoutItemRelationEditBehavior.CHECKBOX | ClientLayoutItemRelationEditBehavior.DROPDOWN | ClientLayoutItemRelationEditBehavior.SEARCH | null | undefined",
|
|
379
|
+
"references": {
|
|
380
|
+
"ClientLayoutItemRelationEditBehavior": {
|
|
381
|
+
"location": "import",
|
|
382
|
+
"path": "../../types",
|
|
383
|
+
"id": "src/types/index.ts::ClientLayoutItemRelationEditBehavior",
|
|
384
|
+
"referenceLocation": "ClientLayoutItemRelationEditBehavior"
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
"required": false,
|
|
389
|
+
"optional": true,
|
|
390
|
+
"docs": {
|
|
391
|
+
"tags": [],
|
|
392
|
+
"text": ""
|
|
393
|
+
},
|
|
394
|
+
"getter": false,
|
|
395
|
+
"setter": false,
|
|
396
|
+
"reflect": false,
|
|
397
|
+
"attribute": "edit-behavior"
|
|
398
|
+
},
|
|
399
|
+
"commonRelation": {
|
|
400
|
+
"type": "string",
|
|
401
|
+
"mutable": false,
|
|
402
|
+
"complexType": {
|
|
403
|
+
"original": "string",
|
|
404
|
+
"resolved": "string | undefined",
|
|
405
|
+
"references": {}
|
|
406
|
+
},
|
|
407
|
+
"required": false,
|
|
408
|
+
"optional": true,
|
|
409
|
+
"docs": {
|
|
410
|
+
"tags": [],
|
|
411
|
+
"text": ""
|
|
412
|
+
},
|
|
413
|
+
"getter": false,
|
|
414
|
+
"setter": false,
|
|
415
|
+
"reflect": false,
|
|
416
|
+
"attribute": "common-relation"
|
|
417
|
+
},
|
|
330
418
|
"multiple": {
|
|
331
419
|
"type": "boolean",
|
|
332
420
|
"mutable": false,
|
|
@@ -405,6 +493,7 @@ export class MrdRelationField {
|
|
|
405
493
|
return {
|
|
406
494
|
"searchQuery": {},
|
|
407
495
|
"searchResults": {},
|
|
496
|
+
"allRecords": {},
|
|
408
497
|
"isLoading": {},
|
|
409
498
|
"selectedItems": {},
|
|
410
499
|
"showResults": {},
|
|
@@ -458,10 +547,52 @@ export class MrdRelationField {
|
|
|
458
547
|
"resolved": "{ query: string; relatedClass: string; }",
|
|
459
548
|
"references": {}
|
|
460
549
|
}
|
|
550
|
+
}, {
|
|
551
|
+
"method": "mrdFetchAll",
|
|
552
|
+
"name": "mrdFetchAll",
|
|
553
|
+
"bubbles": true,
|
|
554
|
+
"cancelable": true,
|
|
555
|
+
"composed": true,
|
|
556
|
+
"docs": {
|
|
557
|
+
"tags": [],
|
|
558
|
+
"text": ""
|
|
559
|
+
},
|
|
560
|
+
"complexType": {
|
|
561
|
+
"original": "{ name: string; relatedClass: string; mostSignificantClass?: string; commonRelation?: string; filter?: string; filterValue?: string }",
|
|
562
|
+
"resolved": "{ name: string; relatedClass: string; mostSignificantClass?: string | undefined; commonRelation?: string | undefined; filter?: string | undefined; filterValue?: string | undefined; }",
|
|
563
|
+
"references": {}
|
|
564
|
+
}
|
|
461
565
|
}];
|
|
462
566
|
}
|
|
463
567
|
static get methods() {
|
|
464
568
|
return {
|
|
569
|
+
"setAllRecords": {
|
|
570
|
+
"complexType": {
|
|
571
|
+
"signature": "(records: RelationSearchResult[]) => Promise<void>",
|
|
572
|
+
"parameters": [{
|
|
573
|
+
"name": "records",
|
|
574
|
+
"type": "RelationSearchResult[]",
|
|
575
|
+
"docs": ""
|
|
576
|
+
}],
|
|
577
|
+
"references": {
|
|
578
|
+
"Promise": {
|
|
579
|
+
"location": "global",
|
|
580
|
+
"id": "global::Promise"
|
|
581
|
+
},
|
|
582
|
+
"RelationSearchResult": {
|
|
583
|
+
"location": "import",
|
|
584
|
+
"path": "../../types",
|
|
585
|
+
"id": "src/types/index.ts::RelationSearchResult",
|
|
586
|
+
"referenceLocation": "RelationSearchResult"
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
"return": "Promise<void>"
|
|
590
|
+
},
|
|
591
|
+
"docs": {
|
|
592
|
+
"text": "",
|
|
593
|
+
"tags": []
|
|
594
|
+
}
|
|
595
|
+
},
|
|
465
596
|
"setSearchResults": {
|
|
466
597
|
"complexType": {
|
|
467
598
|
"signature": "(results: RelationSearchResult[]) => Promise<void>",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* =====================================================================
|
|
2
|
+
API HELPERS
|
|
3
|
+
===================================================================== */
|
|
4
|
+
|
|
5
|
+
const API_BASE = 'http://localhost:8080';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generic fetch wrapper. Accepts both absolute URLs (from _links.self.href)
|
|
9
|
+
* and relative paths (prefixed with API_BASE).
|
|
10
|
+
*/
|
|
11
|
+
async function apiRequest(method, path, token, body) {
|
|
12
|
+
const opts = {
|
|
13
|
+
method,
|
|
14
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
15
|
+
};
|
|
16
|
+
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
17
|
+
const url = path.startsWith('http') ? path : API_BASE + path;
|
|
18
|
+
const resp = await fetch(url, opts);
|
|
19
|
+
const text = await resp.text();
|
|
20
|
+
let json;
|
|
21
|
+
try { json = JSON.parse(text); } catch (_) { json = text; }
|
|
22
|
+
return { status: resp.status, ok: resp.ok, body: json };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function apiFetchTenants(token) {
|
|
26
|
+
const { ok, status, body } = await apiRequest('GET', '/tenants', token);
|
|
27
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
28
|
+
return body; // array of { tenantCode, name, description, logo }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function apiFetchTypes(token, tenantCode) {
|
|
32
|
+
const { ok, status, body } = await apiRequest('GET', `/metadata/${tenantCode}/types?type=BASIC`, token);
|
|
33
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
34
|
+
return body; // array of { name, pluralName, type, ... }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function apiFetchForm(token, tenantCode, pluralName, formHref = null) {
|
|
38
|
+
const path = formHref || `/metadata/${tenantCode}/form?types=${pluralName}`;
|
|
39
|
+
const { ok, status, body } = await apiRequest('GET', path, token);
|
|
40
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
41
|
+
const raw = (body && body.layouts && body.layouts.length > 0) ? body.layouts[0]
|
|
42
|
+
: (Array.isArray(body) && body.length > 0) ? body[0]
|
|
43
|
+
: body;
|
|
44
|
+
return mapApiLayoutToMrdForm(raw);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function apiFetchDashboard(token, tenantCode, pluralName) {
|
|
48
|
+
const language = navigator.language;
|
|
49
|
+
const { ok, status, body } = await apiRequest('GET', `/metadata/${tenantCode}/dashboard/${pluralName}?language=${language}`, token);
|
|
50
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
51
|
+
return body; // { layouts, views, _links }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function apiFetchPage(token, baseHref, pageNumber, sort = '') {
|
|
55
|
+
const sep = baseHref.includes('?') ? '&' : '?';
|
|
56
|
+
let url = `${baseHref}${sep}page=${pageNumber}`;
|
|
57
|
+
if (sort) url += `&sort=${encodeURIComponent(sort)}`;
|
|
58
|
+
const { ok, status, body } = await apiRequest('GET', url, token);
|
|
59
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
60
|
+
return body; // { _embedded, _links, page }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function apiSubmitForm(token, tenantCode, pluralName, values) {
|
|
64
|
+
const { status, ok, body } = await apiRequest('POST', `/data/${tenantCode}/${pluralName}`, token, values);
|
|
65
|
+
return { status, ok, body };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function apiUploadFile(token, tenantCode, file) {
|
|
69
|
+
// Step 1: obtain a one-time upload URL
|
|
70
|
+
const { ok, status, body } = await apiRequest('GET', `/data/${tenantCode}/upload`, token);
|
|
71
|
+
if (!ok) throw new Error(`Upload-URL ophalen mislukt (${status})`);
|
|
72
|
+
const uploadUrl = typeof body === 'string' ? body : String(body);
|
|
73
|
+
|
|
74
|
+
// Step 2: upload the file as multipart (no auth required)
|
|
75
|
+
const formData = new FormData();
|
|
76
|
+
formData.append('file', file);
|
|
77
|
+
const resp = await fetch(uploadUrl, { method: 'POST', body: formData });
|
|
78
|
+
if (!resp.ok) throw new Error(`Bestand uploaden mislukt (${resp.status})`);
|
|
79
|
+
const uris = await resp.json();
|
|
80
|
+
if (!Array.isArray(uris) || uris.length === 0) throw new Error('Geen binary URI ontvangen na upload');
|
|
81
|
+
return uris[0];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function apiSearchRelation(token, tenantCode, mostSignificantClass, query) {
|
|
85
|
+
const q = encodeURIComponent(query);
|
|
86
|
+
const { ok, status, body } = await apiRequest('GET', `/data/${tenantCode}/${mostSignificantClass}?q=${q}`, token);
|
|
87
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
88
|
+
const items = (body._embedded && body._embedded[mostSignificantClass]) || [];
|
|
89
|
+
return items.map(item => ({
|
|
90
|
+
id: item._links.self.href,
|
|
91
|
+
label: item.name || item.label || '?',
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* =====================================================================
|
|
96
|
+
LAYOUT MAPPERS
|
|
97
|
+
Translate flat API layout responses to nested ClientLayoutItem format
|
|
98
|
+
expected by mrd-form.
|
|
99
|
+
===================================================================== */
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Map a single flat API item to the nested ClientLayoutItem structure.
|
|
103
|
+
* Used by both mapApiLayoutToMrdForm (form) and mapApiColumns (table).
|
|
104
|
+
*/
|
|
105
|
+
function mapApiItem(item) {
|
|
106
|
+
if (item.type === 'FIELD') {
|
|
107
|
+
if (item.field) return item; // already nested — pass through
|
|
108
|
+
return {
|
|
109
|
+
type: 'FIELD',
|
|
110
|
+
field: {
|
|
111
|
+
name: item.name,
|
|
112
|
+
label: item.label,
|
|
113
|
+
dataType: item.dataType,
|
|
114
|
+
required: !!item.required,
|
|
115
|
+
multiple: !!item.multiple,
|
|
116
|
+
header: !!item.header,
|
|
117
|
+
defaultValue: item.defaultValue ?? null,
|
|
118
|
+
listItems: item.listItems ?? null,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (item.type === 'RELATION') {
|
|
123
|
+
if (item.relation) return item; // already nested — pass through
|
|
124
|
+
return {
|
|
125
|
+
type: 'RELATION',
|
|
126
|
+
relation: {
|
|
127
|
+
name: item.name,
|
|
128
|
+
label: item.label,
|
|
129
|
+
relatedClass: item.relatedClass,
|
|
130
|
+
mostSignificantClass: item.mostSignificantClass ?? null,
|
|
131
|
+
displayType: item.editBehavior ?? 'SEARCH',
|
|
132
|
+
editBehavior: item.editBehavior ?? null,
|
|
133
|
+
commonRelation: item.commonRelation ?? null,
|
|
134
|
+
required: !!item.required,
|
|
135
|
+
multiple: !!item.multiple,
|
|
136
|
+
defaultValue: item.defaultValue ?? null,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(item.items)) {
|
|
141
|
+
return { ...item, items: item.items.map(mapApiItem) };
|
|
142
|
+
}
|
|
143
|
+
return item;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Map API layout (OBJECT_FORM_DASHBOARD shape) → ClientLayout for mrd-form.
|
|
148
|
+
*
|
|
149
|
+
* IMPORTANT: always map editBehavior and commonRelation from the API response —
|
|
150
|
+
* omitting them causes relation fields to fall back to SEARCH mode.
|
|
151
|
+
* _relationMeta must be keyed by BOTH relatedClass AND mostSignificantClass
|
|
152
|
+
* because mrdSearch sends mostSignificantClass as the lookup key.
|
|
153
|
+
*/
|
|
154
|
+
function mapApiLayoutToMrdForm(raw) {
|
|
155
|
+
if (!raw || !Array.isArray(raw.items)) return raw;
|
|
156
|
+
|
|
157
|
+
function mapItem(item) {
|
|
158
|
+
if (item.type === 'FIELD') {
|
|
159
|
+
return {
|
|
160
|
+
type: 'FIELD',
|
|
161
|
+
field: {
|
|
162
|
+
name: item.name,
|
|
163
|
+
label: item.label,
|
|
164
|
+
dataType: item.dataType,
|
|
165
|
+
required: !!item.required,
|
|
166
|
+
multiple: !!item.multiple,
|
|
167
|
+
header: !!item.header,
|
|
168
|
+
defaultValue: item.defaultValue ?? null,
|
|
169
|
+
listItems: item.listItems ?? null,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (item.type === 'RELATION') {
|
|
174
|
+
return {
|
|
175
|
+
type: 'RELATION',
|
|
176
|
+
relation: {
|
|
177
|
+
name: item.name,
|
|
178
|
+
label: item.label,
|
|
179
|
+
relatedClass: item.relatedClass,
|
|
180
|
+
mostSignificantClass: item.mostSignificantClass ?? null,
|
|
181
|
+
displayType: item.editBehavior ?? 'SEARCH',
|
|
182
|
+
editBehavior: item.editBehavior ?? null,
|
|
183
|
+
commonRelation: item.commonRelation ?? null,
|
|
184
|
+
required: !!item.required,
|
|
185
|
+
multiple: !!item.multiple,
|
|
186
|
+
defaultValue: item.defaultValue ?? null,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// SECTION / GROUP — recurse into children
|
|
191
|
+
if (Array.isArray(item.items)) {
|
|
192
|
+
return { ...item, items: item.items.map(mapItem) };
|
|
193
|
+
}
|
|
194
|
+
return item;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { items: raw.items.map(mapItem) };
|
|
198
|
+
}
|