@mmlogic/components 0.3.10 → 0.4.0
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/{index-BPj2cBXs.js → index-CAzwGt9u.js} +38 -0
- 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 +1280 -1073
- package/dist/collection/collection-manifest.json +23 -23
- package/dist/collection/components/{mrd-document-container/mrd-document-container.scss → documents/mrd-document-container/mrd-document-container.css} +3 -3
- package/dist/collection/components/{mrd-document-container → documents/mrd-document-container}/mrd-document-container.js +54 -8
- package/dist/collection/components/documents/mrd-document-list/folder-tree.js +109 -0
- package/dist/collection/components/{mrd-document-list/mrd-document-list.scss → documents/mrd-document-list/mrd-document-list.css} +17 -4
- package/dist/collection/components/{mrd-document-list → documents/mrd-document-list}/mrd-document-list.js +166 -237
- package/dist/collection/components/documents/mrd-document-list/tree-view.js +75 -0
- package/dist/collection/components/{mrd-document-view/mrd-document-view.scss → documents/mrd-document-view/mrd-document-view.css} +1 -1
- package/dist/collection/components/{mrd-document-view → documents/mrd-document-view}/mrd-document-view.js +4 -4
- package/dist/collection/components/fields/field-helpers.js +17 -0
- package/dist/collection/components/fields/file-upload-common.js +38 -0
- package/dist/collection/components/{mrd-boolean-field/mrd-boolean-field.scss → fields/mrd-boolean-field/mrd-boolean-field.css} +3 -3
- package/dist/collection/components/{mrd-boolean-field → fields/mrd-boolean-field}/mrd-boolean-field.js +1 -1
- package/dist/collection/components/{mrd-currency-field/mrd-currency-field.scss → fields/mrd-currency-field/mrd-currency-field.css} +11 -9
- package/dist/collection/components/{mrd-currency-field → fields/mrd-currency-field}/mrd-currency-field.js +7 -13
- package/dist/collection/components/{mrd-date-field/mrd-date-field.scss → fields/mrd-date-field/mrd-date-field.css} +8 -10
- package/dist/collection/components/{mrd-date-field → fields/mrd-date-field}/mrd-date-field.js +3 -10
- package/dist/collection/components/{mrd-datetime-field/mrd-datetime-field.scss → fields/mrd-datetime-field/mrd-datetime-field.css} +8 -10
- package/dist/collection/components/{mrd-datetime-field → fields/mrd-datetime-field}/mrd-datetime-field.js +3 -10
- package/dist/collection/components/{mrd-email-field/mrd-email-field.scss → fields/mrd-email-field/mrd-email-field.css} +2 -11
- package/dist/collection/components/{mrd-email-field → fields/mrd-email-field}/mrd-email-field.js +6 -13
- package/dist/collection/components/{mrd-file-field/mrd-file-field.scss → fields/mrd-file-field/mrd-file-field.css} +19 -24
- package/dist/collection/components/{mrd-file-field → fields/mrd-file-field}/mrd-file-field.js +13 -24
- package/dist/collection/components/{mrd-hyperlink-field/mrd-hyperlink-field.scss → fields/mrd-hyperlink-field/mrd-hyperlink-field.css} +7 -16
- package/dist/collection/components/{mrd-hyperlink-field → fields/mrd-hyperlink-field}/mrd-hyperlink-field.js +9 -15
- package/dist/collection/components/{mrd-image-field/mrd-image-field.scss → fields/mrd-image-field/mrd-image-field.css} +15 -24
- package/dist/collection/components/{mrd-image-field → fields/mrd-image-field}/mrd-image-field.js +13 -28
- package/dist/collection/components/{mrd-list-field/mrd-list-field.scss → fields/mrd-list-field/mrd-list-field.css} +18 -21
- package/dist/collection/components/{mrd-list-field → fields/mrd-list-field}/mrd-list-field.js +10 -25
- package/dist/collection/components/{mrd-longtext-field/mrd-longtext-field.scss → fields/mrd-longtext-field/mrd-longtext-field.css} +21 -18
- package/dist/collection/components/{mrd-longtext-field → fields/mrd-longtext-field}/mrd-longtext-field.js +3 -10
- package/dist/collection/components/{mrd-number-field/mrd-number-field.scss → fields/mrd-number-field/mrd-number-field.css} +9 -10
- package/dist/collection/components/{mrd-number-field → fields/mrd-number-field}/mrd-number-field.js +7 -6
- package/dist/collection/components/{mrd-relation-field/mrd-relation-field.scss → fields/mrd-relation-field/mrd-relation-field.css} +61 -59
- package/dist/collection/components/{mrd-relation-field → fields/mrd-relation-field}/mrd-relation-field.js +16 -26
- package/dist/collection/components/{mrd-secret-field/mrd-secret-field.scss → fields/mrd-secret-field/mrd-secret-field.css} +7 -10
- package/dist/collection/components/{mrd-secret-field → fields/mrd-secret-field}/mrd-secret-field.js +4 -11
- package/dist/collection/components/{mrd-text-field/mrd-text-field.scss → fields/mrd-text-field/mrd-text-field.css} +2 -11
- package/dist/collection/components/{mrd-text-field → fields/mrd-text-field}/mrd-text-field.js +3 -10
- package/dist/collection/components/{mrd-textarea-field/mrd-textarea-field.scss → fields/mrd-textarea-field/mrd-textarea-field.css} +7 -10
- package/dist/collection/components/{mrd-textarea-field → fields/mrd-textarea-field}/mrd-textarea-field.js +3 -9
- package/dist/collection/components/{mrd-time-field/mrd-time-field.scss → fields/mrd-time-field/mrd-time-field.css} +8 -10
- package/dist/collection/components/{mrd-time-field → fields/mrd-time-field}/mrd-time-field.js +3 -10
- package/dist/collection/components/{mrd-field/mrd-field.scss → form/mrd-field/mrd-field.css} +1 -1
- package/dist/collection/components/{mrd-field → form/mrd-field}/mrd-field.js +3 -3
- package/dist/collection/components/{mrd-form/mrd-form.scss → form/mrd-form/mrd-form.css} +1 -1
- package/dist/collection/components/{mrd-form → form/mrd-form}/mrd-form.js +5 -5
- package/dist/collection/components/layout/mrd-layout-section/field-value.js +111 -0
- package/dist/collection/components/layout/mrd-layout-section/layout-helpers.js +29 -0
- package/dist/collection/components/{mrd-layout-section/mrd-layout-section.scss → layout/mrd-layout-section/mrd-layout-section.css} +4 -4
- package/dist/collection/components/{mrd-layout-section → layout/mrd-layout-section}/mrd-layout-section.js +58 -133
- package/dist/collection/components/{mrd-table/mrd-table.scss → table/mrd-table/mrd-table.css} +11 -13
- package/dist/collection/components/{mrd-table → table/mrd-table}/mrd-table.js +111 -439
- package/dist/collection/components/table/mrd-table/table-filter-popup.js +85 -0
- package/dist/collection/components/table/mrd-table/table-parts.js +110 -0
- package/dist/collection/components/table/mrd-table/table-query.js +178 -0
- package/dist/collection/components/table/mrd-table/virtual-scroll.js +49 -0
- package/dist/collection/dev/app.js +19 -18
- package/dist/collection/dev/example-data.js +58 -43
- package/dist/collection/utils/i18n.js +10 -0
- package/dist/collection/utils/query-params.js +43 -0
- package/dist/components/field-helpers.js +1 -0
- package/dist/components/file-upload-common.js +1 -0
- package/dist/components/i18n.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-container2.js +1 -1
- package/dist/components/mrd-document-list2.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-file-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-hyperlink-field2.js +1 -1
- package/dist/components/mrd-image-field2.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-list-field2.js +1 -1
- package/dist/components/mrd-longtext-field2.js +1 -1
- package/dist/components/mrd-number-field2.js +1 -1
- package/dist/components/mrd-relation-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/query-params.js +1 -0
- package/dist/esm/{index-_tsCCkAi.js → index-DiSEJMA8.js} +38 -1
- package/dist/esm/loader.js +3 -3
- package/dist/esm/mosterdcomponents.js +3 -3
- package/dist/esm/mrd-boolean-field_23.entry.js +1280 -1073
- package/dist/mosterdcomponents/mosterdcomponents.css +1 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-9b3c0917.entry.js +1 -0
- package/dist/mosterdcomponents/p-DiSEJMA8.js +2 -0
- package/dist/types/components/{mrd-document-container → documents/mrd-document-container}/mrd-document-container.d.ts +7 -2
- package/dist/types/components/documents/mrd-document-list/folder-tree.d.ts +66 -0
- package/dist/types/components/{mrd-document-list → documents/mrd-document-list}/mrd-document-list.d.ts +28 -38
- package/dist/types/components/documents/mrd-document-list/tree-view.d.ts +32 -0
- package/dist/types/components/{mrd-document-view → documents/mrd-document-view}/mrd-document-view.d.ts +2 -2
- package/dist/types/components/fields/field-helpers.d.ts +18 -0
- package/dist/types/components/fields/file-upload-common.d.ts +18 -0
- package/dist/types/components/{mrd-boolean-field → fields/mrd-boolean-field}/mrd-boolean-field.d.ts +1 -1
- package/dist/types/components/{mrd-currency-field → fields/mrd-currency-field}/mrd-currency-field.d.ts +2 -2
- package/dist/types/components/{mrd-date-field → fields/mrd-date-field}/mrd-date-field.d.ts +1 -1
- package/dist/types/components/{mrd-datetime-field → fields/mrd-datetime-field}/mrd-datetime-field.d.ts +1 -1
- package/dist/types/components/{mrd-email-field → fields/mrd-email-field}/mrd-email-field.d.ts +1 -1
- package/dist/types/components/{mrd-file-field → fields/mrd-file-field}/mrd-file-field.d.ts +2 -4
- package/dist/types/components/{mrd-hyperlink-field → fields/mrd-hyperlink-field}/mrd-hyperlink-field.d.ts +2 -2
- package/dist/types/components/{mrd-image-field → fields/mrd-image-field}/mrd-image-field.d.ts +2 -4
- package/dist/types/components/{mrd-list-field → fields/mrd-list-field}/mrd-list-field.d.ts +2 -2
- package/dist/types/components/{mrd-longtext-field → fields/mrd-longtext-field}/mrd-longtext-field.d.ts +1 -1
- package/dist/types/components/{mrd-number-field → fields/mrd-number-field}/mrd-number-field.d.ts +2 -2
- package/dist/types/components/{mrd-relation-field → fields/mrd-relation-field}/mrd-relation-field.d.ts +2 -2
- package/dist/types/components/{mrd-secret-field → fields/mrd-secret-field}/mrd-secret-field.d.ts +1 -1
- package/dist/types/components/{mrd-text-field → fields/mrd-text-field}/mrd-text-field.d.ts +1 -1
- package/dist/types/components/{mrd-textarea-field → fields/mrd-textarea-field}/mrd-textarea-field.d.ts +1 -1
- package/dist/types/components/{mrd-time-field → fields/mrd-time-field}/mrd-time-field.d.ts +1 -1
- package/dist/types/components/{mrd-field → form/mrd-field}/mrd-field.d.ts +2 -2
- package/dist/types/components/{mrd-form → form/mrd-form}/mrd-form.d.ts +2 -2
- package/dist/types/components/layout/mrd-layout-section/field-value.d.ts +22 -0
- package/dist/types/components/layout/mrd-layout-section/layout-helpers.d.ts +8 -0
- package/dist/types/components/{mrd-layout-section → layout/mrd-layout-section}/mrd-layout-section.d.ts +4 -8
- package/dist/types/components/{mrd-table → table/mrd-table}/mrd-table.d.ts +6 -13
- package/dist/types/components/table/mrd-table/table-filter-popup.d.ts +35 -0
- package/dist/types/components/table/mrd-table/table-parts.d.ts +61 -0
- package/dist/types/components/table/mrd-table/table-query.d.ts +50 -0
- package/dist/types/components/table/mrd-table/virtual-scroll.d.ts +26 -0
- package/dist/types/components.d.ts +36 -5
- package/dist/types/types/client-layout.d.ts +5 -0
- package/dist/types/utils/query-params.d.ts +14 -0
- package/package.json +7 -2
- package/dist/components/validation.js +0 -1
- package/dist/mosterdcomponents/cell-renderer-B-AJDBcj.js.map +0 -1
- package/dist/mosterdcomponents/client-layout-ChqRA3AF.js.map +0 -1
- package/dist/mosterdcomponents/format-BAfsQfy1.js.map +0 -1
- package/dist/mosterdcomponents/i18n-s1XrKcD2.js.map +0 -1
- package/dist/mosterdcomponents/index-DkY-NQj0.js.map +0 -1
- package/dist/mosterdcomponents/index-DwF_Sp__.js.map +0 -1
- package/dist/mosterdcomponents/index.esm.js.map +0 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +0 -1
- package/dist/mosterdcomponents/p-69452249.entry.js +0 -1
- package/dist/mosterdcomponents/p-_tsCCkAi.js +0 -2
- package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +0 -1
- package/dist/mosterdcomponents/validation-Br9k2Ztw.js.map +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { h } from "@stencil/core";
|
|
2
|
+
import { t } from "../../../utils/i18n";
|
|
3
|
+
import { TEXT_TYPES, NUMERIC_TYPES, DATE_TYPES, NO_FILTER_TYPES } from "./table-query";
|
|
4
|
+
export const FilterEditor = ({ col, dataType, openFilterCol, pendingFilter, locale, cb }) => {
|
|
5
|
+
var _a, _b, _c, _d, _e, _f;
|
|
6
|
+
const pf = pendingFilter;
|
|
7
|
+
if (NO_FILTER_TYPES.has(dataType)) {
|
|
8
|
+
return h("p", { class: "mrd-table__filter-no-support" }, t('filter_no_support', locale));
|
|
9
|
+
}
|
|
10
|
+
if (dataType === 'BOOLEAN') {
|
|
11
|
+
const boolOp = pf.operator;
|
|
12
|
+
const noValueOp = boolOp === 'isEmpty' || boolOp === 'isNotEmpty';
|
|
13
|
+
return (h("div", { class: "mrd-table__filter-radio-group" }, [
|
|
14
|
+
{ labelKey: 'filter_all', value: null },
|
|
15
|
+
{ labelKey: 'yes', value: true },
|
|
16
|
+
{ labelKey: 'no', value: false },
|
|
17
|
+
].map(opt => (h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${openFilterCol}`, checked: !noValueOp && pf.value === opt.value, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined, value: opt.value })) }), t(opt.labelKey, locale)))), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${openFilterCol}`, checked: boolOp === 'isEmpty', onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: 'isEmpty', value: undefined })) }), t('filter_is_empty', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${openFilterCol}`, checked: boolOp === 'isNotEmpty', onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: 'isNotEmpty', value: undefined })) }), t('filter_is_not_empty', locale))));
|
|
18
|
+
}
|
|
19
|
+
if (dataType === 'LIST') {
|
|
20
|
+
const items = (_a = col.listItems) !== null && _a !== void 0 ? _a : [];
|
|
21
|
+
const selected = (_b = pf.values) !== null && _b !== void 0 ? _b : [];
|
|
22
|
+
return (h("div", { class: "mrd-table__filter-list" }, h("div", { class: "mrd-table__filter-list-controls" }, h("button", { class: "mrd-table__filter-list-btn", onClick: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { values: items.map(i => i.key) })) }, t('filter_select_all', locale)), h("button", { class: "mrd-table__filter-list-btn", onClick: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { values: [] })) }, t('filter_select_none', locale))), items.map(item => (h("label", { class: "mrd-table__filter-checkbox-label" }, h("input", { type: "checkbox", checked: selected.includes(item.key), onChange: (e) => cb.onToggleValue(item.key, e.target.checked) }), item.label)))));
|
|
23
|
+
}
|
|
24
|
+
if (TEXT_TYPES.has(dataType) || dataType === 'RELATION') {
|
|
25
|
+
const op = (_c = pf.operator) !== null && _c !== void 0 ? _c : 'startsWith';
|
|
26
|
+
const noInput = op === 'isEmpty' || op === 'isNotEmpty';
|
|
27
|
+
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => cb.onSetPending('operator', e.target.value) }, [
|
|
28
|
+
{ val: 'startsWith', labelKey: 'filter_starts_with' },
|
|
29
|
+
{ val: 'equals', labelKey: 'filter_equals' },
|
|
30
|
+
{ val: 'isEmpty', labelKey: 'filter_is_empty' },
|
|
31
|
+
{ val: 'isNotEmpty', labelKey: 'filter_is_not_empty' },
|
|
32
|
+
].map(o => h("option", { value: o.val, selected: op === o.val }, t(o.labelKey, locale)))), !noInput && (h("input", { type: "text", class: "mrd-table__filter-input", value: String((_d = pf.value) !== null && _d !== void 0 ? _d : ''), placeholder: t('filter_search_value', locale), onInput: (e) => cb.onSetPending('value', e.target.value) }))));
|
|
33
|
+
}
|
|
34
|
+
if (NUMERIC_TYPES.has(dataType)) {
|
|
35
|
+
const numOp = pf.operator;
|
|
36
|
+
const noInput = numOp === 'isEmpty' || numOp === 'isNotEmpty';
|
|
37
|
+
const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
|
|
38
|
+
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
|
|
39
|
+
const val = e.target.value;
|
|
40
|
+
if (val === 'isEmpty' || val === 'isNotEmpty') {
|
|
41
|
+
cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined }));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined }));
|
|
45
|
+
}
|
|
46
|
+
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', locale)), h("option", { value: "isEmpty", selected: numOp === 'isEmpty' }, t('filter_is_empty', locale)), h("option", { value: "isNotEmpty", selected: numOp === 'isNotEmpty' }, t('filter_is_not_empty', locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `nm-${openFilterCol}`, checked: !rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { from: undefined, to: undefined })) }), t('filter_exact', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `nm-${openFilterCol}`, checked: rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null })) }), t('filter_range', locale))), !rangeMode ? (h("input", { type: "number", class: "mrd-table__filter-input", value: pf.value != null ? String(pf.value) : '', onInput: (e) => cb.onSetPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: "number", class: "mrd-table__filter-input", placeholder: t('filter_from', locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => cb.onSetPending('from', e.target.value) }), h("span", { class: "mrd-table__filter-range-sep" }, "\u2013"), h("input", { type: "number", class: "mrd-table__filter-input", placeholder: t('filter_to', locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => cb.onSetPending('to', e.target.value) })))))));
|
|
47
|
+
}
|
|
48
|
+
if (dataType === 'DATETIME') {
|
|
49
|
+
const dtOp = pf.operator;
|
|
50
|
+
const noInput = dtOp === 'isEmpty' || dtOp === 'isNotEmpty';
|
|
51
|
+
const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
|
|
52
|
+
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
|
|
53
|
+
const val = e.target.value;
|
|
54
|
+
if (val === 'isEmpty' || val === 'isNotEmpty') {
|
|
55
|
+
cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined }));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined }));
|
|
59
|
+
}
|
|
60
|
+
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', locale)), h("option", { value: "isEmpty", selected: dtOp === 'isEmpty' }, t('filter_is_empty', locale)), h("option", { value: "isNotEmpty", selected: dtOp === 'isNotEmpty' }, t('filter_is_not_empty', locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: !rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { from: undefined, to: undefined })) }), t('filter_exact', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null })) }), t('filter_range', locale))), !rangeMode ? (h("input", { type: "date", class: "mrd-table__filter-input", value: String((_e = pf.value) !== null && _e !== void 0 ? _e : ''), onInput: (e) => cb.onSetPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range mrd-table__filter-range--stacked" }, h("label", { class: "mrd-table__filter-range-label" }, t('filter_from', locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.from != null ? String(pf.from) : '', onInput: (e) => cb.onSetPending('from', e.target.value) }), h("label", { class: "mrd-table__filter-range-label" }, t('filter_to', locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.to != null ? String(pf.to) : '', onInput: (e) => cb.onSetPending('to', e.target.value) })))))));
|
|
61
|
+
}
|
|
62
|
+
if (DATE_TYPES.has(dataType)) {
|
|
63
|
+
const inputType = dataType === 'DATE' ? 'date' : 'time';
|
|
64
|
+
const dtdOp = pf.operator;
|
|
65
|
+
const noInput = dtdOp === 'isEmpty' || dtdOp === 'isNotEmpty';
|
|
66
|
+
const rangeMode = !noInput && (pf.from !== undefined || pf.to !== undefined);
|
|
67
|
+
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => {
|
|
68
|
+
const val = e.target.value;
|
|
69
|
+
if (val === 'isEmpty' || val === 'isNotEmpty') {
|
|
70
|
+
cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: val, value: undefined, from: undefined, to: undefined }));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
cb.onReplacePending(Object.assign(Object.assign({}, pf), { operator: undefined }));
|
|
74
|
+
}
|
|
75
|
+
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', locale)), h("option", { value: "isEmpty", selected: dtdOp === 'isEmpty' }, t('filter_is_empty', locale)), h("option", { value: "isNotEmpty", selected: dtdOp === 'isNotEmpty' }, t('filter_is_not_empty', locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: !rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { from: undefined, to: undefined })) }), t('filter_exact', locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${openFilterCol}`, checked: rangeMode, onChange: () => cb.onReplacePending(Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null })) }), t('filter_range', locale))), !rangeMode ? (h("input", { type: inputType, class: "mrd-table__filter-input", value: String((_f = pf.value) !== null && _f !== void 0 ? _f : ''), onInput: (e) => cb.onSetPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_from', locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => cb.onSetPending('from', e.target.value) }), h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_to', locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => cb.onSetPending('to', e.target.value) })))))));
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
export const FilterPopup = (props) => {
|
|
80
|
+
var _a;
|
|
81
|
+
const { col, openFilterCol, popupPos, sortField, sortDir, locale, cb } = props;
|
|
82
|
+
const label = (_a = col.label) !== null && _a !== void 0 ? _a : openFilterCol;
|
|
83
|
+
const sortActive = sortField === openFilterCol;
|
|
84
|
+
return (h("div", { class: "mrd-table__filter-popup", style: { top: `${popupPos.top}px`, left: `${popupPos.left}px` }, onClick: (e) => e.stopPropagation() }, h("div", { class: "mrd-table__filter-popup-header" }, h("span", { class: "mrd-table__filter-popup-title" }, label), h("button", { class: "mrd-table__filter-close", onClick: cb.onClose }, "\u2715")), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_sorting', locale)), h("div", { class: "mrd-table__filter-sort-buttons" }, h("button", { class: `mrd-table__filter-sort-btn${sortActive && sortDir === 'asc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => cb.onApplySort('asc') }, "\u25B2 ", t('filter_ascending', locale)), h("button", { class: `mrd-table__filter-sort-btn${sortActive && sortDir === 'desc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => cb.onApplySort('desc') }, "\u25BC ", t('filter_descending', locale)))), h("div", { class: "mrd-table__filter-divider" }), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_section', locale)), h(FilterEditor, Object.assign({}, props))), h("div", { class: "mrd-table__filter-popup-footer" }, h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--clear", onClick: cb.onClear }, t('filter_clear', locale)), h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--apply", onClick: cb.onApply }, t('filter_apply', locale)))));
|
|
85
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { h } from "@stencil/core";
|
|
2
|
+
import { CellRenderer } from "../../../utils/cell-renderer";
|
|
3
|
+
import { t } from "../../../utils/i18n";
|
|
4
|
+
import { formatNumber, formatPercentage, formatCurrency } from "../../../utils/format";
|
|
5
|
+
import { NUMERIC_TYPES } from "./table-query";
|
|
6
|
+
/**
|
|
7
|
+
* Stateless render parts of mrd-table. All state and handlers stay in the
|
|
8
|
+
* component; these components receive plain values and callbacks.
|
|
9
|
+
*/
|
|
10
|
+
export const FilterIcon = () => (h("span", { class: "mrd-table__filter-icon", "aria-hidden": "true" }, h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "currentColor" }, h("path", { d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" }))));
|
|
11
|
+
const TEXTBLOCK_MAX = 200;
|
|
12
|
+
export const TableCell = ({ col, row, locale, onDownload, onOpenTextblock, onOpenJson }) => {
|
|
13
|
+
var _a, _b, _c;
|
|
14
|
+
const dataType = (_a = col.dataType) !== null && _a !== void 0 ? _a : '';
|
|
15
|
+
const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has(dataType);
|
|
16
|
+
const isFile = col.type === 'FIELD' && (dataType === 'FILE' || dataType === 'IMAGE');
|
|
17
|
+
if (isFile) {
|
|
18
|
+
const name = (_b = col.name) !== null && _b !== void 0 ? _b : '';
|
|
19
|
+
const fileVal = row === null || row === void 0 ? void 0 : row[name];
|
|
20
|
+
const href = fileVal === null || fileVal === void 0 ? void 0 : fileVal.href;
|
|
21
|
+
const fileName = fileVal === null || fileVal === void 0 ? void 0 : fileVal.fileName;
|
|
22
|
+
return (h("td", { class: "mrd-table__cell" }, href && fileName ? (h("button", { class: "mrd-table__file-btn", title: fileName, onClick: (e) => {
|
|
23
|
+
e.stopPropagation();
|
|
24
|
+
onDownload({ href, fileName });
|
|
25
|
+
} }, h("svg", { class: "mrd-table__file-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13zm-3 8l-3-3 1.41-1.41L10 14.17l4.59-4.58L16 11l-6 6z" })), t('download', locale))) : ''));
|
|
26
|
+
}
|
|
27
|
+
if (dataType === 'JSON') {
|
|
28
|
+
const name = (_c = col.name) !== null && _c !== void 0 ? _c : '';
|
|
29
|
+
const rawValue = name ? row === null || row === void 0 ? void 0 : row[name] : undefined;
|
|
30
|
+
if (rawValue == null || rawValue === '')
|
|
31
|
+
return h("td", { class: "mrd-table__cell" });
|
|
32
|
+
const compactHtml = CellRenderer.formatJson(rawValue, true);
|
|
33
|
+
const prettyHtml = CellRenderer.formatJson(rawValue, false);
|
|
34
|
+
return (h("td", { class: "mrd-table__cell" }, h("span", { class: "mrd-table__json-preview", innerHTML: compactHtml }), h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); onOpenJson(prettyHtml); }, "aria-label": t('textblock_show_more', locale) }, "\u22EF")));
|
|
35
|
+
}
|
|
36
|
+
if (dataType === 'TEXTBLOCK') {
|
|
37
|
+
const full = CellRenderer.render(col, row, locale);
|
|
38
|
+
if (full.length <= TEXTBLOCK_MAX) {
|
|
39
|
+
return h("td", { class: "mrd-table__cell" }, full);
|
|
40
|
+
}
|
|
41
|
+
const preview = full.slice(0, TEXTBLOCK_MAX) + '…';
|
|
42
|
+
return (h("td", { class: "mrd-table__cell" }, preview, h("button", { class: "mrd-table__textblock-btn", onClick: (e) => { e.stopPropagation(); onOpenTextblock(full); }, "aria-label": t('textblock_show_more', locale) }, "\u22EF")));
|
|
43
|
+
}
|
|
44
|
+
const value = CellRenderer.render(col, row, locale);
|
|
45
|
+
return (h("td", { class: `mrd-table__cell${isNumeric ? ' mrd-table__cell--numeric' : ''}` }, value));
|
|
46
|
+
};
|
|
47
|
+
/** Formats the aggregation total for one column, '' when not applicable. */
|
|
48
|
+
export function formatAggregationValue(col, aggregations, locale) {
|
|
49
|
+
var _a;
|
|
50
|
+
if (col.type !== 'FIELD' || !col.aggregate || !aggregations)
|
|
51
|
+
return '';
|
|
52
|
+
const fn = col.aggregate.toLowerCase();
|
|
53
|
+
const aggData = aggregations[fn];
|
|
54
|
+
const val = typeof aggData === 'object' && aggData !== null
|
|
55
|
+
? aggData[(_a = col.name) !== null && _a !== void 0 ? _a : '']
|
|
56
|
+
: undefined;
|
|
57
|
+
if (val == null)
|
|
58
|
+
return '';
|
|
59
|
+
const dt = col.dataType;
|
|
60
|
+
if (dt === 'INTEGER')
|
|
61
|
+
return formatNumber(val, locale, { maximumFractionDigits: 0 });
|
|
62
|
+
if (dt === 'PERCENTAGE')
|
|
63
|
+
return formatPercentage(val, locale);
|
|
64
|
+
if (dt === 'CURRENCY' && col.currencyCode)
|
|
65
|
+
return formatCurrency(val, col.currencyCode, locale);
|
|
66
|
+
return formatNumber(val, locale);
|
|
67
|
+
}
|
|
68
|
+
export const TotalsRow = ({ columns, aggregations, locale }) => {
|
|
69
|
+
if (!aggregations)
|
|
70
|
+
return null;
|
|
71
|
+
if (!columns.some(c => c.type === 'FIELD' && c.aggregate))
|
|
72
|
+
return null;
|
|
73
|
+
return (h("tfoot", null, h("tr", { class: "mrd-table__totals-row" }, columns.map(col => {
|
|
74
|
+
var _a;
|
|
75
|
+
const val = formatAggregationValue(col, aggregations, locale);
|
|
76
|
+
const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has((_a = col.dataType) !== null && _a !== void 0 ? _a : '');
|
|
77
|
+
return (h("td", { class: `mrd-table__totals-cell${isNumeric ? ' mrd-table__totals-cell--numeric' : ''}` }, val));
|
|
78
|
+
}))));
|
|
79
|
+
};
|
|
80
|
+
export const TextblockModal = ({ text, locale, onClose }) => {
|
|
81
|
+
if (text === null)
|
|
82
|
+
return null;
|
|
83
|
+
return (h("div", { class: "mrd-table__modal-backdrop", onClick: onClose, role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: onClose, "aria-label": t('close', locale) }, "\u2715"), h("p", { class: "mrd-table__modal-text" }, text))));
|
|
84
|
+
};
|
|
85
|
+
export const JsonModal = ({ html, locale, onClose }) => {
|
|
86
|
+
if (html === null)
|
|
87
|
+
return null;
|
|
88
|
+
return (h("div", { class: "mrd-table__modal-backdrop", onClick: onClose, role: "dialog", "aria-modal": "true" }, h("div", { class: "mrd-table__modal", onClick: (e) => e.stopPropagation() }, h("button", { class: "mrd-table__modal-close", onClick: onClose, "aria-label": t('close', locale) }, "\u2715"), h("pre", { class: "mrd-table__modal-json", innerHTML: html }))));
|
|
89
|
+
};
|
|
90
|
+
export const Toolbar = ({ locale, filterMode, filterCount, actions, views, activeViewIdx, viewPopoverOpen, createPickerOpen, createTypes, cb }) => {
|
|
91
|
+
var _a;
|
|
92
|
+
const hasActions = actions.length > 0;
|
|
93
|
+
const hasViewSwitcher = views.length > 1;
|
|
94
|
+
return (h("div", { class: "mrd-table__toolbar" }, h("div", { class: "mrd-table__toolbar-left" }, h("button", { class: `mrd-table__action mrd-table__action--secondary mrd-table__filter-toggle${filterMode ? ' mrd-table__filter-toggle--active' : ''}`, onClick: cb.onFilterToggle }, h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" })), filterCount > 0 && h("span", { class: "mrd-table__filter-badge" }, filterCount), h("span", { class: "mrd-table__action-tooltip" }, filterMode ? t('table_filter_hide', locale) : t('table_filter', locale), filterCount > 0 ? ` (${filterCount} ${t('table_filter_active', locale)})` : '')), filterCount > 0 && (h("button", { class: "mrd-table__action mrd-table__action--secondary", onClick: cb.onClearAllFilters }, h("svg", { class: "mrd-table__action-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })), h("span", { class: "mrd-table__action-tooltip" }, t('table_filter_clear_all', locale))))), hasViewSwitcher && (h("div", { class: "mrd-table__toolbar-center" }, h("div", { class: "mrd-table__view-switcher" }, h("button", { class: "mrd-table__view-arrow", "aria-label": "Previous view", onClick: () => cb.onViewSwitch((activeViewIdx - 1 + views.length) % views.length) }, "\u25C0"), h("div", { class: "mrd-table__view-title-wrap" }, h("button", { class: "mrd-table__view-title", onClick: (e) => cb.onToggleViewPopover(e) }, (_a = views[activeViewIdx]) === null || _a === void 0 ? void 0 : _a.label), viewPopoverOpen && (h("div", { class: "mrd-table__view-popover", onClick: (e) => e.stopPropagation() }, views.map((v, i) => (h("button", { key: String(i), class: `mrd-table__view-popover-item${i === activeViewIdx ? ' mrd-table__view-popover-item--active' : ''}`, onClick: () => {
|
|
95
|
+
if (i !== activeViewIdx)
|
|
96
|
+
cb.onViewSwitch(i);
|
|
97
|
+
cb.onCloseViewPopover();
|
|
98
|
+
} }, v.label)))))), h("button", { class: "mrd-table__view-arrow", "aria-label": "Next view", onClick: () => cb.onViewSwitch((activeViewIdx + 1) % views.length) }, "\u25B6")))), hasActions && (h("div", { class: "mrd-table__toolbar-right" }, actions.map(a => {
|
|
99
|
+
var _a, _b;
|
|
100
|
+
const pickerTypes = a.action === 'create' ? createTypes : null;
|
|
101
|
+
if (pickerTypes === null || pickerTypes === void 0 ? void 0 : pickerTypes.length) {
|
|
102
|
+
return (h("div", { class: "mrd-table__create-picker-wrap", key: `action-${a.action}` }, h("button", { class: `mrd-table__action mrd-table__action--${(_a = a.variant) !== null && _a !== void 0 ? _a : 'secondary'}`, onClick: cb.onToggleCreatePicker }, a.icon
|
|
103
|
+
? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
|
|
104
|
+
: a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)), createPickerOpen && (h("div", { class: "mrd-table__create-picker" }, pickerTypes.map(ct => (h("button", { key: ct.type, class: "mrd-table__create-picker-item", onClick: () => cb.onCreate(ct.type) }, ct.label)))))));
|
|
105
|
+
}
|
|
106
|
+
return (h("button", { key: `action-${a.action}`, class: `mrd-table__action mrd-table__action--${(_b = a.variant) !== null && _b !== void 0 ? _b : 'secondary'}`, disabled: a.disabled, onClick: () => cb.onAction(a.action) }, a.icon
|
|
107
|
+
? h("svg", { class: "mrd-table__action-icon", "aria-hidden": "true" }, h("use", { href: a.icon }))
|
|
108
|
+
: a.label, h("span", { class: "mrd-table__action-tooltip" }, a.label)));
|
|
109
|
+
})))));
|
|
110
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { applyViewFilters } from "../../../utils/query-params";
|
|
2
|
+
/**
|
|
3
|
+
* Pure sort/filter/aggregation query logic for mrd-table.
|
|
4
|
+
* No component state — everything is passed in.
|
|
5
|
+
*/
|
|
6
|
+
export const TEXT_TYPES = new Set(['TEXT', 'TEXTBLOCK', 'EMAIL', 'HYPERLINK']);
|
|
7
|
+
export const NUMERIC_TYPES = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
8
|
+
export const DATE_TYPES = new Set(['DATE', 'DATETIME', 'TIME']);
|
|
9
|
+
export const NO_FILTER_TYPES = new Set(['FILE', 'IMAGE']);
|
|
10
|
+
/** Column types that cannot be sorted or filtered (not stored in PostgreSQL). */
|
|
11
|
+
export const NON_INTERACTIVE_TYPES = new Set(['LONGTEXT', 'JSON', 'FILE', 'IMAGE']);
|
|
12
|
+
export function parseDefaultSort(defaultSort) {
|
|
13
|
+
var _a;
|
|
14
|
+
if (!defaultSort)
|
|
15
|
+
return { field: '', dir: 'asc' };
|
|
16
|
+
const parts = defaultSort.split(',');
|
|
17
|
+
return { field: parts[0].trim(), dir: ((_a = parts[1]) === null || _a === void 0 ? void 0 : _a.trim()) === 'desc' ? 'desc' : 'asc' };
|
|
18
|
+
}
|
|
19
|
+
/** Raw sort query-param value, e.g. "name" or "name,desc". */
|
|
20
|
+
export function buildSortParam(field, dir) {
|
|
21
|
+
if (!field)
|
|
22
|
+
return '';
|
|
23
|
+
return dir === 'desc' ? `${field},desc` : field;
|
|
24
|
+
}
|
|
25
|
+
/** Query params for a page request: page + sort + filterClass + view filters + active column filters. */
|
|
26
|
+
export function buildTableQueryParams(opts) {
|
|
27
|
+
var _a, _b;
|
|
28
|
+
const p = new URLSearchParams();
|
|
29
|
+
if (opts.page > 0)
|
|
30
|
+
p.set('page', String(opts.page));
|
|
31
|
+
if (opts.sort)
|
|
32
|
+
p.set('sort', opts.sort);
|
|
33
|
+
if (opts.filterClass)
|
|
34
|
+
p.set('type', opts.filterClass);
|
|
35
|
+
applyViewFilters(p, opts.viewFilters);
|
|
36
|
+
for (const f of opts.activeFilters) {
|
|
37
|
+
if (f.operator === 'isEmpty') {
|
|
38
|
+
p.set(f.field, '');
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (f.operator === 'isNotEmpty') {
|
|
42
|
+
p.set(f.field + '_notempty', 'true');
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (f.operator === 'startsWith') {
|
|
46
|
+
p.set(f.field + '_startswith', String((_a = f.value) !== null && _a !== void 0 ? _a : ''));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if ((_b = f.values) === null || _b === void 0 ? void 0 : _b.length) {
|
|
50
|
+
p.set(f.field, f.values.join(','));
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (f.value != null)
|
|
54
|
+
p.set(f.field, String(f.value));
|
|
55
|
+
if (f.from != null)
|
|
56
|
+
p.set(f.field + '_from', String(f.from));
|
|
57
|
+
if (f.to != null)
|
|
58
|
+
p.set(f.field + '_to', String(f.to));
|
|
59
|
+
}
|
|
60
|
+
return p.toString();
|
|
61
|
+
}
|
|
62
|
+
/** Groups aggregate columns by function; null when the view has no aggregates. */
|
|
63
|
+
export function buildAggregationParams(columns) {
|
|
64
|
+
var _a;
|
|
65
|
+
const groups = { sum: [], avg: [], count: [] };
|
|
66
|
+
for (const col of columns) {
|
|
67
|
+
if (col.type !== 'FIELD' || !col.aggregate)
|
|
68
|
+
continue;
|
|
69
|
+
const fn = col.aggregate.toLowerCase();
|
|
70
|
+
if (fn in groups)
|
|
71
|
+
groups[fn].push((_a = col.name) !== null && _a !== void 0 ? _a : '');
|
|
72
|
+
}
|
|
73
|
+
const params = {};
|
|
74
|
+
if (groups.sum.length)
|
|
75
|
+
params.sum = groups.sum;
|
|
76
|
+
if (groups.avg.length)
|
|
77
|
+
params.avg = groups.avg;
|
|
78
|
+
if (groups.count.length)
|
|
79
|
+
params.count = groups.count;
|
|
80
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
81
|
+
}
|
|
82
|
+
/** Aggregation query string: the page-0 query params without page/sort, plus sum/avg/count. */
|
|
83
|
+
export function buildAggregationQs(basePageQs, columns) {
|
|
84
|
+
var _a, _b, _c;
|
|
85
|
+
const p = new URLSearchParams(basePageQs);
|
|
86
|
+
p.delete('page');
|
|
87
|
+
p.delete('sort');
|
|
88
|
+
const groups = buildAggregationParams(columns);
|
|
89
|
+
if ((_a = groups === null || groups === void 0 ? void 0 : groups.sum) === null || _a === void 0 ? void 0 : _a.length)
|
|
90
|
+
p.set('sum', groups.sum.join(','));
|
|
91
|
+
if ((_b = groups === null || groups === void 0 ? void 0 : groups.avg) === null || _b === void 0 ? void 0 : _b.length)
|
|
92
|
+
p.set('avg', groups.avg.join(','));
|
|
93
|
+
if ((_c = groups === null || groups === void 0 ? void 0 : groups.count) === null || _c === void 0 ? void 0 : _c.length)
|
|
94
|
+
p.set('count', groups.count.join(','));
|
|
95
|
+
return p.toString();
|
|
96
|
+
}
|
|
97
|
+
/** True when the pending filter actually constrains anything. */
|
|
98
|
+
export function filterHasValue(f) {
|
|
99
|
+
if (f.operator === 'isEmpty' || f.operator === 'isNotEmpty')
|
|
100
|
+
return true;
|
|
101
|
+
if (f.values !== undefined && f.values.length > 0)
|
|
102
|
+
return true;
|
|
103
|
+
if (f.value != null && f.value !== '')
|
|
104
|
+
return true;
|
|
105
|
+
if (typeof f.value === 'boolean')
|
|
106
|
+
return true;
|
|
107
|
+
if (f.from != null && f.from !== '')
|
|
108
|
+
return true;
|
|
109
|
+
if (f.to != null && f.to !== '')
|
|
110
|
+
return true;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
// ── DATETIME filters: the user edits local "YYYY-MM-DD" dates, the API stores UTC ISO ──
|
|
114
|
+
/** UTC ISO string at the start (midnight) of the given local day. */
|
|
115
|
+
export function dateLocalToUTCStart(dateStr) {
|
|
116
|
+
if (!dateStr)
|
|
117
|
+
return dateStr;
|
|
118
|
+
const [year, month, day] = dateStr.split('-').map(Number);
|
|
119
|
+
return new Date(year, month - 1, day).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
120
|
+
}
|
|
121
|
+
/** Start of the day AFTER the given local date (exclusive range end). */
|
|
122
|
+
export function dateLocalToUTCEndExclusive(dateStr) {
|
|
123
|
+
if (!dateStr)
|
|
124
|
+
return dateStr;
|
|
125
|
+
const [year, month, day] = dateStr.split('-').map(Number);
|
|
126
|
+
return new Date(year, month - 1, day + 1).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
127
|
+
}
|
|
128
|
+
/** Stored UTC ISO string back to the local "YYYY-MM-DD" date. */
|
|
129
|
+
export function utcISOToLocalDate(utcStr) {
|
|
130
|
+
if (!utcStr)
|
|
131
|
+
return utcStr;
|
|
132
|
+
const d = new Date(utcStr);
|
|
133
|
+
if (isNaN(d.getTime()))
|
|
134
|
+
return utcStr;
|
|
135
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
136
|
+
}
|
|
137
|
+
/** The stored "to" is the exclusive end (next midnight); subtract a day to recover the entered date. */
|
|
138
|
+
export function utcISOToLocalDateExclusiveEnd(utcStr) {
|
|
139
|
+
if (!utcStr)
|
|
140
|
+
return utcStr;
|
|
141
|
+
const d = new Date(utcStr);
|
|
142
|
+
if (isNaN(d.getTime()))
|
|
143
|
+
return utcStr;
|
|
144
|
+
d.setDate(d.getDate() - 1);
|
|
145
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
146
|
+
}
|
|
147
|
+
/** Applies the local→UTC conversion when a DATETIME filter is submitted.
|
|
148
|
+
* Exact date → range covering the full local day. */
|
|
149
|
+
export function normalizeDatetimeFilter(f) {
|
|
150
|
+
const normalized = Object.assign({}, f);
|
|
151
|
+
if (f.dataType === 'DATETIME' && f.operator !== 'isEmpty' && f.operator !== 'isNotEmpty') {
|
|
152
|
+
if (typeof normalized.value === 'string' && normalized.value) {
|
|
153
|
+
normalized.from = dateLocalToUTCStart(normalized.value);
|
|
154
|
+
normalized.to = dateLocalToUTCEndExclusive(normalized.value);
|
|
155
|
+
normalized.value = undefined;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
if (typeof normalized.from === 'string' && normalized.from)
|
|
159
|
+
normalized.from = dateLocalToUTCStart(normalized.from);
|
|
160
|
+
if (typeof normalized.to === 'string' && normalized.to)
|
|
161
|
+
normalized.to = dateLocalToUTCEndExclusive(normalized.to);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return normalized;
|
|
165
|
+
}
|
|
166
|
+
/** Converts a stored DATETIME filter back to local dates for the popup.
|
|
167
|
+
* A range covering a single local day is restored to exact-date mode. */
|
|
168
|
+
export function datetimeFilterToDisplay(existing) {
|
|
169
|
+
const display = Object.assign({}, existing);
|
|
170
|
+
if (typeof display.from === 'string' && display.from)
|
|
171
|
+
display.from = utcISOToLocalDate(display.from);
|
|
172
|
+
if (typeof display.to === 'string' && display.to)
|
|
173
|
+
display.to = utcISOToLocalDateExclusiveEnd(display.to);
|
|
174
|
+
if (display.from && display.to && display.from === display.to) {
|
|
175
|
+
return Object.assign(Object.assign({}, display), { value: display.from, from: undefined, to: undefined });
|
|
176
|
+
}
|
|
177
|
+
return display;
|
|
178
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure window/paging maths for the mrd-table virtual scroll.
|
|
3
|
+
* The window state itself stays @State in the component; these
|
|
4
|
+
* functions only compute values from it.
|
|
5
|
+
*/
|
|
6
|
+
export const BUFFER = 10;
|
|
7
|
+
/** Wait this long (ms) after the last scroll event before requesting pages,
|
|
8
|
+
* so pages the user quickly scrolls past are skipped. */
|
|
9
|
+
export const REQUEST_DEBOUNCE_MS = 150;
|
|
10
|
+
export function visibleCount(tableHeight, rowHeight) {
|
|
11
|
+
return Math.ceil(tableHeight / rowHeight);
|
|
12
|
+
}
|
|
13
|
+
/** Render window (with buffer) for the given scroll offset. */
|
|
14
|
+
export function computeWindow(scrollTop, rowHeight, tableHeight, total) {
|
|
15
|
+
const visStart = Math.floor(scrollTop / rowHeight);
|
|
16
|
+
const visEnd = Math.min(visStart + visibleCount(tableHeight, rowHeight), total - 1);
|
|
17
|
+
return {
|
|
18
|
+
renderStart: Math.max(0, visStart - BUFFER),
|
|
19
|
+
renderEnd: Math.min(total - 1, visEnd + BUFFER),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** First/last page (0-based) covering the given row window. */
|
|
23
|
+
export function pageRange(start, end, pageSize) {
|
|
24
|
+
return { firstPage: Math.floor(start / pageSize), lastPage: Math.floor(end / pageSize) };
|
|
25
|
+
}
|
|
26
|
+
export function getRowFromPages(pages, index, pageSize) {
|
|
27
|
+
var _a;
|
|
28
|
+
const page = pages.get(Math.floor(index / pageSize));
|
|
29
|
+
return (_a = page === null || page === void 0 ? void 0 : page[index % pageSize]) !== null && _a !== void 0 ? _a : null;
|
|
30
|
+
}
|
|
31
|
+
/** Authoritative row count: a page shorter than pageSize is the last page,
|
|
32
|
+
* so the true total cannot exceed its end index. */
|
|
33
|
+
export function computeEffectiveTotal(loadedPages, pageSize, baseTotal) {
|
|
34
|
+
let effectiveTotal = baseTotal;
|
|
35
|
+
for (const [pageNum, pageRows] of loadedPages) {
|
|
36
|
+
if (pageRows.length < pageSize) {
|
|
37
|
+
effectiveTotal = Math.min(effectiveTotal, pageNum * pageSize + pageRows.length);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return effectiveTotal;
|
|
41
|
+
}
|
|
42
|
+
/** True when some loaded page was short — the exact total is then known. */
|
|
43
|
+
export function isTotalKnownFromPages(loadedPages, pageSize) {
|
|
44
|
+
for (const rows of loadedPages.values()) {
|
|
45
|
+
if (rows.length < pageSize)
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
@@ -119,27 +119,33 @@ function showTab(name) {
|
|
|
119
119
|
|
|
120
120
|
let _detailViewInitialized = false;
|
|
121
121
|
|
|
122
|
-
function initDetailViewTab() {
|
|
122
|
+
async function initDetailViewTab() {
|
|
123
123
|
if (_detailViewInitialized) return;
|
|
124
124
|
_detailViewInitialized = true;
|
|
125
125
|
|
|
126
126
|
const table = document.getElementById('companies-table');
|
|
127
|
-
|
|
127
|
+
if (typeof table.componentOnReady === 'function') {
|
|
128
|
+
await table.componentOnReady();
|
|
129
|
+
} else {
|
|
130
|
+
await customElements.whenDefined('mrd-table');
|
|
131
|
+
}
|
|
128
132
|
|
|
129
|
-
table.
|
|
133
|
+
table.item = window.EXAMPLE_COMPANY_VIEW_ITEM;
|
|
130
134
|
table.totalElements = window.EXAMPLE_COMPANIES.length;
|
|
131
|
-
table.pageSize
|
|
132
|
-
table.locale
|
|
135
|
+
table.pageSize = 20;
|
|
136
|
+
table.locale = 'nl-NL';
|
|
133
137
|
|
|
138
|
+
// Register before init(): init() emits mrdLoadPage for page 0 itself.
|
|
134
139
|
table.addEventListener('mrdLoadPage', async (e) => {
|
|
135
|
-
|
|
140
|
+
logEvent('mrdLoadPage', e.detail);
|
|
141
|
+
await table.setPage(e.detail.page, window.EXAMPLE_COMPANIES, false);
|
|
136
142
|
});
|
|
137
143
|
|
|
138
144
|
table.addEventListener('mrdRowClick', (e) => {
|
|
139
145
|
showCompanyDetail(e.detail);
|
|
140
146
|
});
|
|
141
147
|
|
|
142
|
-
table.init()
|
|
148
|
+
await table.init();
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
function showCompanyDetail(row) {
|
|
@@ -166,24 +172,19 @@ function showCompanyDetail(row) {
|
|
|
166
172
|
const section = document.createElement('mrd-layout-section');
|
|
167
173
|
section.items = layout.items;
|
|
168
174
|
section.data = company;
|
|
169
|
-
section.views = meta.views;
|
|
170
175
|
section.locale = 'nl-NL';
|
|
171
176
|
|
|
172
177
|
section.addEventListener('mrdNavigate', (e) => {
|
|
173
178
|
logEvent('mrdNavigate', e.detail);
|
|
174
179
|
});
|
|
175
180
|
|
|
176
|
-
section.addEventListener('mrdLoadView', async (e) => {
|
|
177
|
-
const { name, viewConfig } = e.detail;
|
|
178
|
-
const mockRows = name === 'view0' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
|
|
179
|
-
await section.setViewPage(name, 0, mockRows, mockRows.length);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
181
|
section.addEventListener('mrdLoadViewPage', async (e) => {
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
182
|
+
// Single mock page: answer every page request with the same rows + total.
|
|
183
|
+
// `name` is the view key: relatedClass for a RELATED_VIEW item.
|
|
184
|
+
const { name, page } = e.detail;
|
|
185
|
+
logEvent('mrdLoadViewPage', e.detail);
|
|
186
|
+
const mockRows = name === 'appointments' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
|
|
187
|
+
await section.setViewPage(name, page, mockRows, mockRows.length);
|
|
187
188
|
});
|
|
188
189
|
|
|
189
190
|
wrapper.appendChild(section);
|
|
@@ -105,6 +105,23 @@ window.EXAMPLE_COMPANY_COLUMNS = [
|
|
|
105
105
|
{ type: 'FIELD', label: 'Employees', name: 'totalEmployees', dataType: 'INTEGER', required: false },
|
|
106
106
|
];
|
|
107
107
|
|
|
108
|
+
/** VIEW layout item for the companies list table (mrd-table `item` prop). */
|
|
109
|
+
window.EXAMPLE_COMPANY_VIEW_ITEM = {
|
|
110
|
+
type: 'VIEW',
|
|
111
|
+
label: 'Companies',
|
|
112
|
+
name: 'companiesView',
|
|
113
|
+
dataClass: 'companies',
|
|
114
|
+
view: {
|
|
115
|
+
name: null,
|
|
116
|
+
defaultView: true,
|
|
117
|
+
defaultSort: 'name',
|
|
118
|
+
values: window.EXAMPLE_COMPANY_COLUMNS,
|
|
119
|
+
type: 'companies',
|
|
120
|
+
singularLabel: 'Company',
|
|
121
|
+
pluralLabel: 'Companies',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
108
125
|
/** Rows for the companies list table. */
|
|
109
126
|
window.EXAMPLE_COMPANIES = [
|
|
110
127
|
{
|
|
@@ -225,7 +242,31 @@ window.EXAMPLE_COMPANY_METADATA = {
|
|
|
225
242
|
label: 'Appointments',
|
|
226
243
|
tabPage: true,
|
|
227
244
|
items: [
|
|
228
|
-
{
|
|
245
|
+
{
|
|
246
|
+
type: 'RELATED_VIEW', label: 'Appointments', name: 'view0',
|
|
247
|
+
relatedClass: 'appointments', fromClass: 'companies', showTitle: false,
|
|
248
|
+
view: {
|
|
249
|
+
name: null,
|
|
250
|
+
defaultView: true,
|
|
251
|
+
defaultSort: 'name',
|
|
252
|
+
values: [
|
|
253
|
+
{ type: 'FIELD', label: 'name', name: 'name', dataType: 'TEXT', required: true },
|
|
254
|
+
{ type: 'RELATION', label: 'Officer', name: 'officer', relatedClass: 'housekeeping.officer', mostSignificantClass: 'entities', required: true },
|
|
255
|
+
{
|
|
256
|
+
type: 'FIELD', label: 'Authority', name: 'authority', dataType: 'LIST', required: false,
|
|
257
|
+
listItems: [
|
|
258
|
+
{ key: 'SOLE', label: 'Sole authority' },
|
|
259
|
+
{ key: 'JOINT', label: 'Joint authority' },
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{ type: 'FIELD', label: 'start date', name: 'activeDate', dataType: 'DATE', required: false },
|
|
263
|
+
{ type: 'FIELD', label: 'end date', name: 'inactiveDate', dataType: 'DATE', required: false },
|
|
264
|
+
],
|
|
265
|
+
type: 'appointments',
|
|
266
|
+
singularLabel: 'Appointment',
|
|
267
|
+
pluralLabel: 'Appointments',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
229
270
|
],
|
|
230
271
|
},
|
|
231
272
|
{
|
|
@@ -234,57 +275,31 @@ window.EXAMPLE_COMPANY_METADATA = {
|
|
|
234
275
|
tabPage: true,
|
|
235
276
|
items: [
|
|
236
277
|
{
|
|
237
|
-
type: 'RELATED_VIEW', label: 'Share classes', name: 'view1',
|
|
278
|
+
type: 'RELATED_VIEW', label: 'Share classes', name: 'view1',
|
|
279
|
+
relatedClass: 'shareClasses', fromClass: 'companies', showTitle: false,
|
|
238
280
|
actions: ['NEW', 'EXPORT'],
|
|
239
281
|
createTypes: [
|
|
240
282
|
{ type: 'ordinaryShares', label: 'Ordinary shares' },
|
|
241
283
|
{ type: 'preferenceShares', label: 'Preference shares' },
|
|
242
284
|
],
|
|
285
|
+
view: {
|
|
286
|
+
name: null,
|
|
287
|
+
defaultView: true,
|
|
288
|
+
defaultSort: 'name',
|
|
289
|
+
values: [
|
|
290
|
+
{ type: 'FIELD', label: 'name', name: 'name', dataType: 'TEXT', required: true },
|
|
291
|
+
{ type: 'FIELD', label: 'Nominal value', name: 'nominalValue', dataType: 'TEXT', required: false },
|
|
292
|
+
{ type: 'FIELD', label: 'Currency', name: 'currency', dataType: 'TEXT', required: false },
|
|
293
|
+
{ type: 'FIELD', label: 'Authorised shares', name: 'authorizedShares', dataType: 'INTEGER', required: false },
|
|
294
|
+
],
|
|
295
|
+
type: 'shareClasses',
|
|
296
|
+
singularLabel: 'Share class',
|
|
297
|
+
pluralLabel: 'Share classes',
|
|
298
|
+
},
|
|
243
299
|
},
|
|
244
300
|
],
|
|
245
301
|
},
|
|
246
302
|
],
|
|
247
|
-
views: {
|
|
248
|
-
view0: {
|
|
249
|
-
name: null,
|
|
250
|
-
defaultView: true,
|
|
251
|
-
defaultSort: 'name',
|
|
252
|
-
values: [
|
|
253
|
-
{ type: 'FIELD', label: 'name', name: 'name', dataType: 'TEXT', required: true },
|
|
254
|
-
{ type: 'RELATION', label: 'Officer', name: 'officer', relatedClass: 'housekeeping.officer', mostSignificantClass: 'entities', required: true },
|
|
255
|
-
{
|
|
256
|
-
type: 'FIELD', label: 'Authority', name: 'authority', dataType: 'LIST', required: false,
|
|
257
|
-
listItems: [
|
|
258
|
-
{ key: 'SOLE', label: 'Sole authority' },
|
|
259
|
-
{ key: 'JOINT', label: 'Joint authority' },
|
|
260
|
-
],
|
|
261
|
-
},
|
|
262
|
-
{ type: 'FIELD', label: 'start date', name: 'activeDate', dataType: 'DATE', required: false },
|
|
263
|
-
{ type: 'FIELD', label: 'end date', name: 'inactiveDate', dataType: 'DATE', required: false },
|
|
264
|
-
],
|
|
265
|
-
type: 'appointments',
|
|
266
|
-
singularLabel: 'Appointment',
|
|
267
|
-
pluralLabel: 'Appointments',
|
|
268
|
-
},
|
|
269
|
-
view1: {
|
|
270
|
-
name: null,
|
|
271
|
-
defaultView: true,
|
|
272
|
-
defaultSort: 'name',
|
|
273
|
-
values: [
|
|
274
|
-
{ type: 'FIELD', label: 'name', name: 'name', dataType: 'TEXT', required: true },
|
|
275
|
-
{ type: 'FIELD', label: 'Nominal value', name: 'nominalValue', dataType: 'TEXT', required: false },
|
|
276
|
-
{ type: 'FIELD', label: 'Currency', name: 'currency', dataType: 'TEXT', required: false },
|
|
277
|
-
{ type: 'FIELD', label: 'Authorised shares', name: 'authorizedShares', dataType: 'INTEGER', required: false },
|
|
278
|
-
],
|
|
279
|
-
type: 'shareClasses',
|
|
280
|
-
singularLabel: 'Share class',
|
|
281
|
-
pluralLabel: 'Share classes',
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
_links: {
|
|
285
|
-
view0: { href: '/data/demo/appointments' },
|
|
286
|
-
view1: { href: '/data/demo/shareClasses' },
|
|
287
|
-
},
|
|
288
303
|
};
|
|
289
304
|
|
|
290
305
|
/** Mock appointment rows for the embedded table. */
|