@platforma-sdk/ui-vue 1.14.0 → 1.14.1
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/lib.js +8568 -8355
- package/dist/lib.umd.cjs +37 -37
- package/dist/src/components/PlTableFilters/PlTableAddFilter.vue.d.ts +20 -0
- package/dist/src/components/PlTableFilters/PlTableAddFilter.vue.d.ts.map +1 -0
- package/dist/src/components/PlTableFilters/PlTableFilterEntry.vue.d.ts +16 -0
- package/dist/src/components/PlTableFilters/PlTableFilterEntry.vue.d.ts.map +1 -0
- package/dist/src/components/{PlAgDataTable → PlTableFilters}/PlTableFilters.vue.d.ts +2 -1
- package/dist/src/components/PlTableFilters/PlTableFilters.vue.d.ts.map +1 -0
- package/dist/src/components/PlTableFilters/filters_logic.d.ts +18 -0
- package/dist/src/components/PlTableFilters/filters_logic.d.ts.map +1 -0
- package/dist/src/components/PlTableFilters/index.d.ts +2 -0
- package/dist/src/components/PlTableFilters/index.d.ts.map +1 -0
- package/dist/src/lib.d.ts +2 -2
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/PlAgDataTable/pl-table-filters.scss +98 -0
- package/src/components/PlAgGridColumnManager/PlAgGridColumnManager.vue +1 -1
- package/src/components/PlTableFilters/PlTableAddFilter.vue +60 -0
- package/src/components/PlTableFilters/PlTableFilterEntry.vue +61 -0
- package/src/components/PlTableFilters/PlTableFilters.vue +249 -0
- package/src/components/PlTableFilters/filters_logic.ts +344 -0
- package/src/components/PlTableFilters/index.ts +1 -0
- package/src/components/PlTableFilters/pl-table-filters.scss +98 -0
- package/src/lib.ts +3 -2
- package/dist/src/components/PlAgDataTable/PlTableFilters.vue.d.ts.map +0 -1
- package/src/components/PlAgDataTable/PlTableFilters.vue +0 -559
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-sdk/ui-vue",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/lib.umd.cjs",
|
|
6
6
|
"module": "dist/lib.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@ag-grid-enterprise/side-bar": "^32.3.3",
|
|
39
39
|
"@ag-grid-enterprise/column-tool-panel": "^32.3.3",
|
|
40
40
|
"@milaboratories/uikit": "^2.2.18",
|
|
41
|
-
"@platforma-sdk/model": "^1.14.
|
|
41
|
+
"@platforma-sdk/model": "^1.14.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@faker-js/faker": "^9.2.0",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
.pl-filter-manager {
|
|
2
|
+
$this: &;
|
|
3
|
+
|
|
4
|
+
&__add-btn {
|
|
5
|
+
height: 40px;
|
|
6
|
+
position: sticky;
|
|
7
|
+
bottom: -16px;
|
|
8
|
+
background-color: var(--bg-elevated-01);
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
padding-left: 12px;
|
|
13
|
+
padding-right: 12px;
|
|
14
|
+
border-radius: 6px;
|
|
15
|
+
border: 1px dashed var(--border-color-div-grey);
|
|
16
|
+
line-height: 0;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__add-btn-title {
|
|
21
|
+
flex-grow: 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&__header {
|
|
25
|
+
height: 40px;
|
|
26
|
+
padding-left: 12px;
|
|
27
|
+
padding-right: 12px;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&__content {
|
|
32
|
+
max-height: 0;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
transition: all .2s ease-in-out;
|
|
35
|
+
padding-top: 0;
|
|
36
|
+
padding-bottom: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&__expand-icon {
|
|
40
|
+
.mask-16 {
|
|
41
|
+
transition: all .15s ease-in-out;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&__toggle,
|
|
46
|
+
&__expand-icon,
|
|
47
|
+
&__delete {
|
|
48
|
+
line-height: 0;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&__toggle,
|
|
53
|
+
&__delete {
|
|
54
|
+
.mask-24 {
|
|
55
|
+
background-color: var(--ic-02);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
&__filter {
|
|
61
|
+
border-radius: 6px;
|
|
62
|
+
border: 1px solid var(--border-color-div-grey);
|
|
63
|
+
background-color: var(--bg-base-light);
|
|
64
|
+
transition: background-color .15s ease-in-out;
|
|
65
|
+
overflow: auto;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&__filter.open {
|
|
69
|
+
background-color: var(--bg-elevated-01);
|
|
70
|
+
|
|
71
|
+
#{$this}__content {
|
|
72
|
+
max-height: 1600px;
|
|
73
|
+
// overflow: auto;
|
|
74
|
+
padding: 24px;
|
|
75
|
+
transition: all .2s ease-in-out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#{$this}__header {
|
|
79
|
+
background: linear-gradient(180deg, #EBFFEB 0%, #FFF 100%);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#{$this}__expand-icon {
|
|
83
|
+
.mask-16 {
|
|
84
|
+
transform: rotate(90deg);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
&__revert-btn {
|
|
90
|
+
padding: 8px 14px;
|
|
91
|
+
border-radius: 6px;
|
|
92
|
+
cursor: pointer;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&__revert-btn:hover {
|
|
96
|
+
background-color: var(--btn-sec-hover-grey);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -56,7 +56,7 @@ onMounted(() => {
|
|
|
56
56
|
</PlBtnGhost>
|
|
57
57
|
</Teleport>
|
|
58
58
|
|
|
59
|
-
<PlSlideModal v-model="slideModal" :
|
|
59
|
+
<PlSlideModal v-model="slideModal" :width="'368px'" close-on-outside-click>
|
|
60
60
|
<template #title>Columns</template>
|
|
61
61
|
|
|
62
62
|
<div ref="listRef" :key="listKey" class="pl-ag-columns">
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { PlBtnGhost, PlBtnPrimary, PlDropdown, PlSlideModal, type ListOption } from '@milaboratories/uikit';
|
|
3
|
+
import type { PlTableFilterColumnId, PlTableFiltersStateEntry, PlTableFilterType, PTableColumnSpec } from '@platforma-sdk/model';
|
|
4
|
+
import { ref, toRefs, watch } from 'vue';
|
|
5
|
+
import PlTableFilterEntry from './PlTableFilterEntry.vue';
|
|
6
|
+
|
|
7
|
+
const show = defineModel<boolean>({ required: true });
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
columnsById: Readonly<Record<PlTableFilterColumnId, PTableColumnSpec>>;
|
|
10
|
+
columnOptions: Readonly<ListOption<PlTableFilterColumnId>[]>;
|
|
11
|
+
filterOptions: Readonly<Record<PlTableFilterColumnId, ListOption<PlTableFilterType>[]>>;
|
|
12
|
+
makeFilter(columnId: string): PlTableFiltersStateEntry;
|
|
13
|
+
}>();
|
|
14
|
+
const { columnsById, columnOptions, filterOptions } = toRefs(props);
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
addFilter: [PlTableFiltersStateEntry];
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
const newFilterColumnId = ref<string>();
|
|
20
|
+
const newFilter = ref<PlTableFiltersStateEntry>();
|
|
21
|
+
watch(
|
|
22
|
+
() => newFilterColumnId.value,
|
|
23
|
+
(newFilterColumnId) => {
|
|
24
|
+
if (newFilterColumnId) newFilter.value = props.makeFilter(newFilterColumnId);
|
|
25
|
+
else newFilter.value = undefined;
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
const discardFilter = () => {
|
|
29
|
+
newFilterColumnId.value = undefined;
|
|
30
|
+
show.value = false;
|
|
31
|
+
};
|
|
32
|
+
const applyFilter = () => {
|
|
33
|
+
if (newFilter.value) {
|
|
34
|
+
emit('addFilter', newFilter.value);
|
|
35
|
+
discardFilter();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<PlSlideModal v-model="show" :close-on-outside-click="false">
|
|
42
|
+
<template #title>Add Filter</template>
|
|
43
|
+
<div class="d-flex flex-column gap-24">
|
|
44
|
+
<PlDropdown v-model="newFilterColumnId" :options="columnOptions" label="Column" placeholder="Choose..." />
|
|
45
|
+
|
|
46
|
+
<div v-if="!newFilter" class="text-subtitle-m" style="color: var(--txt-mask)">Choose a column to view and adjust its options</div>
|
|
47
|
+
|
|
48
|
+
<PlTableFilterEntry
|
|
49
|
+
v-if="!!newFilter"
|
|
50
|
+
v-model="newFilter"
|
|
51
|
+
:column="columnsById[newFilter.columnId]"
|
|
52
|
+
:options="filterOptions[newFilter.columnId]"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
<template #actions>
|
|
56
|
+
<PlBtnPrimary :disabled="!newFilter" @click="applyFilter">Add Filter</PlBtnPrimary>
|
|
57
|
+
<PlBtnGhost :justify-center="false" @click="discardFilter">Cancel</PlBtnGhost>
|
|
58
|
+
</template>
|
|
59
|
+
</PlSlideModal>
|
|
60
|
+
</template>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { PlDropdown, PlTextField, PlToggleSwitch, Slider, type ListOption } from '@milaboratories/uikit';
|
|
3
|
+
import type { PlTableFiltersStateEntry, PlTableFilterType, PTableColumnSpec } from '@platforma-sdk/model';
|
|
4
|
+
import { toRefs } from 'vue';
|
|
5
|
+
import { changeFilterType, parseNumber, parseString, parseRegex, makeWildcardOptions } from './filters_logic';
|
|
6
|
+
|
|
7
|
+
const entry = defineModel<PlTableFiltersStateEntry>({ required: true });
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
column: Readonly<PTableColumnSpec>;
|
|
10
|
+
options: Readonly<ListOption<PlTableFilterType>[]>;
|
|
11
|
+
}>();
|
|
12
|
+
const { column, options } = toRefs(props);
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="d-flex flex-column gap-24">
|
|
17
|
+
<PlDropdown
|
|
18
|
+
:model-value="entry.filter.type"
|
|
19
|
+
:options="options"
|
|
20
|
+
label="Predicate"
|
|
21
|
+
@update:model-value="(type) => (entry = changeFilterType(entry, type!))"
|
|
22
|
+
/>
|
|
23
|
+
<template
|
|
24
|
+
v-if="
|
|
25
|
+
entry.filter.type === 'number_equals' ||
|
|
26
|
+
entry.filter.type === 'number_notEquals' ||
|
|
27
|
+
entry.filter.type === 'number_lessThan' ||
|
|
28
|
+
entry.filter.type === 'number_lessThanOrEqualTo' ||
|
|
29
|
+
entry.filter.type === 'number_greaterThan' ||
|
|
30
|
+
entry.filter.type === 'number_greaterThanOrEqualTo'
|
|
31
|
+
"
|
|
32
|
+
>
|
|
33
|
+
<PlTextField v-model="entry.filter.reference" :parse="(value: string): number => parseNumber(column, value)" label="Reference value" />
|
|
34
|
+
</template>
|
|
35
|
+
<template v-if="entry.filter.type === 'number_between'">
|
|
36
|
+
<PlTextField v-model="entry.filter.lowerBound" :parse="(value: string): number => parseNumber(column, value)" label="Lower bound" />
|
|
37
|
+
<PlToggleSwitch v-model="entry.filter.includeLowerBound" label="Include lower bound" />
|
|
38
|
+
<PlTextField v-model="entry.filter.upperBound" :parse="(value: string): number => parseNumber(column, value)" label="Upper bound" />
|
|
39
|
+
<PlToggleSwitch v-model="entry.filter.includeUpperBound" label="Include upper bound" />
|
|
40
|
+
</template>
|
|
41
|
+
<template
|
|
42
|
+
v-if="
|
|
43
|
+
entry.filter.type === 'string_equals' ||
|
|
44
|
+
entry.filter.type === 'string_notEquals' ||
|
|
45
|
+
entry.filter.type === 'string_contains' ||
|
|
46
|
+
entry.filter.type === 'string_doesNotContain'
|
|
47
|
+
"
|
|
48
|
+
>
|
|
49
|
+
<PlTextField v-model="entry.filter.reference" :parse="(value: string): string => parseString(column, value)" label="Reference value" />
|
|
50
|
+
</template>
|
|
51
|
+
<template v-if="entry.filter.type === 'string_matches' || entry.filter.type === 'string_doesNotMatch'">
|
|
52
|
+
<PlTextField v-model="entry.filter.reference" :parse="parseRegex" label="Reference value" />
|
|
53
|
+
</template>
|
|
54
|
+
<template v-if="entry.filter.type === 'string_containsFuzzyMatch'">
|
|
55
|
+
<PlTextField v-model="entry.filter.reference" :parse="(value: string): string => parseString(column, value)" label="Reference value" />
|
|
56
|
+
<Slider v-model="entry.filter.maxEdits" :max="5" breakpoints label="Maximum nuber of substitutions and indels" />
|
|
57
|
+
<PlToggleSwitch v-model="entry.filter.substitutionsOnly" label="Substitutions only" />
|
|
58
|
+
<PlDropdown v-model="entry.filter.wildcard" :options="makeWildcardOptions(column, entry.filter.reference)" clearable label="Wildcard symbol" />
|
|
59
|
+
</template>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ListOption } from '@milaboratories/uikit';
|
|
3
|
+
import { PlBtnGhost, PlIcon24, PlSlideModal, PlMaskIcon16, PlMaskIcon24 } from '@milaboratories/uikit';
|
|
4
|
+
import { computed, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
|
5
|
+
import type {
|
|
6
|
+
PlTableFiltersModel,
|
|
7
|
+
PlTableFilterType,
|
|
8
|
+
PlTableFilter,
|
|
9
|
+
PTableColumnSpec,
|
|
10
|
+
PlTableFiltersState,
|
|
11
|
+
PlTableFiltersStateEntry,
|
|
12
|
+
PlTableFilterColumnId,
|
|
13
|
+
} from '@platforma-sdk/model';
|
|
14
|
+
import * as lodash from 'lodash';
|
|
15
|
+
import type { PlTableFiltersDefault, PlTableFiltersRestriction } from '../PlAgDataTable/types';
|
|
16
|
+
import PlTableFilterEntry from './PlTableFilterEntry.vue';
|
|
17
|
+
import PlTableAddFilter from './PlTableAddFilter.vue';
|
|
18
|
+
import { PlAgDataTableToolsPanelId } from '../PlAgDataTableToolsPanel/PlAgDataTableToolsPanelId';
|
|
19
|
+
import { filterTypesNumber, filterTypesString, getColumnName, getFilterDefault, getFilterLabel, makeColumnId, makePredicate } from './filters_logic';
|
|
20
|
+
import './pl-table-filters.scss';
|
|
21
|
+
|
|
22
|
+
const model = defineModel<PlTableFiltersModel>({ required: true });
|
|
23
|
+
const props = defineProps<{
|
|
24
|
+
columns: Readonly<PTableColumnSpec[]>;
|
|
25
|
+
restrictions?: Readonly<PlTableFiltersRestriction[]>;
|
|
26
|
+
defaults?: Readonly<PlTableFiltersDefault[]>;
|
|
27
|
+
}>();
|
|
28
|
+
const { columns, restrictions, defaults } = toRefs(props);
|
|
29
|
+
|
|
30
|
+
const columnsById = computed<Record<PlTableFilterColumnId, PTableColumnSpec>>(() => {
|
|
31
|
+
const filterableColumn = columns.value.filter((column) => {
|
|
32
|
+
const type = column.type;
|
|
33
|
+
switch (type) {
|
|
34
|
+
case 'axis':
|
|
35
|
+
return column.spec.type !== 'Bytes';
|
|
36
|
+
case 'column':
|
|
37
|
+
return column.spec.valueType !== 'Bytes';
|
|
38
|
+
default:
|
|
39
|
+
throw Error(`unsupported data type: ${type satisfies never}`);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const result: Record<PlTableFilterColumnId, PTableColumnSpec> = {};
|
|
43
|
+
for (const column of filterableColumn) {
|
|
44
|
+
result[makeColumnId(column)] = column;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
});
|
|
48
|
+
const restrictionsMap = computed<Record<PlTableFilterColumnId, PlTableFilterType[]>>(() => {
|
|
49
|
+
const restrictionsValue = restrictions.value ?? [];
|
|
50
|
+
const map: Record<PlTableFilterColumnId, PlTableFilterType[]> = {};
|
|
51
|
+
for (const [id, column] of Object.entries(columnsById.value)) {
|
|
52
|
+
const entry = lodash.find(restrictionsValue, (entry) => lodash.isEqual(entry.column.id, column.id));
|
|
53
|
+
if (entry) map[id] = entry.allowedFilterTypes;
|
|
54
|
+
}
|
|
55
|
+
return map;
|
|
56
|
+
});
|
|
57
|
+
const defaultsMap = computed<Record<PlTableFilterColumnId, PlTableFiltersStateEntry>>(() => {
|
|
58
|
+
const defaultsValue = defaults.value ?? [];
|
|
59
|
+
const map: Record<PlTableFilterColumnId, PlTableFiltersStateEntry> = {};
|
|
60
|
+
for (const [id, column] of Object.entries(columnsById.value)) {
|
|
61
|
+
const entry = lodash.find(defaultsValue, (entry) => lodash.isEqual(entry.column.id, column.id));
|
|
62
|
+
if (entry) map[id] = { columnId: id, filter: entry.default, disabled: false };
|
|
63
|
+
}
|
|
64
|
+
return map;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/* State upgrader */ (() => {
|
|
68
|
+
let state = model.value.state;
|
|
69
|
+
if (typeof state === 'object' && !Array.isArray(state)) {
|
|
70
|
+
model.value.state = Object.entries(state as unknown as Record<PlTableFilterColumnId, PlTableFilter>).map(([id, filter]) => ({
|
|
71
|
+
columnId: id,
|
|
72
|
+
filter,
|
|
73
|
+
disabled: false,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
const makeState = (state?: PlTableFiltersState): PlTableFiltersState => {
|
|
78
|
+
return state ?? Object.values(defaultsMap.value);
|
|
79
|
+
};
|
|
80
|
+
const reactiveModel = reactive({ state: makeState(model.value.state) });
|
|
81
|
+
watch(
|
|
82
|
+
() => model.value,
|
|
83
|
+
(model) => {
|
|
84
|
+
if (!lodash.isEqual(reactiveModel.state, model.state)) {
|
|
85
|
+
reactiveModel.state = makeState(model.state);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
watch(
|
|
90
|
+
() => reactiveModel,
|
|
91
|
+
(reactiveModel) => {
|
|
92
|
+
if (!lodash.isEqual(reactiveModel.state, model.value.state)) {
|
|
93
|
+
model.value = {
|
|
94
|
+
state: lodash.cloneDeep(reactiveModel.state),
|
|
95
|
+
filters: reactiveModel.state
|
|
96
|
+
.filter((entry) => !entry.disabled)
|
|
97
|
+
.map((entry) => {
|
|
98
|
+
const column = columnsById.value[entry.columnId];
|
|
99
|
+
const { spec, ...columnId } = column;
|
|
100
|
+
const _ = spec;
|
|
101
|
+
return {
|
|
102
|
+
type: 'bySingleColumnV2',
|
|
103
|
+
column: columnId,
|
|
104
|
+
predicate: makePredicate(column, entry.filter),
|
|
105
|
+
};
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
immediate: true,
|
|
112
|
+
deep: true,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
watch(
|
|
116
|
+
() => columnsById.value,
|
|
117
|
+
(columnsById) => {
|
|
118
|
+
// Do not reset filters when join is happening
|
|
119
|
+
if (Object.keys(columnsById).length === 0 && reactiveModel.state !== undefined) return;
|
|
120
|
+
reactiveModel.state = reactiveModel.state.filter((entry) => !!columnsById[entry.columnId]);
|
|
121
|
+
},
|
|
122
|
+
{ immediate: true },
|
|
123
|
+
);
|
|
124
|
+
const filtersOn = computed(() => {
|
|
125
|
+
// Do not indicate filters when join is happening
|
|
126
|
+
return Object.keys(columnsById).length > 0 && reactiveModel.state.length > 0;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const filterOptions = computed<Record<PlTableFilterColumnId, ListOption<PlTableFilterType>[]>>(() => {
|
|
130
|
+
const restrictionsMapValue = restrictionsMap.value;
|
|
131
|
+
const map: Record<PlTableFilterColumnId, ListOption<PlTableFilterType>[]> = {};
|
|
132
|
+
for (const [id, column] of Object.entries(columnsById.value)) {
|
|
133
|
+
const valueType = column.type === 'column' ? column.spec.valueType : column.spec.type;
|
|
134
|
+
let types: PlTableFilterType[] = valueType === 'String' ? filterTypesString : filterTypesNumber;
|
|
135
|
+
|
|
136
|
+
const restrictionsEntry = restrictionsMapValue[id];
|
|
137
|
+
if (restrictionsEntry) types = types.filter((type) => restrictionsEntry.includes(type));
|
|
138
|
+
|
|
139
|
+
map[id] = types.map((type) => ({ value: type, text: getFilterLabel(type) }));
|
|
140
|
+
}
|
|
141
|
+
return map;
|
|
142
|
+
});
|
|
143
|
+
const filterOptionsPresent = computed<boolean>(() => {
|
|
144
|
+
return lodash.some(Object.values(filterOptions.value), (options) => options.length > 0);
|
|
145
|
+
});
|
|
146
|
+
const columnOptions = computed<ListOption<PlTableFilterColumnId>[]>(() =>
|
|
147
|
+
Object.entries(Object.entries(columnsById.value))
|
|
148
|
+
.filter(([_i, [id, _column]]) => filterOptions.value[id].length > 0)
|
|
149
|
+
.filter(([_i, [id, _column]]) => !reactiveModel.state.some((entry) => entry.columnId === id))
|
|
150
|
+
.map(([i, [id, column]]) => ({
|
|
151
|
+
label: getColumnName(column, i),
|
|
152
|
+
value: id,
|
|
153
|
+
})),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const makeFilter = (columnId: string): PlTableFiltersStateEntry =>
|
|
157
|
+
defaultsMap.value[columnId] ?? {
|
|
158
|
+
columnId,
|
|
159
|
+
filter: getFilterDefault(filterOptions.value[columnId][0].value),
|
|
160
|
+
disabled: false,
|
|
161
|
+
};
|
|
162
|
+
const resetFilter = (index: number) => {
|
|
163
|
+
reactiveModel.state[index] = makeFilter(reactiveModel.state[index].columnId);
|
|
164
|
+
};
|
|
165
|
+
const deleteFilter = (index: number) => reactiveModel.state.splice(index, 1);
|
|
166
|
+
|
|
167
|
+
const showManager = ref(false);
|
|
168
|
+
const showAddFilter = ref(false);
|
|
169
|
+
const mounted = ref(false);
|
|
170
|
+
onMounted(() => {
|
|
171
|
+
mounted.value = true;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const openState = reactive<Record<PlTableFilterColumnId, boolean>>({});
|
|
175
|
+
const toggleExpandFilter = (columnId: PlTableFilterColumnId) => {
|
|
176
|
+
if (!openState[columnId]) openState[columnId] = true;
|
|
177
|
+
else delete openState[columnId];
|
|
178
|
+
};
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<template>
|
|
182
|
+
<Teleport v-if="mounted" :to="`#${PlAgDataTableToolsPanelId}`">
|
|
183
|
+
<PlBtnGhost @click.stop="showManager = true">
|
|
184
|
+
Filters
|
|
185
|
+
<template #append>
|
|
186
|
+
<PlIcon24 :name="filtersOn ? 'filter-on' : 'filter'" />
|
|
187
|
+
</template>
|
|
188
|
+
</PlBtnGhost>
|
|
189
|
+
</Teleport>
|
|
190
|
+
|
|
191
|
+
<PlSlideModal v-model="showManager" :close-on-outside-click="false">
|
|
192
|
+
<template #title>Manage Filters</template>
|
|
193
|
+
<div class="pl-filter-manager d-flex flex-column gap-6">
|
|
194
|
+
<div
|
|
195
|
+
v-for="(entry, index) in reactiveModel.state"
|
|
196
|
+
:key="entry.columnId"
|
|
197
|
+
:class="{ open: openState[entry.columnId] }"
|
|
198
|
+
class="pl-filter-manager__filter"
|
|
199
|
+
>
|
|
200
|
+
<div class="pl-filter-manager__header d-flex align-center gap-8" @click="toggleExpandFilter(entry.columnId)">
|
|
201
|
+
<div class="pl-filter-manager__expand-icon">
|
|
202
|
+
<PlMaskIcon16 name="chevron-right" />
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div class="pl-filter-manager__title flex-grow-1 text-s">{{ getColumnName(columnsById[entry.columnId], index) }}</div>
|
|
206
|
+
|
|
207
|
+
<div class="pl-filter-manager__actions d-flex gap-12">
|
|
208
|
+
<div class="pl-filter-manager__toggle" @click.stop="entry.disabled = !entry.disabled">
|
|
209
|
+
<PlMaskIcon24 :name="entry.disabled ? 'view-hide' : 'view-show'" />
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div class="pl-filter-manager__delete" @click.stop="deleteFilter(index)">
|
|
213
|
+
<PlMaskIcon24 name="close" />
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div class="pl-filter-manager__content d-flex gap-24 p-24 flex-column">
|
|
219
|
+
<PlTableFilterEntry v-model="reactiveModel.state[index]" :column="columnsById[entry.columnId]" :options="filterOptions[entry.columnId]" />
|
|
220
|
+
|
|
221
|
+
<div class="d-flex justify-center">
|
|
222
|
+
<div class="pl-filter-manager__revert-btn text-s d-flex align-center gap-8" @click="resetFilter(index)">
|
|
223
|
+
Revert Settings to Default
|
|
224
|
+
<PlMaskIcon24 name="reverse" />
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div v-if="columnOptions.length > 0" class="pl-filter-manager__add-btn" @click="showAddFilter = true">
|
|
231
|
+
<div class="pl-filter-manager__add-btn-icon">
|
|
232
|
+
<PlMaskIcon16 name="add" />
|
|
233
|
+
</div>
|
|
234
|
+
<div class="pl-filter-manager__add-btn-title">Add Filter</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div v-if="!filterOptionsPresent">No filters applicable</div>
|
|
238
|
+
</div>
|
|
239
|
+
</PlSlideModal>
|
|
240
|
+
|
|
241
|
+
<PlTableAddFilter
|
|
242
|
+
v-model="showAddFilter"
|
|
243
|
+
:columns-by-id="columnsById"
|
|
244
|
+
:column-options="columnOptions"
|
|
245
|
+
:filter-options="filterOptions"
|
|
246
|
+
:make-filter="makeFilter"
|
|
247
|
+
@add-filter="(entry) => reactiveModel.state.push(entry)"
|
|
248
|
+
/>
|
|
249
|
+
</template>
|