@itfin/components 1.3.35 → 1.3.37
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/package.json +1 -1
- package/src/assets/scss/components/_pagination.scss +4 -0
- package/src/assets/scss/icons.scss +1 -1
- package/src/components/dropdown/Dropdown.vue +14 -1
- package/src/components/dropdown/DropdownMenu.vue +18 -10
- package/src/components/pagination/Pagination.vue +1 -1
- package/src/components/table/Table2.vue +9 -2
- package/src/components/table/TableBody.vue +48 -22
- package/src/components/table/TableGroup.vue +79 -45
- package/src/components/table/TableHeader.vue +16 -13
- package/src/components/table/index.stories.js +4 -2
- package/src/components/text-field/MoneyField.vue +11 -10
- package/src/locales/en.js +24 -0
- package/src/locales/uk.js +24 -0
- package/src/components/button/1.html +0 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
$bootstrap-icons-font: "bootstrap-icons" !default;
|
|
3
|
-
$bootstrap-icons-font-dir: "
|
|
3
|
+
$bootstrap-icons-font-dir: "/fonts" !default;
|
|
4
4
|
$bootstrap-icons-font-file: "#{$bootstrap-icons-font-dir}/#{$bootstrap-icons-font}" !default;
|
|
5
5
|
$bootstrap-icons-font-hash: "24e3eb84d0bcaf83d77f904c78ac1f47" !default;
|
|
6
6
|
$bootstrap-icons-font-src: url("#{$bootstrap-icons-font-file}.woff2?#{$bootstrap-icons-font-hash}") format("woff2"),
|
|
@@ -24,6 +24,7 @@ class itfDropdown extends Vue {
|
|
|
24
24
|
@Prop({ type: Boolean }) disabled;
|
|
25
25
|
@Prop({ type: Boolean }) text;
|
|
26
26
|
@Prop({ type: Boolean }) appendToBody;
|
|
27
|
+
@Prop({ type: Boolean }) appendToContext;
|
|
27
28
|
@Prop({ validator: (value) => [true, false, 'inside', 'outside'].includes(value), default: true }) autoclose;
|
|
28
29
|
@Prop({ type: Object, default: () => ({}) }) buttonOptions;
|
|
29
30
|
@Prop({ type: Object, default: () => ({}) }) menuOptions;
|
|
@@ -31,7 +32,7 @@ class itfDropdown extends Vue {
|
|
|
31
32
|
modalId = '';
|
|
32
33
|
|
|
33
34
|
render (createElement, context) {
|
|
34
|
-
const { props, slots, data } = context;
|
|
35
|
+
const { props, slots, data, listeners } = context;
|
|
35
36
|
const modalId = `dropdownId${globalModalIndex++}`;
|
|
36
37
|
const { buttonOptions, toggle, text, disabled, label, appendToBody } = props;
|
|
37
38
|
const { button, default: defaultSlot } = slots();
|
|
@@ -51,6 +52,18 @@ class itfDropdown extends Vue {
|
|
|
51
52
|
{
|
|
52
53
|
ref: data.ref,
|
|
53
54
|
props: { ...props, toggleId: modalId },
|
|
55
|
+
on: {
|
|
56
|
+
open: () => {
|
|
57
|
+
if (listeners.open) {
|
|
58
|
+
listeners.open();
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
close: () => {
|
|
62
|
+
if (listeners.close) {
|
|
63
|
+
listeners.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
54
67
|
},
|
|
55
68
|
defaultSlot
|
|
56
69
|
)
|
|
@@ -22,6 +22,7 @@ class itfDropdownMenu extends Vue {
|
|
|
22
22
|
@Prop({ type: Boolean }) disabled;
|
|
23
23
|
@Prop({ type: Boolean }) text;
|
|
24
24
|
@Prop({ type: Boolean }) appendToBody;
|
|
25
|
+
@Prop({ type: Boolean }) appendToContext;
|
|
25
26
|
@Prop({ type: String }) toggleId;
|
|
26
27
|
@Prop({ validator: (value) => [true, false, 'inside', 'outside'].includes(value), default: true }) autoclose;
|
|
27
28
|
@Prop({ type: Object, default: () => ({}) }) buttonOptions;
|
|
@@ -39,32 +40,39 @@ class itfDropdownMenu extends Vue {
|
|
|
39
40
|
const { default: Dropdown } = await import('bootstrap/js/src/dropdown.js');
|
|
40
41
|
let context = document.body;
|
|
41
42
|
this.modalEl = new Dropdown(toggle, {
|
|
42
|
-
reference: '
|
|
43
|
+
reference: 'parent',
|
|
43
44
|
autoClose: this.autoclose
|
|
44
45
|
});
|
|
45
|
-
if (this.
|
|
46
|
+
if (this.appendToContext && this.$el instanceof Node && this.$el.parentNode) {
|
|
47
|
+
context = this.$el.closest('.itf-append-context') || document.body;
|
|
48
|
+
this.$el.parentNode.removeChild(this.$el);
|
|
49
|
+
context.appendChild(this.$el); // should append only to body
|
|
50
|
+
} else if (this.appendToBody && this.$el instanceof Node && this.$el.parentNode) {
|
|
46
51
|
this.$el.parentNode.removeChild(this.$el);
|
|
47
52
|
context.appendChild(this.$el); // should append only to body
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.$emit('open');
|
|
53
|
-
}, 500);
|
|
55
|
+
toggle.addEventListener('shown.bs.dropdown', () => {
|
|
56
|
+
this.$emit('open');
|
|
54
57
|
});
|
|
55
|
-
|
|
58
|
+
toggle.addEventListener('hidden.bs.dropdown', () => {
|
|
56
59
|
this.$emit('close');
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
beforeDestroy() {
|
|
61
|
-
|
|
62
|
-
this.modalEl
|
|
63
|
-
|
|
64
|
+
try {
|
|
65
|
+
if (this.modalEl) {
|
|
66
|
+
this.modalEl.hide();
|
|
67
|
+
this.modalEl.dispose();
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// ignore
|
|
64
71
|
}
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
show() {
|
|
75
|
+
console.info('p[en')
|
|
68
76
|
if (this.modalEl) {
|
|
69
77
|
this.modalEl.show();
|
|
70
78
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<div class="table-view-body">
|
|
7
7
|
<itf-table-group
|
|
8
8
|
:key="index"
|
|
9
|
+
@update="$emit('update', { ...$event, group, groupIndex: index })"
|
|
9
10
|
:id-property="idProperty"
|
|
10
11
|
:columns="columns"
|
|
11
12
|
@update:columns="onColumnsUpdate"
|
|
@@ -20,6 +21,8 @@
|
|
|
20
21
|
:show-add-column="showAddColumn"
|
|
21
22
|
:show-header="!noHeader"
|
|
22
23
|
:schema="schema"
|
|
24
|
+
:no-column-menu="noColumnMenu"
|
|
25
|
+
:no-select-all="noSelectAll"
|
|
23
26
|
@new="$emit('new', $event)"
|
|
24
27
|
@add-column="$emit('add-column', $event)"
|
|
25
28
|
>
|
|
@@ -40,7 +43,6 @@
|
|
|
40
43
|
.scrollable {
|
|
41
44
|
--itf-table-hover-bg: #f2f2f2;
|
|
42
45
|
--itf-table-min-width: 45px;
|
|
43
|
-
--itf-table-line-height: 35px;
|
|
44
46
|
|
|
45
47
|
body[data-theme="dark"] & {
|
|
46
48
|
--itf-table-hover-bg: #393b41;
|
|
@@ -103,6 +105,8 @@ class itfTable2 extends Vue {
|
|
|
103
105
|
@Prop(Boolean) showGrouping;
|
|
104
106
|
@Prop(Boolean) showSummary;
|
|
105
107
|
@Prop(Boolean) noHeader;
|
|
108
|
+
@Prop(Boolean) noColumnMenu;
|
|
109
|
+
@Prop(Boolean) noSelectAll;
|
|
106
110
|
|
|
107
111
|
state = {
|
|
108
112
|
selectedIds: [],
|
|
@@ -123,7 +127,10 @@ class itfTable2 extends Vue {
|
|
|
123
127
|
for (const column of list) {
|
|
124
128
|
const stateColumn = state.columns.find(i => i.property === column.property);
|
|
125
129
|
if (stateColumn) {
|
|
126
|
-
Object.assign(stateColumn,
|
|
130
|
+
Object.assign(stateColumn, {
|
|
131
|
+
...column,
|
|
132
|
+
width: stateColumn.width
|
|
133
|
+
});
|
|
127
134
|
} else {
|
|
128
135
|
state.columns.push(column);
|
|
129
136
|
}
|
|
@@ -18,30 +18,39 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
<div class="indicator sticky">
|
|
21
|
-
<div class="fill
|
|
22
|
-
|
|
21
|
+
<div class="fill table-view-row-count" :class="{'on-rest': !noSelectAll}">
|
|
22
|
+
<span>{{ item[idProperty] }}</span>
|
|
23
23
|
</div>
|
|
24
|
-
<div class="fill on-hover">
|
|
24
|
+
<div v-if="!noSelectAll" class="fill on-hover">
|
|
25
25
|
<itf-checkbox :value="item[idProperty]" />
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
<div accept-group="items" class="table-item-inner">
|
|
29
|
-
<template v-for="(column,
|
|
29
|
+
<template v-for="(column, k) in visibleAttributes">
|
|
30
30
|
<div
|
|
31
31
|
v-if="column.visible !== false"
|
|
32
|
-
:data-column="
|
|
33
|
-
:style="`width: ${column.width}px; left: ${column.left}px;`"
|
|
34
|
-
:class="{'sticky': column.pinned, 'last-sticky-column':
|
|
32
|
+
:data-column="k"
|
|
33
|
+
:style="`width: ${column.width}px; max-width: ${column.width}px; left: ${column.left}px;`"
|
|
34
|
+
:class="{'sticky': column.pinned, 'last-sticky-column': k === lastPinnedIndex, 'flex-grow-1': column.grow, 'px-2': !column.editable, 'p-1': column.editable}"
|
|
35
35
|
class="table-view-item-value d-flex h-100 align-items-stretch">
|
|
36
|
-
<slot :name="`column.${column.
|
|
36
|
+
<slot :name="`column.${column.property}`" :item="item" :column="column">
|
|
37
37
|
<template v-if="column.editable">
|
|
38
|
-
<slot :name="`edit.${column.type}`" :value="getValue(item, column)" :item="item" :column="column">
|
|
39
|
-
<
|
|
38
|
+
<slot :name="`edit.${column.type}`" :update="(val) => updateValue(item, val, n, column)" :value="getValue(item, column)" :item="item" :column="column">
|
|
39
|
+
<itf-text-field class="w-100" v-if="column.type === 'text'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
40
|
+
<itf-hours-field
|
|
41
|
+
class="w-100"
|
|
42
|
+
placeholder="0h 0m"
|
|
43
|
+
v-else-if="column.type === 'time'"
|
|
44
|
+
:hours="getValue(item, column)"
|
|
45
|
+
@update:hours="updateValue(item, $event, n, column)"
|
|
46
|
+
/>
|
|
47
|
+
<itf-textarea class="w-100" :rows="2" autogrow v-else-if="column.type === 'textarea'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
48
|
+
<itf-money-field class="w-100" v-else-if="column.type === 'money'" no-currency-sign currency-disabled :currency-select="false" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
40
49
|
</slot>
|
|
41
50
|
</template>
|
|
42
51
|
<template v-else>
|
|
43
52
|
<slot :name="`format.${column.type}`" :value="getValue(item, column)" :item="item" :column="column">
|
|
44
|
-
|
|
53
|
+
{{getValue(item, column)}}
|
|
45
54
|
</slot>
|
|
46
55
|
</template>
|
|
47
56
|
</slot>
|
|
@@ -55,6 +64,19 @@
|
|
|
55
64
|
</div>
|
|
56
65
|
</div>
|
|
57
66
|
</div>
|
|
67
|
+
|
|
68
|
+
<div v-if="!rows.length" data-test="table-no-results" class="table-view-item">
|
|
69
|
+
<div class="table-row-template">
|
|
70
|
+
<div accept-group="items" class="table-view-body-space"></div>
|
|
71
|
+
<div class="shadow-area"></div>
|
|
72
|
+
<div class="indicator sticky"></div>
|
|
73
|
+
<div class="table-item-inner">
|
|
74
|
+
<div class="table-view-item-value w-100 align-items-center p-3">
|
|
75
|
+
{{$t('components.table.noResults')}}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
58
80
|
</div>
|
|
59
81
|
</template>
|
|
60
82
|
<style lang="scss">
|
|
@@ -78,7 +100,6 @@
|
|
|
78
100
|
align-items: stretch;
|
|
79
101
|
display: flex;
|
|
80
102
|
position: relative;
|
|
81
|
-
line-height: var(--itf-table-line-height);
|
|
82
103
|
min-width: var(--itf-table-min-width);
|
|
83
104
|
|
|
84
105
|
&.highlight-drop-column {
|
|
@@ -208,18 +229,24 @@
|
|
|
208
229
|
<script>
|
|
209
230
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
210
231
|
import get from 'lodash/get';
|
|
211
|
-
import
|
|
232
|
+
import set from 'lodash/set';
|
|
233
|
+
// import { RecycleScroller } from 'vue-virtual-scroller'
|
|
212
234
|
import itfCheckbox from '../checkbox/Checkbox.vue';
|
|
213
235
|
import itfTextField from '../text-field/TextField.vue';
|
|
214
|
-
import
|
|
215
|
-
import '
|
|
236
|
+
import itfMoneyField from '../text-field/MoneyField.vue';
|
|
237
|
+
import itfTextarea from '../text-field/Textarea.vue';
|
|
238
|
+
import itfHoursField from '../text-field/HoursField.vue';
|
|
239
|
+
// import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
|
216
240
|
|
|
217
241
|
export default @Component({
|
|
218
242
|
name: 'itfTableBody',
|
|
219
243
|
components: {
|
|
220
244
|
itfCheckbox,
|
|
245
|
+
itfMoneyField,
|
|
246
|
+
itfHoursField,
|
|
247
|
+
itfTextarea,
|
|
221
248
|
itfTextField,
|
|
222
|
-
RecycleScroller
|
|
249
|
+
// RecycleScroller
|
|
223
250
|
}
|
|
224
251
|
})
|
|
225
252
|
class itfTableBody extends Vue {
|
|
@@ -227,8 +254,7 @@ class itfTableBody extends Vue {
|
|
|
227
254
|
@Prop() rows;
|
|
228
255
|
@Prop() idProperty;
|
|
229
256
|
@Prop(Boolean) showAddColumn;
|
|
230
|
-
|
|
231
|
-
editTypes = {};
|
|
257
|
+
@Prop(Boolean) noSelectAll;
|
|
232
258
|
|
|
233
259
|
getValue(item, column) {
|
|
234
260
|
return get(item, column.property);
|
|
@@ -242,10 +268,10 @@ class itfTableBody extends Vue {
|
|
|
242
268
|
return this.columns.findIndex((column) => column.lastPinned);
|
|
243
269
|
}
|
|
244
270
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
271
|
+
updateValue(item, value, index, column) {
|
|
272
|
+
const newItem = { ...item };
|
|
273
|
+
set(newItem, column.property, value);
|
|
274
|
+
this.$emit('update', { index, item, value: newItem });
|
|
249
275
|
}
|
|
250
276
|
}
|
|
251
277
|
</script>
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
:show-add-column="showAddColumn"
|
|
43
43
|
:rows="rows"
|
|
44
44
|
:schema="schema"
|
|
45
|
+
:no-column-menu="noColumnMenu"
|
|
46
|
+
:no-select-all="noSelectAll"
|
|
45
47
|
:selected-ids="selectedIds"
|
|
46
48
|
@update:selectedIds="$emit('update:selectedIds', $event)"
|
|
47
49
|
@update:columns="$emit('update:columns', $event)"
|
|
@@ -52,9 +54,11 @@
|
|
|
52
54
|
<!-- Сама таблиця -->
|
|
53
55
|
<div v-if="isShowTable">
|
|
54
56
|
<itf-table-body
|
|
57
|
+
@update="$emit('update', $event)"
|
|
55
58
|
:id-property="idProperty"
|
|
56
59
|
:rows="rows"
|
|
57
60
|
:columns="visibleColumns"
|
|
61
|
+
:no-select-all="noSelectAll"
|
|
58
62
|
:show-add-column="showAddColumn">
|
|
59
63
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
|
60
64
|
<slot :name="name" v-bind="slotData || {}"/>
|
|
@@ -79,7 +83,7 @@
|
|
|
79
83
|
</div>
|
|
80
84
|
|
|
81
85
|
<!-- Групування -->
|
|
82
|
-
<div v-if="isShowTable && showSummary" class="table-row-template d-flex align-items-stretch table-summary">
|
|
86
|
+
<div v-if="isShowTable && showSummary" class="table-row-template d-flex align-items-stretch table-summary" :class="{'table-summary-persist': persistSummary}">
|
|
83
87
|
<div class="shadow-area"></div>
|
|
84
88
|
|
|
85
89
|
<div class="table-summary-columns d-flex tw-flex-row align-items-center">
|
|
@@ -87,51 +91,27 @@
|
|
|
87
91
|
v-for="(column, n) in visibleColumns"
|
|
88
92
|
:key="n"
|
|
89
93
|
:data-column="n"
|
|
90
|
-
class="
|
|
91
|
-
:style="`width: ${column.width}px;`">
|
|
92
|
-
<itf-dropdown text>
|
|
94
|
+
class="position-relative line-overflow"
|
|
95
|
+
:style="`width: ${column.width}px; max-width: ${column.width}px;`">
|
|
96
|
+
<itf-dropdown append-to-context text right @open="persistSummary = true" @close="persistSummary = false">
|
|
93
97
|
<template #button>
|
|
94
98
|
<span data-test="summary-column" class="invisible-summary d-flex align-items-center justify-content-end flex-auto">
|
|
95
|
-
<span class="summary-placeholder align-items-center justify-content-center">
|
|
96
|
-
|
|
99
|
+
<span v-if="column.calculate === 'none' || !column.calculate" class="table-summary-column summary-placeholder align-items-center justify-content-center">
|
|
100
|
+
{{$t('components.table.calculate')}}
|
|
97
101
|
<itf-icon name="chevron_down" />
|
|
98
102
|
</span>
|
|
103
|
+
<span v-else>
|
|
104
|
+
<span class="summary-text text-uppercase pe-2">{{getCalculateMethodTitle(column.calculate)}}</span>
|
|
105
|
+
<span>{{getCalculateMethodValue(column.calculate, column)}}</span>
|
|
106
|
+
</span>
|
|
99
107
|
</span>
|
|
100
108
|
</template>
|
|
101
109
|
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
</a>
|
|
108
|
-
</li>
|
|
109
|
-
<li v-if="column.sortable">
|
|
110
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
111
|
-
<itf-icon name="arrow_down" :size="16" class="me-1" />
|
|
112
|
-
Sort By Desc
|
|
113
|
-
</a>
|
|
114
|
-
</li>
|
|
115
|
-
<li v-if="column.groupable">
|
|
116
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
117
|
-
<itf-icon name="episodes" :size="16" class="me-1" />
|
|
118
|
-
Group By
|
|
119
|
-
</a>
|
|
120
|
-
</li>
|
|
121
|
-
<li>
|
|
122
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="togglePinned(n)">
|
|
123
|
-
<itf-icon :name="column.pinned ? 'checkbox_checked' : 'checkbox_empty'" :size="16" class="me-1" />
|
|
124
|
-
<span v-if="column.pinned">Unpin Column</span>
|
|
125
|
-
<span v-else>Pin Column</span>
|
|
126
|
-
</a>
|
|
127
|
-
</li>
|
|
128
|
-
<li>
|
|
129
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="hideColumn(n)">
|
|
130
|
-
<itf-icon name="eye_no" :size="16" class="me-1" />
|
|
131
|
-
Hide
|
|
132
|
-
</a>
|
|
133
|
-
</li>
|
|
134
|
-
</ul>
|
|
110
|
+
<div v-for="(method, m) in calculateMethods" :key="method.id">
|
|
111
|
+
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="selectSummary(method, n)">
|
|
112
|
+
{{method.title}}
|
|
113
|
+
</a>
|
|
114
|
+
</div>
|
|
135
115
|
</itf-dropdown>
|
|
136
116
|
</span>
|
|
137
117
|
</div>
|
|
@@ -289,23 +269,34 @@
|
|
|
289
269
|
opacity: 0.05;
|
|
290
270
|
font-size: .85rem;
|
|
291
271
|
padding-right: .25rem;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
272
|
+
}
|
|
273
|
+
.summary-text {
|
|
274
|
+
color: var(--bs-tertiary-color);
|
|
275
|
+
}
|
|
276
|
+
&.table-summary-persist .table-summary-column,
|
|
277
|
+
&:hover .table-summary-column {
|
|
278
|
+
opacity: 1;
|
|
296
279
|
}
|
|
297
280
|
}
|
|
298
281
|
}
|
|
299
282
|
</style>
|
|
300
283
|
<script>
|
|
284
|
+
import get from 'lodash/get';
|
|
301
285
|
import sortBy from 'lodash/sortBy';
|
|
286
|
+
import uniq from "lodash/uniq";
|
|
287
|
+
import round from "lodash/round";
|
|
302
288
|
import {Vue, Component, Prop, Watch} from 'vue-property-decorator';
|
|
303
289
|
import itfDropdown from '../dropdown/Dropdown.vue';
|
|
304
290
|
import itfButton from '../button/Button.vue';
|
|
305
291
|
import itfIcon from '../icon/Icon.vue';
|
|
306
292
|
import itfTableBody from './TableBody.vue';
|
|
307
293
|
import itfTableHeader from './TableHeader.vue';
|
|
308
|
-
import Sticky from "
|
|
294
|
+
import Sticky from "./sticky";
|
|
295
|
+
|
|
296
|
+
function getNumber(item, prop) {
|
|
297
|
+
const num = Number(get(item, prop));
|
|
298
|
+
return Number.isNaN(num) ? 0 : num;
|
|
299
|
+
}
|
|
309
300
|
|
|
310
301
|
export default @Component({
|
|
311
302
|
name: 'itfTableGroup',
|
|
@@ -330,10 +321,13 @@ class itfTableGroup extends Vue {
|
|
|
330
321
|
@Prop(Boolean) columnResizing;
|
|
331
322
|
@Prop(Boolean) showAddColumn;
|
|
332
323
|
@Prop(Boolean) showHeader;
|
|
324
|
+
@Prop(Boolean) noColumnMenu;
|
|
325
|
+
@Prop(Boolean) noSelectAll;
|
|
333
326
|
@Prop({type: String, default: function() { return this.$t('components.new'); } }) newLabel;
|
|
334
327
|
@Prop({type: Object, default: () => ({})}) schema;
|
|
335
328
|
|
|
336
329
|
isShowTable = true;
|
|
330
|
+
persistSummary = false;
|
|
337
331
|
|
|
338
332
|
get visibleColumns() {
|
|
339
333
|
let list = this.columns;
|
|
@@ -368,7 +362,47 @@ class itfTableGroup extends Vue {
|
|
|
368
362
|
wrap: true,
|
|
369
363
|
stickyClass: 'sticky',
|
|
370
364
|
});
|
|
371
|
-
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
selectSummary(method, index) {
|
|
368
|
+
const columns = [...this.columns];
|
|
369
|
+
columns[index].calculate = method.id;
|
|
370
|
+
this.$emit('update:columns', columns);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
get calculateMethods() {
|
|
374
|
+
return [
|
|
375
|
+
{ id: 'none', title: this.$t('components.table.calculateNone') },
|
|
376
|
+
{ id: 'total', title: this.$t('components.table.calculateTotal'), summary: 'Total', func: (rows, column) => rows.reduce((acc, row) => acc + (getNumber(row, column.property)), 0) },
|
|
377
|
+
{ id: 'average', title: this.$t('components.table.calculateAverage'), summary: 'Average', func: (rows, column) => {
|
|
378
|
+
const sum = rows.reduce((acc, row) => acc + (getNumber(row, column.property)), 0);
|
|
379
|
+
return round(sum / rows.length, 3);
|
|
380
|
+
} },
|
|
381
|
+
{ id: 'min', title: this.$t('components.table.calculateMin'), summary: 'Min', func: (rows, column) => Math.min(...rows.map(row => getNumber(row, column.property))) },
|
|
382
|
+
{ id: 'max', title: this.$t('components.table.calculateMax'), summary: 'Max', func: (rows, column) => Math.max(...rows.map(row => getNumber(row, column.property))) },
|
|
383
|
+
{ id: 'countAll', title: this.$t('components.table.calculateCountAll'), summary: 'Count', func: (rows) => rows.length },
|
|
384
|
+
{ id: 'countValues', title: this.$t('components.table.calculateCountValues'), summary: 'Values', func: (rows, column) => rows.filter(row => !!get(row, column.property)).length },
|
|
385
|
+
{ id: 'countUniqueValues', title: this.$t('components.table.calculateCountUniqueValues'), summary: 'Unique', func: (rows, column) => uniq(rows.filter(row => !!get(row, column.property))).length },
|
|
386
|
+
{ id: 'countEmpty', title: this.$t('components.table.calculateCountEmpty'), summary: 'Empty', func: (rows, column) => rows.filter(row => !get(row, column.property)).length },
|
|
387
|
+
{ id: 'countNotEmpty', title: this.$t('components.table.calculateCountNotEmpty'), summary: 'Not Empty', func: (rows, column) => rows.filter(row => !!get(row, column.property)).length },
|
|
388
|
+
{ id: 'percentEmpty', title: this.$t('components.table.calculatePercentEmpty'), summary: 'Empty', func: (rows, column) => {
|
|
389
|
+
const empty = rows.filter(row => !get(row, column.property)).length;
|
|
390
|
+
return round(empty / rows.length * 100, 3) + '%';
|
|
391
|
+
} },
|
|
392
|
+
{ id: 'percentNotEmpty', title: this.$t('components.table.calculatePercentNotEmpty'), summary: 'Not Empty', func: (rows, column) => {
|
|
393
|
+
const notEmpty = rows.filter(row => !!get(row, column.property)).length;
|
|
394
|
+
return round(notEmpty / rows.length * 100, 3) + '%';
|
|
395
|
+
} },
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getCalculateMethodTitle(methodName) {
|
|
400
|
+
return this.calculateMethods.find(method => method.id === methodName)?.summary;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getCalculateMethodValue(methodName, column) {
|
|
404
|
+
const method = this.calculateMethods.find(method => method.id === methodName);
|
|
405
|
+
return method?.func ? method.func(this.rows, column) : '';
|
|
372
406
|
}
|
|
373
407
|
}
|
|
374
408
|
</script>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<div accept-group="items" class="table-view-body-space" v-dropzone="{ payload: 0 }"></div>
|
|
6
6
|
<div class="shadow-area"></div>
|
|
7
7
|
<div class="table-view-header-value reserved sticky">
|
|
8
|
-
<itf-checkbox v-if="visibleHeader" ungrouped value="all" v-model="selectAll" ref="selectAll" />
|
|
8
|
+
<itf-checkbox v-if="visibleHeader && !noSelectAll" ungrouped value="all" v-model="selectAll" ref="selectAll" />
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<template v-for="(column, n) in visibleAttributes">
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
:data-id="column.Id"
|
|
18
18
|
:class="{'sticky': column.pinned, 'last-sticky-column': n === lastPinnedIndex, 'flex-grow-1': column.grow, [`justify-content-${column.align || 'start'}`]: true}"
|
|
19
19
|
class="table-view-header-value"
|
|
20
|
-
:style="`width: ${column.width}px; left: ${column.left}px;`">
|
|
20
|
+
:style="`width: ${column.width}px; max-width: ${column.width}px; left: ${column.left}px;`">
|
|
21
21
|
<!-- Не треба видаляти колонки, бо вони потрібні для збереження ширини -->
|
|
22
22
|
<div v-if="visibleHeader" accept-group="tablecolumns"
|
|
23
23
|
class="table-view-header-space"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
@drop="reorderColumns"
|
|
32
32
|
v-drag-handle
|
|
33
33
|
v-draggable="{ handle: true, payload: { index: n, item: column }, mirror: {yAxis:false} }">
|
|
34
|
-
<itf-dropdown text append-to-body shadow ref="dropdown" class="w-100">
|
|
34
|
+
<itf-dropdown text append-to-body shadow ref="dropdown" class="w-100" :disabled="noColumnMenu">
|
|
35
35
|
<template #button>
|
|
36
36
|
<span :title="column.title[locale] || column.title['en_US']">
|
|
37
37
|
<span v-if="column.icon" :class="column.icon"></span>
|
|
@@ -43,32 +43,32 @@
|
|
|
43
43
|
<div v-if="column.sortable">
|
|
44
44
|
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
45
45
|
<itf-icon name="arrow_up" :size="16" class="me-1" />
|
|
46
|
-
|
|
46
|
+
{{$t('components.table.sortAscending')}}
|
|
47
47
|
</a>
|
|
48
48
|
</div>
|
|
49
49
|
<div v-if="column.sortable">
|
|
50
50
|
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
51
51
|
<itf-icon name="arrow_down" :size="16" class="me-1" />
|
|
52
|
-
|
|
52
|
+
{{$t('components.table.sortAscending')}}
|
|
53
53
|
</a>
|
|
54
54
|
</div>
|
|
55
55
|
<div v-if="column.groupable">
|
|
56
56
|
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
57
57
|
<itf-icon name="episodes" :size="16" class="me-1" />
|
|
58
|
-
|
|
58
|
+
{{$t('components.table.groupBy')}}
|
|
59
59
|
</a>
|
|
60
60
|
</div>
|
|
61
61
|
<div>
|
|
62
62
|
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="togglePinned(n)">
|
|
63
63
|
<itf-icon :name="column.pinned ? 'checkbox_checked' : 'checkbox_empty'" :size="16" class="me-1" />
|
|
64
|
-
<span v-if="column.pinned">
|
|
65
|
-
<span v-else>
|
|
64
|
+
<span v-if="column.pinned">{{$t('components.table.unfreezeColumn')}}</span>
|
|
65
|
+
<span v-else>{{$t('components.table.freezeColumn')}}</span>
|
|
66
66
|
</a>
|
|
67
67
|
</div>
|
|
68
|
-
<div>
|
|
68
|
+
<div v-if="showAddColumn">
|
|
69
69
|
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="hideColumn(n)">
|
|
70
70
|
<itf-icon name="eye_no" :size="16" class="me-1" />
|
|
71
|
-
|
|
71
|
+
{{$t('components.table.hideColumn')}}
|
|
72
72
|
</a>
|
|
73
73
|
</div>
|
|
74
74
|
</itf-dropdown>
|
|
@@ -85,14 +85,14 @@
|
|
|
85
85
|
</div>
|
|
86
86
|
</template>
|
|
87
87
|
<div v-if="showAddColumn" class="table-view-header-value flex-grow-1 justify-content-start">
|
|
88
|
-
<itf-dropdown v-if="visibleHeader" ref="newDd" text append-to-
|
|
88
|
+
<itf-dropdown v-if="visibleHeader" ref="newDd" text append-to-context shadow autoclose="outside" class="table-header table-header-add-column" data-test="table-header-add-column">
|
|
89
89
|
<template #button>
|
|
90
90
|
<span class="nom-layout-three-columns"></span>
|
|
91
91
|
</template>
|
|
92
92
|
|
|
93
93
|
<itf-sortable :value="sortedColumns" @input="onSortColumns">
|
|
94
94
|
<template v-for="(column, k) in sortedColumns">
|
|
95
|
-
<div :key="`column${k}`" class="d-flex align-items-center justify-content-between px-2 py-1">
|
|
95
|
+
<div :key="`column${k}`" class="d-flex align-items-center justify-content-between" :class="{'px-2 py-1': column.visible !== false}">
|
|
96
96
|
<template v-if="column.visible !== false">
|
|
97
97
|
<div class="d-flex justify-content-between flex-grow-1">
|
|
98
98
|
<div>
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
</itf-sortable>
|
|
112
112
|
|
|
113
113
|
<div v-if="invisibleColumns.length">
|
|
114
|
-
<div class="dropdown-header">
|
|
114
|
+
<div class="dropdown-header">{{$t('components.table.addColumn')}}</div>
|
|
115
115
|
<template v-for="(column, k) in invisibleColumns">
|
|
116
116
|
<a href="" @click.prevent="addColumn(column)" :key="`inv-column${k}`" class="dropdown-item d-flex align-items-center justify-content-between px-2 py-1">
|
|
117
117
|
<div>
|
|
@@ -321,6 +321,8 @@ class itfTableHeader extends Vue {
|
|
|
321
321
|
@Prop(Boolean) columnResizing;
|
|
322
322
|
@Prop(Boolean) showAddColumn;
|
|
323
323
|
@Prop(Boolean) visibleHeader;
|
|
324
|
+
@Prop(Boolean) noColumnMenu;
|
|
325
|
+
@Prop(Boolean) noSelectAll;
|
|
324
326
|
|
|
325
327
|
@Watch('selectedIds')
|
|
326
328
|
@Watch('rows')
|
|
@@ -433,6 +435,7 @@ class itfTableHeader extends Vue {
|
|
|
433
435
|
newWidth = Math.max(columnWidth + delta, 100);
|
|
434
436
|
columns.forEach((column) => {
|
|
435
437
|
column.style.width = `${newWidth}px`;
|
|
438
|
+
column.style['max-width'] = `${newWidth}px`;
|
|
436
439
|
});
|
|
437
440
|
};
|
|
438
441
|
const mouseUpHandler = () => {
|
|
@@ -131,7 +131,7 @@ storiesOf('Common', module)
|
|
|
131
131
|
{
|
|
132
132
|
"property": "Description",
|
|
133
133
|
"title": { "en_US": "Description", "uk_UA": "Прізвище" },
|
|
134
|
-
"type": "
|
|
134
|
+
"type": "textarea",
|
|
135
135
|
"editable": true,
|
|
136
136
|
"sortable": false,
|
|
137
137
|
width: 200,
|
|
@@ -324,7 +324,9 @@ storiesOf('Common', module)
|
|
|
324
324
|
@new="onAdd"
|
|
325
325
|
state-name="test"
|
|
326
326
|
:schema="schema"
|
|
327
|
-
:columns.sync="columns2" :rows="list2"
|
|
327
|
+
:columns.sync="columns2" :rows="list2"
|
|
328
|
+
show-summary
|
|
329
|
+
column-sorting column-resizing show-add-column show-grouping @add-column="onAdd">
|
|
328
330
|
<template #format.employee="{ item }">
|
|
329
331
|
{{item.EmployeeId}}
|
|
330
332
|
</template>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="itf-money-field" :class="{'currency-arrow': !currencyDisabled, 'currency-select': currencySelect}">
|
|
3
|
-
<div :style="`--itf-money-field-padding-left: ${selectedCurrencySymbol.length * 0.6 + 1}rem`">
|
|
4
|
-
<span class="itf-money-field__prepend">{{ selectedCurrencySymbol }}</span>
|
|
3
|
+
<div class="input-group h-100" :style="`--itf-money-field-padding-left: ${noCurrencySign ? 1 : selectedCurrencySymbol.length * 0.6 + 1}rem`">
|
|
4
|
+
<span class="itf-money-field__prepend" v-if="!noCurrencySign">{{ selectedCurrencySymbol }}</span>
|
|
5
5
|
<i-mask-component
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
ref="input"
|
|
7
|
+
v-bind="mask"
|
|
8
|
+
class="form-control"
|
|
9
|
+
:class="{ 'is-invalid': isInvalid(), 'is-valid': isSuccess() }"
|
|
10
|
+
@input="setValue"
|
|
11
|
+
:value="maskedValue"
|
|
12
|
+
:unmask="false"
|
|
13
|
+
:disabled="disabled"
|
|
14
14
|
/>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
@@ -93,6 +93,7 @@ class itfMoneyField extends Vue {
|
|
|
93
93
|
|
|
94
94
|
@Model('input', { default: '' }) value;
|
|
95
95
|
@Prop({ type: Object, default: null }) currency;
|
|
96
|
+
@Prop({ type: Boolean, default: false }) noCurrencySign;
|
|
96
97
|
@Prop({ type: Boolean, default: false }) disabled;
|
|
97
98
|
@Prop({ type: Boolean, default: true }) currencySelect;
|
|
98
99
|
@Prop({ type: Array, default: () => ([]) }) currencies;
|
package/src/locales/en.js
CHANGED
|
@@ -96,5 +96,29 @@ module.exports = {
|
|
|
96
96
|
},
|
|
97
97
|
copyToClipboard: {
|
|
98
98
|
copyingToClipboardWasSuccessful: 'Copying to clipboard was successful',
|
|
99
|
+
},
|
|
100
|
+
table: {
|
|
101
|
+
new: 'New',
|
|
102
|
+
noResults: 'No items',
|
|
103
|
+
sortAscending: 'Sort ascending',
|
|
104
|
+
sortDescending: 'Sort descending',
|
|
105
|
+
groupBy: 'Group by',
|
|
106
|
+
hideColumn: 'Hide in view',
|
|
107
|
+
freezeColumn: 'Freeze up to column',
|
|
108
|
+
unfreezeColumn: 'Unfreeze column',
|
|
109
|
+
addColumn: 'Add column',
|
|
110
|
+
calculate: 'Calculate',
|
|
111
|
+
calculateNone: 'None',
|
|
112
|
+
calculateTotal: 'Total',
|
|
113
|
+
calculateCountAll: 'Count all',
|
|
114
|
+
calculateAverage: 'Average',
|
|
115
|
+
calculateMin: 'Min',
|
|
116
|
+
calculateMax: 'Max',
|
|
117
|
+
calculateCountValues: 'Count values',
|
|
118
|
+
calculateCountUniqueValues: 'Count unique values',
|
|
119
|
+
calculateCountEmpty: 'Count empty',
|
|
120
|
+
calculateCountNotEmpty: 'Count not empty',
|
|
121
|
+
calculatePercentEmpty: 'Percent empty',
|
|
122
|
+
calculatePercentNotEmpty: 'Percent not empty',
|
|
99
123
|
}
|
|
100
124
|
};
|
package/src/locales/uk.js
CHANGED
|
@@ -96,5 +96,29 @@ module.exports = {
|
|
|
96
96
|
},
|
|
97
97
|
copyToClipboard: {
|
|
98
98
|
copyingToClipboardWasSuccessful: 'Скопійовано в буфер',
|
|
99
|
+
},
|
|
100
|
+
table: {
|
|
101
|
+
new: 'Додати',
|
|
102
|
+
noResults: 'Немає записів',
|
|
103
|
+
sortAscending: 'Сортувати за зростанням',
|
|
104
|
+
sortDescending: 'Сортувати за спаданням',
|
|
105
|
+
groupBy: 'Групувати',
|
|
106
|
+
hideColumn: 'Приховати колонку',
|
|
107
|
+
freezeColumn: 'Закріпити колонку',
|
|
108
|
+
unfreezeColumn: 'Відкріпити колонку',
|
|
109
|
+
addColumn: 'Додати колонку',
|
|
110
|
+
calculate: 'Обчислення',
|
|
111
|
+
calculateNone: 'Немає',
|
|
112
|
+
calculateTotal: 'Всього',
|
|
113
|
+
calculateCountAll: 'Кількість',
|
|
114
|
+
calculateAverage: 'Середнє',
|
|
115
|
+
calculateMin: 'Мінімум',
|
|
116
|
+
calculateMax: 'Максимум',
|
|
117
|
+
calculateCountValues: 'Кількість значень',
|
|
118
|
+
calculateCountUniqueValues: 'Кількість унікальних значень',
|
|
119
|
+
calculateCountEmpty: 'Кількість порожніх',
|
|
120
|
+
calculateCountNotEmpty: 'Кількість не порожніх',
|
|
121
|
+
calculatePercentEmpty: 'Відсоток порожніх',
|
|
122
|
+
calculatePercentNotEmpty: 'Відсоток не порожніх',
|
|
99
123
|
}
|
|
100
124
|
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
<div class="notion-overlay-container notion-default-overlay-container"
|
|
2
|
-
style="position: fixed; inset: 0px; z-index: 999; pointer-events: none; overflow: hidden;">
|
|
3
|
-
<div style="position: relative; z-index: 0;"></div>
|
|
4
|
-
<div data-overlay="true" style="pointer-events: auto; position: relative; z-index: 0;">
|
|
5
|
-
<div>
|
|
6
|
-
<div style="position: fixed; top: 0px; left: 0px; width: 100vw; height: 100vh;"></div>
|
|
7
|
-
<div style="position: fixed; left: 347px; top: 463.398px; pointer-events: none;">
|
|
8
|
-
<div style="width: 280px; height: 0px;"></div>
|
|
9
|
-
<div style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: flex-start; align-items: flex-start;">
|
|
10
|
-
<div style="position: relative; top: 100%; pointer-events: auto;">
|
|
11
|
-
<div style="display: flex; align-items: center; position: relative; flex-direction: column-reverse; transform-origin: 0% top; left: 0px; top: 0px;">
|
|
12
|
-
<div role="dialog"
|
|
13
|
-
style="border-radius: 6px; background: white; backdrop-filter: none; position: relative; max-width: calc(-24px + 100vw); box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px; overflow: visible; width: 280px; min-height: 34px; max-height: 634px; display: flex; flex-direction: column;">
|
|
14
|
-
<div style="display: flex; flex-direction: column; overflow-y: auto; flex-grow: 1; height: 100%;">
|
|
15
|
-
<div style="padding: 6px 9px; font-size: 14px; min-height: 34px; display: flex; height: 100%; flex-direction: column; justify-content: space-between; flex-grow: 1; font-weight: 500;">
|
|
16
|
-
<div class="notranslate" spellcheck="true" placeholder=" "
|
|
17
|
-
data-content-editable-leaf="true" contenteditable="true"
|
|
18
|
-
style="max-width: 100%; width: 100%; white-space: pre-wrap; word-break: break-word; caret-color: rgb(55, 53, 47);">
|
|
19
|
-
d
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|