mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
filter is now generic component + fix
* fix select and combobox maxCharBtn
* Now filter is a component extending default fields.
For the moment, we have 2 types of filters: select and keyword.
We have default values that avoid filter ("all" for select and "" for keyword).
Filters are fields so we need to provide a "field" key with same structure as a Field.
We have to define "keys" that will be the keys the filter value will check.
We can set filter :"default" in order to filter the base keys of an object.
We can set filter :"settings" in order to filter settings, data must be an advanced template.
This commit is contained in:
parent
cd274811b3
commit
e7e0c2cfbc
6 changed files with 253 additions and 140 deletions
|
|
@ -5,14 +5,12 @@ import Fields from "@components/Form/Fields.vue";
|
|||
import Title from "@components/Widget/Title.vue";
|
||||
import Subtitle from "@components/Widget/Subtitle.vue";
|
||||
import Combobox from "@components/Forms/Field/Combobox.vue";
|
||||
import Input from "@components/Forms/Field/Input.vue";
|
||||
import Select from "@components/Forms/Field/Select.vue";
|
||||
import Button from "@components/Widget/Button.vue";
|
||||
import Text from "@components/Widget/Text.vue";
|
||||
import Filter from "@components/Widget/Filter.vue";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { plugin_types } from "@utils/variables";
|
||||
import {
|
||||
useFilter,
|
||||
useCheckPluginsValidity,
|
||||
useUpdateTempSettings,
|
||||
useListenTemp,
|
||||
|
|
@ -58,7 +56,11 @@ const props = defineProps({
|
|||
required: true,
|
||||
default: {},
|
||||
},
|
||||
|
||||
filters: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {},
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -74,81 +76,108 @@ const props = defineProps({
|
|||
const data = reactive({
|
||||
currPlugin: "",
|
||||
plugins: [],
|
||||
keyword: "",
|
||||
type: "all",
|
||||
context: "all",
|
||||
base: JSON.parse(JSON.stringify(props.template)),
|
||||
isRegErr: false,
|
||||
isReqErr: false,
|
||||
settingErr: "",
|
||||
pluginErr: "",
|
||||
// Add filtering and check validity with regex and required
|
||||
format: computed(() => {
|
||||
// Check validity
|
||||
setValidity();
|
||||
|
||||
// Deep copy
|
||||
const template = JSON.parse(JSON.stringify(data.base));
|
||||
|
||||
// Add filter logic
|
||||
const filterPlugin = [
|
||||
{
|
||||
type: "select",
|
||||
value: data.type,
|
||||
keys: ["type"],
|
||||
},
|
||||
];
|
||||
|
||||
const filterSettings = [
|
||||
{
|
||||
type: "keyword",
|
||||
value: data.keyword,
|
||||
keys: [
|
||||
"id",
|
||||
"label",
|
||||
"name",
|
||||
"description",
|
||||
"help",
|
||||
"value",
|
||||
"setting_name",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
value: data.context,
|
||||
keys: ["context"],
|
||||
},
|
||||
];
|
||||
|
||||
// Start plugin filtering
|
||||
const filterPlugins = useFilter(template, filterPlugin);
|
||||
// Filter settings
|
||||
filterPlugins.forEach((plugin, id) => {
|
||||
// loop on plugin settings dict
|
||||
const settings = [];
|
||||
for (const [key, value] of Object.entries(plugin.settings)) {
|
||||
// add to value the key as setting_name
|
||||
settings.push({ ...value, setting_name: key });
|
||||
}
|
||||
const filterSettingsData = useFilter(settings, filterSettings);
|
||||
// Transform list of dict by a dict of dict with setting_name as key and add update plugin settings
|
||||
const settingsData = {};
|
||||
filterSettingsData.forEach((setting) => {
|
||||
settingsData[setting.setting_name] = setting;
|
||||
});
|
||||
filterPlugins[id].settings = settingsData;
|
||||
});
|
||||
|
||||
// Case no settings found, remove plugin
|
||||
const filterData = filterPlugins.filter((plugin) => {
|
||||
return Object.keys(plugin.settings).length > 0;
|
||||
});
|
||||
data.plugins = getPluginNames(filterData);
|
||||
data.currPlugin = getFirstPlugin(filterData);
|
||||
return filterData;
|
||||
}),
|
||||
format: JSON.parse(JSON.stringify(props.template)),
|
||||
});
|
||||
|
||||
const filters = [
|
||||
{
|
||||
filter: "default",
|
||||
filterName: "type",
|
||||
type: "select",
|
||||
value: "all",
|
||||
keys: ["type"],
|
||||
field: {
|
||||
id: uuidv4(),
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all"].concat(plugin_types),
|
||||
name: uuidv4(),
|
||||
onlyDown: true,
|
||||
label: "inp_select_plugin_type",
|
||||
containerClass: "setting",
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_select_plugin_type_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
},
|
||||
},
|
||||
{
|
||||
filter: "settings",
|
||||
filterName: "keyword",
|
||||
type: "keyword",
|
||||
value: "",
|
||||
keys: [
|
||||
"id",
|
||||
"label",
|
||||
"name",
|
||||
"description",
|
||||
"help",
|
||||
"value",
|
||||
"setting_name",
|
||||
],
|
||||
field: {
|
||||
id: uuidv4(),
|
||||
value: "",
|
||||
type: "text",
|
||||
name: uuidv4(),
|
||||
containerClass: "setting",
|
||||
label: "inp_search_settings",
|
||||
placeholder: "inp_keyword",
|
||||
isClipboard: false,
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_search_settings_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
},
|
||||
},
|
||||
{
|
||||
filter: "settings",
|
||||
filterName: "context",
|
||||
type: "select",
|
||||
value: "all",
|
||||
keys: ["context"],
|
||||
field: {
|
||||
id: uuidv4(),
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all", "multisite", "global"],
|
||||
name: uuidv4(),
|
||||
onlyDown: true,
|
||||
containerClass: "setting",
|
||||
label: "inp_select_plugin_context",
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_select_plugin_context_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function filter(filterData) {
|
||||
setValidity();
|
||||
data.format = filterData;
|
||||
data.plugins = getPluginNames(filterData);
|
||||
data.currPlugin = getFirstPlugin(filterData);
|
||||
}
|
||||
|
||||
function setValidity() {
|
||||
const [isRegErr, isReqErr, settingErr, settingNameErr, pluginErr, id] =
|
||||
useCheckPluginsValidity(data.base);
|
||||
|
|
@ -202,59 +231,6 @@ const comboboxPlugin = {
|
|||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
};
|
||||
|
||||
const inpKeyword = {
|
||||
id: uuidv4(),
|
||||
value: "",
|
||||
type: "text",
|
||||
name: uuidv4(),
|
||||
label: "inp_search_settings",
|
||||
placeholder: "inp_keyword",
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_search_settings_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
};
|
||||
|
||||
const selectType = {
|
||||
id: uuidv4(),
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all"].concat(plugin_types),
|
||||
name: uuidv4(),
|
||||
onlyDown: true,
|
||||
label: "inp_select_plugin_type",
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_select_plugin_type_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
};
|
||||
|
||||
const selectContext = {
|
||||
id: uuidv4(),
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all", "multisite", "global"],
|
||||
name: uuidv4(),
|
||||
onlyDown: true,
|
||||
label: "inp_select_plugin_context",
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_select_plugin_context_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
};
|
||||
|
||||
const buttonSave = {
|
||||
id: uuidv4(),
|
||||
text: "action_save",
|
||||
|
|
@ -293,17 +269,13 @@ onUnmounted(() => {
|
|||
>
|
||||
<Title type="card" :title="'dashboard_advanced_mode'" />
|
||||
<Subtitle type="card" :subtitle="'dashboard_advanced_mode_subtitle'" />
|
||||
<Container :containerClass="`grid grid-cols-12 col-span-12 w-full`">
|
||||
<Combobox
|
||||
v-bind="comboboxPlugin"
|
||||
:value="data.currPlugin"
|
||||
:values="data.plugins"
|
||||
@inp="data.currPlugin = $event"
|
||||
/>
|
||||
<Input @inp="(v) => (data.keyword = v)" v-bind="inpKeyword" />
|
||||
<Select @inp="(v) => (data.type = v)" v-bind="selectType" />
|
||||
<Select @inp="(v) => (data.context = v)" v-bind="selectContext" />
|
||||
</Container>
|
||||
<Combobox
|
||||
v-bind="comboboxPlugin"
|
||||
:value="data.currPlugin"
|
||||
:values="data.plugins"
|
||||
@inp="data.currPlugin = $event"
|
||||
/>
|
||||
<Filter @filter="(v) => filter(v)" :data="data.base" :filters="filters" />
|
||||
<template v-for="plugin in data.format">
|
||||
<Container
|
||||
data-advanced-form-plugin
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ const emits = defineEmits(["inp"]);
|
|||
<span :id="`${props.id}-text`" class="select-btn-name">
|
||||
{{
|
||||
(props.maxBtnChars && select.value) ||
|
||||
props.value > props.maxBtnChars
|
||||
props.value > +props.maxBtnChars
|
||||
? `${
|
||||
select.value.substring(0, props.maxBtnChars) ||
|
||||
props.value.substring(0, props.maxBtnChars)
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ const emits = defineEmits(["inp"]);
|
|||
<span :id="`${props.id}-text`" class="select-btn-name">
|
||||
{{
|
||||
(props.maxBtnChars && select.value) ||
|
||||
props.value > props.maxBtnChars
|
||||
props.value > +props.maxBtnChars
|
||||
? `${
|
||||
select.value.substring(0, props.maxBtnChars) ||
|
||||
props.value.substring(0, props.maxBtnChars)
|
||||
|
|
|
|||
142
vuejs/client/src/components/Widget/Filter.vue
Normal file
142
vuejs/client/src/components/Widget/Filter.vue
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<script setup>
|
||||
import { defineProps, reactive } from "vue";
|
||||
import Container from "@components/Widget/Container.vue";
|
||||
|
||||
import Input from "@components/Forms/Field/Input.vue";
|
||||
import Select from "@components/Forms/Field/Select.vue";
|
||||
|
||||
import { useFilter } from "@utils/form.js";
|
||||
/**
|
||||
@name Widget/Filter.vue
|
||||
@description This component allow to filter any data object or array with a list of filters.
|
||||
For the moment, we have 2 types of filters: select and keyword.
|
||||
We have default values that avoid filter ("all" for select and "" for keyword).
|
||||
Filters are fields so we need to provide a "field" key with same structure as a Field.
|
||||
We have to define "keys" that will be the keys the filter value will check.
|
||||
We can set filter :"default" in order to filter the base keys of an object.
|
||||
We can set filter :"settings" in order to filter settings, data must be an advanced template.
|
||||
Check example for more details.
|
||||
@example
|
||||
[
|
||||
{
|
||||
filter: "default", // or "settings"
|
||||
type: "select",
|
||||
value: "all",
|
||||
keys: ["type"],
|
||||
field: {
|
||||
inpType: "select",
|
||||
id: uuidv4(),
|
||||
value: "all",
|
||||
// add 'all' as first value
|
||||
values: ["all"].concat(plugin_types),
|
||||
name: uuidv4(),
|
||||
onlyDown: true,
|
||||
label: "inp_select_plugin_type",
|
||||
popovers: [
|
||||
{
|
||||
text: "inp_select_plugin_type_desc",
|
||||
iconName: "info",
|
||||
iconColor: "info",
|
||||
},
|
||||
],
|
||||
columns: { pc: 3, tablet: 4, mobile: 12 },
|
||||
},
|
||||
},
|
||||
...
|
||||
]
|
||||
@param {object} filters - Fields with additional data to be used as filters.
|
||||
@param {object|array} data - Data object or array to filter. Emit a filter event with the filtered data.
|
||||
@param {string} containerClass - Additional class for the container.
|
||||
*/
|
||||
|
||||
const props = defineProps({
|
||||
filters: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {},
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: {},
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const emits = defineEmits(["filter"]);
|
||||
|
||||
const filters = reactive({
|
||||
base: JSON.parse(JSON.stringify(props.filters)),
|
||||
});
|
||||
|
||||
function filterData(filter, value) {
|
||||
// Loop on filter.base and update the "value" key when matching filterName
|
||||
filters.base.forEach((f) => {
|
||||
if (f.filterName === filter.filterName) {
|
||||
f.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Start filtering
|
||||
let template = JSON.parse(JSON.stringify(props.data));
|
||||
const getFilters = JSON.parse(JSON.stringify(filters.base));
|
||||
|
||||
// Filter order for template : plugins, settings
|
||||
|
||||
// Base keys filtering (like plugin)
|
||||
const defaultFilters = getFilters.filter((f) => f.filter === "default");
|
||||
template = useFilter(template, defaultFilters);
|
||||
|
||||
// Specific settings filtering from advanced template
|
||||
const filterSettings = getFilters.filter((f) => f.filter === "settings");
|
||||
|
||||
if (filterSettings.length) {
|
||||
template.forEach((plugin, id) => {
|
||||
// loop on plugin settings dict
|
||||
const settings = [];
|
||||
for (const [key, value] of Object.entries(plugin.settings)) {
|
||||
// add to value the key as setting_name
|
||||
settings.push({ ...value, setting_name: key });
|
||||
}
|
||||
const filterSettingsData = useFilter(settings, filterSettings);
|
||||
// Transform list of dict by a dict of dict with setting_name as key and add update plugin settings
|
||||
const settingsData = {};
|
||||
filterSettingsData.forEach((setting) => {
|
||||
settingsData[setting.setting_name] = setting;
|
||||
});
|
||||
template[id].settings = settingsData;
|
||||
});
|
||||
|
||||
// Case no settings found, remove plugin
|
||||
template = template.filter((plugin) => {
|
||||
return Object.keys(plugin.settings).length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
emits("filter", template);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Container
|
||||
v-if="filters.base"
|
||||
:containerClass="`grid grid-cols-12 col-span-12 w-full`"
|
||||
>
|
||||
<template v-for="filter in filters.base">
|
||||
<Input
|
||||
v-if="filter.type === 'keyword'"
|
||||
@inp="(v) => filterData(filter, v)"
|
||||
v-bind="filter.field"
|
||||
/>
|
||||
<Select
|
||||
v-if="filter.type === 'select'"
|
||||
@inp="(v) => filterData(filter, v)"
|
||||
v-bind="filter.field"
|
||||
/>
|
||||
</template>
|
||||
</Container>
|
||||
</template>
|
||||
|
|
@ -127,6 +127,7 @@ onUpdated(() => {
|
|||
|
||||
<template>
|
||||
<Container :containerClass="`${props.containerClass} table-container`">
|
||||
<slot></slot>
|
||||
<Container
|
||||
:containerClass="`${props.containerWrapClass} table-container-wrap`"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -89,12 +89,11 @@ function useSubmitForm(data) {
|
|||
}
|
||||
];
|
||||
@param {object} plugins - Object with the plugins data.
|
||||
@param {object} filters - Object with the filters data.
|
||||
@param {array} filters - Array with the filters data.
|
||||
*/
|
||||
function useFilter(items, filters) {
|
||||
// loop on filters to determine types
|
||||
filters = removeDefaultFilters(filters);
|
||||
|
||||
// Case no filters, return items
|
||||
if (filters.length === 0) return items;
|
||||
// loop on filters to determine types
|
||||
|
|
@ -102,7 +101,6 @@ function useFilter(items, filters) {
|
|||
filters.forEach((filter) => {
|
||||
if (!filterTypes.includes(filter.type)) filterTypes.push(filter.type);
|
||||
});
|
||||
|
||||
// Deepcopy
|
||||
const data = JSON.parse(JSON.stringify(items));
|
||||
const filterData = [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue