@webitel/ui-sdk 25.4.18 → 25.4.20

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.
Files changed (27) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/ui-sdk.css +1 -1
  3. package/dist/ui-sdk.js +321 -314
  4. package/dist/ui-sdk.umd.cjs +5 -5
  5. package/package.json +1 -1
  6. package/src/api/transformers/index.js +9 -6
  7. package/src/api/transformers/skipIf/skipIf.ts +6 -0
  8. package/src/components/wt-empty/wt-empty.vue +1 -0
  9. package/src/components/wt-icon-action/iconMappings.js +2 -0
  10. package/src/components/wt-icon-action/wt-icon-action.vue +5 -2
  11. package/src/enums/IconAction/IconAction.enum.js +2 -0
  12. package/src/locale/en/en.js +2 -0
  13. package/src/locale/ru/ru.js +2 -0
  14. package/src/locale/ua/ua.js +2 -0
  15. package/src/modules/Filters/v2/filter-presets/api/PresetQuery.api.ts +131 -0
  16. package/src/modules/Filters/v2/filter-presets/components/_shared/input-fields/preset-description-field.vue +36 -0
  17. package/src/modules/Filters/v2/filter-presets/components/_shared/input-fields/preset-name-field.vue +31 -0
  18. package/src/modules/Filters/v2/filter-presets/components/_shared/preset-filters-preview.vue +51 -0
  19. package/src/modules/Filters/v2/filter-presets/components/apply-preset/apply-preset-action.vue +199 -0
  20. package/src/modules/Filters/v2/filter-presets/components/apply-preset/preset-preview.vue +197 -0
  21. package/src/modules/Filters/v2/filter-presets/components/save-preset/overwrite-preset-popup.vue +53 -0
  22. package/src/modules/Filters/v2/filter-presets/components/save-preset/save-preset-action.vue +101 -0
  23. package/src/modules/Filters/v2/filter-presets/components/save-preset/save-preset-popup.vue +129 -0
  24. package/src/modules/Filters/v2/filter-presets/index.ts +10 -0
  25. package/src/modules/Filters/v2/filter-presets/stores/createFilterPresetsStore.ts +14 -0
  26. package/src/modules/Filters/v2/filter-presets/stores/headers/headers.ts +24 -0
  27. package/src/modules/Filters/v2/filters/components/values/filterComponentsMap.ts +147 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "25.04.18",
3
+ "version": "25.04.20",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "vite",
@@ -8,16 +8,19 @@ import notify from './notify/notify.transformer.js';
8
8
  import sanitize from './sanitize/sanitize.transformer.js';
9
9
  import snakeToCamel from './snakeToCamel/snakeToCamel.transformer.js';
10
10
  import starToSearch from './starToSearch/starToSearch.transformer.js';
11
+ import { skipIf } from './skipIf/skipIf';
11
12
 
12
- export default applyTransform;
13
13
  export {
14
+ camelToSnake,
15
+ generateUrl,
14
16
  log,
15
17
  merge,
18
+ mergeEach,
16
19
  notify,
17
- starToSearch,
18
- camelToSnake,
19
- snakeToCamel,
20
20
  sanitize,
21
- generateUrl,
22
- mergeEach,
21
+ skipIf,
22
+ snakeToCamel,
23
+ starToSearch,
23
24
  };
25
+
26
+ export default applyTransform;
@@ -0,0 +1,6 @@
1
+ export const skipIf = (transformer: (...payload: unknown[]) => unknown, ifFn: boolean | ((...payload: unknown[]) => boolean)) => (payload: unknown) => {
2
+ if (typeof ifFn === 'function' ? ifFn() : ifFn) {
3
+ return payload;
4
+ }
5
+ return transformer(payload);
6
+ };
@@ -272,6 +272,7 @@ const onSecondaryClick = onClick('secondary');
272
272
  @use '../../css/main.scss';
273
273
 
