@mmlogic/components 0.1.7 → 0.1.9
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 +204 -30
- package/dist/collection/components/mrd-field/mrd-field.js +35 -16
- package/dist/collection/components/mrd-form/mrd-form.js +238 -3
- package/dist/collection/components/mrd-form/mrd-form.scss +32 -0
- package/dist/collection/components/mrd-relation-field/mrd-relation-field.js +121 -12
- 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 +205 -31
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-2a8cb2eb.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 +52 -0
- package/dist/types/components/mrd-relation-field/mrd-relation-field.d.ts +14 -1
- package/dist/types/components.d.ts +50 -2
- package/dist/types/types/client-layout.d.ts +1 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/p-45c40269.entry.js +0 -1
|
@@ -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
|
+
}
|