@itfin/components 1.5.2 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -1
- package/src/ITFSettings.js +0 -6
- package/src/components/button/Button.vue +1 -3
- package/src/components/button/NativeButton.js +0 -4
- package/src/components/button/index.stories.js +2 -2
- package/src/components/card/BentoGrid.vue +0 -2
- package/src/components/customize/PropertiesList.vue +2 -0
- package/src/components/customize/PropertiesPopupMenu.vue +1 -1
- package/src/components/customize/PropertyItem.vue +24 -6
- package/src/components/datepicker/DatePicker.vue +1 -1
- package/src/components/datepicker/MonthPicker.vue +21 -1
- package/src/components/dropdown/Dropdown.vue +1 -1
- package/src/components/dropdown/DropdownMenu.vue +1 -1
- package/src/components/editable/EditButton.vue +1 -1
- package/src/components/filter/FilterBadge.vue +5 -4
- package/src/components/filter/FilterFacetsList.vue +3 -2
- package/src/components/filter/FilterPanel.vue +20 -6
- package/src/components/icon/components/nomi-calendar-view.vue +4 -0
- package/src/components/icon/components/nomi-help.vue +3 -2
- package/src/components/icon/components/nomi-kanban-view.vue +6 -0
- package/src/components/icon/components/nomi-list-view.vue +7 -0
- package/src/components/icon/components/nomi-lock.vue +1 -1
- package/src/components/icon/components/nomi-project.vue +2 -2
- package/src/components/icon/components/nomi-scissors.vue +1 -1
- package/src/components/icon/components/nomi-table-config.vue +9 -0
- package/src/components/icon/components/nomi-table-view.vue +4 -1
- package/src/components/icon/components/nomi-user.vue +3 -3
- package/src/components/icon/convert-icons.js +0 -3
- package/src/components/icon/icons.js +402 -397
- package/src/components/icon/new-icons/calendar-view.svg +3 -0
- package/src/components/icon/new-icons/help.svg +3 -2
- package/src/components/icon/new-icons/kanban-view.svg +5 -0
- package/src/components/icon/new-icons/list-view.svg +6 -0
- package/src/components/icon/new-icons/lock.svg +1 -1
- package/src/components/icon/new-icons/project.svg +2 -2
- package/src/components/icon/new-icons/scissors.svg +1 -1
- package/src/components/icon/new-icons/table-config.svg +8 -0
- package/src/components/icon/new-icons/table-view.svg +4 -1
- package/src/components/icon/new-icons/user.svg +3 -3
- package/src/components/kanban/BoardCard.vue +1 -1
- package/src/components/kanban/BoardCardTimer.vue +1 -1
- package/src/components/overlay/SensitiveOverlay.vue +4 -2
- package/src/components/panels/Panel.vue +21 -0
- package/src/components/panels/PanelList.vue +14 -3
- package/src/components/table/Table2.vue +65 -60
- package/src/components/table/TableBody.vue +6 -0
- package/src/components/table/TableGroup.vue +13 -4
- package/src/components/table/TableHeader.vue +77 -76
- package/src/components/table/TableRowToggle.vue +9 -1
- package/src/components/table/TableRows.vue +54 -30
- package/src/components/table/table2.scss +15 -34
- package/src/components/tree/TreeEditor.vue +2 -3
- package/src/components/view/View.vue +214 -59
- package/src/helpers/validators.js +35 -9
- package/src/helpers/validators.spec.js +48 -11
- package/src/locales/en.js +1 -1
- package/src/locales/uk.js +4 -9
|
@@ -4,26 +4,29 @@
|
|
|
4
4
|
<itf-filter-panel
|
|
5
5
|
:search-placeholder="searchPlaceholder"
|
|
6
6
|
search
|
|
7
|
+
show-filter
|
|
7
8
|
:visible="!noFilters"
|
|
8
9
|
:filters-only="filtersOnly"
|
|
9
10
|
ref="filters"
|
|
10
11
|
:mini="panel.isMultiple()"
|
|
11
12
|
class="py-2 px-3"
|
|
13
|
+
:static-filters="filters"
|
|
12
14
|
:endpoint="filtersEndpoint"
|
|
13
15
|
:panel="panel"
|
|
14
16
|
v-model="filter"
|
|
15
17
|
@loaded="onFilterSet($event, true)"
|
|
16
18
|
@change="onFilterSet($event, false)"
|
|
19
|
+
@set-table-schema="setTableSchema"
|
|
17
20
|
>
|
|
18
21
|
<template #after-filter-btn>
|
|
19
|
-
<itf-dropdown v-if="$refs.table &&
|
|
22
|
+
<itf-dropdown v-if="$refs.table && tableSchema" shadow append-to-context :button-options="{ default: true, icon: true }" class="h-100" autoclose="outside">
|
|
20
23
|
<template #button>
|
|
21
|
-
<itf-icon new name="table-
|
|
24
|
+
<itf-icon new name="table-config" />
|
|
22
25
|
</template>
|
|
23
26
|
<div class="dropdown-header">
|
|
24
27
|
{{ $t('components.table.columns') }}
|
|
25
28
|
</div>
|
|
26
|
-
<a v-for="(property, n) of
|
|
29
|
+
<a v-for="(property, n) of tableSchema.properties" :key="n" href="" @click.stop.prevent="$refs.table.toggleVisibility(property.property)" class="dropdown-item justify-content-between d-flex gap-3">
|
|
27
30
|
<div class="d-flex align-items-center gap-1">
|
|
28
31
|
<itf-icon v-if="property.icon" :size="24" new :name="property.icon" />
|
|
29
32
|
{{getTitle(property.title)}}
|
|
@@ -42,49 +45,87 @@
|
|
|
42
45
|
</div>
|
|
43
46
|
</a>
|
|
44
47
|
</itf-dropdown>
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
<itf-dropdown v-if="downloadEndpoint" shadow append-to-context :button-options="{ default: true, small: true }" class="h-100" autoclose="outside">
|
|
49
|
+
<template #button>
|
|
50
|
+
<itf-icon name="download"/>
|
|
51
|
+
{{ $t('export') }}
|
|
52
|
+
</template>
|
|
53
|
+
<a v-for="(item, n) in getDownloadLinks()" target="_blank" :key="n" :href="item.link" class="dropdown-item">
|
|
54
|
+
{{item.title}}
|
|
55
|
+
</a>
|
|
56
|
+
</itf-dropdown>
|
|
57
|
+
|
|
58
|
+
<slot name="before-tabs"></slot>
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
striped
|
|
55
|
-
clickable
|
|
56
|
-
column-sorting
|
|
57
|
-
column-resizing
|
|
58
|
-
:indicator-type="indicatorType"
|
|
59
|
-
class="permanent-checkboxes"
|
|
60
|
-
divider-property="hasDivider"
|
|
61
|
-
subrows-property="children"
|
|
62
|
-
:state-name="stateName"
|
|
63
|
-
id-property="id"
|
|
64
|
-
:rows="preparedItems"
|
|
65
|
-
:schema="schema"
|
|
66
|
-
:sorting="sorting"
|
|
67
|
-
:active="activeIds"
|
|
68
|
-
:show-actions="showActions"
|
|
69
|
-
:expanded-ids.sync="expandedIds"
|
|
70
|
-
:indicator-width="indicatorWidth"
|
|
71
|
-
:no-select-all="noSelectAll"
|
|
72
|
-
v-model="selectedIds"
|
|
73
|
-
@row-click="$emit('open', $event)"
|
|
74
|
-
@update:sorting="updateSorting($event)"
|
|
60
|
+
<itf-segmented-control
|
|
61
|
+
v-if="tabs.length > 1"
|
|
62
|
+
class="small"
|
|
63
|
+
v-model="currentTab"
|
|
64
|
+
item-key="value"
|
|
65
|
+
:items="tabs"
|
|
75
66
|
>
|
|
76
|
-
<template
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
67
|
+
<template #item="{ item }">
|
|
68
|
+
<div class="d-flex align-items-center">
|
|
69
|
+
<itf-icon class="text-muted" new :name="item.icon" />
|
|
70
|
+
{{item.text}}
|
|
71
|
+
</div>
|
|
81
72
|
</template>
|
|
82
|
-
</itf-
|
|
73
|
+
</itf-segmented-control>
|
|
74
|
+
</template>
|
|
75
|
+
</itf-filter-panel>
|
|
76
|
+
|
|
77
|
+
<div v-if="currentTab === 'list'" class="position-relative flex-grow-1">
|
|
78
|
+
<div class="position-absolute" style="top: 0; left: 0; right: 0; bottom: 0; overflow: auto;">
|
|
79
|
+
<slot name="list-view" :items="items" :loading="loading"></slot>
|
|
83
80
|
</div>
|
|
84
81
|
</div>
|
|
82
|
+
<slot v-else-if="currentTab === 'board'" name="kanban-view"></slot>
|
|
83
|
+
<slot v-else-if="currentTab === 'calendar'" name="calendar-view"></slot>
|
|
84
|
+
<slot v-else name="table-view">
|
|
85
|
+
<div class="flex-grow-1 px-3 d-flex flex-column">
|
|
86
|
+
<div class="position-relative flex-grow-1">
|
|
87
|
+
<itf-table
|
|
88
|
+
ref="table"
|
|
89
|
+
style="--shadow-area-width: 0px;"
|
|
90
|
+
absolute
|
|
91
|
+
striped
|
|
92
|
+
clickable
|
|
93
|
+
column-sorting
|
|
94
|
+
column-resizing
|
|
95
|
+
:indicator-type="indicatorType"
|
|
96
|
+
class="permanent-checkboxes"
|
|
97
|
+
:state-name="stateName"
|
|
98
|
+
id-property="id"
|
|
99
|
+
divider-property="hasDivider"
|
|
100
|
+
subrows-property="children"
|
|
101
|
+
:sort-as-string="sortAsString"
|
|
102
|
+
:rows="preparedItems"
|
|
103
|
+
:group-by="groupBy"
|
|
104
|
+
:schema="tableSchema"
|
|
105
|
+
:sorting="sorting"
|
|
106
|
+
:active="activeIds"
|
|
107
|
+
:expanded-ids.sync="expandedIds"
|
|
108
|
+
:no-select-all="noSelectAll"
|
|
109
|
+
:show-actions="showActions"
|
|
110
|
+
:indicator-width="indicatorWidth"
|
|
111
|
+
v-model="selectedIds"
|
|
112
|
+
@row-click="$emit('open', $event)"
|
|
113
|
+
@update:sorting="updateSorting($event)"
|
|
114
|
+
>
|
|
115
|
+
<template v-for="(_, name) in $slots" #[name]="slotData">
|
|
116
|
+
<slot :name="name" v-bind="slotData || {}"/>
|
|
117
|
+
</template>
|
|
118
|
+
<template v-for="(_, name) in $scopedSlots" #[name]="slotData">
|
|
119
|
+
<slot :name="name" v-bind="slotData || {}"/>
|
|
120
|
+
</template>
|
|
121
|
+
</itf-table>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</slot>
|
|
85
125
|
|
|
86
126
|
<itf-pagination
|
|
87
127
|
class="my-2 px-3"
|
|
128
|
+
v-if="showPagination"
|
|
88
129
|
show-size
|
|
89
130
|
:size="size"
|
|
90
131
|
:items="preparedItems"
|
|
@@ -95,14 +136,14 @@
|
|
|
95
136
|
@per-page="updateSizePerPage($event)"
|
|
96
137
|
>
|
|
97
138
|
<template #center>
|
|
98
|
-
<slot name="pagination-center" />
|
|
139
|
+
<slot name="pagination-center" :totals="totals" />
|
|
99
140
|
</template>
|
|
100
141
|
</itf-pagination>
|
|
101
142
|
</div>
|
|
102
143
|
|
|
103
144
|
</template>
|
|
104
145
|
<script>
|
|
105
|
-
import {
|
|
146
|
+
import {Vue, ModelSync, Component, Prop, Inject, PropSync, Watch} from 'vue-property-decorator';
|
|
106
147
|
import loading from '../../directives/loading';
|
|
107
148
|
import itfTable from '../table/Table2.vue';
|
|
108
149
|
import itfFilterPanel from '../filter/FilterPanel.vue';
|
|
@@ -110,11 +151,16 @@ import itfPagination from '../pagination/Pagination2.vue';
|
|
|
110
151
|
import itfTableBody from "../table/TableBody.vue";
|
|
111
152
|
import itfIcon from "../icon/Icon.vue";
|
|
112
153
|
import itfDropdown from "../dropdown/Dropdown.vue";
|
|
154
|
+
import itfSegmentedControl from '../segmented-control/SegmentedControl.vue';
|
|
155
|
+
import itfButton from '@itfin/components/src/components/button/Button.vue';
|
|
113
156
|
|
|
114
157
|
export default @Component({
|
|
115
158
|
name: 'itfView',
|
|
116
159
|
components: {
|
|
117
|
-
|
|
160
|
+
itfButton,
|
|
161
|
+
itfSegmentedControl,
|
|
162
|
+
itfDropdown,
|
|
163
|
+
itfIcon,
|
|
118
164
|
itfPagination,
|
|
119
165
|
itfFilterPanel,
|
|
120
166
|
itfTableBody,
|
|
@@ -132,23 +178,33 @@ class itfView extends Vue {
|
|
|
132
178
|
@Prop({ type: Boolean }) filtersOnly;
|
|
133
179
|
@Prop({ type: Boolean }) noFilters;
|
|
134
180
|
@Prop({ type: Array }) filters;
|
|
135
|
-
@Prop({ type: Object
|
|
136
|
-
// @Prop({ default: 20 }) size;
|
|
137
|
-
// @Prop({ default: 1 }) page;
|
|
181
|
+
@Prop({ type: Object }) schema;
|
|
138
182
|
@Prop(String) defaultSorting;
|
|
139
183
|
@Prop(String) endpoint;
|
|
140
184
|
@Prop(String) filtersEndpoint;
|
|
141
185
|
@Prop(String) itemsKey;
|
|
186
|
+
@Prop({ type: String, default: null }) groupBy;
|
|
187
|
+
@Prop({ type: String, default: null }) downloadEndpoint; // префікс апі для завантаження
|
|
188
|
+
@Prop({ type: String, default: 'totals' }) totalsKey;
|
|
142
189
|
@Prop(String) panelKey;
|
|
143
190
|
@Prop(String) stateName;
|
|
144
191
|
@Prop({ type: String, default: 'checkbox' }) indicatorType;
|
|
192
|
+
@Prop({ type: String, default: 'list' }) tab;
|
|
145
193
|
@Prop({ type: String, default () { return this.$t('components.table.search'); } }) searchPlaceholder;
|
|
146
194
|
@Prop() panel;
|
|
147
195
|
@Prop() hardFilter; // встановлює жорсткий фільтр, наприклад, треба завжди показувати лише по контрагенту
|
|
148
196
|
@Prop(Boolean) showActions;
|
|
197
|
+
@Prop({ type: Boolean, default: true }) showPagination;
|
|
198
|
+
@Prop(Boolean) listViewEnabled;
|
|
199
|
+
@Prop(Boolean) kanbanViewEnabled;
|
|
200
|
+
@Prop(Boolean) calendarViewEnabled;
|
|
201
|
+
@Prop(Boolean) noSelectAll;
|
|
202
|
+
@Prop({ type: Boolean, default: true }) tableViewEnabled;
|
|
203
|
+
@Prop(Boolean) sortAsString;
|
|
204
|
+
@Prop(Boolean) oldFormat;
|
|
149
205
|
@Prop({ type: Array, default: () => [] }) disabledIds;
|
|
150
206
|
@Prop({ default: 45 }) indicatorWidth;
|
|
151
|
-
@Prop(
|
|
207
|
+
@Prop({type: Function, default: null }) onSplitSlectedIds // якщо потрібно розділяти вибрані рядки в таблиці на дві групи
|
|
152
208
|
@Prop({ type: Boolean, default: false }) showTotalCount;
|
|
153
209
|
|
|
154
210
|
page = 1;
|
|
@@ -159,6 +215,8 @@ class itfView extends Vue {
|
|
|
159
215
|
filter = {};
|
|
160
216
|
loadingData = false;
|
|
161
217
|
activeIds = [];
|
|
218
|
+
tableColumns = null;
|
|
219
|
+
totals = null;
|
|
162
220
|
expandedIds = [];
|
|
163
221
|
|
|
164
222
|
get preparedItems() {
|
|
@@ -166,6 +224,73 @@ class itfView extends Vue {
|
|
|
166
224
|
return this.items.map(item => ({ ...item, isDisabled: disabledIdsSet.has(item.id) }));
|
|
167
225
|
}
|
|
168
226
|
|
|
227
|
+
@Watch('selectedIds', { deep: true, immediate: true })
|
|
228
|
+
updateSelectedIds() {
|
|
229
|
+
if(this.onSplitSlectedIds && this.items.length && this.selectedIds.length) {
|
|
230
|
+
this.onSplitSlectedIds(this.selectedIds, this.items);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getDownloadLinks() {
|
|
235
|
+
const state = this.$refs.table ? this.$refs.table.getTableState() : null;
|
|
236
|
+
const filter = { ...this.filter };
|
|
237
|
+
const sorting = this.sorting;
|
|
238
|
+
const filterableColumnsNames = (state?.columns ?? []).filter(column => column.visible).map(column => column.property);
|
|
239
|
+
|
|
240
|
+
const filterWithValue = Object.fromEntries(Object.entries(filter).filter(([_, value]) => typeof value !== 'undefined'));
|
|
241
|
+
const params = {
|
|
242
|
+
...filterWithValue,
|
|
243
|
+
sort: sorting
|
|
244
|
+
};
|
|
245
|
+
if (filterableColumnsNames.length) {
|
|
246
|
+
params.columns = filterableColumnsNames.join(',')
|
|
247
|
+
}
|
|
248
|
+
const xlsQueryParams = new URLSearchParams({ ...params, format: 'xlsx' }).toString();
|
|
249
|
+
const csvQueryParams = new URLSearchParams({ ...params, format: 'csv' }).toString();
|
|
250
|
+
return [
|
|
251
|
+
{ title: 'Excel (xlsx)', link: `${this.downloadEndpoint}?${xlsQueryParams}` },
|
|
252
|
+
{ title: 'Plain text (csv)', link: `${this.downloadEndpoint}?${csvQueryParams}` },
|
|
253
|
+
];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
get currentTab() {
|
|
257
|
+
return this.tab;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
set currentTab(val) {
|
|
261
|
+
this.$emit('update:tab', val);
|
|
262
|
+
this.setPanelPayload({ tab: val });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
get tabs() {
|
|
266
|
+
const views = [];
|
|
267
|
+
if (this.listViewEnabled) {
|
|
268
|
+
views.push({ value: 'list', text: this.$t('list'), icon: 'list-view' });
|
|
269
|
+
}
|
|
270
|
+
if (this.kanbanViewEnabled) {
|
|
271
|
+
views.push({ value: 'board', text: this.$t('board'), icon: 'kanban-view' });
|
|
272
|
+
}
|
|
273
|
+
if (this.calendarViewEnabled) {
|
|
274
|
+
views.push({ value: 'calendar', text: this.$t('calendar'), icon: 'calendar-view' });
|
|
275
|
+
}
|
|
276
|
+
if (this.tableViewEnabled) {
|
|
277
|
+
views.push({ value: 'table', text: this.$t('table'), icon: 'table-view' });
|
|
278
|
+
}
|
|
279
|
+
return views;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get tableSchema() {
|
|
283
|
+
if (this.tableColumns) {
|
|
284
|
+
return {
|
|
285
|
+
properties: this.tableColumns
|
|
286
|
+
}
|
|
287
|
+
} else if (this.schema) {
|
|
288
|
+
return this.schema
|
|
289
|
+
} else {
|
|
290
|
+
return {}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
169
294
|
created() {
|
|
170
295
|
const defaultSize = localStorage.getItem('sizePerPage') ?? 20;
|
|
171
296
|
|
|
@@ -195,29 +320,42 @@ class itfView extends Vue {
|
|
|
195
320
|
this.$emit('load', this.filter);
|
|
196
321
|
this.loadingData = true;
|
|
197
322
|
await this.$try(async () => {
|
|
198
|
-
|
|
323
|
+
let filter = { ...this.filter };
|
|
324
|
+
if (this.oldFormat) {
|
|
325
|
+
filter = Object.keys(filter).reduce((acc, key) => {
|
|
326
|
+
acc[`filter[${key}]`] = filter[key];
|
|
327
|
+
return acc;
|
|
328
|
+
}, {})
|
|
329
|
+
}
|
|
330
|
+
this.selectedIds = [];
|
|
331
|
+
const { data, headers } = await this.$axios.get(this.endpoint, {
|
|
199
332
|
preventRaceCondition: true,
|
|
200
333
|
params: {
|
|
201
|
-
...
|
|
334
|
+
...filter,
|
|
202
335
|
page: this.page,
|
|
203
336
|
size: this.size,
|
|
204
337
|
sort: this.sorting
|
|
205
338
|
}
|
|
206
339
|
});
|
|
207
|
-
this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this
|
|
340
|
+
if (this.oldFormat) {
|
|
341
|
+
this.items = data;
|
|
342
|
+
this.page = Number(headers['x-page'] ?? 1);
|
|
343
|
+
this.total = Number(headers['x-count'] ?? 0);
|
|
344
|
+
this.size = Number(headers['x-size'] ?? 20);
|
|
345
|
+
} else {
|
|
346
|
+
this.items = data[this.itemsKey];
|
|
347
|
+
this.totals = data[this.totalsKey];
|
|
348
|
+
this.page = data.meta.page;
|
|
349
|
+
this.total = data.meta.total;
|
|
350
|
+
this.size = data.meta.size;
|
|
214
351
|
}
|
|
215
|
-
|
|
216
|
-
this.$emit('update:items', this.items);
|
|
217
|
-
this.loadingData = false;
|
|
218
|
-
}, () => {
|
|
219
|
-
this.loadingData = false;
|
|
220
352
|
});
|
|
353
|
+
if (res.columns) {
|
|
354
|
+
this.$emit('columns-loaded', res.columns);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this.$emit('update:items', this.items);
|
|
358
|
+
this.loadingData = false;
|
|
221
359
|
this.$emit('loaded', this.filter);
|
|
222
360
|
}
|
|
223
361
|
|
|
@@ -247,6 +385,7 @@ class itfView extends Vue {
|
|
|
247
385
|
updateSizePerPage (val) {
|
|
248
386
|
this.page = 1;
|
|
249
387
|
this.size = val;
|
|
388
|
+
this.selectedIds = [];
|
|
250
389
|
this.setPanelPayload({ page: null, size: val });
|
|
251
390
|
this.loadData();
|
|
252
391
|
localStorage.setItem('sizePerPage', val);
|
|
@@ -283,10 +422,26 @@ class itfView extends Vue {
|
|
|
283
422
|
this.loadData();
|
|
284
423
|
}
|
|
285
424
|
|
|
425
|
+
setTableSchema(tableSchema) {
|
|
426
|
+
this.tableColumns = tableSchema;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
getSorting() {
|
|
430
|
+
return this.sorting;
|
|
431
|
+
}
|
|
432
|
+
|
|
286
433
|
loadFilters() {
|
|
287
434
|
if (this.filtersEndpoint) {
|
|
288
435
|
this.$refs.filters?.loadData();
|
|
289
436
|
}
|
|
290
437
|
}
|
|
438
|
+
|
|
439
|
+
getFilter() {
|
|
440
|
+
return this.filter;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
getTableState() {
|
|
444
|
+
return this.$refs.table ? this.$refs.table.getTableState() : null;
|
|
445
|
+
}
|
|
291
446
|
}
|
|
292
447
|
</script>
|
|
@@ -8,12 +8,12 @@ const LINKED_IN_REGEXP = /(http(s)?:\/\/)?([\w]+\.)?linkedin\.com\/(pub|in|profi
|
|
|
8
8
|
const SPECIAL_CHARS_REGEXP = /^[\w_\-+~,/\\:'"().&*|[\]?# ]+$/i;
|
|
9
9
|
const GREETINGS_REGEXP = /\b(dr|mr|mister|mrs|ms|miss|sir|hello|hi)\b/i;
|
|
10
10
|
const PHONE_REGEXP = /(\+?\(?\+?[0-9]{1,3}\)?[-. ]+([0-9]{2,4})[-. ]?([0-9]{3,5}))|\+?[0-9]{7,}/gi;
|
|
11
|
-
const INTEGER_REGEXP = /^-?\d{0,11}$/;
|
|
12
11
|
const DOUBLE_REGEXP = /^-?\d{0,11}(\.\d{0,8}){0,1}$/;
|
|
13
|
-
const EMAIL_REGEXP =
|
|
12
|
+
const EMAIL_REGEXP = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/g;
|
|
14
13
|
const EMAIL_LIST_REGEXP = /^(\w+((-\w+)|(\.\w+))*@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]{2,4}\s*?,?\s*?)+$/g;
|
|
15
14
|
const HEX_REGEXP = /[0-9A-Fa-f]{6}/;
|
|
16
|
-
const
|
|
15
|
+
const PASSWORD_MIN_LENGTH = 8;
|
|
16
|
+
const PASSWORD_MAX_LENGTH = 50;
|
|
17
17
|
|
|
18
18
|
const STANDART_DATE_FORMAT = 'yyyy-MM-dd';
|
|
19
19
|
|
|
@@ -67,10 +67,6 @@ export function emptyArrayValidation (message) {
|
|
|
67
67
|
return (v, $t = (s) => s) => (Array.isArray(v) && v.length > 0) || (message || $t('components.thisFieldIsRequired'));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
export function integerValidation (message) {
|
|
71
|
-
return (v, $t = (s) => s) => !v || !!(v + '').match(INTEGER_REGEXP) || (message || $t('components.thisFieldMustBeANumberFormatZero'));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
70
|
export function doubleValidation (message) {
|
|
75
71
|
return (v, $t = (s) => s) => !v || !!(v + '').match(DOUBLE_REGEXP) || (message || $t('components.thisFieldMustBeANumberFormatZero'));
|
|
76
72
|
}
|
|
@@ -188,8 +184,38 @@ export function dateSameOrBeforeValidation (dateCompare, message) {
|
|
|
188
184
|
};
|
|
189
185
|
}
|
|
190
186
|
|
|
191
|
-
export function
|
|
187
|
+
export function hasUppercaseValidation(message) {
|
|
188
|
+
return (v, $t) => {
|
|
189
|
+
return !isEmpty(v) && /\p{Lu}/u.test(v + '') || (message || $t('components.atLeastOneUppercaseLetterRequired'));
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function hasLowercaseValidation(message) {
|
|
194
|
+
return (v, $t) => {
|
|
195
|
+
return !isEmpty(v) && /\p{Ll}/u.test(v + '') || (message || $t('components.atLeastOneLowercaseLetterRequired'));
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function hasDigitValidation(message) {
|
|
200
|
+
return (v, $t) => {
|
|
201
|
+
return !isEmpty(v) && /\d/.test(v + '') || (message || $t('components.atLeastOneNumberRequired'));
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function hasSpecialCharValidation(message) {
|
|
192
206
|
return (v, $t) => {
|
|
193
|
-
return !isEmpty(v) &&
|
|
207
|
+
return !isEmpty(v) && /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(v + '') || (message || $t('components.atLeastOneSpecialCharacterRequired'));
|
|
194
208
|
};
|
|
195
209
|
}
|
|
210
|
+
|
|
211
|
+
export function accountPasswordValidation(message) {
|
|
212
|
+
return [
|
|
213
|
+
emptyValidation(message),
|
|
214
|
+
minLengthValidation(PASSWORD_MIN_LENGTH, message),
|
|
215
|
+
lengthValidation(PASSWORD_MAX_LENGTH, message),
|
|
216
|
+
hasUppercaseValidation(message),
|
|
217
|
+
hasLowercaseValidation(message),
|
|
218
|
+
hasDigitValidation(message),
|
|
219
|
+
hasSpecialCharValidation(message),
|
|
220
|
+
];
|
|
221
|
+
}
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
dateAfterValidation,
|
|
8
8
|
dateSameOrAfterValidation,
|
|
9
9
|
dateSameOrBeforeValidation,
|
|
10
|
-
|
|
10
|
+
hasUppercaseValidation,
|
|
11
|
+
hasLowercaseValidation,
|
|
12
|
+
hasDigitValidation,
|
|
13
|
+
hasSpecialCharValidation,
|
|
11
14
|
} from './validators';
|
|
12
15
|
|
|
13
16
|
describe('Validators', () => {
|
|
@@ -83,16 +86,50 @@ describe('Validators', () => {
|
|
|
83
86
|
expect(mediumTextValidation()('0'.repeat(16777216), $t)).toEqual('components.mediumTextLength');
|
|
84
87
|
});
|
|
85
88
|
|
|
89
|
+
test('hasUppercaseValidation', () => {
|
|
90
|
+
expect(hasUppercaseValidation()('', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
|
|
91
|
+
expect(hasUppercaseValidation()(' ', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
|
|
92
|
+
expect(hasUppercaseValidation()(null, $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
|
|
93
|
+
expect(hasUppercaseValidation()(undefined, $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
|
|
94
|
+
expect(hasUppercaseValidation()('A', $t)).toEqual(true);
|
|
95
|
+
expect(hasUppercaseValidation()(' Test ', $t)).toEqual(true);
|
|
96
|
+
expect(hasUppercaseValidation()(' test ', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
|
|
97
|
+
expect(hasUppercaseValidation()(' Тест ', $t)).toEqual(true);
|
|
98
|
+
expect(hasUppercaseValidation()(' тест ', $t)).toEqual('components.atLeastOneUppercaseLetterRequired');
|
|
99
|
+
expect(hasUppercaseValidation()(' ТЕСТ ', $t)).toEqual(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('hasLowercaseValidation', () => {
|
|
103
|
+
expect(hasLowercaseValidation()('', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
|
|
104
|
+
expect(hasLowercaseValidation()(' ', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
|
|
105
|
+
expect(hasLowercaseValidation()(null, $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
|
|
106
|
+
expect(hasLowercaseValidation()(undefined, $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
|
|
107
|
+
expect(hasLowercaseValidation()('a', $t)).toEqual(true);
|
|
108
|
+
expect(hasLowercaseValidation()(' test ', $t)).toEqual(true);
|
|
109
|
+
expect(hasLowercaseValidation()('A', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
|
|
110
|
+
expect(hasLowercaseValidation()(' Test ', $t)).toEqual(true);
|
|
111
|
+
expect(hasLowercaseValidation()(' Тест ', $t)).toEqual(true);
|
|
112
|
+
expect(hasLowercaseValidation()(' тест ', $t)).toEqual(true);
|
|
113
|
+
expect(hasLowercaseValidation()(' ТЕСТ ', $t)).toEqual('components.atLeastOneLowercaseLetterRequired');
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('hasDigitValidation', () => {
|
|
117
|
+
expect(hasDigitValidation()('', $t)).toEqual('components.atLeastOneNumberRequired');
|
|
118
|
+
expect(hasDigitValidation()(' ', $t)).toEqual('components.atLeastOneNumberRequired');
|
|
119
|
+
expect(hasDigitValidation()(null, $t)).toEqual('components.atLeastOneNumberRequired');
|
|
120
|
+
expect(hasDigitValidation()(undefined, $t)).toEqual('components.atLeastOneNumberRequired');
|
|
121
|
+
expect(hasDigitValidation()('1', $t)).toEqual(true);
|
|
122
|
+
expect(hasDigitValidation()(' 1 ', $t)).toEqual(true);
|
|
123
|
+
expect(hasDigitValidation()('a', $t)).toEqual('components.atLeastOneNumberRequired');
|
|
124
|
+
});
|
|
86
125
|
|
|
87
|
-
test('
|
|
88
|
-
expect(
|
|
89
|
-
expect(
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
92
|
-
expect(
|
|
93
|
-
expect(
|
|
94
|
-
expect(
|
|
95
|
-
expect(passwordValidation()(' ТестТест1 ', $t)).toEqual('components.passwordValidation');
|
|
96
|
-
expect(passwordValidation()(' ТестТест1@ ', $t)).toEqual(true);
|
|
126
|
+
test('hasSpecialCharValidation', () => {
|
|
127
|
+
expect(hasSpecialCharValidation()('', $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
|
|
128
|
+
expect(hasSpecialCharValidation()(' ', $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
|
|
129
|
+
expect(hasSpecialCharValidation()(null, $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
|
|
130
|
+
expect(hasSpecialCharValidation()(undefined, $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
|
|
131
|
+
expect(hasSpecialCharValidation()('@', $t)).toEqual(true);
|
|
132
|
+
expect(hasSpecialCharValidation()(' @ ', $t)).toEqual(true);
|
|
133
|
+
expect(hasSpecialCharValidation()('a', $t)).toEqual('components.atLeastOneSpecialCharacterRequired');
|
|
97
134
|
});
|
|
98
135
|
});
|
package/src/locales/en.js
CHANGED
|
@@ -104,7 +104,7 @@ module.exports = {
|
|
|
104
104
|
copyingToClipboardWasSuccessful: 'Copying to clipboard was successful',
|
|
105
105
|
},
|
|
106
106
|
table: {
|
|
107
|
-
new: 'New',
|
|
107
|
+
new: 'New row',
|
|
108
108
|
noResults: 'No items',
|
|
109
109
|
sortAscending: 'Sort ascending',
|
|
110
110
|
sortDescending: 'Sort descending',
|
package/src/locales/uk.js
CHANGED
|
@@ -28,8 +28,7 @@ module.exports = {
|
|
|
28
28
|
pleaseEnterValidURL: 'Будь ласка, введіть дійсний URL',
|
|
29
29
|
pleaseEnteraValidHex: 'Будь ласка, введіть дійсний код кольору',
|
|
30
30
|
passwordsDontMatch: 'Паролі не збігаються',
|
|
31
|
-
|
|
32
|
-
thisFieldMustBeANumberFormatZero: 'Це поле має містити число (формат 0.00)',
|
|
31
|
+
thisFieldMustBeANumberFormatZero: 'Це поле має містити числом (формат 0.00)',
|
|
33
32
|
theValueMustBeGreaterThanOrEqualToMinAndMax: 'Значення має бути більше або дорівнювати {min} і менше або дорівнювати {max}',
|
|
34
33
|
theValueMustBeGreaterThanOrEqualToMin: 'Значення має бути більше або дорівнювати {min}',
|
|
35
34
|
mustBeLessThanMax: 'Має бути менше ніж {max}',
|
|
@@ -61,7 +60,6 @@ module.exports = {
|
|
|
61
60
|
atLeastOneLowercaseLetterRequired: 'Має бути принаймні одна мала літера',
|
|
62
61
|
atLeastOneNumberRequired: 'Має бути принаймні одна цифра',
|
|
63
62
|
atLeastOneSpecialCharacterRequired: 'Має бути принаймні один спеціальний символ (@,$,!,%,*,?,&)',
|
|
64
|
-
dropzoneText: 'Натисніть або перетягніть файл для завантаження',
|
|
65
63
|
|
|
66
64
|
select: {
|
|
67
65
|
loading: 'Завантаження...',
|
|
@@ -105,12 +103,12 @@ module.exports = {
|
|
|
105
103
|
copyingToClipboardWasSuccessful: 'Скопійовано в буфер',
|
|
106
104
|
},
|
|
107
105
|
pagination: {
|
|
108
|
-
itemsPerPage: '
|
|
106
|
+
itemsPerPage: 'К-ть на сторінці',
|
|
109
107
|
previous: 'Попередня',
|
|
110
108
|
next: 'Наступна',
|
|
111
109
|
},
|
|
112
110
|
table: {
|
|
113
|
-
new: 'Додати',
|
|
111
|
+
new: 'Додати рядок',
|
|
114
112
|
noResults: 'Немає записів',
|
|
115
113
|
sortAscending: 'Сортувати за зростанням',
|
|
116
114
|
sortDescending: 'Сортувати за спаданням',
|
|
@@ -158,8 +156,5 @@ module.exports = {
|
|
|
158
156
|
from: 'Від',
|
|
159
157
|
to: 'До',
|
|
160
158
|
value: 'Значення',
|
|
161
|
-
}
|
|
162
|
-
passwordValidation: 'Пароль має містити принаймні 8 символів, включаючи великі літери, малі літери, цифри та спеціальні символи (@, $, !, %, *, ?, &)',
|
|
163
|
-
iveGotIt: 'Зрозуміло',
|
|
164
|
-
totalItems: 'Всього',
|
|
159
|
+
}
|
|
165
160
|
};
|