@webitel/ui-datalist 26.8.11 → 26.8.13
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/modules/filters/components/config/dynamic-view/dynamic-filter-config-form.vue +1 -0
- package/src/modules/filters/components/config/static-view/__tests__/static-filter-field.spec.ts +127 -0
- package/src/modules/filters/components/config/static-view/static-filter-field.vue +17 -2
- package/src/modules/filters/components/search-bar/__tests__/dynamic-filter-search.spec.ts +97 -0
- package/src/modules/filters/components/search-bar/dynamic-filter-search.vue +7 -5
- package/src/modules/filters/composables/useFilterConfigsToolkit.ts +5 -5
- package/src/modules/filters/composables/useFilterValueChange.ts +1 -10
- package/src/modules/filters/modules/filterConfig/classes/FilterConfig.ts +9 -1
- package/src/modules/filters/modules/filterConfig/components/_shared/date-time-filter/date-time-options/__tests__/date-time-options-filter-value-field.spec.ts +43 -0
- package/src/modules/filters/modules/filterConfig/components/_shared/date-time-filter/date-time-options/date-time-options-filter-value-field.vue +81 -43
- package/src/modules/filters/modules/filterConfig/components/_shared/date-time-filter/filterConfig.ts +27 -0
- package/src/modules/filters/modules/filterConfig/components/_shared/string-filter/string-filter-value-field.vue +11 -2
- package/src/modules/filters/modules/filterConfig/components/agent/agent-filter-value-field.vue +6 -7
- package/src/modules/filters/modules/filterConfig/components/rating/rating-from-to-filter-value-field.vue +33 -7
- package/src/modules/filters/modules/filterConfig/index.ts +2 -1
- package/types/.tsbuildinfo +1 -1
- package/types/modules/filters/composables/useFilterValueChange.d.ts +0 -10
- package/types/modules/filters/modules/filterConfig/classes/FilterConfig.d.ts +5 -1
- package/types/modules/filters/modules/filterConfig/components/_shared/date-time-filter/date-time-options/date-time-options-filter-value-field.vue.d.ts +10 -0
- package/types/modules/filters/modules/filterConfig/components/_shared/date-time-filter/filterConfig.d.ts +7 -0
- package/types/modules/filters/modules/filterConfig/components/_shared/string-filter/string-filter-value-field.vue.d.ts +2 -0
- package/types/modules/filters/modules/filterConfig/components/agent/agent-filter-value-field.vue.d.ts +2 -2
- package/types/modules/filters/modules/filterConfig/components/rating/rating-from-to-filter-value-field.vue.d.ts +5 -3
- package/types/modules/filters/modules/filterConfig/index.d.ts +2 -1
package/package.json
CHANGED
package/src/modules/filters/components/config/static-view/__tests__/static-filter-field.spec.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createFiltersManager,
|
|
6
|
+
type IFiltersManager,
|
|
7
|
+
} from '../../../../classes/FiltersManager';
|
|
8
|
+
import { createFilterConfig } from '../../../../modules/filterConfig/classes/createFilterConfig';
|
|
9
|
+
import { FilterOption } from '../../../../modules/filterConfig/enums/FilterOption';
|
|
10
|
+
import StaticFilterField from '../static-filter-field.vue';
|
|
11
|
+
|
|
12
|
+
/* stands in for a real value input: the field under test only cares that it
|
|
13
|
+
emits update:modelValue */
|
|
14
|
+
const StubInput = {
|
|
15
|
+
props: [
|
|
16
|
+
'modelValue',
|
|
17
|
+
],
|
|
18
|
+
emits: [
|
|
19
|
+
'update:modelValue',
|
|
20
|
+
],
|
|
21
|
+
template: '<div class="stub-input" />',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const deletableConfig = createFilterConfig({
|
|
25
|
+
name: FilterOption.Agent,
|
|
26
|
+
valueInputComponent: StubInput,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/* same shape as the queue logs date range: a seeded default the list cannot run without */
|
|
30
|
+
const notDeletableConfig = createFilterConfig({
|
|
31
|
+
name: FilterOption.JoinedAt,
|
|
32
|
+
notDeletable: true,
|
|
33
|
+
valueInputComponent: StubInput,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const mountField = (
|
|
37
|
+
filterConfig: typeof deletableConfig,
|
|
38
|
+
filtersManager: IFiltersManager,
|
|
39
|
+
) =>
|
|
40
|
+
mount(StaticFilterField, {
|
|
41
|
+
props: {
|
|
42
|
+
filterConfig,
|
|
43
|
+
filter: filtersManager.getFilter(filterConfig.name),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const emitValue = (wrapper: ReturnType<typeof mountField>, value: unknown) =>
|
|
48
|
+
wrapper.findComponent(StubInput).vm.$emit('update:modelValue', value);
|
|
49
|
+
|
|
50
|
+
describe('StaticFilterField', () => {
|
|
51
|
+
let filtersManager: IFiltersManager;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
filtersManager = createFiltersManager();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('adds the filter on its first value', () => {
|
|
58
|
+
const wrapper = mountField(deletableConfig, filtersManager);
|
|
59
|
+
|
|
60
|
+
emitValue(
|
|
61
|
+
wrapper,
|
|
62
|
+
[
|
|
63
|
+
1,
|
|
64
|
+
],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(wrapper.emitted('add:filter')).toEqual([
|
|
68
|
+
[
|
|
69
|
+
{
|
|
70
|
+
name: FilterOption.Agent,
|
|
71
|
+
value: [
|
|
72
|
+
1,
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('updates an already applied filter', () => {
|
|
80
|
+
filtersManager.addFilter({
|
|
81
|
+
name: FilterOption.Agent,
|
|
82
|
+
value: [
|
|
83
|
+
1,
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const wrapper = mountField(deletableConfig, filtersManager);
|
|
88
|
+
|
|
89
|
+
emitValue(
|
|
90
|
+
wrapper,
|
|
91
|
+
[
|
|
92
|
+
2,
|
|
93
|
+
],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
expect(wrapper.emitted('add:filter')).toBeUndefined();
|
|
97
|
+
expect(wrapper.emitted('update:filter')).toHaveLength(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('deletes an applied filter when its field is cleared', () => {
|
|
101
|
+
filtersManager.addFilter({
|
|
102
|
+
name: FilterOption.Agent,
|
|
103
|
+
value: [
|
|
104
|
+
1,
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const wrapper = mountField(deletableConfig, filtersManager);
|
|
109
|
+
|
|
110
|
+
emitValue(wrapper, []);
|
|
111
|
+
|
|
112
|
+
expect(wrapper.emitted('delete:filter')).toHaveLength(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('keeps a notDeletable filter when its field is cleared', () => {
|
|
116
|
+
filtersManager.addFilter({
|
|
117
|
+
name: FilterOption.JoinedAt,
|
|
118
|
+
value: 'rdt_today',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const wrapper = mountField(notDeletableConfig, filtersManager);
|
|
122
|
+
|
|
123
|
+
emitValue(wrapper, undefined);
|
|
124
|
+
|
|
125
|
+
expect(wrapper.emitted('delete:filter')).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
:class="{
|
|
4
|
+
'static-filter-field--pair': hidePresets,
|
|
5
|
+
}"
|
|
6
|
+
class="static-filter-field"
|
|
7
|
+
>
|
|
3
8
|
<component
|
|
4
9
|
:is="filterConfig.valueInputComponent"
|
|
10
|
+
:disable-default-value="true"
|
|
5
11
|
:disable-validation="true /*for static filters validation is not needed (different presentation with dynamic filters)*/"
|
|
6
12
|
:filter-config="filterConfig"
|
|
7
13
|
:hide-label="true /*for static filters need to hide label and display placeholder (different presentation with dynamic filters)*/"
|
|
14
|
+
:static-view="true"
|
|
8
15
|
:model-value="filterValue"
|
|
9
16
|
:placeholder="filterConfig.label"
|
|
10
17
|
@update:model-value="onValueChange"
|
|
@@ -29,6 +36,10 @@ const emit = defineEmits<StaticFilterEmits>();
|
|
|
29
36
|
|
|
30
37
|
const filterValue = computed(() => props.filter?.value);
|
|
31
38
|
|
|
39
|
+
const hidePresets = computed(
|
|
40
|
+
() => 'hidePresets' in props.filterConfig && props.filterConfig.hidePresets,
|
|
41
|
+
);
|
|
42
|
+
|
|
32
43
|
const { onValueChange } = useFilterValueChange({
|
|
33
44
|
filterConfig: () => props.filterConfig,
|
|
34
45
|
filter: () => props.filter,
|
|
@@ -36,9 +47,13 @@ const { onValueChange } = useFilterValueChange({
|
|
|
36
47
|
});
|
|
37
48
|
</script>
|
|
38
49
|
|
|
39
|
-
<style
|
|
50
|
+
<style scoped>
|
|
40
51
|
.static-filter-field {
|
|
41
52
|
flex: 1;
|
|
42
53
|
min-width: 0;
|
|
43
54
|
}
|
|
55
|
+
|
|
56
|
+
.static-filter-field--pair {
|
|
57
|
+
grid-column: span 2;
|
|
58
|
+
}
|
|
44
59
|
</style>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { reactive } from 'vue';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createFiltersManager,
|
|
7
|
+
type IFiltersManager,
|
|
8
|
+
} from '../../../classes/FiltersManager';
|
|
9
|
+
import DynamicFilterSearch from '../dynamic-filter-search.vue';
|
|
10
|
+
|
|
11
|
+
const searchModeOptions = [
|
|
12
|
+
{
|
|
13
|
+
value: 'name',
|
|
14
|
+
text: 'Name',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: 'about',
|
|
18
|
+
text: 'About',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const newFiltersManager = () =>
|
|
23
|
+
reactive(createFiltersManager()) as IFiltersManager;
|
|
24
|
+
|
|
25
|
+
const mountSearch = (
|
|
26
|
+
filtersManager: IFiltersManager,
|
|
27
|
+
props: Record<string, unknown> = {},
|
|
28
|
+
) =>
|
|
29
|
+
shallowMount(DynamicFilterSearch, {
|
|
30
|
+
props: {
|
|
31
|
+
filtersManager,
|
|
32
|
+
...props,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/* wt-search-bar is an async component, shallowMount renders it as the root stub */
|
|
37
|
+
const searchBarValue = (wrapper: ReturnType<typeof mountSearch>) =>
|
|
38
|
+
wrapper.attributes('value');
|
|
39
|
+
|
|
40
|
+
describe('dynamic-filter-search', () => {
|
|
41
|
+
it('restores the value of the filter of the current search mode', async () => {
|
|
42
|
+
const filtersManager = newFiltersManager();
|
|
43
|
+
|
|
44
|
+
const wrapper = mountSearch(filtersManager, {
|
|
45
|
+
searchModeOptions,
|
|
46
|
+
searchMode: 'name',
|
|
47
|
+
isFiltersRestoring: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
filtersManager.addFilter({
|
|
51
|
+
name: 'name',
|
|
52
|
+
value: 'john',
|
|
53
|
+
});
|
|
54
|
+
await wrapper.setProps({
|
|
55
|
+
isFiltersRestoring: false,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(searchBarValue(wrapper)).toBe('john');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('switches the search mode to the restored search filter', async () => {
|
|
62
|
+
const filtersManager = newFiltersManager();
|
|
63
|
+
|
|
64
|
+
const wrapper = mountSearch(filtersManager, {
|
|
65
|
+
searchModeOptions,
|
|
66
|
+
searchMode: '',
|
|
67
|
+
isFiltersRestoring: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
filtersManager.addFilter({
|
|
71
|
+
name: 'about',
|
|
72
|
+
value: 'manager',
|
|
73
|
+
});
|
|
74
|
+
await wrapper.setProps({
|
|
75
|
+
isFiltersRestoring: false,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(searchBarValue(wrapper)).toBe('manager');
|
|
79
|
+
expect(wrapper.emitted('update:searchMode')?.at(-1)).toEqual([
|
|
80
|
+
'about',
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('restores the value of a single search filter', () => {
|
|
85
|
+
const filtersManager = newFiltersManager();
|
|
86
|
+
filtersManager.addFilter({
|
|
87
|
+
name: 'q',
|
|
88
|
+
value: 'john',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const wrapper = mountSearch(filtersManager, {
|
|
92
|
+
singleSearchName: 'q',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(searchBarValue(wrapper)).toBe('john');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -155,12 +155,14 @@ watch(
|
|
|
155
155
|
(next) => {
|
|
156
156
|
if (next) return;
|
|
157
157
|
|
|
158
|
-
const searchModes =
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
const searchModes = hasSearchModes.value
|
|
159
|
+
? (props.searchModeOptions?.map((option) => option.value) ?? [])
|
|
160
|
+
: [
|
|
161
|
+
defaultSearchName,
|
|
162
|
+
];
|
|
163
|
+
|
|
161
164
|
if (hasSearchModes.value && !searchMode.value) {
|
|
162
|
-
searchMode.value =
|
|
163
|
-
props.searchModeOptions?.map((option) => option.value)[0] || '';
|
|
165
|
+
searchMode.value = searchModes[0] || '';
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
for (const mode of searchModes) {
|
|
@@ -62,17 +62,17 @@ export const useFilterConfigsToolkit = ({
|
|
|
62
62
|
* make filterConfigs from standard filterOptions
|
|
63
63
|
*/
|
|
64
64
|
.map((opt) => {
|
|
65
|
-
if (opt instanceof FilterConfig) {
|
|
66
|
-
return opt;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
65
|
if (typeof opt === 'string') {
|
|
70
66
|
return createFilterConfig({
|
|
71
67
|
name: opt,
|
|
72
68
|
});
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
|
|
71
|
+
if (opt instanceof FilterConfig || opt.valueInputComponent) {
|
|
72
|
+
return opt as BaseFilterConfig;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return createFilterConfig({
|
|
76
76
|
...opt,
|
|
77
77
|
name: opt.name,
|
|
78
78
|
});
|
|
@@ -10,16 +10,6 @@ type StaticFilterEmit = <K extends keyof StaticFilterEmits>(
|
|
|
10
10
|
...args: StaticFilterEmits[K]
|
|
11
11
|
) => void;
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Turns a new filter value into the right manager event:
|
|
15
|
-
* - empty value (except booleans) → `delete:filter` (only if the filter exists)
|
|
16
|
-
* - no filter yet → `add:filter`
|
|
17
|
-
* - filter exists → `update:filter`, keeping its label
|
|
18
|
-
*
|
|
19
|
-
* Shared by the static filter field and the column filter.
|
|
20
|
-
*
|
|
21
|
-
* [WTEL-7727](https://webitel.atlassian.net/browse/WTEL-7727)
|
|
22
|
-
*/
|
|
23
13
|
export const useFilterValueChange = ({
|
|
24
14
|
filterConfig,
|
|
25
15
|
filter,
|
|
@@ -35,6 +25,7 @@ export const useFilterValueChange = ({
|
|
|
35
25
|
|
|
36
26
|
if (isEmpty(value) && typeof value !== 'boolean') {
|
|
37
27
|
if (!currentFilter) return;
|
|
28
|
+
if (config.notDeletable) return;
|
|
38
29
|
return emit('delete:filter', currentFilter);
|
|
39
30
|
}
|
|
40
31
|
|
|
@@ -9,6 +9,7 @@ export interface BaseFilterConfig {
|
|
|
9
9
|
valuePreviewComponent: Component;
|
|
10
10
|
label?: ReturnType<MessageResolver> | string;
|
|
11
11
|
notDeletable?: boolean;
|
|
12
|
+
showFilterName?: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export type FilterConfigBaseParams = {
|
|
@@ -47,7 +48,14 @@ export type FilterConfigSearchMethodParams = [
|
|
|
47
48
|
}?,
|
|
48
49
|
];
|
|
49
50
|
|
|
50
|
-
export
|
|
51
|
+
export interface IDateRangeFilterConfig extends BaseFilterConfig {
|
|
52
|
+
readonly hidePresets: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type AnyFilterConfig =
|
|
56
|
+
| IWtSysTypeFilterConfig
|
|
57
|
+
| IDateRangeFilterConfig
|
|
58
|
+
| BaseFilterConfig;
|
|
51
59
|
|
|
52
60
|
/**
|
|
53
61
|
* Loose shapes forwarded into filter-config lookup calls by
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import { RelativeDatetimeValue } from '@webitel/ui-sdk/enums';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import DateTimeOptionsFilterValueField from '../date-time-options-filter-value-field.vue';
|
|
6
|
+
|
|
7
|
+
const mountField = (props: Record<string, unknown> = {}) =>
|
|
8
|
+
shallowMount(DateTimeOptionsFilterValueField, {
|
|
9
|
+
props,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('DateTimeOptionsFilterValueField default value', () => {
|
|
13
|
+
it('preselects today when it is the only value source', () => {
|
|
14
|
+
const wrapper = mountField();
|
|
15
|
+
|
|
16
|
+
expect(wrapper.emitted('update:modelValue')).toEqual([
|
|
17
|
+
[
|
|
18
|
+
RelativeDatetimeValue.Today,
|
|
19
|
+
],
|
|
20
|
+
]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
the static panel lists every configured filter, so a field seeding itself
|
|
25
|
+
would apply a filter the user never set
|
|
26
|
+
*/
|
|
27
|
+
it('seeds nothing when disableDefaultValue is set', () => {
|
|
28
|
+
const wrapper = mountField({
|
|
29
|
+
disableDefaultValue: true,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('leaves an existing value alone', () => {
|
|
36
|
+
const wrapper = mountField({
|
|
37
|
+
disableDefaultValue: true,
|
|
38
|
+
modelValue: RelativeDatetimeValue.ThisWeek,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<div
|
|
3
|
+
:class="{
|
|
4
|
+
'date-time-options-filter-value-field--range': hidePresets,
|
|
5
|
+
}"
|
|
6
|
+
class="date-time-options-filter-value-field"
|
|
7
|
+
>
|
|
8
|
+
<template v-if="!hidePresets">
|
|
9
|
+
<wt-radio
|
|
10
|
+
v-for="value of radioOpts"
|
|
11
|
+
:key="value"
|
|
12
|
+
:selected="selectedRadioValue"
|
|
13
|
+
:label="t(`webitelUI.filters.datetime.${value}`)"
|
|
14
|
+
:value="value"
|
|
15
|
+
@update:selected="handleRadioChange"
|
|
16
|
+
/>
|
|
17
|
+
</template>
|
|
11
18
|
<wt-datepicker
|
|
12
19
|
v-if="showDatepickers"
|
|
13
20
|
:model-value="absoluteModel?.from"
|
|
14
21
|
:label="t('reusable.from')"
|
|
15
22
|
show-time
|
|
16
23
|
required
|
|
17
|
-
|
|
24
|
+
:v="!disableValidation && v$.from"
|
|
18
25
|
@update:model-value="changeAbsoluteValue($event, 'from')"
|
|
19
26
|
/>
|
|
20
27
|
<wt-datepicker
|
|
@@ -23,7 +30,7 @@
|
|
|
23
30
|
:label="t('reusable.to')"
|
|
24
31
|
show-time
|
|
25
32
|
required
|
|
26
|
-
|
|
33
|
+
:v="!disableValidation && v$.to"
|
|
27
34
|
@update:model-value="changeAbsoluteValue($event, 'to')"
|
|
28
35
|
/>
|
|
29
36
|
</div>
|
|
@@ -34,14 +41,12 @@ import { useVuelidate } from '@vuelidate/core';
|
|
|
34
41
|
import { required } from '@vuelidate/validators';
|
|
35
42
|
import { WtRadio } from '@webitel/ui-sdk/components';
|
|
36
43
|
import { RelativeDatetimeValue } from '@webitel/ui-sdk/enums';
|
|
37
|
-
import { isEmpty } from '@webitel/ui-sdk/scripts';
|
|
44
|
+
import { isEmpty, normalizeToTimestamp } from '@webitel/ui-sdk/scripts';
|
|
38
45
|
import { endOfToday, startOfToday } from 'date-fns';
|
|
39
|
-
import { computed,
|
|
46
|
+
import { computed, onMounted, watch } from 'vue';
|
|
40
47
|
import { useI18n } from 'vue-i18n';
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
disableValidation?: boolean;
|
|
44
|
-
}>();
|
|
49
|
+
import type { IDateRangeFilterConfig } from '../../../../classes/FilterConfig';
|
|
45
50
|
|
|
46
51
|
const model = defineModel<
|
|
47
52
|
| RelativeDatetimeValue
|
|
@@ -51,6 +56,19 @@ const model = defineModel<
|
|
|
51
56
|
}
|
|
52
57
|
>();
|
|
53
58
|
|
|
59
|
+
const props = defineProps<{
|
|
60
|
+
filterConfig?: IDateRangeFilterConfig;
|
|
61
|
+
disableValidation?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* @description
|
|
64
|
+
* Suppresses the preselected preset. The static filters panel lists every
|
|
65
|
+
* configured filter, so a field that seeds itself would apply a filter the
|
|
66
|
+
* user never set.
|
|
67
|
+
*/
|
|
68
|
+
disableDefaultValue?: boolean;
|
|
69
|
+
staticView?: boolean;
|
|
70
|
+
}>();
|
|
71
|
+
|
|
54
72
|
const emit = defineEmits<{
|
|
55
73
|
'update:invalid': [
|
|
56
74
|
boolean,
|
|
@@ -59,6 +77,10 @@ const emit = defineEmits<{
|
|
|
59
77
|
|
|
60
78
|
const { t } = useI18n();
|
|
61
79
|
|
|
80
|
+
const hidePresets = computed(
|
|
81
|
+
() => !!props.staticView && !!props.filterConfig?.hidePresets,
|
|
82
|
+
);
|
|
83
|
+
|
|
62
84
|
const radioOpts = [
|
|
63
85
|
RelativeDatetimeValue.Today,
|
|
64
86
|
RelativeDatetimeValue.ThisWeek,
|
|
@@ -66,37 +88,40 @@ const radioOpts = [
|
|
|
66
88
|
RelativeDatetimeValue.Custom,
|
|
67
89
|
];
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
if (!props.disableDefaultValue && isEmpty(model.value)) {
|
|
92
|
+
model.value = radioOpts[0];
|
|
93
|
+
}
|
|
70
94
|
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
/* initialize */
|
|
74
|
-
selectedRadioValue.value = radioOpts[0];
|
|
75
|
-
model.value = selectedRadioValue.value;
|
|
76
|
-
} else if (typeof model.value === 'string') {
|
|
77
|
-
/* RelativeDatetimeValue */
|
|
78
|
-
selectedRadioValue.value = model.value;
|
|
79
|
-
} else {
|
|
80
|
-
/* { from, to } */
|
|
81
|
-
selectedRadioValue.value = RelativeDatetimeValue.Custom;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
95
|
+
const selectedRadioValue = computed<string>(() => {
|
|
96
|
+
if (isEmpty(model.value)) return '';
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
return typeof model.value === 'string'
|
|
99
|
+
? model.value
|
|
100
|
+
: RelativeDatetimeValue.Custom;
|
|
101
|
+
});
|
|
86
102
|
|
|
87
103
|
const absoluteModel = computed(() => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
if (isEmpty(model.value)) return undefined;
|
|
105
|
+
|
|
106
|
+
if (typeof model.value === 'object') return model.value;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
from: normalizeToTimestamp(model.value, {
|
|
110
|
+
round: 'start',
|
|
111
|
+
}),
|
|
112
|
+
to: normalizeToTimestamp(model.value, {
|
|
113
|
+
round: 'end',
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
91
116
|
});
|
|
92
117
|
|
|
93
118
|
const showDatepickers = computed(() => {
|
|
94
|
-
return
|
|
119
|
+
return (
|
|
120
|
+
hidePresets.value ||
|
|
121
|
+
selectedRadioValue.value === RelativeDatetimeValue.Custom
|
|
122
|
+
);
|
|
95
123
|
});
|
|
96
124
|
|
|
97
|
-
const from = computed(() => absoluteModel.value?.from);
|
|
98
|
-
const to = computed(() => absoluteModel.value?.to);
|
|
99
|
-
|
|
100
125
|
const v$ = useVuelidate(
|
|
101
126
|
computed(() => ({
|
|
102
127
|
from: showDatepickers.value
|
|
@@ -111,15 +136,18 @@ const v$ = useVuelidate(
|
|
|
111
136
|
: {},
|
|
112
137
|
})),
|
|
113
138
|
{
|
|
114
|
-
from,
|
|
115
|
-
to,
|
|
139
|
+
from: computed(() => absoluteModel.value?.from),
|
|
140
|
+
to: computed(() => absoluteModel.value?.to),
|
|
116
141
|
},
|
|
117
142
|
{
|
|
118
143
|
$autoDirty: true,
|
|
119
144
|
},
|
|
120
145
|
);
|
|
121
146
|
|
|
122
|
-
|
|
147
|
+
onMounted(() => {
|
|
148
|
+
if (!props.disableValidation) v$.value.$touch();
|
|
149
|
+
});
|
|
150
|
+
|
|
123
151
|
watch(
|
|
124
152
|
() => v$.value.$invalid,
|
|
125
153
|
(invalid) => {
|
|
@@ -132,7 +160,7 @@ watch(
|
|
|
132
160
|
|
|
133
161
|
const handleRadioChange = (selected: string | number | boolean | object) => {
|
|
134
162
|
const value = selected as RelativeDatetimeValue;
|
|
135
|
-
|
|
163
|
+
|
|
136
164
|
if (value === RelativeDatetimeValue.Custom) {
|
|
137
165
|
model.value = {
|
|
138
166
|
from: startOfToday().getTime(),
|
|
@@ -145,7 +173,7 @@ const handleRadioChange = (selected: string | number | boolean | object) => {
|
|
|
145
173
|
|
|
146
174
|
const changeAbsoluteValue = (value: number, prop: 'from' | 'to') => {
|
|
147
175
|
const newModelValue = {
|
|
148
|
-
...(
|
|
176
|
+
...(absoluteModel.value as {
|
|
149
177
|
from: number;
|
|
150
178
|
to: number;
|
|
151
179
|
}),
|
|
@@ -162,4 +190,14 @@ const changeAbsoluteValue = (value: number, prop: 'from' | 'to') => {
|
|
|
162
190
|
flex-direction: column;
|
|
163
191
|
gap: var(--spacing-xs);
|
|
164
192
|
}
|
|
193
|
+
|
|
194
|
+
.date-time-options-filter-value-field--range {
|
|
195
|
+
flex-direction: row;
|
|
196
|
+
align-items: start;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.date-time-options-filter-value-field--range > * {
|
|
200
|
+
flex: 1;
|
|
201
|
+
min-width: 0;
|
|
202
|
+
}
|
|
165
203
|
</style>
|
package/src/modules/filters/modules/filterConfig/components/_shared/date-time-filter/filterConfig.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FilterConfig,
|
|
3
|
+
type FilterConfigBaseParams,
|
|
4
|
+
type IDateRangeFilterConfig,
|
|
5
|
+
} from '../../../classes/FilterConfig';
|
|
6
|
+
import DateTimeOptionsFilterValueField from './date-time-options/date-time-options-filter-value-field.vue';
|
|
7
|
+
import DateTimeOptionsFilterValuePreview from './date-time-options/date-time-options-filter-value-preview.vue';
|
|
8
|
+
|
|
9
|
+
class DateRangeFilterConfig
|
|
10
|
+
extends FilterConfig
|
|
11
|
+
implements IDateRangeFilterConfig
|
|
12
|
+
{
|
|
13
|
+
readonly hidePresets = true;
|
|
14
|
+
|
|
15
|
+
constructor(params: FilterConfigBaseParams) {
|
|
16
|
+
super({
|
|
17
|
+
...params,
|
|
18
|
+
valueInputComponent: DateTimeOptionsFilterValueField,
|
|
19
|
+
valuePreviewComponent: DateTimeOptionsFilterValuePreview,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type { DateRangeFilterConfig };
|
|
25
|
+
|
|
26
|
+
export const createDateRangeFilterConfig = (params: FilterConfigBaseParams) =>
|
|
27
|
+
new DateRangeFilterConfig(params);
|