@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/CHANGELOG.md +9 -0
- package/dist/ui-sdk.js +1 -1
- package/dist/ui-sdk.umd.cjs +1 -1
- package/package.json +1 -1
- package/src/api/transformers/index.js +9 -6
- package/src/api/transformers/skipIf/skipIf.ts +6 -0
- package/src/modules/Filters/v2/filter-presets/api/PresetQuery.api.ts +131 -0
- package/src/modules/Filters/v2/filter-presets/components/_shared/input-fields/preset-description-field.vue +36 -0
- package/src/modules/Filters/v2/filter-presets/components/_shared/input-fields/preset-name-field.vue +31 -0
- package/src/modules/Filters/v2/filter-presets/components/_shared/preset-filters-preview.vue +51 -0
- package/src/modules/Filters/v2/filter-presets/components/apply-preset/apply-preset-action.vue +199 -0
- package/src/modules/Filters/v2/filter-presets/components/apply-preset/preset-preview.vue +197 -0
- package/src/modules/Filters/v2/filter-presets/components/save-preset/overwrite-preset-popup.vue +53 -0
- package/src/modules/Filters/v2/filter-presets/components/save-preset/save-preset-action.vue +101 -0
- package/src/modules/Filters/v2/filter-presets/components/save-preset/save-preset-popup.vue +129 -0
- package/src/modules/Filters/v2/filter-presets/index.ts +10 -0
- package/src/modules/Filters/v2/filter-presets/stores/createFilterPresetsStore.ts +14 -0
- package/src/modules/Filters/v2/filter-presets/stores/headers/headers.ts +24 -0
- package/src/modules/Filters/v2/filters/components/values/filterComponentsMap.ts +147 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
21
|
+
skipIf,
|
|
22
|
+
snakeToCamel,
|
|
23
|
+
starToSearch,
|
|
23
24
|
};
|
|
25
|
+
|
|
26
|
+
export default applyTransform;
|
|
@@ -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>
|
package/src/modules/Filters/v2/filter-presets/components/_shared/input-fields/preset-name-field.vue
ADDED
|
@@ -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>
|