@itfin/components 1.4.35 → 1.4.36
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/components/app/App.vue +3 -6
- package/src/components/checkbox/Checkbox.vue +1 -1
- package/src/components/checkbox/RadioBox.vue +6 -13
- package/src/components/filter/FilterPanel.vue +37 -16
- package/src/components/panels/PanelItemEdit.vue +6 -8
- package/src/components/panels/PanelList.vue +33 -102
- package/src/components/panels/helpers.ts +11 -29
- package/src/components/table/Table2.vue +65 -61
- package/src/components/table/TableBody.vue +6 -0
- package/src/components/table/TableGroup.vue +14 -4
- package/src/components/table/TableHeader.vue +12 -7
- package/src/components/table/TableRowToggle.vue +9 -1
- package/src/components/table/TableRows.vue +55 -43
- package/src/components/table/table2.scss +15 -25
- package/src/components/view/View.vue +15 -8
package/package.json
CHANGED
|
@@ -74,12 +74,9 @@ class itfApp extends Vue {
|
|
|
74
74
|
try {
|
|
75
75
|
await func();
|
|
76
76
|
} catch (err) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (errFunc) {
|
|
81
|
-
errFunc(err);
|
|
82
|
-
}
|
|
77
|
+
this.showError(err.message);
|
|
78
|
+
if (errFunc) {
|
|
79
|
+
errFunc(err);
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="itf-checkbox form-check" :class="{ 'form-switch': this.switch, 'itf-checkbox__large': large, 'itf-checkbox__medium': medium }">
|
|
4
4
|
<input class="form-check-input" ref="input" :id="id" type="checkbox" name="checkbox" v-model="isChecked" :disabled="isDisabled" />
|
|
5
|
-
<label :for="id" class="form-check-label
|
|
5
|
+
<label :for="id" class="form-check-label">
|
|
6
6
|
<slot name="label">
|
|
7
7
|
{{label}}
|
|
8
8
|
<slot name="icon"></slot>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="itf-radio-box form-check card" :class="{ '
|
|
2
|
+
<div class="itf-radio-box form-check card" :class="{ 'itf-radio__large': large, 'itf-radio__medium': medium, 'active': isChecked, 'right': right, 'left': !right }">
|
|
3
3
|
<input class="form-check-input" :id="id" type="radio" :name="radioName" v-model="isChecked" :value="true" :disabled="disabled" />
|
|
4
4
|
<label :for="id" slot="label" class="form-check-label card-body">
|
|
5
5
|
|
|
@@ -18,17 +18,6 @@
|
|
|
18
18
|
position: relative;
|
|
19
19
|
cursor: pointer;
|
|
20
20
|
|
|
21
|
-
&:not(.disabled) {
|
|
22
|
-
cursor: pointer;
|
|
23
|
-
|
|
24
|
-
.form-check-label {
|
|
25
|
-
cursor: pointer;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.form-check-label {
|
|
30
|
-
cursor: not-allowed;
|
|
31
|
-
}
|
|
32
21
|
&.left {
|
|
33
22
|
padding: 0 0 0 2.5rem;
|
|
34
23
|
|
|
@@ -49,9 +38,13 @@
|
|
|
49
38
|
&.active {
|
|
50
39
|
background-color: rgba(var(--bs-primary-rgb),.1) !important;
|
|
51
40
|
}
|
|
52
|
-
&:hover
|
|
41
|
+
&:hover {
|
|
53
42
|
background-color: rgba(0,0,0,.05);
|
|
54
43
|
}
|
|
44
|
+
|
|
45
|
+
.form-check-label {
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
}
|
|
55
48
|
}
|
|
56
49
|
</style>
|
|
57
50
|
<script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="itf-filter-panel d-flex flex-column
|
|
2
|
+
<div class="itf-filter-panel d-flex flex-column align-items-start" :class="{'gap-3': !filtersOnly}">
|
|
3
3
|
<div v-if="!filtersOnly" class="d-flex gap-2 justify-content-between w-100">
|
|
4
4
|
<slot name="search">
|
|
5
5
|
<div>
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
class="itf-filter-panel__badge"
|
|
36
36
|
:ref="'item-' + n"
|
|
37
37
|
v-model="filter[facet.name]"
|
|
38
|
-
:is-default="filter[facet.name].isDefault"
|
|
39
|
-
:text="filter[facet.name].label"
|
|
38
|
+
:is-default="filter[facet.name] && filter[facet.name].isDefault"
|
|
39
|
+
:text="filter[facet.name] && filter[facet.name].label"
|
|
40
40
|
:type="facet.type"
|
|
41
41
|
:icon="facet.icon"
|
|
42
42
|
:options="facet.options"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
<slot name="after-filters"></slot>
|
|
54
54
|
</div>
|
|
55
|
-
<div v-if="loading">
|
|
55
|
+
<div v-if="loading && !visibleFilters.length">
|
|
56
56
|
<span class="itf-spinner"></span>
|
|
57
57
|
{{$t('loading')}}
|
|
58
58
|
</div>
|
|
@@ -177,6 +177,8 @@ class FilterPanel extends Vue {
|
|
|
177
177
|
(entries) => {
|
|
178
178
|
entries.forEach(entry => {
|
|
179
179
|
const index = parseInt(entry.target.dataset.index);
|
|
180
|
+
const filter = this.filters[index];
|
|
181
|
+
const value = this.filter[filter.name];
|
|
180
182
|
if (entry.isIntersecting) {
|
|
181
183
|
this.visibleItems.add(index); // Додаємо, якщо елемент у полі зору
|
|
182
184
|
} else {
|
|
@@ -206,7 +208,7 @@ class FilterPanel extends Vue {
|
|
|
206
208
|
|
|
207
209
|
get visibleFilters() {
|
|
208
210
|
if (this.mini) {
|
|
209
|
-
return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden)
|
|
211
|
+
return sortBy(this.filters, (f) => this.filter[f.name].isDefault).filter(f => !f.options?.hidden);
|
|
210
212
|
}
|
|
211
213
|
return this.filters.filter(f => !f.options?.hidden);
|
|
212
214
|
}
|
|
@@ -233,15 +235,7 @@ class FilterPanel extends Vue {
|
|
|
233
235
|
|
|
234
236
|
this.filters = this.staticFilters ?? [];
|
|
235
237
|
if (this.endpoint) {
|
|
236
|
-
this.
|
|
237
|
-
await this.$try(async () => {
|
|
238
|
-
const payload = this.panel ? this.panel.getPayload() : {};
|
|
239
|
-
const {filters, tableSchema} = await this.$axios.$get(this.endpoint, { params: payload });
|
|
240
|
-
this.filters = filters;
|
|
241
|
-
this.$emit('set-table-schema', tableSchema);
|
|
242
|
-
this.loadFiltersValue();
|
|
243
|
-
});
|
|
244
|
-
this.loading = false;
|
|
238
|
+
this.loadData();
|
|
245
239
|
} else {
|
|
246
240
|
this.loadFiltersValue();
|
|
247
241
|
}
|
|
@@ -250,6 +244,23 @@ class FilterPanel extends Vue {
|
|
|
250
244
|
}
|
|
251
245
|
}
|
|
252
246
|
|
|
247
|
+
async loadData() {
|
|
248
|
+
this.loading = true;
|
|
249
|
+
await this.$try(async () => {
|
|
250
|
+
const payload = this.panel ? this.panel.getPayload() : {};
|
|
251
|
+
const {filters, tableSchema} = await this.$axios.$get(this.endpoint, {
|
|
252
|
+
preventRaceCondition: true,
|
|
253
|
+
params: payload
|
|
254
|
+
});
|
|
255
|
+
this.filters = filters;
|
|
256
|
+
this.loading = false;
|
|
257
|
+
this.$emit('set-table-schema', tableSchema);
|
|
258
|
+
this.loadFiltersValue();
|
|
259
|
+
}, () => {
|
|
260
|
+
this.loading = false;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
253
264
|
toggleFilters() {
|
|
254
265
|
this.showFilters = !this.showFilters;
|
|
255
266
|
if (this.stateName) {
|
|
@@ -269,7 +280,7 @@ class FilterPanel extends Vue {
|
|
|
269
280
|
filterValue.to = payload.to;
|
|
270
281
|
} else {
|
|
271
282
|
filter[item.name] = payload[item.name] ? this.formatValue(item, { value: payload[item.name] }) : { isDefault: true, ...item.options.defaultValue };
|
|
272
|
-
filterValue[item.name] = payload[item.name]
|
|
283
|
+
filterValue[item.name] = payload[item.name];
|
|
273
284
|
}
|
|
274
285
|
}
|
|
275
286
|
}
|
|
@@ -282,6 +293,7 @@ class FilterPanel extends Vue {
|
|
|
282
293
|
this.filter = filter;
|
|
283
294
|
this.filterValue = filterValue;
|
|
284
295
|
this.$emit('input', this.filterValue);
|
|
296
|
+
this.$emit('loaded', this.filterValue);
|
|
285
297
|
this.initObserver();
|
|
286
298
|
}
|
|
287
299
|
}
|
|
@@ -318,6 +330,7 @@ class FilterPanel extends Vue {
|
|
|
318
330
|
this.panel.setPayload({ ...payload, ...this.filterValue });
|
|
319
331
|
}
|
|
320
332
|
this.$emit('input', this.filterValue);
|
|
333
|
+
this.$emit('change', this.filterValue);
|
|
321
334
|
}
|
|
322
335
|
|
|
323
336
|
get daysList() {
|
|
@@ -363,7 +376,11 @@ class FilterPanel extends Vue {
|
|
|
363
376
|
}
|
|
364
377
|
} else if (facet.type === 'date') {
|
|
365
378
|
const date = DateTime.fromISO(value.value);
|
|
366
|
-
value.label = (date.isValid ?
|
|
379
|
+
value.label = (date.isValid ? date : DateTime.fromISO(facet.options.defaultValue.value ?? DateTime.now().toISO())).toFormat('dd MMM yyyy');
|
|
380
|
+
value.isDefault = facet.options.defaultValue ? value.value === facet.options.defaultValue.value : false;
|
|
381
|
+
} else if (facet.type === 'month') {
|
|
382
|
+
const date = DateTime.fromISO(value.value);
|
|
383
|
+
value.label = capitalizeFirstLetter((date.isValid ? date : DateTime.fromISO(facet.options.defaultValue.value)).toFormat('LLLL yyyy'));
|
|
367
384
|
value.isDefault = facet.options.defaultValue ? value.value === facet.options.defaultValue.value : false;
|
|
368
385
|
} else if (facet.type === 'facets-list') {
|
|
369
386
|
const firstItem = facet.options.items.find(item => item.value === (Array.isArray(value.value) ? value.value[0] : value.value));
|
|
@@ -402,6 +419,10 @@ class FilterPanel extends Vue {
|
|
|
402
419
|
}
|
|
403
420
|
value.hidden = facet.options?.hidden ?? false;
|
|
404
421
|
return value;
|
|
422
|
+
|
|
423
|
+
function capitalizeFirstLetter(string) {
|
|
424
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
425
|
+
}
|
|
405
426
|
}
|
|
406
427
|
}
|
|
407
428
|
</script>
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-loading="loading" class="px-3 pt-2 h-100
|
|
2
|
+
<div v-loading="loading" class="px-3 pt-2 h-100">
|
|
3
3
|
<itf-form
|
|
4
4
|
ref="editForm"
|
|
5
|
-
class="d-flex flex-column justify-content-between
|
|
5
|
+
class="d-flex flex-column justify-content-between h-100"
|
|
6
6
|
@keydown.native.shift.enter.stop.prevent="onSaveClick"
|
|
7
7
|
@keydown.native.esc.stop.prevent="$emit('cancel')"
|
|
8
8
|
>
|
|
9
9
|
<slot></slot>
|
|
10
10
|
<div class="py-3 justify-content-end d-flex align-items-center sticky-container">
|
|
11
11
|
<div v-if="!hideFooter">
|
|
12
|
-
<itf-button v-tooltip.delay="'Hot key: Esc'" secondary
|
|
13
|
-
<span>{{
|
|
12
|
+
<itf-button v-tooltip.delay="'Hot key: Esc'" secondary :loading="loading" :disabled="loading" @click="$emit('cancel')">
|
|
13
|
+
<span>{{ $t('components.modal.cancel') }}</span>
|
|
14
14
|
</itf-button>
|
|
15
|
-
<itf-button v-tooltip.delay="'Hot key: Shift + Enter'" primary
|
|
16
|
-
<span>{{
|
|
15
|
+
<itf-button v-tooltip.delay="'Hot key: Shift + Enter'" primary :loading="loading" :disabled="loading" @click="onSaveClick">
|
|
16
|
+
<span>{{ $t('components.modal.save') }}</span>
|
|
17
17
|
</itf-button>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
@@ -51,8 +51,6 @@ import itfButton from '../button/Button.vue';
|
|
|
51
51
|
export default class PanelItemEdit extends Vue {
|
|
52
52
|
@Prop(Boolean) loading;
|
|
53
53
|
@Prop(Boolean) hideFooter;
|
|
54
|
-
@Prop({ type: String, default: function() { return this.$t('components.modal.save') } }) saveBtnText;
|
|
55
|
-
@Prop({ type: String, default: function() { return this.$t('components.modal.cancel') } }) cancelBtnText;
|
|
56
54
|
|
|
57
55
|
onSaveClick() {
|
|
58
56
|
if (this.$refs.editForm && !this.$refs.editForm.doValidation()) {
|
|
@@ -17,20 +17,14 @@
|
|
|
17
17
|
:icon="panel.icon"
|
|
18
18
|
:payload="panel.payload"
|
|
19
19
|
:expandable="panelsStack.length > 1"
|
|
20
|
-
:isFullSize="isFullSize"
|
|
21
20
|
:collapsed="panel.isCollapsed"
|
|
22
21
|
:closeable="panel.isCloseable"
|
|
23
22
|
:animate="panel.isAnimate"
|
|
24
23
|
@open="openPanel($event[0], $event[1], n + 1)"
|
|
25
24
|
@expand="expandPanel(panel)"
|
|
26
25
|
@fullsize="fullsizePanel(panel)"
|
|
27
|
-
@collapse="collapsePanel(panel)"
|
|
28
26
|
@close="closePanel(panel)"
|
|
29
|
-
@open-menu="$emit('open-menu', panel.type, panel.payload)"
|
|
30
27
|
>
|
|
31
|
-
<template #before-header>
|
|
32
|
-
<slot name="before-header" :panel="panel" :index="n" :payload="panel.payload"></slot>
|
|
33
|
-
</template>
|
|
34
28
|
<slot
|
|
35
29
|
:name="panel.type"
|
|
36
30
|
:panel="panel"
|
|
@@ -40,9 +34,9 @@
|
|
|
40
34
|
:close="() => closePanel(panel)"
|
|
41
35
|
:expand="() => expandPanel(panel)"
|
|
42
36
|
:fullsize="() => fullsizePanel(panel)">
|
|
43
|
-
<component
|
|
37
|
+
<component :is="panels[panel.type].default || panels[panel.type]" :panel="panel" :payload="panel.payload" />
|
|
44
38
|
</slot>
|
|
45
|
-
<template v-if="$scopedSlots[`${panel.type}.title`] || panel.
|
|
39
|
+
<template v-if="$scopedSlots[`${panel.type}.title`] || panels[panel.type].title" #title>
|
|
46
40
|
<slot
|
|
47
41
|
:name="`${panel.type}.title`"
|
|
48
42
|
:panel="panel"
|
|
@@ -52,10 +46,10 @@
|
|
|
52
46
|
:close="() => closePanel(panel)"
|
|
53
47
|
:expand="() => expandPanel(panel)"
|
|
54
48
|
:fullsize="() => fullsizePanel(panel)">
|
|
55
|
-
<component v-if="panel.
|
|
49
|
+
<component v-if="panels[panel.type].title" :is="panels[panel.type].title" :panel="panel" :payload="panel.payload" />
|
|
56
50
|
</slot>
|
|
57
51
|
</template>
|
|
58
|
-
<template v-if="$scopedSlots[`${panel.type}.buttons`] || panel.
|
|
52
|
+
<template v-if="$scopedSlots[`${panel.type}.buttons`] || panels[panel.type].buttons" #buttons>
|
|
59
53
|
<slot
|
|
60
54
|
:name="`${panel.type}.buttons`"
|
|
61
55
|
:panel="panel"
|
|
@@ -65,10 +59,10 @@
|
|
|
65
59
|
:close="() => closePanel(panel)"
|
|
66
60
|
:expand="() => expandPanel(panel)"
|
|
67
61
|
:fullsize="() => fullsizePanel(panel)">
|
|
68
|
-
<component v-if="panel.
|
|
62
|
+
<component v-if="panels[panel.type].buttons" :is="panels[panel.type].buttons" :panel="panel" :payload="panel.payload" />
|
|
69
63
|
</slot>
|
|
70
64
|
</template>
|
|
71
|
-
<template v-if="$scopedSlots[`${panel.type}.header`] || panel.
|
|
65
|
+
<template v-if="$scopedSlots[`${panel.type}.header`] || panels[panel.type].header" #header>
|
|
72
66
|
<slot
|
|
73
67
|
:name="`${panel.type}.header`"
|
|
74
68
|
:panel="panel"
|
|
@@ -78,7 +72,7 @@
|
|
|
78
72
|
:close="() => closePanel(panel)"
|
|
79
73
|
:expand="() => expandPanel(panel)"
|
|
80
74
|
:fullsize="() => fullsizePanel(panel)">
|
|
81
|
-
<component v-if="panel.
|
|
75
|
+
<component v-if="panels[panel.type].header" :is="panels[panel.type].header" :panel="panel" :payload="panel.payload" />
|
|
82
76
|
</slot>
|
|
83
77
|
</template>
|
|
84
78
|
</panel>
|
|
@@ -154,6 +148,7 @@ $double-an-time: $an-time * 2;
|
|
|
154
148
|
//transition: opacity $an-time linear;
|
|
155
149
|
}
|
|
156
150
|
}
|
|
151
|
+
|
|
157
152
|
//.slide-enter-active > div {
|
|
158
153
|
// opacity: 0;
|
|
159
154
|
//}
|
|
@@ -164,10 +159,8 @@ $double-an-time: $an-time * 2;
|
|
|
164
159
|
</style>
|
|
165
160
|
<script lang="ts">
|
|
166
161
|
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
167
|
-
import
|
|
168
|
-
import
|
|
169
|
-
import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
|
|
170
|
-
import {emitGlobalEvent, setRootPanelList} from "@itfin/components/src/components/panels";
|
|
162
|
+
import Panel from './Panel';
|
|
163
|
+
import {hashToStack, stackToHash} from "./helpers";
|
|
171
164
|
|
|
172
165
|
interface VisualOptions {
|
|
173
166
|
title: string;
|
|
@@ -202,7 +195,6 @@ export interface IPanel {
|
|
|
202
195
|
|
|
203
196
|
@Component({
|
|
204
197
|
components: {
|
|
205
|
-
itfIcon,
|
|
206
198
|
Panel
|
|
207
199
|
},
|
|
208
200
|
directives: {
|
|
@@ -216,15 +208,12 @@ export interface IPanel {
|
|
|
216
208
|
export default class PanelList extends Vue {
|
|
217
209
|
@Prop() firstPanel: IPanel;
|
|
218
210
|
@Prop() panels: Record<string, Component>;
|
|
219
|
-
@Prop({ default: () => {} }) searchPanel: (type: string) => boolean;
|
|
220
|
-
@Prop({ type: String, default: 'path' }) routeType: string;
|
|
221
211
|
|
|
222
212
|
panelsStack:IPanel[] = [];
|
|
223
213
|
|
|
224
214
|
nextId:number = 0;
|
|
225
215
|
|
|
226
216
|
created() {
|
|
227
|
-
setRootPanelList(this);
|
|
228
217
|
if (this.firstPanel) {
|
|
229
218
|
this.internalOpenPanel(this.firstPanel.type, this.firstPanel.payload);
|
|
230
219
|
}
|
|
@@ -278,30 +267,18 @@ export default class PanelList extends Vue {
|
|
|
278
267
|
this.panelsStack = newStack;
|
|
279
268
|
}
|
|
280
269
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
panel = await this.searchPanel(type, this.panels);
|
|
285
|
-
if (!panel) {
|
|
286
|
-
console.error(`Panel type "${type}" not found`);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
panel.type = type;
|
|
270
|
+
internalOpenPanel(type: string, payload: any = {}, openIndex?: number, noEvents = false) {
|
|
271
|
+
if (!this.panels[type]) {
|
|
272
|
+
return;
|
|
290
273
|
}
|
|
291
|
-
if (typeof
|
|
274
|
+
if (typeof this.panels[type].caption !== 'function') {
|
|
292
275
|
throw new Error('Panel component must have a "caption" function');
|
|
293
276
|
}
|
|
294
277
|
const newPanel:any = {
|
|
295
278
|
id: this.nextId++,
|
|
296
|
-
nocard:
|
|
297
|
-
title:
|
|
298
|
-
icon:
|
|
299
|
-
components: {
|
|
300
|
-
default: panel.default ?? undefined,
|
|
301
|
-
buttons: panel.buttons ?? undefined,
|
|
302
|
-
header: panel.header ?? undefined,
|
|
303
|
-
title: panel.title ?? undefined,
|
|
304
|
-
},
|
|
279
|
+
nocard: this.panels[type].nocard,
|
|
280
|
+
title: this.panels[type].caption(this.$t.bind(this), payload),
|
|
281
|
+
icon: this.panels[type].icon ? this.panels[type].icon(this.$t.bind(this), payload) : null,
|
|
305
282
|
type,
|
|
306
283
|
payload,
|
|
307
284
|
isCollapsed: false,
|
|
@@ -312,7 +289,7 @@ export default class PanelList extends Vue {
|
|
|
312
289
|
newPanel.isCloseable = false;
|
|
313
290
|
}
|
|
314
291
|
let newStack = [...this.panelsStack];
|
|
315
|
-
if (
|
|
292
|
+
if (this.panels[type].permanentExpanded && newStack.length) {
|
|
316
293
|
for (const panel of newStack) {
|
|
317
294
|
panel.isCollapsed = true;
|
|
318
295
|
}
|
|
@@ -322,32 +299,25 @@ export default class PanelList extends Vue {
|
|
|
322
299
|
isAnimation = newStack.length === openIndex;
|
|
323
300
|
newStack = newStack.slice(0, openIndex);
|
|
324
301
|
}
|
|
325
|
-
if (newStack.length > 0 && !newStack.find(p => !p.isCollapsed)) {
|
|
326
|
-
// якщо немає відкритих панелей, то перша панель має бути розгорнута
|
|
327
|
-
newStack[0].isCollapsed = false;
|
|
328
|
-
}
|
|
329
302
|
this.panelsStack = newStack;
|
|
330
303
|
return new Promise(res => {
|
|
331
304
|
this.$nextTick(() => { // щоб панелі змінювались при редагуванні
|
|
332
305
|
const n = newStack.length;
|
|
333
306
|
newPanel.isAnimate = isAnimation;
|
|
334
|
-
newPanel.permanentExpanded = !!
|
|
307
|
+
newPanel.permanentExpanded = !!this.panels[type].permanentExpanded;
|
|
335
308
|
newPanel.emit = (event, ...args) => this.emitEvent(event, ...args);
|
|
336
|
-
newPanel.open = (type, payload
|
|
309
|
+
newPanel.open = (type, payload) => this.openPanel(type, payload, n + 1);
|
|
337
310
|
newPanel.close = () => this.closePanel(newPanel);
|
|
338
311
|
newPanel.expand = () => this.expandPanel(newPanel);
|
|
339
312
|
newPanel.getTitle = () => newPanel.title;
|
|
340
313
|
newPanel.getIcon = () => newPanel.icon;
|
|
341
|
-
newPanel.setTitle = (title: string) => { newPanel.title = title;
|
|
314
|
+
newPanel.setTitle = (title: string) => { newPanel.title = title; };
|
|
342
315
|
newPanel.setIcon = (icon: string) => { newPanel.icon = icon; };
|
|
343
|
-
newPanel.on = (eventName
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (!newPanel.__events[evName]) {
|
|
347
|
-
newPanel.__events[evName] = [];
|
|
348
|
-
}
|
|
349
|
-
newPanel.__events[evName].push(func);
|
|
316
|
+
newPanel.on = (eventName, func: (event: string, ...args: any[]) => any) => {
|
|
317
|
+
if (!newPanel.__events[eventName]) {
|
|
318
|
+
newPanel.__events[eventName] = [];
|
|
350
319
|
}
|
|
320
|
+
newPanel.__events[eventName].push(func);
|
|
351
321
|
};
|
|
352
322
|
newPanel.off = (eventName, func: (event: string, ...args: any[]) => any) => {
|
|
353
323
|
if (newPanel.__events[eventName]) {
|
|
@@ -367,7 +337,9 @@ export default class PanelList extends Vue {
|
|
|
367
337
|
newPanel.getPayload = () => newPanel.payload;
|
|
368
338
|
newPanel.setPayload = (value: any) => {
|
|
369
339
|
newPanel.payload = value;
|
|
370
|
-
this.
|
|
340
|
+
newPanel.title = this.panels[type].caption(this.$t.bind(this), value);
|
|
341
|
+
newPanel.icon = this.panels[type].icon ? this.panels[type].icon(this.$t.bind(this), payload) : null,
|
|
342
|
+
this.setPanelHash()
|
|
371
343
|
}
|
|
372
344
|
newStack.push(newPanel);
|
|
373
345
|
this.panelsStack = newStack;
|
|
@@ -381,19 +353,12 @@ export default class PanelList extends Vue {
|
|
|
381
353
|
});
|
|
382
354
|
}
|
|
383
355
|
|
|
384
|
-
updateTitle() {
|
|
385
|
-
const titles = this.panelsStack.map(p => p.getTitle()).filter(Boolean).reverse();
|
|
386
|
-
this.$root.$options.head.titleChunk = titles.join(' / ');
|
|
387
|
-
this.$meta().refresh();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
356
|
async openPanel(type: string, payload: any, openIndex?: number) {
|
|
391
357
|
await this.internalOpenPanel(type, payload, openIndex);
|
|
392
|
-
this.setPanelHash()
|
|
358
|
+
this.setPanelHash()
|
|
393
359
|
}
|
|
394
360
|
|
|
395
361
|
emitEvent(event: string, ...args: any[]) {
|
|
396
|
-
emitGlobalEvent(event, ...args);
|
|
397
362
|
for (const panel of this.panelsStack) {
|
|
398
363
|
if (panel.__events[event]) {
|
|
399
364
|
for (const func of panel.__events[event]) {
|
|
@@ -423,40 +388,12 @@ export default class PanelList extends Vue {
|
|
|
423
388
|
fullsizePanel(panel: IPanel) {
|
|
424
389
|
const newStack = [...this.panelsStack];
|
|
425
390
|
for (const p of newStack) {
|
|
426
|
-
p.isLastOpened = !p.isCollapsed && p !== panel;
|
|
427
391
|
p.isCollapsed = p !== panel;
|
|
428
392
|
}
|
|
429
393
|
this.panelsStack = newStack;
|
|
430
394
|
this.setPanelHash();
|
|
431
395
|
}
|
|
432
396
|
|
|
433
|
-
get isFullSize() {
|
|
434
|
-
return this.panelsStack.filter(p => !p.isCollapsed).length === 1;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
expandPanel(panel: IPanel) {
|
|
438
|
-
const newStack = [...this.panelsStack];
|
|
439
|
-
const index = newStack.findIndex(p => p.id === panel.id);
|
|
440
|
-
newStack[index].isCollapsed = false;
|
|
441
|
-
this.panelsStack = newStack;
|
|
442
|
-
this.ensureOnlyTwoOpenPanels(panel.id);
|
|
443
|
-
this.setPanelHash();
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
collapsePanel(panel: IPanel) {
|
|
447
|
-
const newStack = [...this.panelsStack];
|
|
448
|
-
const currenctIndex = newStack.findIndex(p => p.id === panel.id);
|
|
449
|
-
const lastOpenedIndex = newStack.findIndex(p => p.isLastOpened);
|
|
450
|
-
if (lastOpenedIndex !== -1) { // якщо зебрежена остання відкрита панель
|
|
451
|
-
newStack[lastOpenedIndex].isCollapsed = false
|
|
452
|
-
} else if (newStack[currenctIndex-1]) { // якщо після оновлення сторінки відсутнє значення "остання відкрита", то відкриваємо ту, що зліва
|
|
453
|
-
newStack[currenctIndex-1].isCollapsed = false;
|
|
454
|
-
}
|
|
455
|
-
this.panelsStack = newStack;
|
|
456
|
-
this.ensureOnlyTwoOpenPanels(panel.id);
|
|
457
|
-
this.setPanelHash();
|
|
458
|
-
}
|
|
459
|
-
|
|
460
397
|
getPanels(type) {
|
|
461
398
|
return this.panelsStack.filter(panel => panel.type === type);
|
|
462
399
|
}
|
|
@@ -466,19 +403,14 @@ export default class PanelList extends Vue {
|
|
|
466
403
|
}
|
|
467
404
|
|
|
468
405
|
setPanelHash() {
|
|
469
|
-
const hash = stackToHash(this.panelsStack
|
|
470
|
-
|
|
471
|
-
this.$router.push({ path: `/${hash}` });
|
|
472
|
-
} else {
|
|
473
|
-
this.$router.push({ hash });
|
|
474
|
-
}
|
|
475
|
-
this.updateTitle();
|
|
406
|
+
const hash = stackToHash(this.panelsStack).replace(/^#/, '');
|
|
407
|
+
this.$router.push({ hash });
|
|
476
408
|
}
|
|
477
409
|
|
|
478
410
|
async parsePanelHash() {
|
|
479
|
-
const hash =
|
|
411
|
+
const {hash} = location;
|
|
480
412
|
if (hash) {
|
|
481
|
-
const panels = hashToStack(hash
|
|
413
|
+
const panels = hashToStack(hash);
|
|
482
414
|
const newStack = [];
|
|
483
415
|
this.panelsStack = [];
|
|
484
416
|
for (const panelIndex in panels) {
|
|
@@ -499,7 +431,6 @@ export default class PanelList extends Vue {
|
|
|
499
431
|
}
|
|
500
432
|
this.panelsStack = newStack;
|
|
501
433
|
this.emitEvent('panels.changed', this.panelsStack);
|
|
502
|
-
this.updateTitle();
|
|
503
434
|
}
|
|
504
435
|
}
|
|
505
436
|
|
|
@@ -1,51 +1,33 @@
|
|
|
1
|
-
import JSON5 from 'json5'
|
|
2
|
-
import {isPathType} from "./index";
|
|
3
|
-
|
|
4
1
|
export interface IPanel {
|
|
5
2
|
type: string;
|
|
6
3
|
payload?: any;
|
|
7
4
|
isCollapsed?: boolean;
|
|
8
5
|
}
|
|
9
6
|
|
|
10
|
-
const COLLAPSE_SYMBOL = '~'
|
|
11
|
-
const PARAMS_SYMBOL = ';'
|
|
12
|
-
|
|
13
7
|
export function stackToHash(stack: IPanel[]) {
|
|
14
8
|
const hash = stack.map(panel => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}).join(isPathType() ? '/' : '&');
|
|
19
|
-
return isPathType() ? `/${hash}` : `#${hash}`;
|
|
9
|
+
return `${panel.type}${panel.isCollapsed ? '' : '!'}=${JSON.stringify(panel.payload || {})}`;
|
|
10
|
+
}).join('&');
|
|
11
|
+
return `#${hash}`;
|
|
20
12
|
}
|
|
21
13
|
|
|
22
14
|
|
|
23
15
|
export function hashToStack(hash: string|undefined): IPanel[] {
|
|
24
16
|
let stack:IPanel[] = [];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const [type, payload] = item.split(PARAMS_SYMBOL);
|
|
32
|
-
const isCollapsed = type.includes(COLLAPSE_SYMBOL);
|
|
17
|
+
if (hash) {
|
|
18
|
+
const str = hash.replace(/^#/, '');
|
|
19
|
+
|
|
20
|
+
stack = str.split('&').map(item => {
|
|
21
|
+
const [type, payload] = item.split('=');
|
|
22
|
+
const isCollapsed = !type.includes('!');
|
|
33
23
|
let payloadObj:any = {};
|
|
34
24
|
try {
|
|
35
|
-
|
|
36
|
-
if (!json.startsWith('{')) {
|
|
37
|
-
json = `{${json}`; // Ensure it starts with a '{' to be valid JSON
|
|
38
|
-
}
|
|
39
|
-
if (!json.endsWith('}')) {
|
|
40
|
-
json += '}'; // Ensure it ends with a '}' to be valid JSON
|
|
41
|
-
}
|
|
42
|
-
payloadObj = JSON5.parse(json);
|
|
25
|
+
payloadObj = JSON.parse(decodeURIComponent(payload));
|
|
43
26
|
} catch (e) {
|
|
44
27
|
// ignore
|
|
45
|
-
console.warn(`Error parsing payload for type ${type}:`, payload, e);
|
|
46
28
|
}
|
|
47
29
|
return {
|
|
48
|
-
type: type.replace(
|
|
30
|
+
type: type.replace('!', ''),
|
|
49
31
|
isCollapsed,
|
|
50
32
|
payload: payloadObj
|
|
51
33
|
};
|
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
'permanent-checkboxes': selectedIds.length
|
|
8
8
|
}" :style="{ '--indicator-area-width': `${indicatorType === 'none' ? 1 : indicatorWidth}px`, '--shadow-area-width': `${shadowWidth}px` }">
|
|
9
9
|
<itf-notice-popout :visible="showGroupOperations" class="rounded-3 bg-black text-white">
|
|
10
|
-
<div class="d-flex gap-2 ps-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
<div class="opacity-50">•</div>
|
|
14
|
-
</slot>
|
|
10
|
+
<div class="d-flex gap-2 ps-3 align-items-center small itf-table2_mass-operations">
|
|
11
|
+
<div>{{$tc('components.table.selectedItems', selectedIds.length, { n: selectedIds.length })}}</div>
|
|
12
|
+
<div class="opacity-50">•</div>
|
|
15
13
|
<a href="" class="me-3 opacity-50 text-white text-decoration-none" @click.stop.prevent="selectedIds = []">{{$t('components.table.cancelSelected')}}</a>
|
|
16
14
|
<div>
|
|
17
15
|
<slot name="group-operations"></slot>
|
|
@@ -19,61 +17,64 @@
|
|
|
19
17
|
</div>
|
|
20
18
|
</itf-notice-popout>
|
|
21
19
|
<div class="scrollable scrollable-x">
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
20
|
+
<itf-checkbox-group v-model="selectedIds">
|
|
21
|
+
<template v-for="(group, index) in groups">
|
|
22
|
+
<div class="table-view-body">
|
|
23
|
+
<itf-table-group
|
|
24
|
+
:key="index"
|
|
25
|
+
@update="$emit('update', { ...$event, group, groupIndex: index })"
|
|
26
|
+
@row-click="$emit('row-click', $event)"
|
|
27
|
+
:id-property="idProperty"
|
|
28
|
+
:columns="columns"
|
|
29
|
+
@update:columns="onColumnsUpdate"
|
|
30
|
+
:rows="group.rows"
|
|
31
|
+
:title="group.name"
|
|
32
|
+
:selected-ids.sync="selectedIds"
|
|
33
|
+
:add-new-rows="addNewRows"
|
|
34
|
+
:shadow-width="shadowWidth"
|
|
35
|
+
:column-sorting="columnSorting"
|
|
36
|
+
:column-resizing="columnResizing"
|
|
37
|
+
:show-grouping="showGrouping"
|
|
38
|
+
:show-summary="showSummary"
|
|
39
|
+
:show-add-column="showAddColumn"
|
|
40
|
+
:show-actions="showActions"
|
|
41
|
+
:show-header="!noHeader"
|
|
42
|
+
:schema="schema"
|
|
43
|
+
:editable="editable"
|
|
44
|
+
:no-column-menu="noColumnMenu"
|
|
45
|
+
:no-select-all="noSelectAll"
|
|
46
|
+
:currencies="currencies"
|
|
47
|
+
:currency="currency"
|
|
48
|
+
:subrows-property="subrowsProperty"
|
|
49
|
+
:async-subrows-property="asyncSubrowsProperty"
|
|
50
|
+
:divider-property="dividerProperty"
|
|
51
|
+
:indicator-type="indicatorType"
|
|
52
|
+
:expanded-all="expandedAll"
|
|
53
|
+
:indicatorWidth="indicatorWidth"
|
|
54
|
+
:striped="striped"
|
|
55
|
+
:expanded-ids="expandedIds"
|
|
56
|
+
:css-property="cssProperty"
|
|
57
|
+
:sticky-header="stickyHeader"
|
|
58
|
+
:editable-property="editableProperty"
|
|
59
|
+
:sorting.sync="_sorting"
|
|
60
|
+
:sort-as-string="sortAsString"
|
|
61
|
+
:active="active"
|
|
62
|
+
@loadChildren="$emit('loadChildren', $event)"
|
|
63
|
+
@update:expanded-ids="$emit('update:expanded-ids', $event)"
|
|
64
|
+
@new="$emit('new', $event)"
|
|
65
|
+
@filter="$emit('filter', $event)"
|
|
66
|
+
@add-column="$emit('add-column', $event)"
|
|
67
|
+
>
|
|
68
|
+
<template v-for="(_, name) in $slots" #[name]="slotData">
|
|
69
|
+
<slot :name="name" v-bind="slotData || {}" />
|
|
70
|
+
</template>
|
|
71
|
+
<template v-for="(_, name) in $scopedSlots" #[name]="slotData">
|
|
72
|
+
<slot :name="name" v-bind="slotData || {}" />
|
|
73
|
+
</template>
|
|
74
|
+
</itf-table-group>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
</itf-checkbox-group>
|
|
77
78
|
</div>
|
|
78
79
|
</div>
|
|
79
80
|
|
|
@@ -104,11 +105,13 @@ export default @Component({
|
|
|
104
105
|
})
|
|
105
106
|
class itfTable2 extends Vue {
|
|
106
107
|
// @Prop({ required: true, type: Array }) columns;
|
|
108
|
+
@Prop(Boolean) sortAsString;
|
|
107
109
|
@Prop({ required: true, type: Array }) rows;
|
|
108
110
|
@Prop({ type: String, default: null }) groupBy;
|
|
109
111
|
@Prop({ type: String, default: null }) idProperty;
|
|
110
112
|
@Prop({ type: String, default: null }) cssProperty;
|
|
111
113
|
@Prop({ type: String, default: null }) subrowsProperty;
|
|
114
|
+
@Prop({ type: String, default: null }) asyncSubrowsProperty;
|
|
112
115
|
@Prop({ type: String, default: null }) dividerProperty;
|
|
113
116
|
@Prop({ type: String, default: null }) editableProperty;
|
|
114
117
|
@Prop({ default: null }) active;
|
|
@@ -244,7 +247,8 @@ class itfTable2 extends Vue {
|
|
|
244
247
|
@Watch('selectedIds')
|
|
245
248
|
onSelectedIdsUpdate(selectedIds) {
|
|
246
249
|
this.state.selectedIds = selectedIds;
|
|
247
|
-
|
|
250
|
+
// метод saveTableState не зберігає selectedIds в localStorage, не впевнений що він тут треба
|
|
251
|
+
// this.saveTableState();
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
onColumnsUpdate(columns) {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
:columns="columns"
|
|
8
8
|
:id-property="idProperty"
|
|
9
9
|
:subrows-property="subrowsProperty"
|
|
10
|
+
:async-subrows-property="asyncSubrowsProperty"
|
|
10
11
|
:divider-property="dividerProperty"
|
|
11
12
|
:show-add-column="showAddColumn"
|
|
12
13
|
:show-actions="showActions"
|
|
@@ -144,6 +145,7 @@ class itfTableBody extends Vue {
|
|
|
144
145
|
@Prop() rows;
|
|
145
146
|
@Prop() idProperty;
|
|
146
147
|
@Prop() subrowsProperty;
|
|
148
|
+
@Prop() asyncSubrowsProperty;
|
|
147
149
|
@Prop() dividerProperty;
|
|
148
150
|
@Prop() active;
|
|
149
151
|
@Prop(Boolean) showAddColumn;
|
|
@@ -164,6 +166,10 @@ class itfTableBody extends Vue {
|
|
|
164
166
|
this.$emit('update:expanded-ids', this.expandedIds.includes(item[this.idProperty])
|
|
165
167
|
? this.expandedIds.filter((id) => id !== item[this.idProperty])
|
|
166
168
|
: [...this.expandedIds, item[this.idProperty]]);
|
|
169
|
+
|
|
170
|
+
if (this.asyncSubrowsProperty && item[this.asyncSubrowsProperty] && item[this.asyncSubrowsProperty]) {
|
|
171
|
+
this.$emit('loadChildren', item);
|
|
172
|
+
}
|
|
167
173
|
}
|
|
168
174
|
}
|
|
169
175
|
</script>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<div class="shadow-area"></div>
|
|
18
18
|
<div class="header-wrapper" :class="{'header-additional-column': showAddColumn}" @click.prevent="toggleGroup">
|
|
19
19
|
<a class="header-content position-sticky d-flex align-items-center">
|
|
20
|
-
<itf-button
|
|
20
|
+
<itf-button icon small secondary class="collapse-arrow">
|
|
21
21
|
<itf-icon :name="isShowTable ? 'chevron_down' : 'chevron_right'"/>
|
|
22
22
|
</itf-button>
|
|
23
23
|
<span class="d-flex align-items-center line-overflow group-header-value text-primary"
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
:show-add-column="showAddColumn"
|
|
40
40
|
:show-actions="showActions"
|
|
41
41
|
:id-property="idProperty"
|
|
42
|
+
:sort-as-string="sortAsString"
|
|
42
43
|
:rows="rows"
|
|
43
44
|
:schema="schema"
|
|
44
45
|
:editable="editable"
|
|
@@ -61,11 +62,13 @@
|
|
|
61
62
|
@row-click="$emit('row-click', $event)"
|
|
62
63
|
:id-property="idProperty"
|
|
63
64
|
:subrows-property="subrowsProperty"
|
|
65
|
+
:async-subrows-property="asyncSubrowsProperty"
|
|
64
66
|
:divider-property="dividerProperty"
|
|
65
67
|
:rows="rows"
|
|
66
68
|
:editable="editable"
|
|
67
69
|
:currency="currency"
|
|
68
70
|
:currencies="currencies"
|
|
71
|
+
:sort-as-string="sortAsString"
|
|
69
72
|
:columns="visibleColumns"
|
|
70
73
|
:no-select-all="noSelectAll"
|
|
71
74
|
:selected-ids="selectedIds"
|
|
@@ -78,6 +81,7 @@
|
|
|
78
81
|
:css-property="cssProperty"
|
|
79
82
|
:editable-property="editableProperty"
|
|
80
83
|
:active="active"
|
|
84
|
+
@loadChildren="$emit('loadChildren', $event)"
|
|
81
85
|
@update:expanded-ids="$emit('update:expanded-ids', $event)"
|
|
82
86
|
>
|
|
83
87
|
<template v-for="(_, name) in $slots" #[name]="slotData">
|
|
@@ -91,11 +95,11 @@
|
|
|
91
95
|
|
|
92
96
|
<!-- Лінія додати нову -->
|
|
93
97
|
<div v-if="isShowTable && addNewRows"
|
|
94
|
-
class="table-row-template d-flex align-items-stretch">
|
|
98
|
+
class="table-row-template table-row-template__new-row d-flex align-items-stretch">
|
|
95
99
|
<div class="shadow-area"></div>
|
|
96
100
|
<a href="" @click.prevent="$emit('new', title)" data-test="table-add-new-item"
|
|
97
101
|
class="d-flex align-items-center flex-grow-1 table-add-new-item text-decoration-none">
|
|
98
|
-
<span class="d-sticky d-flex align-items-center py-1">
|
|
102
|
+
<span class="d-sticky d-flex align-items-center py-1 px-2 small">
|
|
99
103
|
<itf-icon name="plus"/>
|
|
100
104
|
<span>{{ newLabel }}</span>
|
|
101
105
|
</span>
|
|
@@ -264,7 +268,11 @@
|
|
|
264
268
|
min-height: var(--table-small-row-size);
|
|
265
269
|
}
|
|
266
270
|
|
|
271
|
+
.table-row-template.table-row-template__new-row {
|
|
272
|
+
min-height: 2rem;
|
|
273
|
+
}
|
|
267
274
|
.table-add-new-item {
|
|
275
|
+
background-color: var(--itf-table-header-bg);
|
|
268
276
|
border-right:var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
|
|
269
277
|
border-left:var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
|
|
270
278
|
border-bottom: var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
|
|
@@ -273,7 +281,7 @@
|
|
|
273
281
|
border-bottom-right-radius: var(--itf-table-table-border-radius);
|
|
274
282
|
|
|
275
283
|
& > span {
|
|
276
|
-
left: var(--shadow-area-width);
|
|
284
|
+
left: calc(var(--shadow-area-width) + 4px);
|
|
277
285
|
position: sticky;
|
|
278
286
|
padding-left: var(--shadow-area-width);
|
|
279
287
|
//border-left: var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
|
|
@@ -359,6 +367,7 @@ class itfTableGroup extends Vue {
|
|
|
359
367
|
@Prop() title;
|
|
360
368
|
@Prop() idProperty;
|
|
361
369
|
@Prop() subrowsProperty;
|
|
370
|
+
@Prop() asyncSubrowsProperty;
|
|
362
371
|
@Prop() dividerProperty;
|
|
363
372
|
@Prop() currency;
|
|
364
373
|
@Prop() currencies;
|
|
@@ -378,6 +387,7 @@ class itfTableGroup extends Vue {
|
|
|
378
387
|
@Prop(Boolean) expandedAll;
|
|
379
388
|
@Prop(Boolean) striped;
|
|
380
389
|
@Prop(Boolean) stickyHeader;
|
|
390
|
+
@Prop(Boolean) sortAsString;
|
|
381
391
|
@Prop() indicatorWidth;
|
|
382
392
|
@Prop() shadowWidth;
|
|
383
393
|
@Prop() cssProperty;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<div ref="container" class="table-row-template">
|
|
5
5
|
<div accept-group="items" class="table-view-body-space" v-dropzone="{ payload: 0 }"></div>
|
|
6
6
|
<div class="shadow-area"></div>
|
|
7
|
-
<div class="table-view-header-value reserved sticky">
|
|
8
|
-
<itf-checkbox v-if="indicatorType
|
|
7
|
+
<div v-if="indicatorType !== 'none'" class="table-view-header-value reserved sticky">
|
|
8
|
+
<itf-checkbox v-if="indicatorType !== 'none' && visibleHeader && !noSelectAll" ungrouped value="all" v-model="selectAll" ref="selectAll" />
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<template v-for="(column, n) in visibleAttributes">
|
|
@@ -29,14 +29,16 @@
|
|
|
29
29
|
<div v-if="visibleHeader" group="tablecolumns"
|
|
30
30
|
class="table-header"
|
|
31
31
|
@drop="reorderColumns"
|
|
32
|
-
v-draggable="{ handle: true, payload: { index: n, item: column }, mirror: {yAxis:false} }">
|
|
32
|
+
v-draggable="{ dragHandleClass: null, handle: true, payload: { index: n, item: column }, mirror: {yAxis:false} }">
|
|
33
33
|
<itf-dropdown text append-to-body shadow ref="dropdown" class="w-100" :disabled="noColumnMenu">
|
|
34
34
|
<template #button>
|
|
35
35
|
<div class="itf-table2__header-title d-flex w-100 align-items-center" :title="getTitle(column.title)">
|
|
36
36
|
<itf-icon class="itf-table2__header-icon" new v-if="column.icon" :name="column.icon"></itf-icon>
|
|
37
37
|
<div class="flex-grow-1 w-100 itf-table2__title-container d-flex align-items-center" :class="{'justify-content-end': column.align === 'end'}">
|
|
38
|
-
<div class="itf-table2__title text-truncate">
|
|
39
|
-
|
|
38
|
+
<div class="itf-table2__title text-truncate">
|
|
39
|
+
{{getTitle(column.title)}}
|
|
40
|
+
<div v-if="column.prefix" class="itf-table2__subtitle text-truncate" :class="{'text-end': column.align === 'end'}" v-text="column.prefix" />
|
|
41
|
+
</div>
|
|
40
42
|
</div>
|
|
41
43
|
</div>
|
|
42
44
|
<itf-icon v-if="sortColumnParams[column.property]" :name="sortColumnParams[column.property] === 'asc' ? 'sort-asc' : 'sort-desc'" new :size="20" class="ms-1" />
|
|
@@ -194,6 +196,7 @@ class itfTableHeader extends Vue {
|
|
|
194
196
|
@Prop(Boolean) noColumnMenu;
|
|
195
197
|
@Prop(Boolean) noSelectAll;
|
|
196
198
|
@Prop(Boolean) editable;
|
|
199
|
+
@Prop(Boolean) sortAsString;
|
|
197
200
|
@Prop() idProperty;
|
|
198
201
|
@Prop() indicatorType;
|
|
199
202
|
|
|
@@ -411,8 +414,10 @@ class itfTableHeader extends Vue {
|
|
|
411
414
|
}
|
|
412
415
|
|
|
413
416
|
sortBy(column, order) {
|
|
414
|
-
let sort =
|
|
415
|
-
|
|
417
|
+
let sort = { [column.property]: order };
|
|
418
|
+
if (this.sortAsString) {
|
|
419
|
+
sort = order === 'desc' ? `-${column.property}` : column.property;
|
|
420
|
+
}
|
|
416
421
|
this.$emit('update:sorting', sort);
|
|
417
422
|
}
|
|
418
423
|
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<div @click.prevent.stop="toggle" class="d-flex align-items-center flex-nowrap" :class="{'active-toggle': visible}">
|
|
4
4
|
<div class="item-toggle text-muted">
|
|
5
|
-
<template v-if="visible &&
|
|
5
|
+
<template v-if="visible && loading">
|
|
6
|
+
<div class="itf-spinner"></div>
|
|
7
|
+
</template>
|
|
8
|
+
<template v-else-if="visible && expanded">
|
|
6
9
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
7
10
|
width="16" height="16" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
|
8
11
|
<path d="M184.7,413.1l2.1-1.8l156.5-136c5.3-4.6,8.6-11.5,8.6-19.2c0-7.7-3.4-14.6-8.6-19.2L187.1,101l-2.6-2.3
|
|
@@ -23,6 +26,10 @@
|
|
|
23
26
|
</div>
|
|
24
27
|
</template>
|
|
25
28
|
<style lang="scss" scoped>
|
|
29
|
+
.itf-spinner {
|
|
30
|
+
width: 1rem;
|
|
31
|
+
height: 1rem;
|
|
32
|
+
}
|
|
26
33
|
.active-toggle {
|
|
27
34
|
cursor: pointer;
|
|
28
35
|
}
|
|
@@ -43,6 +50,7 @@ export default @Component({
|
|
|
43
50
|
class itfTableRowToggle extends Vue {
|
|
44
51
|
@Prop(Boolean) expanded;
|
|
45
52
|
@Prop(Boolean) visible;
|
|
53
|
+
@Prop(Boolean) loading;
|
|
46
54
|
|
|
47
55
|
toggle() {
|
|
48
56
|
this.$emit('toggle');
|
|
@@ -25,6 +25,22 @@
|
|
|
25
25
|
<span v-if="indicatorType === 'order'">{{ (n + 1) }}</span>
|
|
26
26
|
<span v-else-if="indicatorType === 'property'">{{ item[idProperty] }}</span>
|
|
27
27
|
<span v-else-if="indicatorType === 'checkbox'"><itf-checkbox :value="item[idProperty]" /></span>
|
|
28
|
+
<a href="" @click.prevent.stop="$emit('toggle', item)" v-else-if="indicatorType === 'toggle'">
|
|
29
|
+
<template v-if="subrowsProperty && item[subrowsProperty] && item[subrowsProperty].length">
|
|
30
|
+
<template v-if="item[subrowsProperty] && item[subrowsProperty].length && !isExpanded(item)">
|
|
31
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-square" viewBox="0 0 16 16">
|
|
32
|
+
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>
|
|
33
|
+
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/>
|
|
34
|
+
</svg>
|
|
35
|
+
</template>
|
|
36
|
+
<template v-else>
|
|
37
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-dash-square" viewBox="0 0 16 16">
|
|
38
|
+
<path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>
|
|
39
|
+
<path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8"/>
|
|
40
|
+
</svg>
|
|
41
|
+
</template>
|
|
42
|
+
</template>
|
|
43
|
+
</a>
|
|
28
44
|
</div>
|
|
29
45
|
</div>
|
|
30
46
|
<div accept-group="items" class="table-item-inner" @click="$emit('row-click', item)">
|
|
@@ -33,37 +49,37 @@
|
|
|
33
49
|
v-if="column.visible !== false"
|
|
34
50
|
:data-column="k"
|
|
35
51
|
:style="`width: ${column.width}px; max-width: ${column.width}px; left: ${column.left}px;`"
|
|
36
|
-
:class="{'
|
|
52
|
+
:class="{'sticky': column.pinned, 'last-sticky-column': k === lastPinnedIndex, 'editable': column.editable && editable}"
|
|
37
53
|
class="table-view-item-value d-flex h-100">
|
|
38
|
-
<div class="table-view-item-value-content" :class="{'px-2': !(column.editable && editable)}">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
<div class="table-view-item-value-content align-items-center" :class="{'justify-content-end': column.align === 'end', 'px-2': !(column.editable && editable)}">
|
|
55
|
+
<slot
|
|
56
|
+
:name="`column.${column.property}`"
|
|
57
|
+
:toggle="() => $emit('toggle', item)"
|
|
58
|
+
:hasSubitems="hasSubitems(item)"
|
|
59
|
+
:isExpanded="!!(hasSubitems(item) && !isExpanded(item))"
|
|
60
|
+
:level="level" :editable="column.editable && editable" :item="item" :column="column" :update="(val) => updateValue(item, val, n, column)" :value="getValue(item, column)">
|
|
61
|
+
<template v-if="column.editable && editable && (!editableProperty || item[editableProperty])">
|
|
62
|
+
<slot :name="`edit.${column.type}`" :level="level" :toggle="() => $emit('toggle', item)" :update="(val) => updateValue(item, val, n, column)" :value="getValue(item, column)" :item="item" :column="column">
|
|
63
|
+
<itf-text-field class="w-100 h-100" v-if="column.type === 'text'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
64
|
+
<itf-text-field class="w-100 h-100" v-if="column.type === 'number'" type="number" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
65
|
+
<itf-hours-field
|
|
66
|
+
class="w-100 h-100"
|
|
67
|
+
placeholder="00h 00m"
|
|
68
|
+
v-else-if="column.type === 'time'"
|
|
69
|
+
:hours="getValue(item, column)"
|
|
70
|
+
@update:hours="updateValue(item, $event, n, column)"
|
|
71
|
+
/>
|
|
72
|
+
<itf-textarea class="w-100 h-100" :rows="1" autogrow v-else-if="column.type === 'textarea'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
73
|
+
<itf-money-field class="w-100 h-100" currency-disabled :currency="currency" :currencies="currencies" v-else-if="column.type === 'money'" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" />
|
|
74
|
+
<itf-select class="w-100 h-100" v-else-if="column.type === 'select'" :reduce="(item) => item.value" :value="getValue(item, column)" @input="updateValue(item, $event, n, column)" :options="column.options"></itf-select>
|
|
75
|
+
</slot>
|
|
76
|
+
</template>
|
|
77
|
+
<span v-else class="text-truncate">
|
|
78
|
+
<slot :name="`format.${column.type}`" :toggle="() => $emit('toggle', item)" :level="level" :value="getValue(item, column)" :update="(value) => updateValue(item, value, n, column)" :item="item" :column="column">
|
|
79
|
+
{{getValue(item, column)}}
|
|
80
|
+
</slot>
|
|
81
|
+
</span>
|
|
82
|
+
</slot>
|
|
67
83
|
</div>
|
|
68
84
|
</div>
|
|
69
85
|
</template>
|
|
@@ -86,6 +102,7 @@
|
|
|
86
102
|
:columns="columns"
|
|
87
103
|
:id-property="idProperty"
|
|
88
104
|
:subrows-property="subrowsProperty"
|
|
105
|
+
:async-subrows-property="asyncSubrowsProperty"
|
|
89
106
|
:show-add-column="showAddColumn"
|
|
90
107
|
:show-actions="showActions"
|
|
91
108
|
:no-select-all="noSelectAll"
|
|
@@ -145,6 +162,7 @@ class itfTableRows extends Vue {
|
|
|
145
162
|
@Prop() rows;
|
|
146
163
|
@Prop() idProperty;
|
|
147
164
|
@Prop() subrowsProperty;
|
|
165
|
+
@Prop() asyncSubrowsProperty;
|
|
148
166
|
@Prop() dividerProperty;
|
|
149
167
|
@Prop() active;
|
|
150
168
|
@Prop(Boolean) showAddColumn;
|
|
@@ -174,6 +192,12 @@ class itfTableRows extends Vue {
|
|
|
174
192
|
return this.columns.findIndex((column) => column.lastPinned);
|
|
175
193
|
}
|
|
176
194
|
|
|
195
|
+
hasSubitems(item) {
|
|
196
|
+
const hasFactItems = this.subrowsProperty && item[this.subrowsProperty] && item[this.subrowsProperty].length;
|
|
197
|
+
const hasPlanItems = this.asyncSubrowsProperty && item[this.asyncSubrowsProperty] && item[this.asyncSubrowsProperty];
|
|
198
|
+
return !!(hasFactItems || hasPlanItems);
|
|
199
|
+
}
|
|
200
|
+
|
|
177
201
|
updateValue(item, value, index, column) {
|
|
178
202
|
const newItem = { ...item };
|
|
179
203
|
if (newItem[column.property] !== value) {
|
|
@@ -188,20 +212,8 @@ class itfTableRows extends Vue {
|
|
|
188
212
|
}
|
|
189
213
|
}
|
|
190
214
|
|
|
191
|
-
updateValues(item, values, index, column) {
|
|
192
|
-
const newItem = { ...item };
|
|
193
|
-
Object.assign(newItem, values);
|
|
194
|
-
this.$emit('update', {
|
|
195
|
-
index,
|
|
196
|
-
item,
|
|
197
|
-
inputValue: values,
|
|
198
|
-
value: newItem,
|
|
199
|
-
column
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
215
|
isActive(id) {
|
|
204
|
-
if (!this.idProperty) {
|
|
216
|
+
if (!this.idProperty || !this.active) {
|
|
205
217
|
return false;
|
|
206
218
|
}
|
|
207
219
|
if (Array.isArray(this.active)) {
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
--itf-table-alt-bg: #F9FAFB;
|
|
8
8
|
--itf-table-alt-selected-bg: #eff1f3;
|
|
9
9
|
--itf-table-header-bg: #f5f7f8;
|
|
10
|
-
--itf-table-header-color: #
|
|
10
|
+
--itf-table-header-color: #575b63;
|
|
11
11
|
--itf-table-mirror-bg: #F2F4F7;
|
|
12
12
|
--itf-table-border-color: transparent; //var(--itf-table-header-bg);
|
|
13
|
-
--itf-table-header-border-color: #
|
|
13
|
+
--itf-table-header-border-color: #8E97A533;
|
|
14
14
|
--itf-table-border-base-color: var(--itf-table-header-bg); // кольори границь таблиці без внутрішніх рядків
|
|
15
15
|
--itf-table-border-base-width: 2px;
|
|
16
16
|
--itf-table-hover-header-bg: #dfe5ef;
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
--itf-table-summary-text: var(--bs-tertiary-color);
|
|
24
24
|
--itf-table-table-border-radius: 1rem;
|
|
25
25
|
--itf-table-header-height: 2.25rem;
|
|
26
|
+
--itf-table-divider-bg: #F7F8FA;
|
|
27
|
+
--itf-table-divider-border: rgba(238, 238, 238, 1);
|
|
26
28
|
|
|
27
29
|
--group-title-height: 40px;
|
|
28
30
|
--table-row-height: none;
|
|
@@ -43,6 +45,9 @@ body[data-theme="dark"] {
|
|
|
43
45
|
--itf-table-selected-bg: #011534;
|
|
44
46
|
--itf-table-active-bg: #022e72;
|
|
45
47
|
--itf-table-summary-text: #82909d80;
|
|
48
|
+
--itf-table-border-base-color: var(--itf-table-header-bg);
|
|
49
|
+
--itf-table-divider-bg: #0f0f0f;
|
|
50
|
+
--itf-table-divider-border: rgb(100, 100, 100, .1);
|
|
46
51
|
}
|
|
47
52
|
.itf-table2 {
|
|
48
53
|
font-size: var(--itf-table-content-font-size, var(--itf-table-font-size));
|
|
@@ -60,7 +65,7 @@ body[data-theme="dark"] {
|
|
|
60
65
|
height: 100%;
|
|
61
66
|
}
|
|
62
67
|
.scroller {
|
|
63
|
-
margin-bottom:
|
|
68
|
+
//margin-bottom: .5rem;
|
|
64
69
|
}
|
|
65
70
|
.scrollable-x {
|
|
66
71
|
overflow-x: scroll;
|
|
@@ -97,34 +102,18 @@ body[data-theme="dark"] {
|
|
|
97
102
|
position: sticky;
|
|
98
103
|
top: 0;
|
|
99
104
|
bottom: 0;
|
|
100
|
-
right:
|
|
105
|
+
right: -5px;
|
|
101
106
|
z-index: 8;
|
|
107
|
+
padding-right: 5px;
|
|
108
|
+
padding-left: 5px;
|
|
102
109
|
display: flex;
|
|
103
110
|
align-items: center;
|
|
104
|
-
|
|
105
|
-
@media (max-width: 768px) {
|
|
106
|
-
position: relative;
|
|
107
|
-
opacity: 1;
|
|
108
|
-
}
|
|
109
111
|
}
|
|
110
112
|
.on-hover {
|
|
111
113
|
opacity: 0;
|
|
112
|
-
width: 0;
|
|
113
|
-
padding: 4px .5rem;
|
|
114
|
-
overflow: hidden;
|
|
115
114
|
pointer-events: none;
|
|
116
|
-
position: absolute;
|
|
117
|
-
right: 0;
|
|
118
|
-
background: linear-gradient(90deg, transparent 0, var(--itf-table2-row-bg) 10px);
|
|
119
|
-
|
|
120
|
-
@media (max-width: 768px) {
|
|
121
|
-
width: max-content;
|
|
122
|
-
opacity: 1;
|
|
123
|
-
position: relative;
|
|
124
|
-
}
|
|
125
115
|
}
|
|
126
116
|
.table-row-template:hover .on-hover {
|
|
127
|
-
width: max-content;
|
|
128
117
|
opacity: 1;
|
|
129
118
|
pointer-events: all;
|
|
130
119
|
}
|
|
@@ -451,11 +440,11 @@ body[data-theme="dark"] {
|
|
|
451
440
|
}
|
|
452
441
|
|
|
453
442
|
&__row-divider {
|
|
454
|
-
background-color:
|
|
443
|
+
background-color: var(--itf-table-divider-bg);
|
|
455
444
|
height: 5px;
|
|
456
445
|
padding: 0;
|
|
457
|
-
border-top: 1px solid
|
|
458
|
-
border-bottom: 1px solid
|
|
446
|
+
border-top: 1px solid var(--itf-table-divider-border);
|
|
447
|
+
border-bottom: 1px solid var(--itf-table-divider-border);
|
|
459
448
|
}
|
|
460
449
|
//&:hover, &.permanent-editable-border {
|
|
461
450
|
// .table-view-item-value.editable {
|
|
@@ -468,6 +457,7 @@ body[data-theme="dark"] {
|
|
|
468
457
|
position: relative;
|
|
469
458
|
z-index: 2;
|
|
470
459
|
width: 100%;
|
|
460
|
+
display: flex;
|
|
471
461
|
height: 100%;
|
|
472
462
|
background: var(--itf-table2-row-bg)
|
|
473
463
|
}
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
:endpoint="filtersEndpoint"
|
|
11
11
|
:panel="panel"
|
|
12
12
|
v-model="filter"
|
|
13
|
-
@
|
|
13
|
+
@loaded="onFilterSet($event, true)"
|
|
14
|
+
@change="onFilterSet($event, false)"
|
|
14
15
|
@set-table-schema="setTableSchema"
|
|
15
16
|
>
|
|
16
17
|
<template #after-filter-btn>
|
|
@@ -213,7 +214,7 @@ class itfView extends Vue {
|
|
|
213
214
|
|
|
214
215
|
getDownloadLinks() {
|
|
215
216
|
const state = this.$refs.table ? this.$refs.table.getTableState() : null;
|
|
216
|
-
const filter = this.filter;
|
|
217
|
+
const filter = { ...this.filter };
|
|
217
218
|
const sorting = this.sorting;
|
|
218
219
|
const filterableColumnsNames = (state?.columns ?? []).filter(column => column.visible).map(column => column.property);
|
|
219
220
|
|
|
@@ -297,7 +298,7 @@ class itfView extends Vue {
|
|
|
297
298
|
this.$emit('load', this.filter);
|
|
298
299
|
this.loadingData = true;
|
|
299
300
|
await this.$try(async () => {
|
|
300
|
-
let filter = this.filter;
|
|
301
|
+
let filter = { ...this.filter };
|
|
301
302
|
if (this.oldFormat) {
|
|
302
303
|
filter = Object.keys(filter).reduce((acc, key) => {
|
|
303
304
|
acc[`filter[${key}]`] = filter[key];
|
|
@@ -370,7 +371,9 @@ class itfView extends Vue {
|
|
|
370
371
|
sources.forEach(source => {
|
|
371
372
|
if (source && typeof source === 'object') {
|
|
372
373
|
Object.entries(source).forEach(([key, value]) => {
|
|
373
|
-
if (value
|
|
374
|
+
if (key === 'page' && value === null) {
|
|
375
|
+
delete target[key];
|
|
376
|
+
} else if (value !== undefined) {
|
|
374
377
|
target[key] = value;
|
|
375
378
|
}
|
|
376
379
|
});
|
|
@@ -380,10 +383,14 @@ class itfView extends Vue {
|
|
|
380
383
|
}
|
|
381
384
|
}
|
|
382
385
|
|
|
383
|
-
onFilterSet(filter) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
386
|
+
onFilterSet(filter, keepPage = false) {
|
|
387
|
+
if (keepPage) {
|
|
388
|
+
// при завантаженні сторінки не потрібно скидувати сторінку
|
|
389
|
+
this.setPanelPayload({ ...filter });
|
|
390
|
+
} else {
|
|
391
|
+
this.page = 1;
|
|
392
|
+
this.setPanelPayload({ ...filter, page: null });
|
|
393
|
+
}
|
|
387
394
|
this.loadData();
|
|
388
395
|
}
|
|
389
396
|
|