@webitel/ui-sdk 25.4.19 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webitel/ui-sdk",
3
- "version": "25.04.19",
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
+ };
@@ -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>
@@ -0,0 +1,197 @@
1
+ <template>
2
+ <article class="preset-preview">
3
+ <wt-expansion-panel
4
+ :collapsed="collapsed"
5
+ @closed="clearEdit"
6
+ >
7
+ <template #title>
8
+ <header class="preset-preview-title-wrapper">
9
+ <!--
10
+ this <div> is used only for @click.stop to be set, so that expansion panel toggle won't trigger.
11
+ it would be great to use @click.stop to wt-ratio,
12
+ but with @vue/compat build and direct input it won't be working like that :(
13
+ -->
14
+ <div @click.stop>
15
+ <wt-radio
16
+ :selected="isSelected"
17
+ :value="true"
18
+ @input="emit('preset:select', preset)"
19
+ />
20
+ </div>
21
+ <p
22
+ :title="preset.name"
23
+ class="preset-preview-name"
24
+ >
25
+ {{ preset.name }}
26
+ </p>
27
+ </header>
28
+ </template>
29
+ <template #actions="{ open }">
30
+ <wt-icon-action
31
+ v-if="!editMode"
32
+ action="edit"
33
+ @click.stop="startEdit({ open })"
34
+ />
35
+
36
+ <wt-icon-action
37
+ v-if="editMode"
38
+ action="delete"
39
+ @click.stop="emit('preset:delete', preset)"
40
+ />
41
+
42
+ <wt-icon-action
43
+ v-if="editMode"
44
+ :disabled="v$.$invalid"
45
+ action="save"
46
+ @click.stop="submitEdit"
47
+ />
48
+
49
+ <wt-icon-action
50
+ v-if="editMode"
51
+ action="cancel"
52
+ @click.stop="clearEdit"
53
+ />
54
+ </template>
55
+ <template #default>
56
+ <div class="preset-preview-content">
57
+ <preset-name-field
58
+ v-if="editMode"
59
+ v-model:model-value="editDraft.name"
60
+ :v="v$.name"
61
+ @update:model-value="nameAlreadyExistsError = false"
62
+ />
63
+
64
+ <preset-filters-preview
65
+ :filters="presetFilters"
66
+ />
67
+
68
+ <preset-description-field
69
+ v-model:model-value="editDraft.description"
70
+ :preview-mode="!editMode"
71
+ />
72
+ </div>
73
+ </template>
74
+ </wt-expansion-panel>
75
+ </article>
76
+ </template>
77
+
78
+ <script lang="ts" setup>
79
+ import {EnginePresetQuery} from "webitel-sdk";
80
+ import {computed, ref} from "vue";
81
+ import {useVuelidate} from "@vuelidate/core";
82
+ import {required} from "@vuelidate/validators";
83
+ import {AxiosError} from "axios";
84
+ import {WtExpansionPanel, WtIconAction, WtRadio} from "../../../../../../components/index";
85
+ import {createFiltersManager} from "../../../filters/index";
86
+ import PresetFiltersPreview from "../_shared/preset-filters-preview.vue";
87
+ import PresetNameField from "../_shared/input-fields/preset-name-field.vue";
88
+ import PresetDescriptionField from "../_shared/input-fields/preset-description-field.vue";
89
+
90
+ type Props = {
91
+ preset: EnginePresetQuery;
92
+ isSelected: boolean;
93
+ collapsed: boolean;
94
+ }
95
+
96
+ const props = defineProps<Props>();
97
+
98
+ const emit = defineEmits<{
99
+ 'preset:select': [EnginePresetQuery];
100
+ 'preset:update': [{ preset: EnginePresetQuery, onSuccess: () => void, onFailure: (err: AxiosError) => void }];
101
+ 'preset:delete': [EnginePresetQuery];
102
+ }>();
103
+
104
+ const presetFilters = computed(() => {
105
+ const snapshot = props.preset?.preset?.['filtersManager.toString'];
106
+ if (!snapshot) return [];
107
+
108
+ const filtersManager = createFiltersManager();
109
+ filtersManager.fromString(snapshot);
110
+
111
+ return filtersManager.getFiltersList();
112
+ });
113
+
114
+ const editMode = ref(false);
115
+
116
+ /**
117
+ * updating request in progress flag
118
+ * */
119
+ const editing = ref(false);
120
+
121
+ const nameAlreadyExistsError = ref(false);
122
+
123
+ const editDraft = ref({
124
+ name: '',
125
+ description: '',
126
+ });
127
+
128
+ const fillDraft = () => {
129
+ editDraft.value = {
130
+ name: props.preset.name,
131
+ description: props.preset.description,
132
+ };
133
+ };
134
+
135
+ fillDraft();
136
+
137
+ const v$ = useVuelidate(computed(() => {
138
+ return {
139
+ name: {
140
+ required,
141
+ alreadyExists: () => !nameAlreadyExistsError.value,
142
+ },
143
+ };
144
+ }), editDraft, {$autoDirty: true});
145
+ v$.value.$touch();
146
+
147
+ const startEdit = ({open: openExpansion}) => {
148
+ openExpansion();
149
+ editMode.value = true;
150
+ };
151
+
152
+ const clearEdit = () => {
153
+ editing.value = false;
154
+ editMode.value = false;
155
+ };
156
+
157
+ const submitEdit = () => {
158
+ const preset: EnginePresetQuery = {
159
+ ...props.preset,
160
+ ...editDraft.value,
161
+ };
162
+ const onFailure = (err: AxiosError) => {
163
+ if (err.status === 409) {
164
+ nameAlreadyExistsError.value = true;
165
+ }
166
+ editing.value = false;
167
+ };
168
+
169
+ emit('preset:update', {
170
+ preset,
171
+ onSuccess: clearEdit,
172
+ onFailure,
173
+ });
174
+ };
175
+ </script>
176
+
177
+ <style lang="scss" scoped>
178
+ .preset-preview-title-wrapper {
179
+ display: flex;
180
+ min-width: 0;
181
+ gap: var(--spacing-xs);
182
+ }
183
+
184
+ .preset-preview-name {
185
+ overflow: hidden;
186
+ flex: 1 1 0;
187
+ white-space: nowrap;
188
+ text-overflow: ellipsis;
189
+ }
190
+
191
+ .preset-preview-content {
192
+ display: flex;
193
+ flex-direction: column;
194
+ gap: var(--spacing-xs);
195
+ padding: var(--spacing-xs);
196
+ }
197
+ </style>