@mmlogic/components 0.5.1 → 0.5.3
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/cell-renderer-7KX9Qbml.js +983 -0
- package/dist/cjs/{client-layout-RZ3Ix-4a.js → client-layout-CqVuJumR.js} +9 -0
- package/dist/cjs/{index-DzRfoAPz.js → index-DVbdkYO3.js} +468 -6
- package/dist/cjs/index.cjs.js +4 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/mosterdcomponents.cjs.js +2 -2
- package/dist/cjs/{mrd-boolean-field_23.cjs.entry.js → mrd-boolean-field_25.cjs.entry.js} +3017 -951
- package/dist/cjs/mrd-merge-view.cjs.entry.js +273 -0
- package/dist/collection/collection-manifest.json +3 -0
- package/dist/collection/components/documents/mrd-document-view/mrd-document-view.js +7 -8
- package/dist/collection/components/email/mrd-email-view/mrd-email-view.css +126 -0
- package/dist/collection/components/email/mrd-email-view/mrd-email-view.js +247 -0
- package/dist/collection/components/fields/file-upload-common.js +23 -7
- package/dist/collection/components/fields/mrd-boolean-field/mrd-boolean-field.js +1 -1
- package/dist/collection/components/fields/mrd-currency-field/mrd-currency-field.js +1 -1
- package/dist/collection/components/fields/mrd-date-field/mrd-date-field.js +1 -1
- package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.js +1 -1
- package/dist/collection/components/fields/mrd-email-field/mrd-email-field.js +1 -1
- package/dist/collection/components/fields/mrd-file-field/mrd-file-field.css +20 -1
- package/dist/collection/components/fields/mrd-file-field/mrd-file-field.js +108 -47
- package/dist/collection/components/fields/mrd-image-field/mrd-image-field.css +44 -0
- package/dist/collection/components/fields/mrd-image-field/mrd-image-field.js +126 -58
- package/dist/collection/components/fields/mrd-longtext-field/mrd-longtext-field.js +1 -1
- package/dist/collection/components/fields/mrd-number-field/mrd-number-field.js +1 -1
- package/dist/collection/components/fields/mrd-secret-field/mrd-secret-field.js +2 -2
- package/dist/collection/components/fields/mrd-text-field/mrd-text-field.js +1 -1
- package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +9 -5
- package/dist/collection/components/fields/mrd-time-field/mrd-time-field.js +1 -1
- package/dist/collection/components/form/mrd-field/mrd-field.js +5 -5
- package/dist/collection/components/form/mrd-form/mrd-form.js +55 -6
- package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.css +145 -0
- package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.js +281 -0
- package/dist/collection/components/layout/mrd-layout-section/field-value.js +5 -1
- package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.css +10 -0
- package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +38 -8
- package/dist/collection/components/merge/mrd-merge-view/merge-diff.js +99 -0
- package/dist/collection/components/merge/mrd-merge-view/merge-parts.js +44 -0
- package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.css +244 -0
- package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.js +397 -0
- package/dist/collection/dev/api.js +41 -2
- package/dist/collection/dev/app.js +135 -17
- package/dist/collection/dev/example-data.js +126 -2
- package/dist/collection/types/client-layout.js +6 -0
- package/dist/collection/utils/cell-renderer.js +6 -1
- package/dist/collection/utils/document-attachments.js +27 -14
- package/dist/collection/utils/i18n.js +224 -0
- package/dist/components/cell-renderer.js +1 -0
- package/dist/components/client-layout.js +1 -1
- package/dist/components/document-attachments.js +1 -1
- package/dist/components/file-upload-common.js +1 -1
- package/dist/components/format.js +1 -1
- package/dist/components/i18n.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/mrd-boolean-field2.js +1 -1
- package/dist/components/mrd-currency-field2.js +1 -1
- package/dist/components/mrd-date-field2.js +1 -1
- package/dist/components/mrd-datetime-field2.js +1 -1
- package/dist/components/mrd-document-view2.js +1 -1
- package/dist/components/mrd-email-field2.js +1 -1
- package/dist/components/mrd-email-view.d.ts +11 -0
- package/dist/components/mrd-email-view.js +1 -0
- package/dist/components/mrd-email-view2.js +1 -0
- package/dist/components/mrd-field2.js +1 -1
- package/dist/components/mrd-file-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-image-field2.js +1 -1
- package/dist/components/mrd-invoice-view.d.ts +11 -0
- package/dist/components/mrd-invoice-view.js +1 -0
- package/dist/components/mrd-invoice-view2.js +1 -0
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-longtext-field2.js +1 -1
- package/dist/components/mrd-merge-view.d.ts +11 -0
- package/dist/components/mrd-merge-view.js +1 -0
- package/dist/components/mrd-number-field2.js +1 -1
- package/dist/components/mrd-secret-field2.js +1 -1
- package/dist/components/mrd-table2.js +1 -1
- package/dist/components/mrd-text-field2.js +1 -1
- package/dist/components/mrd-textarea-field2.js +1 -1
- package/dist/components/mrd-time-field2.js +1 -1
- package/dist/components/purify.es.js +2 -0
- package/dist/esm/cell-renderer-DIsNX9D7.js +974 -0
- package/dist/esm/{client-layout-DCOAAH92.js → client-layout-Bc42CfJB.js} +7 -1
- package/dist/esm/{index-DiSe6vjv.js → index-CDiBvvJU.js} +468 -6
- package/dist/esm/index.js +1 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/mosterdcomponents.js +3 -3
- package/dist/esm/{mrd-boolean-field_23.entry.js → mrd-boolean-field_25.entry.js} +2949 -885
- package/dist/esm/mrd-merge-view.entry.js +271 -0
- package/dist/mosterdcomponents/index.esm.js +1 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-4e061416.entry.js +1 -0
- package/dist/mosterdcomponents/p-910ebfee.entry.js +3 -0
- package/dist/mosterdcomponents/{p-DCOAAH92.js → p-Bc42CfJB.js} +1 -1
- package/dist/mosterdcomponents/p-CDiBvvJU.js +2 -0
- package/dist/mosterdcomponents/p-Ce2QDq0t.js +1 -0
- package/dist/types/components/documents/mrd-document-view/mrd-document-view.d.ts +2 -2
- package/dist/types/components/email/mrd-email-view/mrd-email-view.d.ts +52 -0
- package/dist/types/components/fields/file-upload-common.d.ts +19 -1
- package/dist/types/components/fields/mrd-file-field/mrd-file-field.d.ts +24 -10
- package/dist/types/components/fields/mrd-image-field/mrd-image-field.d.ts +27 -11
- package/dist/types/components/fields/mrd-textarea-field/mrd-textarea-field.d.ts +1 -0
- package/dist/types/components/form/mrd-form/mrd-form.d.ts +16 -1
- package/dist/types/components/invoice/mrd-invoice-view/mrd-invoice-view.d.ts +60 -0
- package/dist/types/components/layout/mrd-layout-section/mrd-layout-section.d.ts +13 -0
- package/dist/types/components/merge/mrd-merge-view/merge-diff.d.ts +43 -0
- package/dist/types/components/merge/mrd-merge-view/merge-parts.d.ts +37 -0
- package/dist/types/components/merge/mrd-merge-view/mrd-merge-view.d.ts +68 -0
- package/dist/types/components.d.ts +445 -19
- package/dist/types/types/client-layout.d.ts +6 -0
- package/dist/types/utils/document-attachments.d.ts +12 -5
- package/package.json +2 -1
- package/dist/mosterdcomponents/p-DiSe6vjv.js +0 -2
- package/dist/mosterdcomponents/p-cdecc698.entry.js +0 -1
|
@@ -10,7 +10,7 @@ let _locale = navigator.language || 'nl-NL';
|
|
|
10
10
|
let _dashboardData = null; // full dashboard response
|
|
11
11
|
let _dashboardRecord = null; // data-object voor object/form dashboards
|
|
12
12
|
let _activeLayoutIndex = 0;
|
|
13
|
-
let _dashboardType = 'class'; // 'class' | 'general' | 'navigation' | 'object' | 'form'
|
|
13
|
+
let _dashboardType = 'class'; // 'class' | 'general' | 'navigation' | 'object' | 'form' | 'merge'
|
|
14
14
|
let _sectionGeneration = 0; // incremented on each renderSection(); prevents stale fetches
|
|
15
15
|
let _navHistory = []; // stack van { dashboardData, dashboardRecord, activeLayoutIndex }
|
|
16
16
|
let _meUser = null; // { id: href, label: name } — resolved once after login via /accounts/me
|
|
@@ -266,6 +266,41 @@ function initFilePreviewTab() {
|
|
|
266
266
|
section.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
|
|
267
267
|
section.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
|
|
268
268
|
});
|
|
269
|
+
|
|
270
|
+
// Invoice View demo (commons.invoice archetype, TASK-0199) — the `lines`
|
|
271
|
+
// RELATED_VIEW is rendered inside mrd-invoice-view via slot="lines", but the
|
|
272
|
+
// page/sort data-loading stays owned by mrd-layout-section, exactly like any
|
|
273
|
+
// other RELATED_VIEW: answer mrdLoadViewPage the same way the Detail View tab
|
|
274
|
+
// does for appointments/share classes.
|
|
275
|
+
const invoiceSection = document.getElementById('invoice-view');
|
|
276
|
+
const invoicePreviewEl = document.getElementById('invoice-view-preview');
|
|
277
|
+
invoiceSection.items = window.EXAMPLE_INVOICE_ITEMS;
|
|
278
|
+
invoiceSection.data = window.EXAMPLE_INVOICE_DATA;
|
|
279
|
+
invoiceSection.archetypes = [window.EXAMPLE_INVOICE_ARCHETYPE];
|
|
280
|
+
invoiceSection.addEventListener('mrdLoadViewPage', async (e) => {
|
|
281
|
+
const { name, page } = e.detail;
|
|
282
|
+
logEvent('mrdLoadViewPage', e.detail);
|
|
283
|
+
await invoiceSection.setViewPage(name, page, window.EXAMPLE_INVOICE_LINES, window.EXAMPLE_INVOICE_LINES.length);
|
|
284
|
+
});
|
|
285
|
+
invoiceSection.addEventListener('mrdAttachments', (e) => {
|
|
286
|
+
logEvent('mrdAttachments', e.detail);
|
|
287
|
+
renderAttachmentPreview(invoicePreviewEl, e.detail.attachments);
|
|
288
|
+
});
|
|
289
|
+
invoiceSection.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
|
|
290
|
+
invoiceSection.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
|
|
291
|
+
|
|
292
|
+
// Email View demo (commons.email archetype, TASK-0199) — fully read-only, no
|
|
293
|
+
// RELATED_VIEW/table involved.
|
|
294
|
+
const emailSection = document.getElementById('email-view');
|
|
295
|
+
const emailPreviewEl = document.getElementById('email-view-preview');
|
|
296
|
+
emailSection.data = window.EXAMPLE_EMAIL_DATA;
|
|
297
|
+
emailSection.archetypes = [window.EXAMPLE_EMAIL_ARCHETYPE];
|
|
298
|
+
emailSection.addEventListener('mrdAttachments', (e) => {
|
|
299
|
+
logEvent('mrdAttachments', e.detail);
|
|
300
|
+
renderAttachmentPreview(emailPreviewEl, e.detail.attachments);
|
|
301
|
+
});
|
|
302
|
+
emailSection.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
|
|
303
|
+
emailSection.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
|
|
269
304
|
}
|
|
270
305
|
|
|
271
306
|
/* =====================================================================
|
|
@@ -337,7 +372,7 @@ async function loadCompactTypes(tenantCode) {
|
|
|
337
372
|
function onDashboardTypeChange(type) {
|
|
338
373
|
_dashboardType = type;
|
|
339
374
|
const needsClass = type === 'class' || type === 'create';
|
|
340
|
-
const needsObject = type === 'object' || type === 'form';
|
|
375
|
+
const needsObject = type === 'object' || type === 'form' || type === 'merge';
|
|
341
376
|
document.getElementById('controls-class-group').classList.toggle('hidden', !needsClass);
|
|
342
377
|
document.getElementById('controls-object-group').classList.toggle('hidden', !needsObject);
|
|
343
378
|
if (needsClass && _selectedTenant) loadCompactTypes(_selectedTenant);
|
|
@@ -384,14 +419,17 @@ async function loadDashboard() {
|
|
|
384
419
|
dashboard = await apiFetchNavigationPane(token, _selectedTenant, name);
|
|
385
420
|
|
|
386
421
|
} else {
|
|
387
|
-
// 'object' of '
|
|
422
|
+
// 'object', 'form' of 'merge': fetch record → gebruik _links.metadata.href of _links.form.href
|
|
388
423
|
const objectHref = document.getElementById('object-href-input').value.trim();
|
|
389
424
|
if (!objectHref) throw new Error('Vul een Object URL in');
|
|
390
425
|
const recResp = await apiRequest('GET', objectHref, token);
|
|
391
426
|
if (!recResp.ok) throw new Error(`${recResp.status}: kon object niet ophalen`);
|
|
392
427
|
recordData = recResp.body;
|
|
393
428
|
|
|
394
|
-
|
|
429
|
+
// 'merge' reuses the form layout too: it lists real editable fields
|
|
430
|
+
// (firstName, lastName, ...) and already excludes hard formula fields
|
|
431
|
+
// like `name` — see LayoutHelper.buildDefaultObjectFormDashboard().
|
|
432
|
+
const linkKey = (_dashboardType === 'form' || _dashboardType === 'merge') ? 'form' : 'metadata';
|
|
395
433
|
const dashHref = recordData._links?.[linkKey]?.href;
|
|
396
434
|
if (!dashHref) throw new Error(`Geen _links.${linkKey} gevonden in object`);
|
|
397
435
|
|
|
@@ -447,6 +485,11 @@ async function renderSection(index) {
|
|
|
447
485
|
return;
|
|
448
486
|
}
|
|
449
487
|
|
|
488
|
+
if (_dashboardType === 'merge') {
|
|
489
|
+
await renderMergeView(layout, _dashboardRecord, container);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
450
493
|
const section = document.createElement('mrd-layout-section');
|
|
451
494
|
section.items = layout.items;
|
|
452
495
|
section.data = _dashboardRecord ?? { _links: _dashboardData._links ?? {} };
|
|
@@ -612,7 +655,7 @@ async function renderSection(index) {
|
|
|
612
655
|
try {
|
|
613
656
|
const result = await apiRequest('POST', `${_baseHref}${path}`, authGetToken(), values);
|
|
614
657
|
if (result.ok) {
|
|
615
|
-
const href = result.
|
|
658
|
+
const href = result.location;
|
|
616
659
|
if (href) await section.setViewCreatedObject(name, href);
|
|
617
660
|
} else {
|
|
618
661
|
console.error('[mrdCreateObject] mislukt', result.status, result.body);
|
|
@@ -865,17 +908,20 @@ async function renderForm(layout, record = null, selfHref = null, containerEl =
|
|
|
865
908
|
if (_meUser) form.me = _meUser;
|
|
866
909
|
form.values = record ? initialValues : {};
|
|
867
910
|
|
|
868
|
-
// Upload
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
})
|
|
911
|
+
// Upload each selected file immediately (fires once per file for `multiple` fields);
|
|
912
|
+
// pass `file` back into setFieldValue so mrd-form can resolve the right array entry.
|
|
913
|
+
// The resolved value is { href, fileName } — same shape mrd-table/mrd-layout-section
|
|
914
|
+
// already expect when reading FILE/IMAGE data back — so the filename displays correctly
|
|
915
|
+
// instead of falling back to the upload URI's UUID.
|
|
916
|
+
form.addEventListener('mrdUpload', async (e) => {
|
|
917
|
+
const { name, file } = e.detail;
|
|
918
|
+
try {
|
|
919
|
+
const uri = await apiUploadFile(authGetToken(), _selectedTenant, file);
|
|
920
|
+
await form.setFieldValue(name, { href: uri, fileName: file.name }, file);
|
|
921
|
+
} catch (err) {
|
|
922
|
+
console.error(`[upload] mislukt voor veld "${name}":`, err);
|
|
923
|
+
}
|
|
924
|
+
});
|
|
879
925
|
|
|
880
926
|
form.addEventListener('mrdSubmit', async (e) => {
|
|
881
927
|
document.getElementById('panel-response').innerHTML =
|
|
@@ -914,7 +960,7 @@ async function renderForm(layout, record = null, selfHref = null, containerEl =
|
|
|
914
960
|
renderResponse(200, 'Geen wijzigingen.');
|
|
915
961
|
return;
|
|
916
962
|
}
|
|
917
|
-
const result = await
|
|
963
|
+
const result = await apiWriteAndFetch('PATCH', selfHref, authGetToken(), patch);
|
|
918
964
|
renderResponse(result.status, result.body);
|
|
919
965
|
} else {
|
|
920
966
|
// POST mode: new record
|
|
@@ -973,6 +1019,78 @@ async function renderForm(layout, record = null, selfHref = null, containerEl =
|
|
|
973
1019
|
});
|
|
974
1020
|
}
|
|
975
1021
|
|
|
1022
|
+
/* =====================================================================
|
|
1023
|
+
MERGE — LOAD & RENDER (TASK-0177)
|
|
1024
|
+
===================================================================== */
|
|
1025
|
+
|
|
1026
|
+
/** Flatten SECTION/GROUP → FIELD/RELATION, same tree shape mrd-form flattens internally. */
|
|
1027
|
+
function flattenLayoutItems(items) {
|
|
1028
|
+
const result = [];
|
|
1029
|
+
(items ?? []).forEach(item => {
|
|
1030
|
+
if (item.type === 'SECTION' || item.type === 'GROUP') {
|
|
1031
|
+
result.push(...flattenLayoutItems(item.items));
|
|
1032
|
+
} else if (item.type === 'FIELD' || item.type === 'RELATION') {
|
|
1033
|
+
result.push(item);
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
return result;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Mount mrd-merge-view for the record loaded via the 'merge' dashboard type.
|
|
1041
|
+
* The record itself is object A (winner); the user searches for B inside
|
|
1042
|
+
* the component. mrd-merge-view never does network I/O itself — this wires
|
|
1043
|
+
* mrdSearch/mrdMerge to the real merge endpoint. Has no exit affordance of
|
|
1044
|
+
* its own — a host embedding this in an overlay page provides its own close
|
|
1045
|
+
* button, same as elsewhere in this dev preview.
|
|
1046
|
+
*/
|
|
1047
|
+
async function renderMergeView(layout, record, containerEl) {
|
|
1048
|
+
const fields = flattenLayoutItems(layout.items);
|
|
1049
|
+
const selfHref = record?._links?.self?.href ?? '';
|
|
1050
|
+
// /data/{tenant}/{mostSignificantClass}/{id} → segment right before the id.
|
|
1051
|
+
const segments = selfHref.split('/').filter(Boolean);
|
|
1052
|
+
const mostSignificantClass = segments.length >= 2 ? segments[segments.length - 2] : '';
|
|
1053
|
+
|
|
1054
|
+
containerEl.innerHTML = `<mrd-merge-view id="live-merge" locale="${escHtml(_locale)}"></mrd-merge-view>`;
|
|
1055
|
+
const mergeEl = containerEl.querySelector('#live-merge');
|
|
1056
|
+
if (typeof mergeEl.componentOnReady === 'function') {
|
|
1057
|
+
await mergeEl.componentOnReady();
|
|
1058
|
+
} else {
|
|
1059
|
+
await customElements.whenDefined('mrd-merge-view');
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
mergeEl.fields = fields;
|
|
1063
|
+
mergeEl.currentHref = selfHref;
|
|
1064
|
+
mergeEl.currentRecord = record ?? {};
|
|
1065
|
+
mergeEl.currentLabel = record?.name ?? '';
|
|
1066
|
+
mergeEl.basicType = mostSignificantClass;
|
|
1067
|
+
mergeEl.mostSignificantClass = mostSignificantClass;
|
|
1068
|
+
|
|
1069
|
+
mergeEl.addEventListener('mrdSearch', async (e) => {
|
|
1070
|
+
logEvent('mrdSearch (merge)', e.detail);
|
|
1071
|
+
const { query } = e.detail;
|
|
1072
|
+
try {
|
|
1073
|
+
const results = await apiSearchMergeCandidates(authGetToken(), _selectedTenant, mostSignificantClass, query);
|
|
1074
|
+
await mergeEl.setSearchResults(results);
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
console.error('[mrdSearch/merge] mislukt:', err);
|
|
1077
|
+
await mergeEl.setSearchResults([]);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
mergeEl.addEventListener('mrdMerge', async (e) => {
|
|
1082
|
+
logEvent('mrdMerge', e.detail);
|
|
1083
|
+
const { winnerHref, loserHref, patch } = e.detail;
|
|
1084
|
+
try {
|
|
1085
|
+
const result = await apiMerge(authGetToken(), winnerHref, loserHref, patch);
|
|
1086
|
+
renderResponse(200, result);
|
|
1087
|
+
_dashboardRecord = result; // reflect the patched winner if the same layout is reloaded
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
renderResponse(500, err.message);
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
976
1094
|
/* =====================================================================
|
|
977
1095
|
RESPONSE RENDERING
|
|
978
1096
|
===================================================================== */
|
|
@@ -69,7 +69,7 @@ window.EXAMPLE_LAYOUT = {
|
|
|
69
69
|
label: "Assets",
|
|
70
70
|
items: [
|
|
71
71
|
{ type: "FIELD", name: "projectLogo", label: "Project Logo", dataType: "IMAGE", required: false, accept: "image/*", maxSize: 5242880 },
|
|
72
|
-
{ type: "FIELD", name: "attachments", label: "Attachments", dataType: "FILE", required: false, accept: ".pdf,.doc,.docx,.zip", maxSize: 10485760 }
|
|
72
|
+
{ type: "FIELD", name: "attachments", label: "Attachments", dataType: "FILE", required: false, accept: ".pdf,.doc,.docx,.zip", maxSize: 10485760, multiple: true }
|
|
73
73
|
]
|
|
74
74
|
},
|
|
75
75
|
{ type: "RELATION", name: "projectManager", label: "Project Manager", relatedClass: "Employee", displayType: "SEARCH", required: true, multiple: false },
|
|
@@ -91,7 +91,15 @@ window.EXAMPLE_VALUES = {
|
|
|
91
91
|
budget: { amount: 150000, currency: "EUR" },
|
|
92
92
|
isPublic: true,
|
|
93
93
|
startDate: "2026-01-15",
|
|
94
|
-
contactEmail: "team@mosterd.nl"
|
|
94
|
+
contactEmail: "team@mosterd.nl",
|
|
95
|
+
// FILE/IMAGE values from the API are { href, fileName } — same shape as everywhere
|
|
96
|
+
// else in this library (mrd-table, mrd-document-view) — so the field shows the real
|
|
97
|
+
// filename instead of the href's UUID.
|
|
98
|
+
projectLogo: { href: "/uploads/proj-9f2c/logo.png", fileName: "mosterd-logo.png" },
|
|
99
|
+
attachments: [
|
|
100
|
+
{ href: "/uploads/proj-9f2c/9f2c-contract.pdf", fileName: "contract-2026.pdf" },
|
|
101
|
+
{ href: "/uploads/proj-9f2c/1a3b-invoice.pdf", fileName: "invoice-042.pdf" },
|
|
102
|
+
],
|
|
95
103
|
};
|
|
96
104
|
|
|
97
105
|
// ─── Companies detail view demo ────────────────────────────────────────────────
|
|
@@ -441,3 +449,119 @@ window.EXAMPLE_DOCUMENT_DATA = {
|
|
|
441
449
|
dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
|
|
442
450
|
},
|
|
443
451
|
};
|
|
452
|
+
|
|
453
|
+
// ─── Invoice View demo (commons.invoice archetype, TASK-0199) ────────────────
|
|
454
|
+
// Exercises mrd-invoice-view via mrd-layout-section's archetype path. `lines`
|
|
455
|
+
// is a real RELATED_VIEW item bound to the commons.invoiceLine archetype — it
|
|
456
|
+
// is rendered *inside* mrd-invoice-view via slot="lines" (mrd-layout-section
|
|
457
|
+
// still owns the table's own page/sort wiring, see renderInvoiceObject()).
|
|
458
|
+
|
|
459
|
+
window.EXAMPLE_INVOICE_ARCHETYPE = {
|
|
460
|
+
archetype: 'commons.invoice',
|
|
461
|
+
slots: {
|
|
462
|
+
invoiceNumber: 'invoiceNumber', invoiceDate: 'invoiceDate', dueDate: 'dueDate',
|
|
463
|
+
netAmount: 'netAmount', taxAmount: 'taxAmount', amount: 'amount', status: 'status',
|
|
464
|
+
customer: 'customer', supplier: 'supplier', lines: 'lines', attachments: 'attachments', container: 'dossier',
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
window.EXAMPLE_INVOICE_ITEMS = [
|
|
469
|
+
{
|
|
470
|
+
type: 'FIELD', name: 'status', label: 'Status', dataType: 'LIST', required: false,
|
|
471
|
+
listItems: [
|
|
472
|
+
{ key: 'draft', label: 'Concept', color: '#6b7280', backgroundColor: '#f3f4f6' },
|
|
473
|
+
{ key: 'sent', label: 'Verzonden', color: '#1d4ed8', backgroundColor: '#dbeafe' },
|
|
474
|
+
{ key: 'paid', label: 'Betaald', color: '#15803d', backgroundColor: '#dcfce7' },
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
type: 'RELATED_VIEW', label: 'Factuurregels', name: 'lines',
|
|
479
|
+
relatedClass: 'demo.invoiceLine', fromClass: 'demo.invoice', showTitle: true,
|
|
480
|
+
archetypes: [{ archetype: 'commons.invoiceLine', slots: { description: 'description', quantity: 'quantity', unitPrice: 'unitPrice', amount: 'amount' } }],
|
|
481
|
+
view: {
|
|
482
|
+
name: null,
|
|
483
|
+
defaultView: true,
|
|
484
|
+
values: [
|
|
485
|
+
{ type: 'FIELD', label: 'Omschrijving', name: 'description', dataType: 'TEXT', required: true },
|
|
486
|
+
{ type: 'FIELD', label: 'Aantal', name: 'quantity', dataType: 'DECIMAL', required: true },
|
|
487
|
+
{ type: 'FIELD', label: 'Prijs', name: 'unitPrice', dataType: 'CURRENCY', required: true },
|
|
488
|
+
{ type: 'FIELD', label: 'Bedrag', name: 'amount', dataType: 'CURRENCY', required: true, aggregate: 'sum' },
|
|
489
|
+
],
|
|
490
|
+
type: 'demo.invoiceLine',
|
|
491
|
+
singularLabel: 'Factuurregel',
|
|
492
|
+
pluralLabel: 'Factuurregels',
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
|
|
497
|
+
window.EXAMPLE_INVOICE_DATA = {
|
|
498
|
+
invoiceNumber: 'INV-2026-0142',
|
|
499
|
+
invoiceDate: '2026-06-01',
|
|
500
|
+
dueDate: '2026-06-30',
|
|
501
|
+
status: 'sent',
|
|
502
|
+
netAmount: { amount: 1000, currency: 'EUR' },
|
|
503
|
+
taxAmount: { amount: 210, currency: 'EUR' },
|
|
504
|
+
amount: { amount: 1210, currency: 'EUR' },
|
|
505
|
+
attachments: [
|
|
506
|
+
{ href: '/data/demo/invoices/142/attachments/0', fileName: 'invoice-142.pdf', mimeType: 'application/pdf' },
|
|
507
|
+
],
|
|
508
|
+
_links: {
|
|
509
|
+
self: { href: '/data/demo/invoices/142' },
|
|
510
|
+
customer: { href: '/data/demo/entities/acme', name: 'Acme Corp.' },
|
|
511
|
+
supplier: { href: '/data/demo/entities/mosterd', name: 'Mosterd B.V.' },
|
|
512
|
+
dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
window.EXAMPLE_INVOICE_LINES = [
|
|
517
|
+
{
|
|
518
|
+
description: 'Consultancy — juni 2026',
|
|
519
|
+
quantity: 10,
|
|
520
|
+
unitPrice: { amount: 80, currency: 'EUR' },
|
|
521
|
+
amount: { amount: 800, currency: 'EUR' },
|
|
522
|
+
_links: { self: { href: '/data/demo/invoiceLines/1' } },
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
description: 'Projectmanagement',
|
|
526
|
+
quantity: 4,
|
|
527
|
+
unitPrice: { amount: 50, currency: 'EUR' },
|
|
528
|
+
amount: { amount: 200, currency: 'EUR' },
|
|
529
|
+
_links: { self: { href: '/data/demo/invoiceLines/2' } },
|
|
530
|
+
},
|
|
531
|
+
];
|
|
532
|
+
|
|
533
|
+
// ─── Email View demo (commons.email archetype, TASK-0199) ────────────────────
|
|
534
|
+
// Exercises mrd-email-view via mrd-layout-section's archetype path. Fully
|
|
535
|
+
// read-only — no RELATED_VIEW/table involved.
|
|
536
|
+
|
|
537
|
+
window.EXAMPLE_EMAIL_ARCHETYPE = {
|
|
538
|
+
archetype: 'commons.email',
|
|
539
|
+
slots: {
|
|
540
|
+
subject: 'subject', date: 'date', from: 'from', to: 'to', cc: 'cc',
|
|
541
|
+
bodyHtml: 'bodyHtml', bodyText: 'bodyText', headers: 'headers',
|
|
542
|
+
attachments: 'attachments', container: 'dossier',
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
window.EXAMPLE_EMAIL_DATA = {
|
|
547
|
+
subject: 'RE: Contract 2026 — laatste versie bijgevoegd',
|
|
548
|
+
date: '2026-06-02T14:32:00',
|
|
549
|
+
bodyHtml: '<p>Beste Jan,</p><p>Bijgaand de laatste versie van het contract, inclusief de besproken wijzigingen in artikel 4.</p><p>Met vriendelijke groet,<br>Anne</p>',
|
|
550
|
+
headers: 'Message-ID: <abc123@mail.example.com>\nReceived: from mail.example.com by mx.mosterd.com',
|
|
551
|
+
attachments: [
|
|
552
|
+
{ href: '/data/demo/emails/9/attachments/0', fileName: 'contract-2026-v3.pdf', mimeType: 'application/pdf' },
|
|
553
|
+
{ href: '/data/demo/emails/9/attachments/1', fileName: 'wijzigingen.docx' },
|
|
554
|
+
],
|
|
555
|
+
_links: {
|
|
556
|
+
from: { href: '/data/demo/entities/anne', name: 'Anne de Vries <anne@mosterd.com>' },
|
|
557
|
+
to: {
|
|
558
|
+
href: '/data/demo/entities/jan', name: 'Jan Bakker <jan@acme.com>',
|
|
559
|
+
values: [{ href: '/data/demo/entities/jan', name: 'Jan Bakker <jan@acme.com>' }],
|
|
560
|
+
},
|
|
561
|
+
cc: {
|
|
562
|
+
href: '/data/demo/entities/pietcc', name: 'Piet Claassen <piet@acme.com>',
|
|
563
|
+
values: [{ href: '/data/demo/entities/pietcc', name: 'Piet Claassen <piet@acme.com>' }],
|
|
564
|
+
},
|
|
565
|
+
dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
|
|
566
|
+
},
|
|
567
|
+
};
|
|
@@ -72,6 +72,12 @@ export var ClientLayoutNavigateBehaviour;
|
|
|
72
72
|
})(ClientLayoutNavigateBehaviour || (ClientLayoutNavigateBehaviour = {}));
|
|
73
73
|
/** Well-known archetype ids the client renders specially. */
|
|
74
74
|
export const ARCHETYPE_DOCUMENT = 'commons.document';
|
|
75
|
+
export const ARCHETYPE_INVOICE = 'commons.invoice';
|
|
76
|
+
export const ARCHETYPE_EMAIL = 'commons.email';
|
|
77
|
+
/** Bound by a `commons.invoice`'s `lines` relation target class. Used to find the
|
|
78
|
+
* RELATED_VIEW item for the line items generically, by archetype id rather than
|
|
79
|
+
* by name/class string. */
|
|
80
|
+
export const ARCHETYPE_INVOICE_LINE = 'commons.invoiceLine';
|
|
75
81
|
/**
|
|
76
82
|
* Find a binding for `archetypeId` in a published `archetypes` list.
|
|
77
83
|
* Returns `undefined` when the archetype is absent — callers fall back to the
|
|
@@ -17,7 +17,12 @@ export class CellRenderer {
|
|
|
17
17
|
const name = (_d = column.name) !== null && _d !== void 0 ? _d : '';
|
|
18
18
|
const dataType = column.dataType;
|
|
19
19
|
const listItems = column.listItems;
|
|
20
|
-
|
|
20
|
+
let raw = row === null || row === void 0 ? void 0 : row[name];
|
|
21
|
+
// History-enabled fields are stored as { current, history: [...] } — render
|
|
22
|
+
// the current value, same as mrd-layout-section's field rendering.
|
|
23
|
+
if (column.historyEnabled && raw != null && typeof raw === 'object' && !Array.isArray(raw) && 'current' in raw) {
|
|
24
|
+
raw = raw.current;
|
|
25
|
+
}
|
|
21
26
|
if (raw == null || raw === '')
|
|
22
27
|
return '';
|
|
23
28
|
const values = Array.isArray(raw) ? raw : [raw];
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Extract
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Extract an archetype's file slot from a data record as a `DocumentAttachment[]` —
|
|
3
|
+
* empty when the slot is unmapped or has no value. `slot` defaults to `'file'` (the
|
|
4
|
+
* commons.document single-file slot); pass `'attachments'` for the multi-file slots
|
|
5
|
+
* on commons.email / commons.invoice. A single-object slot value yields a one-entry
|
|
6
|
+
* array, an array-valued slot yields one entry per item.
|
|
7
|
+
* Shared by mrd-document-view / mrd-invoice-view / mrd-email-view (on load/data-change)
|
|
8
|
+
* and mrd-document-container (row-click prefetch on mrdNavigate), so all report the
|
|
9
|
+
* exact same shape.
|
|
6
10
|
*/
|
|
7
|
-
export function attachmentsFromRecord(archetype, record) {
|
|
8
|
-
var _a
|
|
9
|
-
const field = (_a = archetype === null || archetype === void 0 ? void 0 : archetype.slots) === null || _a === void 0 ? void 0 : _a[
|
|
11
|
+
export function attachmentsFromRecord(archetype, record, slot = 'file') {
|
|
12
|
+
var _a;
|
|
13
|
+
const field = (_a = archetype === null || archetype === void 0 ? void 0 : archetype.slots) === null || _a === void 0 ? void 0 : _a[slot];
|
|
10
14
|
if (!field)
|
|
11
15
|
return [];
|
|
12
|
-
const
|
|
13
|
-
if (!
|
|
16
|
+
const raw = record === null || record === void 0 ? void 0 : record[field];
|
|
17
|
+
if (!raw)
|
|
14
18
|
return [];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
20
|
+
return items.filter(Boolean).map(item => {
|
|
21
|
+
var _a, _b, _c;
|
|
22
|
+
return ({
|
|
23
|
+
href: item.href,
|
|
24
|
+
fileName: (_a = item.fileName) !== null && _a !== void 0 ? _a : String(item),
|
|
25
|
+
mimeType: (_c = (_b = item.mimeType) !== null && _b !== void 0 ? _b : item.contentType) !== null && _c !== void 0 ? _c : undefined,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Dedup key for the componentDidLoad/@Watch('data') "emit mrdAttachments only on
|
|
30
|
+
* change" guard shared by mrd-document-view / mrd-invoice-view / mrd-email-view. */
|
|
31
|
+
export function attachmentsKey(attachments) {
|
|
32
|
+
return attachments.map(a => a.href).join('|');
|
|
20
33
|
}
|