274
274
  .wt-empty {
275
+ box-sizing: border-box;
275
276
  display: flex;
276
277
  align-items: center;
277
278
  flex-direction: column;
@@ -13,6 +13,8 @@ export const WtIconActionIconMappings = Object.freeze({
13
13
  [IconAction.COLUMNS]: 'column-select',
14
14
  [IconAction.VARIABLES]: 'variable-select',
15
15
  [IconAction.CLOSE]: 'close--filled',
16
+ [IconAction.CANCEL]: 'close',
16
17
  [IconAction.CLEAR]: 'clear',
17
18
  [IconAction.ADD_FILTER]: 'add-filter',
19
+ [IconAction.SAVE]: 'save',
18
20
  });
@@ -4,7 +4,8 @@
4
4
  <wt-icon-btn
5
5
  :disabled="disabled"
6
6
  :icon="iconAction.icon"
7
- @click="emit('click')"
7
+ @click="emit('click', $event)"
8
+ @mousedown="emit('mousedown', $event)"
8
9
  />
9
10
  </template>
10
11
  {{ t(iconAction.hint) }}
@@ -33,6 +34,8 @@ const props = defineProps({
33
34
  IconAction.HISTORY,
34
35
  IconAction.DOWNLOAD,
35
36
  IconAction.REFRESH,
37
+ IconAction.SAVE,
38
+ IconAction.CANCEL,
36
39
  ]).includes(v),
37
40
  },
38
41
  disabled: {
@@ -41,7 +44,7 @@ const props = defineProps({
41
44
  },
42
45
  });
43
46
 
44
- const emit = defineEmits(['click']);
47
+ const emit = defineEmits(['click', 'mousedown']);
45
48
 
46
49
  const { t } = useI18n();
47
50
 
@@ -13,9 +13,11 @@ const IconAction = Object.freeze({
13
13
  COLLAPSE: 'collapse',
14
14
  EXPAND: 'expand',
15
15
  CLOSE: 'close',
16
+ CANCEL: 'cancel',
16
17
  VARIABLES: 'variables',
17
18
  CLEAR: 'clear',
18
19
  ADD_FILTER: 'add-filter',
20
+ SAVE: 'save',
19
21
  });
20
22
 
21
23
  export default IconAction;
@@ -418,6 +418,8 @@ export default {
418
418
  [IconAction.CLEAR]: ({ linked }) =>
419
419
  linked('webitelUI.tableActions.filterReset'),
420
420
  [IconAction.ADD_FILTER]: ({ linked }) => linked('reusable.add'),
421
+ [IconAction.SAVE]: ({ linked }) => linked('reusable.save'),
422
+ [IconAction.CANCEL]: ({ linked }) => linked('reusable.cancel'),
421
423
  },
422
424
  },
423
425
  errorPages: {
@@ -416,6 +416,8 @@ export default {
416
416
  [IconAction.CLEAR]: ({ linked }) =>
417
417
  linked('webitelUI.tableActions.filterReset'),
418
418
  [IconAction.ADD_FILTER]: ({ linked }) => linked('reusable.add'),
419
+ [IconAction.SAVE]: ({ linked }) => linked('reusable.save'),
420
+ [IconAction.CANCEL]: ({ linked }) => linked('reusable.cancel'),
419
421
  },
420
422
  },
421
423
  errorPages: {
@@ -416,6 +416,8 @@ export default {
416
416
  [IconAction.CLEAR]: ({ linked }) =>
417
417
  linked('webitelUI.tableActions.filterReset'),
418
418
  [IconAction.ADD_FILTER]: ({ linked }) => linked('reusable.add'),
419
+ [IconAction.SAVE]: ({ linked }) => linked('reusable.save'),
420
+ [IconAction.CANCEL]: ({ linked }) => linked('reusable.cancel'),
419
421
  },
420
422
  },
