@itfin/components 1.3.36 → 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/components/dropdown/Dropdown.vue +13 -1
- package/src/components/dropdown/DropdownMenu.vue +11 -8
- package/src/components/pagination/Pagination.vue +1 -1
- package/src/components/table/Table2.vue +8 -4
- package/src/components/table/TableBody.vue +39 -16
- package/src/components/table/TableGroup.vue +77 -44
- package/src/components/table/TableHeader.vue +15 -12
- package/src/components/table/index.stories.js +3 -1
- package/src/components/text-field/MoneyField.vue +9 -9
- package/src/locales/en.js +24 -1
- package/src/locales/uk.js +24 -1
- package/src/components/button/1.html +0 -30
package/package.json
CHANGED
|
@@ -32,7 +32,7 @@ class itfDropdown extends Vue {
|
|
|
32
32
|
modalId = '';
|
|
33
33
|
|
|
34
34
|
render (createElement, context) {
|
|
35
|
-
const { props, slots, data } = context;
|
|
35
|
+
const { props, slots, data, listeners } = context;
|
|
36
36
|
const modalId = `dropdownId${globalModalIndex++}`;
|
|
37
37
|
const { buttonOptions, toggle, text, disabled, label, appendToBody } = props;
|
|
38
38
|
const { button, default: defaultSlot } = slots();
|
|
@@ -52,6 +52,18 @@ class itfDropdown extends Vue {
|
|
|
52
52
|
{
|
|
53
53
|
ref: data.ref,
|
|
54
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
|
+
}
|
|
55
67
|
},
|
|
56
68
|
defaultSlot
|
|
57
69
|
)
|
|
@@ -52,24 +52,27 @@ class itfDropdownMenu extends Vue {
|
|
|
52
52
|
context.appendChild(this.$el); // should append only to body
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.$emit('open');
|
|
58
|
-
}, 500);
|
|
55
|
+
toggle.addEventListener('shown.bs.dropdown', () => {
|
|
56
|
+
this.$emit('open');
|
|
59
57
|
});
|
|
60
|
-
|
|
58
|
+
toggle.addEventListener('hidden.bs.dropdown', () => {
|
|
61
59
|
this.$emit('close');
|
|
62
60
|
});
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
beforeDestroy() {
|
|
66
|
-
|
|
67
|
-
this.modalEl
|
|
68
|
-
|
|
64
|
+
try {
|
|
65
|
+
if (this.modalEl) {
|
|
66
|
+
this.modalEl.hide();
|
|
67
|
+
this.modalEl.dispose();
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// ignore
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
show() {
|
|
75
|
+
console.info('p[en')
|
|
73
76
|
if (this.modalEl) {
|
|
74
77
|
this.modalEl.show();
|
|
75
78
|
}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
:show-add-column="showAddColumn"
|
|
22
22
|
:show-header="!noHeader"
|
|
23
23
|
:schema="schema"
|
|
24
|
+
:no-column-menu="noColumnMenu"
|
|
25
|
+
:no-select-all="noSelectAll"
|
|
24
26
|
@new="$emit('new', $event)"
|
|
25
27
|
@add-column="$emit('add-column', $event)"
|
|
26
28
|
>
|
|
@@ -41,7 +43,6 @@
|
|
|
41
43
|
.scrollable {
|
|
42
44
|
--itf-table-hover-bg: #f2f2f2;
|
|
43
45
|
--itf-table-min-width: 45px;
|
|
44
|
-
--itf-table-line-height: 35px;
|
|
45
46
|
|
|
46
47
|
body[data-theme="dark"] & {
|
|
47
48
|
--itf-table-hover-bg: #393b41;
|
|
@@ -76,7 +77,6 @@ import itfButton from '../button/Button.vue';
|
|
|
76
77
|
import itfIcon from '../icon/Icon.vue';
|
|
77
78
|
import itfTableGroup from './TableGroup.vue';
|
|
78
79
|
import itfTableHeader from './TableHeader.vue';
|
|
79
|
-
import itfTableBody from "@/components/table/TableBody.vue";
|
|
80
80
|
|
|
81
81
|
export default @Component({
|
|
82
82
|
name: 'itfTable2',
|
|
@@ -84,7 +84,6 @@ export default @Component({
|
|
|
84
84
|
return { tableEl: this }; // do not use Provide from vue-property-decorator
|
|
85
85
|
},
|
|
86
86
|
components: {
|
|
87
|
-
itfTableBody,
|
|
88
87
|
itfCheckboxGroup,
|
|
89
88
|
itfTableHeader,
|
|
90
89
|
itfButton,
|
|
@@ -106,6 +105,8 @@ class itfTable2 extends Vue {
|
|
|
106
105
|
@Prop(Boolean) showGrouping;
|
|
107
106
|
@Prop(Boolean) showSummary;
|
|
108
107
|
@Prop(Boolean) noHeader;
|
|
108
|
+
@Prop(Boolean) noColumnMenu;
|
|
109
|
+
@Prop(Boolean) noSelectAll;
|
|
109
110
|
|
|
110
111
|
state = {
|
|
111
112
|
selectedIds: [],
|
|
@@ -126,7 +127,10 @@ class itfTable2 extends Vue {
|
|
|
126
127
|
for (const column of list) {
|
|
127
128
|
const stateColumn = state.columns.find(i => i.property === column.property);
|
|
128
129
|
if (stateColumn) {
|
|
129
|
-
Object.assign(stateColumn,
|
|
130
|
+
Object.assign(stateColumn, {
|
|
131
|
+
...column,
|
|
132
|
+
width: stateColumn.width
|
|
133
|
+
});
|
|
130
134
|
} else {
|
|
131
135
|
state.columns.push(column);
|
|
132
136
|
}
|
|
@@ -18,10 +18,10 @@
|
|
|
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>
|
|
@@ -30,22 +30,27 @@
|
|
|
30
30
|
<div
|
|
31
31
|
v-if="column.visible !== false"
|
|
32
32
|
:data-column="k"
|
|
33
|
-
:style="`width: ${column.width}px; left: ${column.left}px;`"
|
|
34
|
-
:class="{'sticky': column.pinned, 'last-sticky-column': k === lastPinnedIndex, 'flex-grow-1': column.grow}"
|
|
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
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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)" />
|
|
44
49
|
</slot>
|
|
45
50
|
</template>
|
|
46
51
|
<template v-else>
|
|
47
52
|
<slot :name="`format.${column.type}`" :value="getValue(item, column)" :item="item" :column="column">
|
|
48
|
-
|
|
53
|
+
{{getValue(item, column)}}
|
|
49
54
|
</slot>
|
|
50
55
|
</template>
|
|
51
56
|
</slot>
|
|
@@ -59,6 +64,19 @@
|
|
|
59
64
|
</div>
|
|
60
65
|
</div>
|
|
61
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>
|
|
62
80
|
</div>
|
|
63
81
|
</template>
|
|
64
82
|
<style lang="scss">
|
|
@@ -82,7 +100,6 @@
|
|
|
82
100
|
align-items: stretch;
|
|
83
101
|
display: flex;
|
|
84
102
|
position: relative;
|
|
85
|
-
line-height: var(--itf-table-line-height);
|
|
86
103
|
min-width: var(--itf-table-min-width);
|
|
87
104
|
|
|
88
105
|
&.highlight-drop-column {
|
|
@@ -212,11 +229,13 @@
|
|
|
212
229
|
<script>
|
|
213
230
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
214
231
|
import get from 'lodash/get';
|
|
232
|
+
import set from 'lodash/set';
|
|
215
233
|
// import { RecycleScroller } from 'vue-virtual-scroller'
|
|
216
234
|
import itfCheckbox from '../checkbox/Checkbox.vue';
|
|
217
235
|
import itfTextField from '../text-field/TextField.vue';
|
|
218
236
|
import itfMoneyField from '../text-field/MoneyField.vue';
|
|
219
237
|
import itfTextarea from '../text-field/Textarea.vue';
|
|
238
|
+
import itfHoursField from '../text-field/HoursField.vue';
|
|
220
239
|
// import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
|
221
240
|
|
|
222
241
|
export default @Component({
|
|
@@ -224,6 +243,7 @@ export default @Component({
|
|
|
224
243
|
components: {
|
|
225
244
|
itfCheckbox,
|
|
226
245
|
itfMoneyField,
|
|
246
|
+
itfHoursField,
|
|
227
247
|
itfTextarea,
|
|
228
248
|
itfTextField,
|
|
229
249
|
// RecycleScroller
|
|
@@ -234,6 +254,7 @@ class itfTableBody extends Vue {
|
|
|
234
254
|
@Prop() rows;
|
|
235
255
|
@Prop() idProperty;
|
|
236
256
|
@Prop(Boolean) showAddColumn;
|
|
257
|
+
@Prop(Boolean) noSelectAll;
|
|
237
258
|
|
|
238
259
|
getValue(item, column) {
|
|
239
260
|
return get(item, column.property);
|
|
@@ -247,8 +268,10 @@ class itfTableBody extends Vue {
|
|
|
247
268
|
return this.columns.findIndex((column) => column.lastPinned);
|
|
248
269
|
}
|
|
249
270
|
|
|
250
|
-
updateValue(item, value, index) {
|
|
251
|
-
|
|
271
|
+
updateValue(item, value, index, column) {
|
|
272
|
+
const newItem = { ...item };
|
|
273
|
+
set(newItem, column.property, value);
|
|
274
|
+
this.$emit('update', { index, item, value: newItem });
|
|
252
275
|
}
|
|
253
276
|
}
|
|
254
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)"
|
|
@@ -56,6 +58,7 @@
|
|
|
56
58
|
:id-property="idProperty"
|
|
57
59
|
:rows="rows"
|
|
58
60
|
:columns="visibleColumns"
|
|
61
|
+
:no-select-all="noSelectAll"
|
|
59
62
|
:show-add-column="showAddColumn">
|
|
60
63
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
|
61
64
|
<slot :name="name" v-bind="slotData || {}"/>
|
|
@@ -80,7 +83,7 @@
|
|
|
80
83
|
</div>
|
|
81
84
|
|
|
82
85
|
<!-- Групування -->
|
|
83
|
-
<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}">
|
|
84
87
|
<div class="shadow-area"></div>
|
|
85
88
|
|
|
86
89
|
<div class="table-summary-columns d-flex tw-flex-row align-items-center">
|
|
@@ -88,51 +91,27 @@
|
|
|
88
91
|
v-for="(column, n) in visibleColumns"
|
|
89
92
|
:key="n"
|
|
90
93
|
:data-column="n"
|
|
91
|
-
class="
|
|
92
|
-
:style="`width: ${column.width}px;`">
|
|
93
|
-
<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">
|
|
94
97
|
<template #button>
|
|
95
98
|
<span data-test="summary-column" class="invisible-summary d-flex align-items-center justify-content-end flex-auto">
|
|
96
|
-
<span class="summary-placeholder align-items-center justify-content-center">
|
|
97
|
-
|
|
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')}}
|
|
98
101
|
<itf-icon name="chevron_down" />
|
|
99
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>
|
|
100
107
|
</span>
|
|
101
108
|
</template>
|
|
102
109
|
|
|
103
|
-
<
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
</a>
|
|
109
|
-
</li>
|
|
110
|
-
<li v-if="column.sortable">
|
|
111
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
112
|
-
<itf-icon name="arrow_down" :size="16" class="me-1" />
|
|
113
|
-
Sort By Desc
|
|
114
|
-
</a>
|
|
115
|
-
</li>
|
|
116
|
-
<li v-if="column.groupable">
|
|
117
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;">
|
|
118
|
-
<itf-icon name="episodes" :size="16" class="me-1" />
|
|
119
|
-
Group By
|
|
120
|
-
</a>
|
|
121
|
-
</li>
|
|
122
|
-
<li>
|
|
123
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="togglePinned(n)">
|
|
124
|
-
<itf-icon :name="column.pinned ? 'checkbox_checked' : 'checkbox_empty'" :size="16" class="me-1" />
|
|
125
|
-
<span v-if="column.pinned">Unpin Column</span>
|
|
126
|
-
<span v-else>Pin Column</span>
|
|
127
|
-
</a>
|
|
128
|
-
</li>
|
|
129
|
-
<li>
|
|
130
|
-
<a class="dropdown-item d-flex align-items-center" href="javascript:;" @click="hideColumn(n)">
|
|
131
|
-
<itf-icon name="eye_no" :size="16" class="me-1" />
|
|
132
|
-
Hide
|
|
133
|
-
</a>
|
|
134
|
-
</li>
|
|
135
|
-
</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>
|
|
136
115
|
</itf-dropdown>
|
|
137
116
|
</span>
|
|
138
117
|
</div>
|
|
@@ -290,16 +269,22 @@
|
|
|
290
269
|
opacity: 0.05;
|
|
291
270
|
font-size: .85rem;
|
|
292
271
|
padding-right: .25rem;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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;
|
|
297
279
|
}
|
|
298
280
|
}
|
|
299
281
|
}
|
|
300
282
|
</style>
|
|
301
283
|
<script>
|
|
284
|
+
import get from 'lodash/get';
|
|
302
285
|
import sortBy from 'lodash/sortBy';
|
|
286
|
+
import uniq from "lodash/uniq";
|
|
287
|
+
import round from "lodash/round";
|
|
303
288
|
import {Vue, Component, Prop, Watch} from 'vue-property-decorator';
|
|
304
289
|
import itfDropdown from '../dropdown/Dropdown.vue';
|
|
305
290
|
import itfButton from '../button/Button.vue';
|
|
@@ -308,6 +293,11 @@ import itfTableBody from './TableBody.vue';
|
|
|
308
293
|
import itfTableHeader from './TableHeader.vue';
|
|
309
294
|
import Sticky from "./sticky";
|
|
310
295
|
|
|
296
|
+
function getNumber(item, prop) {
|
|
297
|
+
const num = Number(get(item, prop));
|
|
298
|
+
return Number.isNaN(num) ? 0 : num;
|
|
299
|
+
}
|
|
300
|
+
|
|
311
301
|
export default @Component({
|
|
312
302
|
name: 'itfTableGroup',
|
|
313
303
|
components: {
|
|
@@ -331,10 +321,13 @@ class itfTableGroup extends Vue {
|
|
|
331
321
|
@Prop(Boolean) columnResizing;
|
|
332
322
|
@Prop(Boolean) showAddColumn;
|
|
333
323
|
@Prop(Boolean) showHeader;
|
|
324
|
+
@Prop(Boolean) noColumnMenu;
|
|
325
|
+
@Prop(Boolean) noSelectAll;
|
|
334
326
|
@Prop({type: String, default: function() { return this.$t('components.new'); } }) newLabel;
|
|
335
327
|
@Prop({type: Object, default: () => ({})}) schema;
|
|
336
328
|
|
|
337
329
|
isShowTable = true;
|
|
330
|
+
persistSummary = false;
|
|
338
331
|
|
|
339
332
|
get visibleColumns() {
|
|
340
333
|
let list = this.columns;
|
|
@@ -369,7 +362,47 @@ class itfTableGroup extends Vue {
|
|
|
369
362
|
wrap: true,
|
|
370
363
|
stickyClass: 'sticky',
|
|
371
364
|
});
|
|
372
|
-
|
|
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) : '';
|
|
373
406
|
}
|
|
374
407
|
}
|
|
375
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>
|
|
@@ -92,7 +92,7 @@
|
|
|
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 = () => {
|
|
@@ -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: ${noCurrencySign ?
|
|
3
|
+
<div class="input-group h-100" :style="`--itf-money-field-padding-left: ${noCurrencySign ? 1 : selectedCurrencySymbol.length * 0.6 + 1}rem`">
|
|
4
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
|
|
package/src/locales/en.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* eslint-disable comma-dangle */
|
|
2
2
|
module.exports = {
|
|
3
|
-
new: 'New',
|
|
4
3
|
today: 'Today',
|
|
5
4
|
tomorrow: 'Tomorrow',
|
|
6
5
|
inAWeek: 'In a week',
|
|
@@ -97,5 +96,29 @@ module.exports = {
|
|
|
97
96
|
},
|
|
98
97
|
copyToClipboard: {
|
|
99
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',
|
|
100
123
|
}
|
|
101
124
|
};
|
package/src/locales/uk.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* eslint-disable comma-dangle */
|
|
2
2
|
module.exports = {
|
|
3
|
-
new: 'Додати',
|
|
4
3
|
today: 'Сьогодні',
|
|
5
4
|
tomorrow: 'Завтра',
|
|
6
5
|
inAWeek: 'Через тиждень',
|
|
@@ -97,5 +96,29 @@ module.exports = {
|
|
|
97
96
|
},
|
|
98
97
|
copyToClipboard: {
|
|
99
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: 'Відсоток не порожніх',
|
|
100
123
|
}
|
|
101
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>
|