421
423
  errorPages: {
@@ -0,0 +1,131 @@
1
+ import {EngineCreatePresetQueryRequest, EnginePresetQuery, PresetQueryServiceApiFactory} from 'webitel-sdk';
2
+ import {getDefaultGetListResponse, getDefaultGetParams, getDefaultInstance, getDefaultOpenAPIConfig} from '../../../../../api/defaults/index';
3
+ import applyTransform, {
4
+ camelToSnake,
5
+ merge,
6
+ notify,
7
+ snakeToCamel,
8
+ starToSearch,
9
+ skipIf,
10
+ } from '../../../../../api/transformers/index';
11
+
12
+ const instance = getDefaultInstance();
13
+ const configuration = getDefaultOpenAPIConfig();
14
+
15
+ const service = PresetQueryServiceApiFactory(configuration, '', instance);
16
+
17
+ type GetPresetListRequestConfig = {
18
+ transformers: {
19
+ useStarToSearch?: boolean,
20
+ },
21
+ };
22
+
23
+ const getPresetList = async (params, config: GetPresetListRequestConfig) => {
24
+
25
+ const useStarToSearch = config?.transformers?.useStarToSearch ?? true;
26
+
27
+ const {
28
+ page,
29
+ size,
30
+ search,
31
+ sort,
32
+ fields,
33
+ presetNamespace,
34
+ id,
35
+ } = applyTransform(params, [
36
+ merge(getDefaultGetParams()),
37
+ (params) => useStarToSearch ? starToSearch('search')(params) : params,
38
+ ]);
39
+
40
+ try {
41
+ const response = await service.searchPresetQuery(
42
+ page,
43
+ size,
44
+ search,
45
+ sort,
46
+ fields || ['id', 'name', 'preset', 'description'],
47
+ id,
48
+ );
49
+ const {items, next} = applyTransform(response.data, [
50
+ snakeToCamel(),
51
+ merge(getDefaultGetListResponse()),
52
+ ]);
53
+ return {
54
+ items: applyTransform(items, [
55
+ (items) => items.filter(({preset}) => preset.namespace === presetNamespace),
56
+ ]),
57
+ next,
58
+ };
59
+ } catch (err) {
60
+ throw applyTransform(err, [
61
+ notify,
62
+ ]);
63
+ }
64
+ };
65
+
66
+ const addPreset = async ({preset, namespace}: {
67
+ preset: EngineCreatePresetQueryRequest,
68
+ namespace: string
69
+ }): Promise<EnginePresetQuery> => {
70
+ const item = applyTransform(preset, [
71
+ camelToSnake(),
72
+ (item) => {
73
+ item.preset.namespace = namespace;
74
+ return item;
75
+ },
76
+ ]);
77
+ try {
78
+ const response = await service.createPresetQuery(item);
79
+ return applyTransform(response.data, [
80
+ snakeToCamel(),
81
+ ]);
82
+ } catch (err) {
83
+ throw applyTransform(err, [
84
+ skipIf(notify, (err) => err.status === 409),
85
+ ]);
86
+ }
87
+ };
88
+
89
+ const updatePreset = async ({item: itemInstance, id}) => {
90
+ const item = applyTransform(itemInstance, [
91
+ camelToSnake(),
92
+ ]);
93
+ try {
94
+ const response = await service.updatePresetQuery(id, item);
95
+ return applyTransform(response.data, [
96
+ snakeToCamel(),
97
+ ]);
98
+ } catch (err) {
99
+ throw applyTransform(err, [
100
+ skipIf(notify, (err) => err.status === 409),
101
+ ]);
102
+ }
103
+ };
104
+
105
+ const deletePreset = async ({id}) => {
106
+ try {
107
+ const response = await service.deletePresetQuery(id);
108
+ return applyTransform(response.data, [
109
+ ]);
110
+ } catch (err) {
111
+ throw applyTransform(err, [
112
+ notify,
113
+ ]);
114
+ }
115
+ };
116
+
117
+ const PresetQueryAPI = {
118
+ getList: getPresetList,
119
+ add: addPreset,
120
+ update: updatePreset,
121
+ delete: deletePreset,
122
+ };
123
+
124
+ export {
125
+ getPresetList,
126
+ addPreset,
127
+ updatePreset,
128
+ deletePreset,
129
+ };
130
+
131
+ export default PresetQueryAPI;
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <div
3
+ v-if="props.previewMode && model"
4
+ >
5
+ <wt-label>
6
+ {{ t('reusable.description') }}
7
+ </wt-label>
8
+ <p>
9
+ {{ model }}
10
+ </p>
11
+ </div>
12
+
13
+ <wt-textarea
14
+ v-if="!props.previewMode"
15
+ :value="model"
16
+ :label="t('reusable.description')"
17
+ @input="model = $event"
18
+ />
19
+ </template>
20
+
21
+ <script setup lang="ts">
22
+ import {useI18n} from "vue-i18n";
23
+ import { WtTextarea, WtLabel } from '../../../../../../../components/index';
24
+
25
+ const model = defineModel<string>();
26
+
27
+ const props = defineProps<{
28
+ previewMode?: boolean;
29
+ }>();
30
+
31
+ const { t } = useI18n();
32
+ </script>
33
+
34
+ <style scoped lang="scss">
35
+
36
+ </style>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <wt-input
3
+ v-model="model"
4
+ :v="props.v"
5
+ :label="t('reusable.name')"
6
+ required
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import {useI18n} from "vue-i18n";
12
+ import {Validation} from '@vuelidate/core';
13
+ import { WtInput } from '../../../../../../../components/index';
14
+
15
+ type ModelValue = string;
16
+
17
+ const model = defineModel<ModelValue>();
18
+
19
+ type Props = {
20
+ v: Validation;
21
+ };
22
+
23
+ const props = defineProps<Props>();
24
+
25
+ const { t } = useI18n();
26
+
27
+ </script>
28
+
29
+ <style scoped lang="scss">
30
+
31
+ </style>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <div class="save-preset-filters-preview">
3
+ <wt-label>
4
+ {{ t('webitelUI.filters.filterName') }}
5
+ </wt-label>
6
+ <dynamic-filter-panel-wrapper>
7
+ <template #filters>
8
+ <dynamic-filter-preview
9
+ v-for="(filter) of props.filters"
10
+ :key="filter.name"
11
+ :filter="filter"
12
+ dummy
13
+ >
14
+ <template #info>
15
+ <component
16
+ :is="getFilterFieldComponent(filter.name, 'previewField')"
17
+ :value="filter.value"
18
+ >
19
+ </component>
20
+ </template>
21
+ </dynamic-filter-preview>
22
+ </template>
23
+ </dynamic-filter-panel-wrapper>
24
+ </div>
25
+
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ import {useI18n} from "vue-i18n";
30
+ import DynamicFilterPreview
31
+ from "../../../filters/components/preview/dynamic-filter-preview.vue";
32
+ import DynamicFilterPanelWrapper
33
+ from "../../../filters/components/dynamic-filter-panel-wrapper.vue";
34
+ import type {IFilter} from "../../../filters/types/Filter.d.ts";
35
+ import {getFilterFieldComponent} from "../../../filters/components/values/filterComponentsMap";
36
+
37
+ type Props = {
38
+ filters: IFilter[];
39
+ };
40
+
41
+ const props = defineProps<Props>();
42
+
43
+ const { t } = useI18n();
44
+
45
+ </script>
46
+
47
+ <style scoped lang="scss">
48
+ .preset-filters-preview {
49
+
50
+ }
51
+ </style>
@@ -0,0 +1,199 @@
1
+ <template>
2
+ <div class="apply-preset-action">
3
+ <wt-icon-btn
4
+ icon="load-preset"
5
+ @click="showPresetsList = true"
6
+ />
7
+ <wt-popup
8
+ :shown="showPresetsList"
9
+ size="sm"
10
+ @close="showPresetsList = false"
11
+ >
12
+ <template #title>
13
+ {{ `${t('vocabulary.apply')} ${t('filters.preset.preset').toLowerCase()}` }}
14
+ </template>
15
+
16
+ <template #main>
17
+ <section class="apply-preset-main-content">
18
+ <wt-search-bar
19
+ :value="search"
20
+ @search="search = $event"
21
+ />
22
+
23
+ <wt-empty
24
+ v-show="showEmpty"
25
+ :image="imageEmpty"
26
+ :text="textEmpty"
27
+ />
28
+
29
+ <section class="available-presets-list">
30
+ <preset-preview
31
+ v-for="(preset, index) of dataList"
32
+ :key="preset.id"
33
+ :collapsed="!!index"
34
+ :is-selected="preset === selectedPreset"
35
+ :preset="preset"
36
+ @preset:select="selectedPreset = preset"
37
+ @preset:update="updatePreset"
38
+ @preset:delete="() => deleteEls([preset])"
39
+ />
40
+ </section>
41
+ <!-- TODO: infinite scroll -->
42
+ <!-- <wt-intersection-observer-->
43
+ <!-- :loading="isLoading"-->
44
+ <!-- :next="false"-->
45
+ <!-- @next="updatePage(page + 1)"-->
46
+ <!-- />-->
47
+ </section>
48
+ </template>
49
+
50
+ <template #actions>
51
+ <wt-button
52
+ :disabled="!selectedPreset"
53
+ @click="applySelectedPreset"
54
+ >
55
+ {{ t('vocabulary.apply') }}
56
+ </wt-button>
57
+ <wt-button
58
+ color="secondary"
59
+ @click="showPresetsList = false"
60
+ >
61
+ {{ t('reusable.cancel') }}
62
+ </wt-button>
63
+ </template>
64
+ </wt-popup>
65
+ </div>
66
+ </template>
67
+
68
+ <script lang="ts" setup>
69
+ import {computed, inject, ref, watch} from "vue";
70
+ import {useI18n} from "vue-i18n";
71
+ import {type StoreDefinition, storeToRefs } from "pinia";
72
+ import {WtButton, WtIconBtn, WtPopup, WtSearchBar, WtEmpty} from "../../../../../../components/index";
73
+ import {useTableEmpty} from "../../../../../TableComponentModule/composables/useTableEmpty";
74
+ import PresetQueryAPI from '../../api/PresetQuery.api.ts';
75
+ import PresetPreview from "./preset-preview.vue";
76
+
77
+ const props = defineProps<{
78
+ /**
79
+ * presets "section" namespace
80
+ */
81
+ namespace: string;
82
+ usePresetsStore: StoreDefinition;
83
+ }>();
84
+
85
+ const emit = defineEmits<{
86
+ apply: [string];
87
+ }>();
88
+
89
+ const eventBus = inject('$eventBus');
90
+
91
+ const {t} = useI18n();
92
+
93
+ const showPresetsList = ref(false);
94
+
95
+ const presetsStore = props.usePresetsStore();
96
+ const {
97
+ dataList,
98
+ error,
99
+ isLoading,
100
+ filtersManager,
101
+ page,
102
+ } = storeToRefs(presetsStore);
103
+
104
+ const {
105
+ loadDataList,
106
+ initialize,
107
+ updateSize,
108
+ deleteEls,
109
+ } = presetsStore;
110
+
111
+ updateSize(1000);
112
+
113
+ const {
114
+ showEmpty,
115
+ image: imageEmpty,
116
+ text: textEmpty,
117
+ } = useTableEmpty({
118
+ dataList,
119
+ isLoading,
120
+ error,
121
+ filters: computed(() => filtersManager.value.getAllValues()),
122
+ });
123
+
124
+ filtersManager.value.addFilter({name: 'presetNamespace', value: props.namespace});
125
+
126
+ watch(showPresetsList, () => {
127
+ initialize();
128
+
129
+ watch(showPresetsList, (value) => {
130
+ if (value) {
131
+ loadDataList();
132
+ }
133
+ });
134
+ }, {once: true});
135
+
136
+ const search = computed({
137
+ get: () => {
138
+ return filtersManager.value.getFilter('search')?.value || '';
139
+ },
140
+ set: (value) => {
141
+ filtersManager.value.addFilter({name: 'search', value});
142
+ }
143
+ });
144
+
145
+ const selectedPreset = ref();
146
+
147
+ const applySelectedPreset = () => {
148
+ const filtersSnapshot = selectedPreset.value.preset['filtersManager.toString'];
149
+ emit('apply', filtersSnapshot);
150
+
151
+ selectedPreset.value = null;
152
+ showPresetsList.value = false;
153
+ };
154
+
155
+ const updatePreset = async ({preset, onSuccess, onFailure}) => {
156
+ try {
157
+ await PresetQueryAPI.update({
158
+ item: { ...preset, section: props.namespace },
159
+ id: preset.id,
160
+ });
161
+ onSuccess();
162
+ return loadDataList();
163
+ } catch (err) {
164
+ onFailure(err);
165
+ throw err;
166
+ }
167
+ };
168
+ </script>
169
+
170
+ <style lang="scss" scoped>
171
+ .apply-preset-action .wt-popup {
172
+ :deep(.wt-popup__popup) {
173
+ height: 480px;
174
+ }
175
+ }
176
+
177
+ .apply-preset-main-content {
178
+ display: flex;
179
+ height: 100%;
180
+ flex-direction: column;
181
+ gap: var(--spacing-xs);
182
+
183
+ .wt-empty {
184
+ flex-grow: 1;
185
+ max-width: 100%;
186
+ }
187
+ }
188
+
189
+ .available-presets-list {
190
+ @extend %wt-scrollbar;
191
+
192
+ display: flex;
193
+ overflow-y: auto;
194
+ flex-direction: column;
195
+ max-height: 400px;
196
+ gap: var(--spacing-xs);
197
+ scrollbar-gutter: stable;
198
+ }
199
+ </style>